&& git push origin v0.X.X`
- [ ] create GitHub release
- [ ] final test install on `openpilot.comma.ai`
-- [ ] update production
-- [ ] Post on Discord, X, etc.
+- [ ] update factory provisioning
+- [ ] close out milestone
+- [ ] post on Discord, X, etc.
+```
diff --git a/release/ci/install_github_runner.sh b/release/ci/install_github_runner.sh
index f6b1e217c5..9f11e4841c 100755
--- a/release/ci/install_github_runner.sh
+++ b/release/ci/install_github_runner.sh
@@ -5,6 +5,7 @@ set -e
DEFAULT_REPO_URL="https://github.com/sunnypilot"
START_AT_BOOT=false
RESTORE_MODE=false
+RUNNER_VERSION="2.325.0"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
@@ -108,9 +109,9 @@ setup_system_configs() {
install_runner() {
echo "Downloading and setting up runner..."
cd "$RUNNER_DIR"
- curl -o actions-runner-linux-arm64-2.322.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.322.0/actions-runner-linux-arm64-2.322.0.tar.gz
- sudo -u ${RUNNER_USER} tar -xzf ./actions-runner-linux-arm64-2.322.0.tar.gz
- sudo rm ./actions-runner-linux-arm64-2.322.0.tar.gz
+ curl -o actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
+ sudo -u ${RUNNER_USER} tar -xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
+ sudo rm ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
sudo chmod +x ./config.sh
}
@@ -149,25 +150,32 @@ EOL
}
install_service() {
+ local service_name
+ if [ -f "${RUNNER_DIR}/.service" ]; then
+ service_name=$(cat "${RUNNER_DIR}/.service")
+ else
+ service_name="actions.runner.sunnypilot.$(uname -n)"
+ fi
+
+ create_service_template
remount_rw
+ local service_path="/etc/systemd/system/${service_name}"
echo "Installing systemd service..."
+ if [ -f "${service_path}" ]; then
+ echo "Service ${service_path} found in systemd, we will delete it"
+ sudo rm -f "${service_path}"
+ fi
+
cd "$RUNNER_DIR"
sudo ./svc.sh install $RUNNER_USER
if [ "$START_AT_BOOT" = false ]; then
- local service_name
- if [ -f "${RUNNER_DIR}/.service" ]; then
- service_name=$(cat "${RUNNER_DIR}/.service")
- else
- service_name="actions.runner.sunnypilot.$(uname -n)"
- fi
sudo systemctl disable "${service_name}"
fi
remount_ro
}
check_restore_prerequisites() {
- local needs_restore=false
local can_restore=false
local service_name=""
@@ -189,25 +197,16 @@ check_restore_prerequisites() {
exit 1
fi
- # Then check if restoration is needed (if either service or user is missing)
- if ! systemctl list-unit-files "${service_name}" &>/dev/null; then
- echo "Service ${service_name} not found in systemd"
- needs_restore=true
- fi
-
if ! id "${RUNNER_USER}" &>/dev/null; then
echo "User ${RUNNER_USER} does not exist"
- needs_restore=true
fi
# Only proceed if we can restore AND need to restore
- if [ "$can_restore" = true ] && [ "$needs_restore" = true ]; then
- echo "Restoration is needed and possible"
+ if [ "$can_restore" = true ]; then
+ echo "Restoration is possible"
return 0
else
- if [ "$needs_restore" = false ]; then
- echo "System is already properly configured (user and service exist)"
- fi
+ echo "No restoration possible"
exit 0
fi
}
@@ -226,7 +225,6 @@ perform_install() {
setup_system_configs
install_runner
set_directory_permissions
- create_service_template
configure_runner
install_service
echo "Installation completed successfully"
diff --git a/selfdrive/assets/icons/microphone.png b/selfdrive/assets/icons/microphone.png
new file mode 100644
index 0000000000..6cb9cc0254
--- /dev/null
+++ b/selfdrive/assets/icons/microphone.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9fc1f7f31d41f26ea7d6f52b3096f7a91844a3b897bc233a8489253c46f0403b
+size 6324
diff --git a/selfdrive/assets/offroad/fcc.html b/selfdrive/assets/offroad/fcc.html
index 793bea533c..960a7a06cb 100644
--- a/selfdrive/assets/offroad/fcc.html
+++ b/selfdrive/assets/offroad/fcc.html
@@ -12,11 +12,10 @@
Quectel/EG25-G
FCC ID: XMR201903EG25G
-
- This device complies with Part 15 of the FCC Rules.
- Operation is subject to the following two conditions:
+
This device complies with Part 15 of the FCC Rules.
+ Operation is subject to the following two conditions:
- (1) this device may not cause harmful interference, and
+
(1) this device may not cause harmful interference, and
(2) this device must accept any interference received, including interference that may cause undesired operation.
The following test reports are subject to this declaration:
diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py
index 1b9a1522a1..8448af56db 100755
--- a/selfdrive/car/card.py
+++ b/selfdrive/car/card.py
@@ -307,6 +307,7 @@ class Car:
# sunnypilot
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
+ self.v_cruise_helper.read_custom_set_speed_params()
time.sleep(0.1)
diff --git a/selfdrive/car/cruise.py b/selfdrive/car/cruise.py
index d696566ba5..01ef2d7c99 100644
--- a/selfdrive/car/cruise.py
+++ b/selfdrive/car/cruise.py
@@ -3,6 +3,7 @@ import numpy as np
from cereal import car
from openpilot.common.conversions import Conversions as CV
+from openpilot.sunnypilot.selfdrive.car.cruise_ext import VCruiseHelperSP
# WARNING: this value was determined based on the model's training distribution,
@@ -28,8 +29,9 @@ CRUISE_INTERVAL_SIGN = {
}
-class VCruiseHelper:
+class VCruiseHelper(VCruiseHelperSP):
def __init__(self, CP):
+ VCruiseHelperSP.__init__(self)
self.CP = CP
self.v_cruise_kph = V_CRUISE_UNSET
self.v_cruise_cluster_kph = V_CRUISE_UNSET
@@ -99,7 +101,7 @@ class VCruiseHelper:
if not self.button_change_states[button_type]["enabled"]:
return
- v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
+ long_press, v_cruise_delta = VCruiseHelperSP.update_v_cruise_delta(self, long_press, v_cruise_delta)
if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
else:
diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py
index 576e73b7e6..9ef985fcc4 100755
--- a/selfdrive/controls/controlsd.py
+++ b/selfdrive/controls/controlsd.py
@@ -44,7 +44,7 @@ class Controls(ControlsExt):
self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
- 'driverMonitoringState', 'onroadEvents', 'driverAssistance'] + self.sm_services_ext,
+ 'driverMonitoringState', 'onroadEvents', 'driverAssistance', 'liveDelay'] + self.sm_services_ext,
poll='selfdriveState')
self.pm = messaging.PubMaster(['carControl', 'controlsState'] + self.pm_services_ext)
@@ -93,6 +93,8 @@ class Controls(ControlsExt):
torque_params.frictionCoefficientFiltered)
self.LaC.extension.update_model_v2(self.sm['modelV2'])
+ calculated_lag = self.LaC.extension.lagd_torqued_main(self.CP, self.sm['liveDelay'])
+ self.LaC.extension.update_lateral_lag(calculated_lag)
long_plan = self.sm['longitudinalPlan']
model_v2 = self.sm['modelV2']
diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py
index 56eeac3bdf..51eb694b4c 100644
--- a/selfdrive/controls/lib/drive_helpers.py
+++ b/selfdrive/controls/lib/drive_helpers.py
@@ -1,5 +1,4 @@
import numpy as np
-from cereal import log
from opendbc.car.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.common.realtime import DT_CTRL, DT_MDL
@@ -40,14 +39,6 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll):
return float(new_curvature), limited_accel or limited_max_curv
-def get_speed_error(modelV2: log.ModelDataV2, v_ego: float) -> float:
- # ToDo: Try relative error, and absolute speed
- if len(modelV2.temporalPose.trans):
- vel_err = np.clip(modelV2.temporalPose.trans[0] - v_ego, -MAX_VEL_ERR, MAX_VEL_ERR)
- return float(vel_err)
- return 0.0
-
-
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05):
if len(speeds) == len(t_idxs):
v_now = speeds[0]
diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py
index 5edf4cf91b..467f403103 100644
--- a/selfdrive/controls/lib/latcontrol_torque.py
+++ b/selfdrive/controls/lib/latcontrol_torque.py
@@ -31,7 +31,6 @@ class LatControlTorque(LatControl):
self.pid = PIDController(self.torque_params.kp, self.torque_params.ki,
k_f=self.torque_params.kf, pos_limit=self.steer_max, neg_limit=-self.steer_max)
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
- self.use_steering_angle = self.torque_params.useSteeringAngle
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.extension = LatControlTorqueExt(self, CP, CP_SP)
@@ -47,16 +46,9 @@ class LatControlTorque(LatControl):
output_torque = 0.0
pid_log.active = False
else:
- actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
+ actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
- if self.use_steering_angle:
- actual_curvature = actual_curvature_vm
- curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
- else:
- assert calibrated_pose is not None
- actual_curvature_pose = calibrated_pose.angular_velocity.yaw / CS.vEgo
- actual_curvature = np.interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_pose])
- curvature_deadzone = 0.0
+ curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
desired_lateral_accel = desired_curvature * CS.vEgo ** 2
# desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py
index 4e842f8d1b..46aec8e611 100755
--- a/selfdrive/controls/lib/longitudinal_planner.py
+++ b/selfdrive/controls/lib/longitudinal_planner.py
@@ -11,7 +11,7 @@ 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 T_IDXS as T_IDXS_MPC
-from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_speed_error, get_accel_from_plan
+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
@@ -54,6 +54,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
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'
LongitudinalPlannerSP.__init__(self, self.CP, self.mpc)
self.fcw = False
@@ -63,7 +64,6 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
self.prev_accel_clip = [ACCEL_MIN, ACCEL_MAX]
- self.v_model_error = 0.0
self.output_a_target = 0.0
self.output_should_stop = False
@@ -73,12 +73,12 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
self.solverExecutionTime = 0.0
@staticmethod
- def parse_model(model_msg, model_error):
+ def parse_model(model_msg):
if (len(model_msg.position.x) == ModelConstants.IDX_N and
len(model_msg.velocity.x) == ModelConstants.IDX_N and
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
- x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
- v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
+ x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x)
+ v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x)
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC))
else:
@@ -94,9 +94,13 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
def update(self, sm):
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
+ if not self.mlsim:
+ self.mpc.mode = self.mode
LongitudinalPlannerSP.update(self, sm)
if dec_mpc_mode := self.get_mpc_mode():
self.mode = dec_mpc_mode
+ if not self.mlsim:
+ self.mpc.mode = dec_mpc_mode
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
@@ -133,9 +137,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
- # Compute model v_ego error
- self.v_model_error = get_speed_error(sm['modelV2'], v_ego)
- x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], self.v_model_error)
+ x, v, a, j, 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
@@ -171,7 +173,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
- if self.mode == 'acc':
+ if self.mode == 'acc' or not self.mlsim:
output_a_target = output_a_target_mpc
self.output_should_stop = output_should_stop_mpc
else:
diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py
deleted file mode 100755
index 7207bd1ef1..0000000000
--- a/selfdrive/debug/format_fingerprints.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-import jinja2
-import os
-
-from cereal import car
-from openpilot.common.basedir import BASEDIR
-from opendbc.car.interfaces import get_interface_attr
-
-Ecu = car.CarParams.Ecu
-
-CARS = get_interface_attr('CAR')
-FW_VERSIONS = get_interface_attr('FW_VERSIONS')
-FINGERPRINTS = get_interface_attr('FINGERPRINTS')
-ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
-
-FINGERPRINTS_PY_TEMPLATE = jinja2.Template("""
-{%- if FINGERPRINTS[brand] %}
-# ruff: noqa: E501
-{% endif %}
-{% if FW_VERSIONS[brand] %}
-from opendbc.car.structs import CarParams
-{% endif %}
-from opendbc.car.{{brand}}.values import CAR
-{% if FW_VERSIONS[brand] %}
-
-Ecu = CarParams.Ecu
-{% endif %}
-{% if comments +%}
-{{ comments | join() }}
-{% endif %}
-{% if FINGERPRINTS[brand] %}
-
-FINGERPRINTS = {
-{% for car, fingerprints in FINGERPRINTS[brand].items() %}
- CAR.{{car.name}}: [{
-{% for fingerprint in fingerprints %}
-{% if not loop.first %}
- {{ "{" }}
-{% endif %}
- {% for key, value in fingerprint.items() %}{{key}}: {{value}}{% if not loop.last %}, {% endif %}{% endfor %}
-
- }{% if loop.last %}]{% endif %},
-{% endfor %}
-{% endfor %}
-}
-{% endif %}
-
-FW_VERSIONS{% if not FW_VERSIONS[brand] %}: dict[str, dict[tuple, list[bytes]]]{% endif %} = {
-{% for car, _ in FW_VERSIONS[brand].items() %}
- CAR.{{car.name}}: {
-{% for key, fw_versions in FW_VERSIONS[brand][car].items() %}
- (Ecu.{{ECU_NAME[key[0]]}}, 0x{{"%0x" | format(key[1] | int)}}, \
-{% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [
- {% for fw_version in (fw_versions + extra_fw_versions.get(car, {}).get(key, [])) | unique | sort %}
- {{fw_version}},
- {% endfor %}
- ],
-{% endfor %}
- },
-{% endfor %}
-}
-
-""", trim_blocks=True)
-
-
-def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tuple, list[bytes]]] = None):
- extra_fw_versions = extra_fw_versions or {}
-
- fingerprints_file = os.path.join(BASEDIR, f"opendbc/car/{brand}/fingerprints.py")
- with open(fingerprints_file) as f:
- comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line]
-
- with open(fingerprints_file, "w") as f:
- f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME,
- FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=FW_VERSIONS,
- extra_fw_versions=extra_fw_versions))
-
-
-if __name__ == "__main__":
- for brand in FW_VERSIONS.keys():
- format_brand_fw_versions(brand)
diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py
index e265b70f1a..59f30dbf77 100755
--- a/selfdrive/locationd/calibrationd.py
+++ b/selfdrive/locationd/calibrationd.py
@@ -13,6 +13,7 @@ from typing import NoReturn
from cereal import log, car
import cereal.messaging as messaging
+from openpilot.system.hardware import HARDWARE
from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process
@@ -36,8 +37,11 @@ RPY_INIT = np.array([0.0,0.0,0.0])
WIDE_FROM_DEVICE_EULER_INIT = np.array([0.0, 0.0, 0.0])
HEIGHT_INIT = np.array([1.22])
-# These values are needed to accommodate the model frame in the narrow cam of the C3
-PITCH_LIMITS = np.array([-0.09074112085129739, 0.17])
+# These values are needed to accommodate the model frame in the narrow cam
+if HARDWARE.get_device_type() == 'mici':
+ PITCH_LIMITS = np.array([-0.143101, 0.22235988])
+else:
+ PITCH_LIMITS = np.array([-0.09074112085129739, 0.17])
YAW_LIMITS = np.array([-0.06912048084718224, 0.06912048084718235])
DEBUG = os.getenv("DEBUG") is not None
diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py
index a3e3ae4a8c..bf4588a40c 100644
--- a/selfdrive/locationd/helpers.py
+++ b/selfdrive/locationd/helpers.py
@@ -82,6 +82,12 @@ class PointBuckets:
total_points_valid = self.__len__() >= self.min_points_total
return individual_buckets_valid and total_points_valid
+ def get_valid_percent(self) -> int:
+ total_points_perc = min(self.__len__() / self.min_points_total * 100, 100)
+ individual_buckets_perc = min(min(len(v) / min_pts * 100 for v, min_pts in
+ zip(self.buckets.values(), self.buckets_min_points.values(), strict=True)), 100)
+ return int((total_points_perc + individual_buckets_perc) / 2)
+
def is_calculable(self) -> bool:
return all(len(v) > 0 for v in self.buckets.values())
diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py
index e070c16b1c..d7834f7f1f 100755
--- a/selfdrive/locationd/lagd.py
+++ b/selfdrive/locationd/lagd.py
@@ -157,8 +157,7 @@ class LateralLagEstimator:
block_count: int = BLOCK_NUM, min_valid_block_count: int = BLOCK_NUM_NEEDED, block_size: int = BLOCK_SIZE,
window_sec: float = MOVING_WINDOW_SEC, okay_window_sec: float = MIN_OKAY_WINDOW_SEC, min_recovery_buffer_sec: float = MIN_RECOVERY_BUFFER_SEC,
min_vego: float = MIN_VEGO, min_yr: float = MIN_ABS_YAW_RATE, min_ncc: float = MIN_NCC,
- max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE,
- enabled: bool = True):
+ max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE):
self.dt = dt
self.window_sec = window_sec
self.okay_window_sec = okay_window_sec
@@ -173,7 +172,6 @@ class LateralLagEstimator:
self.min_confidence = min_confidence
self.max_lat_accel = max_lat_accel
self.max_lat_accel_diff = max_lat_accel_diff
- self.enabled = enabled
self.t = 0.0
self.lat_active = False
@@ -208,7 +206,7 @@ class LateralLagEstimator:
liveDelay = msg.liveDelay
valid_mean_lag, valid_std, current_mean_lag, current_std = self.block_avg.get()
- if self.enabled and self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std):
+ if self.block_avg.valid_blocks >= self.min_valid_block_count and not np.isnan(valid_mean_lag) and not np.isnan(valid_std):
if valid_std > MAX_LAG_STD:
liveDelay.status = log.LiveDelayData.Status.invalid
else:
@@ -229,6 +227,8 @@ class LateralLagEstimator:
liveDelay.lateralDelayEstimateStd = 0.0
liveDelay.validBlocks = self.block_avg.valid_blocks
+ liveDelay.calPerc = min(100 * (self.block_avg.valid_blocks * self.block_size + self.block_avg.idx) //
+ (self.min_valid_block_count * self.block_size), 100)
if debug:
liveDelay.points = self.block_avg.values.flatten().tolist()
@@ -369,10 +369,7 @@ def main():
params = Params()
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
- # TODO: remove me, lagd is in shadow mode on release
- is_release = params.get_bool("IsReleaseBranch")
-
- lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency, enabled=not is_release)
+ lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency)
if (initial_lag_params := retrieve_initial_lag(params, CP)) is not None:
lag, valid_blocks = initial_lag_params
lag_learner.reset(lag, valid_blocks)
diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py
index a7f9c75ab4..a3dfce9c29 100644
--- a/selfdrive/locationd/test/test_lagd.py
+++ b/selfdrive/locationd/test/test_lagd.py
@@ -94,6 +94,7 @@ class TestLagd:
assert np.allclose(msg.liveDelay.lateralDelay, estimator.initial_lag)
assert np.allclose(msg.liveDelay.lateralDelayEstimate, estimator.initial_lag)
assert msg.liveDelay.validBlocks == 0
+ assert msg.liveDelay.calPerc == 0
def test_estimator_basics(self, subtests):
for lag_frames in range(5):
@@ -107,18 +108,7 @@ class TestLagd:
assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED
-
- def test_disabled_estimator(self):
- mocked_CP = car.CarParams(steerActuatorDelay=0.8)
- estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, enabled=False)
- lag_frames = 5
- process_messages(estimator, lag_frames, int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_NUM_NEEDED * BLOCK_SIZE)
- msg = estimator.get_msg(True)
- assert msg.liveDelay.status == 'unestimated'
- assert np.allclose(msg.liveDelay.lateralDelay, 1.0, atol=0.01)
- assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
- assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
- assert msg.liveDelay.validBlocks == BLOCK_NUM_NEEDED
+ assert msg.liveDelay.calPerc == 100
def test_estimator_masking(self):
mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19)
@@ -127,6 +117,7 @@ class TestLagd:
msg = estimator.get_msg(True)
assert np.allclose(msg.liveDelay.lateralDelayEstimate, lag_frames * DT, atol=0.01)
assert np.allclose(msg.liveDelay.lateralDelayEstimateStd, 0.0, atol=0.01)
+ assert msg.liveDelay.calPerc == 100
@pytest.mark.skipif(PC, reason="only on device")
@pytest.mark.timeout(60)
diff --git a/selfdrive/locationd/test/test_torqued.py b/selfdrive/locationd/test/test_torqued.py
new file mode 100644
index 0000000000..53f3120c36
--- /dev/null
+++ b/selfdrive/locationd/test/test_torqued.py
@@ -0,0 +1,25 @@
+from cereal import car
+from openpilot.selfdrive.locationd.torqued import TorqueEstimator
+
+
+def test_cal_percent():
+ est = TorqueEstimator(car.CarParams())
+ msg = est.get_msg()
+ assert msg.liveTorqueParameters.calPerc == 0
+
+ for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
+ est.filtered_points.buckets_min_points.values(), strict=True):
+ for _ in range(int(min_pts)):
+ est.filtered_points.add_point((low + high) / 2.0, 0.0)
+
+ # enough bucket points, but not enough total points
+ msg = est.get_msg()
+ assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
+
+ # add enough points to bucket with most capacity
+ key = list(est.filtered_points.buckets)[0]
+ for _ in range(est.min_points_total - len(est.filtered_points)):
+ est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
+
+ msg = est.get_msg()
+ assert msg.liveTorqueParameters.calPerc == 100
diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py
index 5ed86e457f..0066708319 100755
--- a/selfdrive/locationd/torqued.py
+++ b/selfdrive/locationd/torqued.py
@@ -11,6 +11,8 @@ from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, PoseCalibrator, Pose
+from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
+
HISTORY = 5 # secs
POINTS_PER_BUCKET = 1500
MIN_POINTS_TOTAL = 4000
@@ -49,8 +51,10 @@ class TorqueBuckets(PointBuckets):
break
-class TorqueEstimator(ParameterEstimator):
+class TorqueEstimator(ParameterEstimator, LagdToggle):
def __init__(self, CP, decimated=False, track_all_points=False):
+ super().__init__()
+ self.CP = CP
self.hist_len = int(HISTORY / DT_MDL)
self.lag = 0.0
self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
@@ -176,7 +180,7 @@ class TorqueEstimator(ParameterEstimator):
elif which == "liveCalibration":
self.calibrator.feed_live_calib(msg)
elif which == "liveDelay":
- self.lag = msg.lateralDelay
+ self.lag = self.lagd_torqued_main(self.CP, msg)
# calculate lateral accel from past steering torque
elif which == "livePose":
if len(self.raw_points['steer_torque']) == self.hist_len:
@@ -233,6 +237,7 @@ class TorqueEstimator(ParameterEstimator):
liveTorqueParameters.latAccelOffsetFiltered = float(self.filtered_params['latAccelOffset'].x)
liveTorqueParameters.frictionCoefficientFiltered = float(self.filtered_params['frictionCoefficient'].x)
liveTorqueParameters.totalBucketPoints = len(self.filtered_points)
+ liveTorqueParameters.calPerc = self.filtered_points.get_valid_percent()
liveTorqueParameters.decay = self.decay
liveTorqueParameters.maxResets = self.resets
return msg
diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript
index 10f3876190..0da3c24e19 100644
--- a/selfdrive/modeld/SConscript
+++ b/selfdrive/modeld/SConscript
@@ -56,17 +56,15 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
tg_compile(flags, model_name)
# Compile BIG model if USB GPU is available
-import subprocess
-from tinygrad import Device
-
-# 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)
-if b"AMD" in devs:
- del Device
- print("USB GPU detected... building")
- flags = "AMD=1 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")
+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 = "AMD=1 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/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py
index 2a3df3f652..7ab31ed2b4 100755
--- a/selfdrive/modeld/dmonitoringmodeld.py
+++ b/selfdrive/modeld/dmonitoringmodeld.py
@@ -14,7 +14,6 @@ import pickle
import ctypes
import numpy as np
from pathlib import Path
-from setproctitle import setproctitle
from cereal import messaging
from cereal.messaging import PubMaster, SubMaster
@@ -25,7 +24,6 @@ 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.selfdrive.modeld.parse_model_outputs import sigmoid
-from openpilot.system import sentry
MODEL_WIDTH, MODEL_HEIGHT = DM_INPUT_SIZE
CALIB_LEN = 3
@@ -133,12 +131,8 @@ def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts:
def main():
- setproctitle(PROCESS_NAME)
config_realtime_process(7, 5)
- sentry.set_tag("daemon", PROCESS_NAME)
- cloudlog.bind(daemon=PROCESS_NAME)
-
cl_context = CLContext()
model = ModelState(cl_context)
cloudlog.warning("models loaded, dmonitoringmodeld starting")
@@ -180,7 +174,4 @@ if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
- cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
- except Exception:
- sentry.capture_exception()
- raise
+ cloudlog.warning("got SIGINT")
diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py
index a91c6395c7..a2b54b420e 100644
--- a/selfdrive/modeld/fill_model_msg.py
+++ b/selfdrive/modeld/fill_model_msg.py
@@ -89,13 +89,6 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(modelV2.orientation, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.T_FROM_CURRENT_EULER].T)
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
- # temporal pose
- temporal_pose = modelV2.temporalPose
- temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
- temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
- temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist()
- temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist()
-
# poly path
fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T)
diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py
index 1e3d1782e6..31bfd707bb 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -19,7 +19,6 @@ import numpy as np
import cereal.messaging as messaging
from cereal import car, log
from pathlib import Path
-from setproctitle import setproctitle
from cereal.messaging import PubMaster, SubMaster
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from opendbc.car.car_helpers import get_demo_car_params
@@ -29,14 +28,15 @@ 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.common.transformations.model import get_warp_matrix
-from openpilot.system import sentry
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
-from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
+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.sunnypilot.livedelay.lagd_toggle import LagdToggle
+
PROCESS_NAME = "selfdrive.modeld.modeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
@@ -46,8 +46,8 @@ 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'
-LAT_SMOOTH_SECONDS = 0.0
-LONG_SMOOTH_SECONDS = 0.0
+LAT_SMOOTH_SECONDS = 0.1
+LONG_SMOOTH_SECONDS = 0.3
MIN_LAT_CONTROL_SPEED = 0.3
@@ -60,7 +60,11 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
action_t=long_action_t)
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
- desired_curvature = model_output['desired_curvature'][0, 0]
+ desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
+ plan[:,Plan.ORIENTATION_RATE][:,2],
+ ModelConstants.T_IDXS,
+ v_ego,
+ lat_action_t)
if v_ego > MIN_LAT_CONTROL_SPEED:
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
else:
@@ -86,10 +90,21 @@ class ModelState:
prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext):
- self.frames = {
- 'input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP),
- 'big_input_imgs': DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP)
- }
+ self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
+ 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.frames = {name: DrivingModelFrame(context, ModelConstants.TEMPORAL_SKIP) for name in self.vision_input_names}
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
self.full_features_buffer = np.zeros((1, ModelConstants.FULL_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32)
@@ -106,18 +121,6 @@ class ModelState:
'features_buffer': np.zeros((1, ModelConstants.INPUT_HISTORY_BUFFER_LEN, ModelConstants.FEATURE_LEN), dtype=np.float32),
}
- with open(VISION_METADATA_PATH, 'rb') as f:
- vision_metadata = pickle.load(f)
- self.vision_input_shapes = vision_metadata['input_shapes']
- 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]
-
# 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)
@@ -135,7 +138,7 @@ class ModelState:
parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()}
return parsed_model_outputs
- def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray,
+ 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'][0] = 0
@@ -148,8 +151,7 @@ class ModelState:
self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention']
self.numpy_inputs['lateral_control_params'][:] = inputs['lateral_control_params']
- imgs_cl = {'input_imgs': self.frames['input_imgs'].prepare(buf, transform.flatten()),
- 'big_input_imgs': self.frames['big_input_imgs'].prepare(wbuf, transform_wide.flatten())}
+ 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
@@ -177,7 +179,7 @@ class ModelState:
# TODO model only uses last value now
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :]
- self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
+ self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict}
if SEND_RAW_PRED:
@@ -189,9 +191,6 @@ class ModelState:
def main(demo=False):
cloudlog.warning("modeld init")
- sentry.set_tag("daemon", PROCESS_NAME)
- cloudlog.bind(daemon=PROCESS_NAME)
- setproctitle(PROCESS_NAME)
if not USBGPU:
# USB GPU currently saturates a core so can't do this yet,
# also need to move the aux USB interrupts for good timings
@@ -254,6 +253,8 @@ def main(demo=False):
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
cloudlog.info("modeld got CarParams: %s", CP.brand)
+ modeld_lagd = LagdToggle()
+
# TODO this needs more thought, use .2s extra for now to estimate other delays
# TODO Move smooth seconds to action function
long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS
@@ -299,7 +300,7 @@ def main(demo=False):
is_rhd = sm["driverMonitoringState"].isRHD
frame_id = sm["roadCameraState"].frameId
v_ego = max(sm["carState"].vEgo, 0.)
- lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
+ lat_delay = modeld_lagd.lagd_main(CP, sm, model)
lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32)
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
@@ -328,14 +329,16 @@ def main(demo=False):
if prepare_only:
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
+ bufs = {name: buf_extra if 'big' in name else buf_main for name in model.vision_input_names}
+ transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.vision_input_names}
inputs:dict[str, np.ndarray] = {
'desire': vec_desire,
'traffic_convention': traffic_convention,
'lateral_control_params': lateral_control_params,
- }
+ }
mt1 = time.perf_counter()
- model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
+ model_output = model.run(bufs, transforms, inputs, prepare_only)
mt2 = time.perf_counter()
model_execution_time = mt2 - mt1
@@ -375,7 +378,4 @@ if __name__ == "__main__":
args = parser.parse_args()
main(demo=args.demo)
except KeyboardInterrupt:
- cloudlog.warning(f"child {PROCESS_NAME} got SIGINT")
- except Exception:
- sentry.capture_exception()
- raise
+ cloudlog.warning("got SIGINT")
diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx
index 3601bbb5da..2fb716997d 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:98f0121ccb6f850077b04cc91bd33d370fc6cbdc2bd35f1ab55628a15a813f36
-size 15966721
+oid sha256:1741cad23f6f451782b5db6182218749ee12072e393d57eac36d8d5c55d9358a
+size 15583374
diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx
index aee6d8f1bf..fe636fb682 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:897f80d0388250f99bba69b6a8434560cc0fd83157cbeb0bc134c67fe4e64624
-size 34882971
+oid sha256:3d2bd82ba42341dba1bda5426e45c4c646db604c9ac422156eaa2b9ef26194f9
+size 46265993
diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py
index 810c44ccb9..783572d436 100644
--- a/selfdrive/modeld/parse_model_outputs.py
+++ b/selfdrive/modeld/parse_model_outputs.py
@@ -88,6 +88,12 @@ class Parser:
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_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_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
+ out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
+ for k in ['lead_prob', 'lane_lines_prob']:
+ self.parse_binary_crossentropy(k, outs)
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
@@ -95,17 +101,10 @@ class Parser:
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
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_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
- self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
- out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
if 'lat_planner_solution' in outs:
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
if 'desired_curvature' in outs:
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
- for k in ['lead_prob', 'lane_lines_prob']:
- self.parse_binary_crossentropy(k, outs)
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
return outs
diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py
index 3e48718002..9bfc638db3 100755
--- a/selfdrive/selfdrived/events.py
+++ b/selfdrive/selfdrived/events.py
@@ -585,11 +585,6 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
},
EventName.noGps: {
- ET.PERMANENT: Alert(
- "Poor GPS reception",
- "Ensure device has a clear view of the sky",
- AlertStatus.normal, AlertSize.mid,
- Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=600.)
},
EventName.tooDistracted: {
diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py
index edd0ae7d7c..dfda2c31f7 100755
--- a/selfdrive/selfdrived/selfdrived.py
+++ b/selfdrive/selfdrived/selfdrived.py
@@ -382,16 +382,16 @@ class SelfdriveD(CruiseHelper):
if (planner_fcw or model_fcw) and not self.CP.notCar:
self.events.add(EventName.fcw)
+ # GPS checks
+ gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
+ if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
+ self.events.add(EventName.noGps)
+ if gps_ok:
+ self.distance_traveled = 0
+ self.distance_traveled += abs(CS.vEgo) * DT_CTRL
+
# TODO: fix simulator
if not SIMULATION or REPLAY:
- # Not show in first 1.5 km to allow for driving out of garage. This event shows after 5 minutes
- gps_ok = self.sm.recv_frame[self.gps_location_service] > 0 and (self.sm.frame - self.sm.recv_frame[self.gps_location_service]) * DT_CTRL < 2.0
- if not gps_ok and self.sm['livePose'].inputsOK and (self.distance_traveled > 1500):
- self.events.add(EventName.noGps)
- if gps_ok:
- self.distance_traveled = 0
- self.distance_traveled += abs(CS.vEgo) * DT_CTRL
-
if self.sm['modelV2'].frameDropPerc > 20:
self.events.add(EventName.modeldLagging)
diff --git a/selfdrive/test/ci_shell.sh b/selfdrive/test/ci_shell.sh
deleted file mode 100755
index 76f0a9f78c..0000000000
--- a/selfdrive/test/ci_shell.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
-OP_ROOT="$DIR/../../"
-
-if [ -z "$BUILD" ]; then
- docker pull ghcr.io/commaai/openpilot-base:latest
-else
- docker build --cache-from ghcr.io/commaai/openpilot-base:latest -t ghcr.io/commaai/openpilot-base:latest -f $OP_ROOT/Dockerfile.openpilot_base .
-fi
-
-docker run \
- -it \
- --rm \
- --volume $OP_ROOT:$OP_ROOT \
- --workdir $PWD \
- --env PYTHONPATH=$OP_ROOT \
- ghcr.io/commaai/openpilot-base:latest \
- /bin/bash
diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py
index 201baf6b0e..59b8cf8250 100755
--- a/selfdrive/test/process_replay/model_replay.py
+++ b/selfdrive/test/process_replay/model_replay.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import os
+import pickle
import sys
from collections import defaultdict
from typing import Any
@@ -189,22 +190,44 @@ def model_replay(lr, frs):
print("----------------- Model Timing -----------------")
print("------------------------------------------------")
print(tabulate(rows, header, tablefmt="simple_grid", stralign="center", numalign="center", floatfmt=".4f"))
- assert timings_ok
+ assert timings_ok or PC
return msgs
+def get_frames():
+ regen_cache = "--regen-cache" in sys.argv
+ frames_cache = '/tmp/model_replay_cache' if PC else '/data/model_replay_cache'
+ os.makedirs(frames_cache, exist_ok=True)
+
+ cache_name = f'{frames_cache}/{TEST_ROUTE}_{SEGMENT}_{START_FRAME}_{END_FRAME}.pkl'
+ if os.path.isfile(cache_name) and not regen_cache:
+ try:
+ print(f"Loading frames from cache {cache_name}")
+ return pickle.load(open(cache_name, "rb"))
+ except Exception as e:
+ print(f"Failed to load frames from cache {cache_name}: {e}")
+
+ frs = {
+ 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
+ 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
+ 'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), pix_fmt='nv12', cache_size=END_FRAME - START_FRAME),
+ }
+ for fr in frs.values():
+ for fidx in range(START_FRAME, END_FRAME):
+ fr.get(fidx)
+ fr.it = None
+ print(f"Dumping frame cache {cache_name}")
+ pickle.dump(frs, open(cache_name, "wb"))
+ return frs
+
if __name__ == "__main__":
update = "--update" in sys.argv or (os.getenv("GIT_BRANCH", "") == 'master')
replay_dir = os.path.dirname(os.path.abspath(__file__))
# load logs
lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT, "rlog.zst")))
- frs = {
- 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "fcamera.hevc"), readahead=True),
- 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "dcamera.hevc"), readahead=True),
- 'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, "ecamera.hevc"), readahead=True)
- }
+ frs = get_frames()
log_msgs = []
# run replays
diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index 8966be7e20..bc5ccf7845 100755
--- a/selfdrive/test/process_replay/process_replay.py
+++ b/selfdrive/test/process_replay/process_replay.py
@@ -27,7 +27,7 @@ from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera
from openpilot.selfdrive.test.process_replay.migration import migrate_all
from openpilot.selfdrive.test.process_replay.capture import ProcessOutputCapture
from openpilot.tools.lib.logreader import LogIterable
-from openpilot.tools.lib.framereader import BaseFrameReader
+from openpilot.tools.lib.framereader import FrameReader
# Numpy gives different results based on CPU features after version 19
NUMPY_TOLERANCE = 1e-7
@@ -209,6 +209,7 @@ class ProcessContainer:
streams_metas = available_streams(all_msgs)
for meta in streams_metas:
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)
vipc_server.start_listener()
@@ -224,7 +225,7 @@ class ProcessContainer:
def start(
self, params_config: dict[str, Any], environ_config: dict[str, Any],
- all_msgs: LogIterable, frs: dict[str, BaseFrameReader] | None,
+ all_msgs: LogIterable, frs: dict[str, FrameReader] | None,
fingerprint: str | None, capture_output: bool
):
with self.prefix as p:
@@ -266,7 +267,7 @@ class ProcessContainer:
self.prefix.clean_dirs()
self._clean_env()
- def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, BaseFrameReader] | None) -> list[capnp._DynamicStructReader]:
+ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] | None) -> list[capnp._DynamicStructReader]:
assert self.rc and self.pm and self.sockets and self.process.proc
output_msgs = []
@@ -296,7 +297,7 @@ class ProcessContainer:
camera_state = getattr(m, m.which())
camera_meta = meta_from_camera_state(m.which())
assert frs is not None
- img = frs[m.which()].get(camera_state.frameId, pix_fmt="nv12")[0]
+ img = frs[m.which()].get(camera_state.frameId)
self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(),
camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof)
self.msg_queue = []
@@ -655,7 +656,7 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args,
def replay_process(
- cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] = None,
+ 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
) -> list[capnp._DynamicStructReader]:
@@ -683,7 +684,7 @@ def replay_process(
def _replay_multi_process(
- cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] | None, fingerprint: str | None,
+ cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None, fingerprint: str | None,
custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool
) -> list[capnp._DynamicStructReader]:
if fingerprint is not None:
diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit
index e692f56eba..79c03c65a8 100644
--- a/selfdrive/test/process_replay/ref_commit
+++ b/selfdrive/test/process_replay/ref_commit
@@ -1 +1 @@
-9e2fe2942fbf77f24bccdbef15893831f9c0b390
\ No newline at end of file
+de16c6fbe14e121c5e74cd944ce03a0e4672540d
\ No newline at end of file
diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py
index 273659c9ff..ec35a5c3ac 100755
--- a/selfdrive/test/process_replay/regen.py
+++ b/selfdrive/test/process_replay/regen.py
@@ -3,40 +3,17 @@ import os
import argparse
import time
import capnp
-import numpy as np
from typing import Any
from collections.abc import Iterable
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \
check_openpilot_enabled, check_most_messages_valid, get_custom_params_from_lr
-from openpilot.selfdrive.test.process_replay.vision_meta import DRIVER_CAMERA_FRAME_SIZES
from openpilot.selfdrive.test.update_ci_routes import upload_route
-from openpilot.tools.lib.framereader import FrameReader, BaseFrameReader, FrameType
+from openpilot.tools.lib.framereader import FrameReader
from openpilot.tools.lib.logreader import LogReader, LogIterable, save_log
from openpilot.tools.lib.openpilotci import get_url
-class DummyFrameReader(BaseFrameReader):
- def __init__(self, w: int, h: int, frame_count: int, pix_val: int):
- self.pix_val = pix_val
- self.w, self.h = w, h
- self.frame_count = frame_count
- self.frame_type = FrameType.raw
-
- def get(self, idx, count=1, pix_fmt="rgb24"):
- if pix_fmt == "rgb24":
- shape = (self.h, self.w, 3)
- elif pix_fmt == "nv12" or pix_fmt == "yuv420p":
- shape = (int((self.h * self.w) * 3 / 2),)
- else:
- raise NotImplementedError
-
- return [np.full(shape, self.pix_val, dtype=np.uint8) for _ in range(count)]
-
- @staticmethod
- def zero_dcamera():
- return DummyFrameReader(*DRIVER_CAMERA_FRAME_SIZES[("tici", "ar0231")], 1200, 0)
-
def regen_segment(
lr: LogIterable, frs: dict[str, Any] = None,
@@ -64,7 +41,7 @@ def setup_data_readers(
frs['wideRoadCameraState'] = FrameReader(get_url(route, str(sidx), "ecamera.hevc"))
if needs_driver_cam:
if dummy_driver_cam:
- frs['driverCameraState'] = DummyFrameReader.zero_dcamera()
+ frs['driverCameraState'] = FrameReader(get_url(route, str(sidx), "fcamera.hevc")) # Use fcam as dummy
else:
device_type = next(str(msg.initData.deviceType) for msg in lr if msg.which() == "initData")
assert device_type != "neo", "Driver camera not supported on neo segments. Use dummy dcamera."
diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py
index 14e1b2b79f..39c1e408eb 100755
--- a/selfdrive/test/process_replay/test_processes.py
+++ b/selfdrive/test/process_replay/test_processes.py
@@ -19,7 +19,6 @@ from openpilot.tools.lib.logreader import LogReader, save_log
IS_AZURE_TOKEN_DEFINED = os.getenv("AZURE_TOKEN")
source_segments = [
- ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.COMMA_BODY
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA
("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.HYUNDAI_KIA_EV6 (+ QCOM GPS)
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.TOYOTA_PRIUS
@@ -44,7 +43,6 @@ source_segments = [
]
segments = [
- ("BODY", "regen2F3C7259F1B|2025-04-08--23-00-23--0"),
("HYUNDAI", "regenAA0FC4ED71E|2025-04-08--22-57-50--0"),
("HYUNDAI2", "regenAFB9780D823|2025-04-08--23-00-34--0"),
("TOYOTA", "regen218A4DCFAA1|2025-04-08--22-57-51--0"),
@@ -65,7 +63,7 @@ segments = [
]
# dashcamOnly makes don't need to be tested until a full port is done
-excluded_interfaces = ["mock", "tesla"]
+excluded_interfaces = ["mock", "body"]
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
@@ -253,7 +251,7 @@ if __name__ == "__main__":
continue
# to speed things up, we only test all segments on card
- if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD', 'RIVIAN', 'TESLA'):
+ 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")
diff --git a/selfdrive/test/process_replay/test_regen.py b/selfdrive/test/process_replay/test_regen.py
index 6c4b48c8d5..5f26daf786 100644
--- a/selfdrive/test/process_replay/test_regen.py
+++ b/selfdrive/test/process_replay/test_regen.py
@@ -1,6 +1,6 @@
from parameterized import parameterized
-from openpilot.selfdrive.test.process_replay.regen import regen_segment, DummyFrameReader
+from openpilot.selfdrive.test.process_replay.regen import regen_segment
from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled
from openpilot.tools.lib.openpilotci import get_url
from openpilot.tools.lib.logreader import LogReader
@@ -18,7 +18,7 @@ def ci_setup_data_readers(route, sidx):
lr = LogReader(get_url(route, sidx, "rlog.bz2"))
frs = {
'roadCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
- 'driverCameraState': DummyFrameReader.zero_dcamera()
+ 'driverCameraState': FrameReader(get_url(route, sidx, "fcamera.hevc")),
}
if next((True for m in lr if m.which() == "wideRoadCameraState"), False):
frs["wideRoadCameraState"] = FrameReader(get_url(route, sidx, "ecamera.hevc"))
diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript
index 7a69173270..c5ee10eb25 100644
--- a/selfdrive/ui/SConscript
+++ b/selfdrive/ui/SConscript
@@ -112,7 +112,7 @@ 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], LIBS=raylib_libs)
# keep installers small
- assert f[0].get_size() < 1300*1e3, f[0].get_size()
+ assert f[0].get_size() < 1900*1e3, f[0].get_size()
# build watch3
if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'):
diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py
index 55de9e82f1..0fa91da552 100644
--- a/selfdrive/ui/layouts/home.py
+++ b/selfdrive/ui/layouts/home.py
@@ -4,10 +4,12 @@ from collections.abc import Callable
from enum import IntEnum
from openpilot.common.params import Params
from openpilot.selfdrive.ui.widgets.offroad_alerts import UpdateAlert, OffroadAlert
+from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButton
+from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
+from openpilot.selfdrive.ui.widgets.setup import SetupWidget
from openpilot.system.ui.lib.text_measure import measure_text_cached
-from openpilot.system.ui.lib.label import gui_label
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR
-
+from openpilot.system.ui.lib.widget import Widget
HEADER_HEIGHT = 80
HEAD_BUTTON_FONT_SIZE = 40
@@ -25,8 +27,9 @@ class HomeLayoutState(IntEnum):
ALERTS = 2
-class HomeLayout:
+class HomeLayout(Widget):
def __init__(self):
+ super().__init__()
self.params = Params()
self.update_alert = UpdateAlert()
@@ -47,6 +50,10 @@ class HomeLayout:
self.update_notif_rect = rl.Rectangle(0, 0, 200, HEADER_HEIGHT - 10)
self.alert_notif_rect = rl.Rectangle(0, 0, 220, HEADER_HEIGHT - 10)
+ self._prime_widget = PrimeWidget()
+ self._setup_widget = SetupWidget()
+
+ self._exp_mode_button = ExperimentalModeButton()
self._setup_callbacks()
def _setup_callbacks(self):
@@ -59,9 +66,7 @@ class HomeLayout:
def _set_state(self, state: HomeLayoutState):
self.current_state = state
- def render(self, rect: rl.Rectangle):
- self._update_layout_rects(rect)
-
+ def _render(self, rect: rl.Rectangle):
current_time = time.time()
if current_time - self.last_refresh >= REFRESH_INTERVAL:
self._refresh()
@@ -78,16 +83,16 @@ class HomeLayout:
elif self.current_state == HomeLayoutState.ALERTS:
self._render_alerts_view()
- def _update_layout_rects(self, rect: rl.Rectangle):
+ def _update_layout_rects(self):
self.header_rect = rl.Rectangle(
- rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, HEADER_HEIGHT
+ self._rect.x + CONTENT_MARGIN, self._rect.y + CONTENT_MARGIN, self._rect.width - 2 * CONTENT_MARGIN, HEADER_HEIGHT
)
- content_y = rect.y + CONTENT_MARGIN + HEADER_HEIGHT + SPACING
- content_height = rect.height - CONTENT_MARGIN - HEADER_HEIGHT - SPACING - CONTENT_MARGIN
+ content_y = self._rect.y + CONTENT_MARGIN + HEADER_HEIGHT + SPACING
+ content_height = self._rect.height - CONTENT_MARGIN - HEADER_HEIGHT - SPACING - CONTENT_MARGIN
self.content_rect = rl.Rectangle(
- rect.x + CONTENT_MARGIN, content_y, rect.width - 2 * CONTENT_MARGIN, content_height
+ self._rect.x + CONTENT_MARGIN, content_y, self._rect.width - 2 * CONTENT_MARGIN, content_height
)
left_width = self.content_rect.width - RIGHT_COLUMN_WIDTH - SPACING
@@ -170,28 +175,25 @@ class HomeLayout:
self.offroad_alert.render(self.content_rect)
def _render_left_column(self):
- rl.draw_rectangle_rounded(self.left_column_rect, 0.02, 10, PRIME_BG_COLOR)
- gui_label(self.left_column_rect, "Prime Widget", 48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+ self._prime_widget.render(self.left_column_rect)
def _render_right_column(self):
- widget_height = (self.right_column_rect.height - SPACING) // 2
-
+ exp_height = 125
exp_rect = rl.Rectangle(
- self.right_column_rect.x, self.right_column_rect.y, self.right_column_rect.width, widget_height
+ self.right_column_rect.x, self.right_column_rect.y, self.right_column_rect.width, exp_height
)
- rl.draw_rectangle_rounded(exp_rect, 0.02, 10, PRIME_BG_COLOR)
- gui_label(exp_rect, "Experimental Mode", 36, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+ self._exp_mode_button.render(exp_rect)
setup_rect = rl.Rectangle(
self.right_column_rect.x,
- self.right_column_rect.y + widget_height + SPACING,
+ self.right_column_rect.y + exp_height + SPACING,
self.right_column_rect.width,
- widget_height,
+ self.right_column_rect.height - exp_height - SPACING,
)
- rl.draw_rectangle_rounded(setup_rect, 0.02, 10, PRIME_BG_COLOR)
- gui_label(setup_rect, "Setup", 36, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
+ self._setup_widget.render(setup_rect)
def _refresh(self):
+ # TODO: implement _update_state with a timer
self.update_available = self.update_alert.refresh()
self.alert_count = self.offroad_alert.refresh()
self._update_state_priority(self.update_available, self.alert_count > 0)
diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py
index 08ce50407d..a3081143f4 100644
--- a/selfdrive/ui/layouts/main.py
+++ b/selfdrive/ui/layouts/main.py
@@ -1,10 +1,12 @@
import pyray as rl
from enum import IntEnum
+import cereal.messaging as messaging
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
-from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.selfdrive.ui.layouts.settings.settings import SettingsLayout, PanelType
+from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView
+from openpilot.system.ui.lib.widget import Widget
class MainState(IntEnum):
@@ -13,14 +15,15 @@ class MainState(IntEnum):
ONROAD = 2
-class MainLayout:
+class MainLayout(Widget):
def __init__(self):
+ super().__init__()
+
+ self._pm = messaging.PubMaster(['userFlag'])
+
self._sidebar = Sidebar()
- self._sidebar_visible = True
self._current_mode = MainState.HOME
self._prev_onroad = False
- self._window_rect = None
- self._current_callback: callable | None = None
# Initialize layouts
self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()}
@@ -31,32 +34,23 @@ class MainLayout:
# Set callbacks
self._setup_callbacks()
- def render(self, rect):
- self._current_callback = None
-
- self._update_layout_rects(rect)
+ def _render(self, _):
self._handle_onroad_transition()
self._render_main_content()
- self._handle_input()
-
- if self._current_callback:
- self._current_callback()
def _setup_callbacks(self):
- self._sidebar.set_callbacks(
- on_settings=lambda: setattr(self, '_current_callback', self._on_settings_clicked),
- on_flag=lambda: setattr(self, '_current_callback', self._on_flag_clicked),
- )
- self._layouts[MainState.SETTINGS].set_callbacks(
- on_close=lambda: setattr(self, '_current_callback', self._set_mode_for_state)
- )
+ self._sidebar.set_callbacks(on_settings=self._on_settings_clicked,
+ on_flag=self._on_flag_clicked)
+ self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
+ self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
+ self._layouts[MainState.ONROAD].set_callbacks(on_click=self._on_onroad_clicked)
+ device.add_interactive_timeout_callback(self._set_mode_for_state)
- def _update_layout_rects(self, rect):
- self._window_rect = rect
- self._sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
+ 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_visible else 0
- self._content_rect = rl.Rectangle(rect.y + x_offset, rect.y, rect.width - x_offset, 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)
def _handle_onroad_transition(self):
if ui_state.started != self._prev_onroad:
@@ -66,31 +60,34 @@ class MainLayout:
def _set_mode_for_state(self):
if ui_state.started:
+ # Don't hide sidebar from interactive timeout
+ if self._current_mode != MainState.ONROAD:
+ self._sidebar.set_visible(False)
self._current_mode = MainState.ONROAD
- self._sidebar_visible = False
else:
self._current_mode = MainState.HOME
- self._sidebar_visible = True
+ self._sidebar.set_visible(True)
+
+ def open_settings(self, panel_type: PanelType):
+ self._layouts[MainState.SETTINGS].set_current_panel(panel_type)
+ self._current_mode = MainState.SETTINGS
+ self._sidebar.set_visible(False)
def _on_settings_clicked(self):
- self._current_mode = MainState.SETTINGS
- self._sidebar_visible = False
+ self.open_settings(PanelType.DEVICE)
def _on_flag_clicked(self):
- pass
+ user_flag = messaging.new_message('userFlag')
+ user_flag.valid = True
+ self._pm.send('userFlag', user_flag)
+
+ def _on_onroad_clicked(self):
+ self._sidebar.set_visible(not self._sidebar.is_visible)
def _render_main_content(self):
# Render sidebar
- if self._sidebar_visible:
+ if self._sidebar.is_visible:
self._sidebar.render(self._sidebar_rect)
- content_rect = self._content_rect if self._sidebar_visible else self._window_rect
+ content_rect = self._content_rect if self._sidebar.is_visible else self._rect
self._layouts[self._current_mode].render(content_rect)
-
- def _handle_input(self):
- if self._current_mode != MainState.ONROAD or not rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
- return
-
- mouse_pos = rl.get_mouse_position()
- if rl.check_collision_point_rec(mouse_pos, self._content_rect):
- self._sidebar_visible = not self._sidebar_visible
diff --git a/selfdrive/ui/layouts/network.py b/selfdrive/ui/layouts/network.py
index bf52f18876..c227facd04 100644
--- a/selfdrive/ui/layouts/network.py
+++ b/selfdrive/ui/layouts/network.py
@@ -1,19 +1,17 @@
import pyray as rl
+from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
from openpilot.system.ui.widgets.network import WifiManagerUI
-class NetworkLayout:
+class NetworkLayout(Widget):
def __init__(self):
+ super().__init__()
self.wifi_manager = WifiManagerWrapper()
self.wifi_ui = WifiManagerUI(self.wifi_manager)
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
self.wifi_ui.render(rect)
- @property
- def require_full_screen(self):
- return self.wifi_ui.require_full_screen
-
def shutdown(self):
self.wifi_manager.shutdown()
diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py
index 6ab5be2792..ce6e9e4af6 100644
--- a/selfdrive/ui/layouts/settings/developer.py
+++ b/selfdrive/ui/layouts/settings/developer.py
@@ -1,5 +1,8 @@
-from openpilot.system.ui.lib.list_view import ListView, toggle_item
+from openpilot.system.ui.lib.list_view import toggle_item
+from openpilot.system.ui.lib.scroller import Scroller
+from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
+from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item
# Description constants
DESCRIPTIONS = {
@@ -8,11 +11,16 @@ DESCRIPTIONS = {
"See https://docs.comma.ai/how-to/connect-to-comma for more info."
),
'joystick_debug_mode': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)",
+ 'ssh_key': (
+ "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."
+ ),
}
-class DeveloperLayout:
+class DeveloperLayout(Widget):
def __init__(self):
+ super().__init__()
self._params = Params()
items = [
toggle_item(
@@ -21,6 +29,7 @@ class DeveloperLayout:
initial_state=self._params.get_bool("AdbEnabled"),
callback=self._on_enable_adb,
),
+ ssh_key_item("SSH Key", description=DESCRIPTIONS["ssh_key"]),
toggle_item(
"Joystick Debug Mode",
description=DESCRIPTIONS["joystick_debug_mode"],
@@ -41,10 +50,10 @@ class DeveloperLayout:
),
]
- self._list_widget = ListView(items)
+ self._scroller = Scroller(items, line_separator=True, spacing=0)
- def render(self, rect):
- self._list_widget.render(rect)
+ def _render(self, rect):
+ self._scroller.render(rect)
def _on_enable_adb(self): pass
def _on_joystick_debug_mode(self): pass
diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py
index 5051dd6ba6..69b7822ba9 100644
--- a/selfdrive/ui/layouts/settings/device.py
+++ b/selfdrive/ui/layouts/settings/device.py
@@ -1,47 +1,150 @@
-from openpilot.system.ui.lib.list_view import ListView, text_item, button_item
+import os
+import json
+
+from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
+from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog
+from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.hardware import TICI
+from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.list_view import text_item, button_item, dual_button_item
+from openpilot.system.ui.lib.scroller import Scroller
+from openpilot.system.ui.lib.widget import Widget, DialogResult
+from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
+from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
+from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog, alert_dialog
+from openpilot.system.ui.widgets.html_render import HtmlRenderer
# Description constants
DESCRIPTIONS = {
'pair_device': "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.",
'driver_camera': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)",
'reset_calibration': (
- "openpilot requires the device to be mounted within 4° left or right and within 5° " +
- "up or 9° down. openpilot is continuously calibrating, resetting is rarely required."
+ "openpilot requires the device to be mounted within 4° left or right and within 5° " +
+ "up or 9° down. openpilot is continuously calibrating, resetting is rarely required."
),
'review_guide': "Review the rules, features, and limitations of openpilot",
}
-class DeviceLayout:
+class DeviceLayout(Widget):
def __init__(self):
- params = Params()
- dongle_id = params.get("DongleId", encoding="utf-8") or "N/A"
- serial = params.get("HardwareSerial") or "N/A"
+ super().__init__()
+
+ 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: HtmlRenderer | None = None
+
+ items = self._initialize_items()
+ self._scroller = Scroller(items, line_separator=True, spacing=0)
+
+ def _initialize_items(self):
+ dongle_id = self._params.get("DongleId", encoding="utf-8") or "N/A"
+ serial = self._params.get("HardwareSerial") or "N/A"
items = [
text_item("Dongle ID", dongle_id),
text_item("Serial", serial),
- button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], self._on_pair_device),
- button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], self._on_driver_camera),
- button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], self._on_reset_calibration),
+ button_item("Pair Device", "PAIR", DESCRIPTIONS['pair_device'], callback=self._pair_device),
+ button_item("Driver Camera", "PREVIEW", DESCRIPTIONS['driver_camera'], callback=self._show_driver_camera, enabled=ui_state.is_offroad),
+ button_item("Reset Calibration", "RESET", DESCRIPTIONS['reset_calibration'], callback=self._reset_calibration_prompt),
+ regulatory_btn := button_item("Regulatory", "VIEW", callback=self._on_regulatory),
button_item("Review Training Guide", "REVIEW", DESCRIPTIONS['review_guide'], self._on_review_training_guide),
+ button_item("Change Language", "CHANGE", callback=self._show_language_selection, enabled=ui_state.is_offroad),
+ dual_button_item("Reboot", "Power Off", left_callback=self._reboot_prompt, right_callback=self._power_off_prompt),
]
+ regulatory_btn.set_visible(TICI)
+ return items
- if TICI:
- items.append(button_item("Regulatory", "VIEW", callback=self._on_regulatory))
+ def _render(self, rect):
+ self._scroller.render(rect)
- items.append(button_item("Change Language", "CHANGE", callback=self._on_change_language))
+ def _show_language_selection(self):
+ try:
+ languages_file = os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")
+ with open(languages_file, encoding='utf-8') as f:
+ languages = json.load(f)
- self._list_widget = ListView(items)
+ self._select_language_dialog = MultiOptionDialog("Select a language", languages)
+ gui_app.set_modal_overlay(self._select_language_dialog, callback=self._handle_language_selection)
+ except FileNotFoundError:
+ pass
- def render(self, rect):
- self._list_widget.render(rect)
+ def _handle_language_selection(self, result: int):
+ if result == 1 and self._select_language_dialog:
+ selected_language = self._select_language_dialog.selection
+ self._params.put("LanguageSetting", selected_language)
+
+ self._select_language_dialog = None
+
+ 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 _reset_calibration_prompt(self):
+ if ui_state.engaged:
+ gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reset Calibration"))
+ return
+
+ gui_app.set_modal_overlay(
+ lambda: confirm_dialog("Are you sure you want to reset calibration?", "Reset"),
+ callback=self._reset_calibration,
+ )
+
+ def _reset_calibration(self, result: int):
+ if ui_state.engaged or result != DialogResult.CONFIRM:
+ return
+
+ self._params.remove("CalibrationParams")
+ self._params.remove("LiveTorqueParameters")
+ self._params.remove("LiveParameters")
+ self._params.remove("LiveParametersV2")
+ self._params.remove("LiveDelay")
+ self._params.put_bool("OnroadCycleRequested", True)
+
+ def _reboot_prompt(self):
+ if ui_state.engaged:
+ gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Reboot"))
+ return
+
+ gui_app.set_modal_overlay(
+ lambda: confirm_dialog("Are you sure you want to reboot?", "Reboot"),
+ callback=self._perform_reboot,
+ )
+
+ def _perform_reboot(self, result: int):
+ if not ui_state.engaged and result == DialogResult.CONFIRM:
+ self._params.put_bool_nonblocking("DoReboot", True)
+
+ def _power_off_prompt(self):
+ if ui_state.engaged:
+ gui_app.set_modal_overlay(lambda: alert_dialog("Disengage to Power Off"))
+ return
+
+ gui_app.set_modal_overlay(
+ lambda: confirm_dialog("Are you sure you want to power off?", "Power Off"),
+ callback=self._perform_power_off,
+ )
+
+ 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))
+
+ def _on_regulatory(self):
+ if not self._fcc_dialog:
+ self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html"))
+
+ gui_app.set_modal_overlay(self._fcc_dialog,
+ callback=lambda result: setattr(self, '_fcc_dialog', None),
+ )
- def _on_pair_device(self): pass
- def _on_driver_camera(self): pass
- def _on_reset_calibration(self): pass
def _on_review_training_guide(self): pass
- def _on_regulatory(self): pass
- def _on_change_language(self): pass
diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py
new file mode 100644
index 0000000000..298f66e276
--- /dev/null
+++ b/selfdrive/ui/layouts/settings/firehose.py
@@ -0,0 +1,180 @@
+import pyray as rl
+import json
+import time
+import threading
+
+from openpilot.common.api import Api, api_get
+from openpilot.common.params import Params
+from openpilot.common.swaglog import cloudlog
+from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.wrap_text import wrap_text
+from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+from openpilot.system.ui.lib.widget import Widget
+from openpilot.selfdrive.ui.ui_state import ui_state
+
+
+TITLE = "Firehose Mode"
+DESCRIPTION = (
+ "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."
+)
+INSTRUCTIONS = (
+ "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"
+ + "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."
+)
+
+
+class FirehoseLayout(Widget):
+ PARAM_KEY = "ApiCache_FirehoseStats"
+ GREEN = rl.Color(46, 204, 113, 255)
+ RED = rl.Color(231, 76, 60, 255)
+ GRAY = rl.Color(68, 68, 68, 255)
+ LIGHT_GRAY = rl.Color(228, 228, 228, 255)
+ UPDATE_INTERVAL = 30 # seconds
+
+ def __init__(self):
+ super().__init__()
+ self.params = Params()
+ self.segment_count = self._get_segment_count()
+ self.scroll_panel = GuiScrollPanel()
+
+ self.running = True
+ self.update_thread = threading.Thread(target=self._update_loop, daemon=True)
+ self.update_thread.start()
+ self.last_update_time = 0
+
+ def _get_segment_count(self) -> int:
+ stats = self.params.get(self.PARAM_KEY, encoding='utf8')
+ if not stats:
+ return 0
+ try:
+ return int(json.loads(stats).get("firehose", 0))
+ except Exception:
+ cloudlog.exception(f"Failed to decode firehose stats: {stats}")
+ return 0
+
+ def __del__(self):
+ self.running = False
+ if self.update_thread and self.update_thread.is_alive():
+ self.update_thread.join(timeout=1.0)
+
+ def _render(self, rect: rl.Rectangle):
+ # Calculate content dimensions
+ content_width = rect.width - 80
+ content_height = self._calculate_content_height(int(content_width))
+ content_rect = rl.Rectangle(rect.x, rect.y, rect.width, content_height)
+
+ # Handle scrolling and render with clipping
+ scroll_offset = self.scroll_panel.handle_scroll(rect, content_rect)
+ rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
+ self._render_content(rect, scroll_offset)
+ rl.end_scissor_mode()
+
+ def _calculate_content_height(self, content_width: int) -> int:
+ height = 80 # Top margin
+
+ # Title
+ height += 100 + 40
+
+ # Description
+ desc_font = gui_app.font(FontWeight.NORMAL)
+ desc_lines = wrap_text(desc_font, DESCRIPTION, 45, content_width)
+ height += len(desc_lines) * 45 + 40
+
+ # Status section
+ height += 32 # Separator
+ status_text, _ = self._get_status()
+ status_lines = wrap_text(gui_app.font(FontWeight.BOLD), status_text, 60, content_width)
+ height += len(status_lines) * 60 + 20
+
+ # Contribution count (if available)
+ if self.segment_count > 0:
+ contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
+ contrib_lines = wrap_text(gui_app.font(FontWeight.BOLD), contrib_text, 52, content_width)
+ height += len(contrib_lines) * 52 + 20
+
+ # Instructions section
+ height += 32 # Separator
+ inst_lines = wrap_text(gui_app.font(FontWeight.NORMAL), INSTRUCTIONS, 40, content_width)
+ height += len(inst_lines) * 40 + 40 # Bottom margin
+
+ return height
+
+ def _render_content(self, rect: rl.Rectangle, scroll_offset: rl.Vector2):
+ x = int(rect.x + 40)
+ y = int(rect.y + 40 + scroll_offset.y)
+ w = int(rect.width - 80)
+
+ # Title
+ title_font = gui_app.font(FontWeight.MEDIUM)
+ rl.draw_text_ex(title_font, TITLE, rl.Vector2(x, y), 100, 0, rl.WHITE)
+ y += 140
+
+ # Description
+ y = self._draw_wrapped_text(x, y, w, DESCRIPTION, gui_app.font(FontWeight.NORMAL), 45, rl.WHITE)
+ y += 40
+
+ # Separator
+ rl.draw_rectangle(x, y, w, 2, self.GRAY)
+ y += 30
+
+ # Status
+ status_text, status_color = self._get_status()
+ y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color)
+ y += 20
+
+ # Contribution count (if available)
+ if self.segment_count > 0:
+ contrib_text = f"{self.segment_count} segment(s) of your driving is in the training dataset so far."
+ y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE)
+ y += 20
+
+ # Separator
+ rl.draw_rectangle(x, y, w, 2, self.GRAY)
+ y += 30
+
+ # Instructions
+ self._draw_wrapped_text(x, y, w, INSTRUCTIONS, gui_app.font(FontWeight.NORMAL), 40, self.LIGHT_GRAY)
+
+ def _draw_wrapped_text(self, x, y, width, text, font, size, color):
+ wrapped = wrap_text(font, text, size, width)
+ for line in wrapped:
+ rl.draw_text_ex(font, line, rl.Vector2(x, y), size, 0, color)
+ y += size
+ return y
+
+ def _get_status(self) -> tuple[str, rl.Color]:
+ network_type = ui_state.sm["deviceState"].networkType
+ network_metered = ui_state.sm["deviceState"].networkMetered
+
+ if not network_metered and network_type != 0: # Not metered and connected
+ return "ACTIVE", self.GREEN
+ else:
+ return "INACTIVE: connect to an unmetered network", self.RED
+
+ def _fetch_firehose_stats(self):
+ try:
+ dongle_id = self.params.get("DongleId", encoding='utf8')
+ if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
+ return
+ identity_token = Api(dongle_id).get_token()
+ response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token)
+ if response.status_code == 200:
+ data = response.json()
+ self.segment_count = data.get("firehose", 0)
+ self.params.put(self.PARAM_KEY, json.dumps(data))
+ except Exception as e:
+ cloudlog.error(f"Failed to fetch firehose stats: {e}")
+
+ def _update_loop(self):
+ while self.running:
+ if not ui_state.started:
+ self._fetch_firehose_stats()
+ time.sleep(self.UPDATE_INTERVAL)
diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py
index 1afa4f8f4a..d4dc420b7f 100644
--- a/selfdrive/ui/layouts/settings/settings.py
+++ b/selfdrive/ui/layouts/settings/settings.py
@@ -2,15 +2,15 @@ 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.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
+from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
-from openpilot.system.ui.lib.application import gui_app, FontWeight
-from openpilot.system.ui.lib.label import gui_text_box
+from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.selfdrive.ui.layouts.network import NetworkLayout
+from openpilot.system.ui.lib.widget import Widget
# Import individual panels
@@ -18,9 +18,8 @@ SETTINGS_CLOSE_TEXT = "X"
# Constants
SIDEBAR_WIDTH = 500
CLOSE_BTN_SIZE = 200
-NAV_BTN_HEIGHT = 80
+NAV_BTN_HEIGHT = 110
PANEL_MARGIN = 50
-SCROLL_SPEED = 30
# Colors
SIDEBAR_COLOR = rl.BLACK
@@ -29,7 +28,6 @@ CLOSE_BTN_COLOR = rl.Color(41, 41, 41, 255)
CLOSE_BTN_PRESSED = rl.Color(59, 59, 59, 255)
TEXT_NORMAL = rl.Color(128, 128, 128, 255)
TEXT_SELECTED = rl.Color(255, 255, 255, 255)
-TEXT_PRESSED = rl.Color(173, 173, 173, 255)
class PanelType(IntEnum):
@@ -45,23 +43,22 @@ class PanelType(IntEnum):
class PanelInfo:
name: str
instance: object
- button_rect: rl.Rectangle
+ button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
-class SettingsLayout:
+class SettingsLayout(Widget):
def __init__(self):
- self._params = Params()
+ super().__init__()
self._current_panel = PanelType.DEVICE
- self._max_scroll = 0.0
# Panel configuration
self._panels = {
- PanelType.DEVICE: PanelInfo("Device", DeviceLayout(), rl.Rectangle(0, 0, 0, 0)),
- PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout(), rl.Rectangle(0, 0, 0, 0)),
- PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout(), rl.Rectangle(0, 0, 0, 0)),
- PanelType.FIREHOSE: PanelInfo("Firehose", None, rl.Rectangle(0, 0, 0, 0)),
- PanelType.NETWORK: PanelInfo("Network", NetworkLayout(), rl.Rectangle(0, 0, 0, 0)),
- PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout(), rl.Rectangle(0, 0, 0, 0)),
+ PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
+ PanelType.NETWORK: PanelInfo("Network", NetworkLayout()),
+ PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
+ PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
+ PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
+ PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout()),
}
self._font_medium = gui_app.font(FontWeight.MEDIUM)
@@ -73,7 +70,7 @@ class SettingsLayout:
def set_callbacks(self, on_close: Callable):
self._close_callback = on_close
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
# Calculate layout
sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
panel_rect = rl.Rectangle(rect.x + SIDEBAR_WIDTH, rect.y, rect.width - SIDEBAR_WIDTH, rect.height)
@@ -82,9 +79,6 @@ class SettingsLayout:
self._draw_sidebar(sidebar_rect)
self._draw_current_panel(panel_rect)
- if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
- self.handle_mouse_release(rl.get_mouse_position())
-
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
@@ -109,17 +103,9 @@ class SettingsLayout:
self._close_btn_rect = close_btn_rect
# Navigation buttons
- nav_start_y = rect.y + 300
- button_spacing = 20
-
- i = 0
+ y = rect.y + 300
for panel_type, panel_info in self._panels.items():
- button_rect = rl.Rectangle(
- rect.x + 50,
- nav_start_y + i * (NAV_BTN_HEIGHT + button_spacing),
- rect.width - 150, # Right-aligned with margin
- NAV_BTN_HEIGHT,
- )
+ button_rect = rl.Rectangle(rect.x + 50, y, rect.width - 150, NAV_BTN_HEIGHT)
# Button styling
is_selected = panel_type == self._current_panel
@@ -133,7 +119,8 @@ class SettingsLayout:
# Store button rect for click detection
panel_info.button_rect = button_rect
- i += 1
+
+ y += NAV_BTN_HEIGHT
def _draw_current_panel(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded(
@@ -144,17 +131,8 @@ class SettingsLayout:
panel = self._panels[self._current_panel]
if panel.instance:
panel.instance.render(content_rect)
- else:
- gui_text_box(
- content_rect,
- f"Demo {self._panels[self._current_panel].name} Panel",
- font_size=170,
- color=rl.WHITE,
- alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
- alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
- )
- def handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool:
+ def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
# Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback:
@@ -164,20 +142,15 @@ class SettingsLayout:
# 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._switch_to_panel(panel_type)
+ self.set_current_panel(panel_type)
return True
return False
- def _switch_to_panel(self, panel_type: PanelType):
+ def set_current_panel(self, panel_type: PanelType):
if panel_type != self._current_panel:
self._current_panel = panel_type
- def set_current_panel(self, index: int, param: str = ""):
- panel_types = list(self._panels.keys())
- if 0 <= index < len(panel_types):
- self._switch_to_panel(panel_types[index])
-
def close_settings(self):
if self._close_callback:
self._close_callback()
diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py
index e57e13c148..b97e306ff9 100644
--- a/selfdrive/ui/layouts/settings/software.py
+++ b/selfdrive/ui/layouts/settings/software.py
@@ -1,7 +1,20 @@
-from openpilot.system.ui.lib.list_view import ListView, button_item, text_item
+from openpilot.common.params import Params
+from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.list_view import button_item, text_item
+from openpilot.system.ui.lib.scroller import Scroller
+from openpilot.system.ui.lib.widget import Widget, DialogResult
+from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog
-class SoftwareLayout:
+
+class SoftwareLayout(Widget):
def __init__(self):
+ super().__init__()
+
+ self._params = Params()
+ items = self._init_items()
+ self._scroller = Scroller(items, line_separator=True, spacing=0)
+
+ def _init_items(self):
items = [
text_item("Current Version", ""),
button_item("Download", "CHECK", callback=self._on_download_update),
@@ -9,13 +22,21 @@ class SoftwareLayout:
button_item("Target Branch", "SELECT", callback=self._on_select_branch),
button_item("Uninstall", "UNINSTALL", callback=self._on_uninstall),
]
+ return items
- self._list_widget = ListView(items)
-
- def render(self, rect):
- self._list_widget.render(rect)
+ def _render(self, rect):
+ self._scroller.render(rect)
def _on_download_update(self): pass
def _on_install_update(self): pass
def _on_select_branch(self): pass
- def _on_uninstall(self): pass
+
+ def _on_uninstall(self):
+ def handle_uninstall_confirmation(result):
+ if result == DialogResult.CONFIRM:
+ self._params.put_bool("DoUninstall", True)
+
+ gui_app.set_modal_overlay(
+ lambda: confirm_dialog("Are you sure you want to uninstall?", "Uninstall"),
+ callback=handle_uninstall_confirmation,
+ )
diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py
index d966baf9cd..7374d0f2ae 100644
--- a/selfdrive/ui/layouts/settings/toggles.py
+++ b/selfdrive/ui/layouts/settings/toggles.py
@@ -1,4 +1,6 @@
-from openpilot.system.ui.lib.list_view import ListView, toggle_item
+from openpilot.system.ui.lib.list_view import multiple_button_item, toggle_item
+from openpilot.system.ui.lib.scroller import Scroller
+from openpilot.system.ui.lib.widget import Widget
from openpilot.common.params import Params
# Description constants
@@ -8,18 +10,25 @@ DESCRIPTIONS = {
"Your attention is required at all times to use this feature."
),
"DisengageOnAccelerator": "When enabled, pressing the accelerator pedal will disengage openpilot.",
+ "LongitudinalPersonality": (
+ "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."
+ ),
"IsLdwEnabled": (
"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)."
+ "without a turn signal activated while driving over 31 mph (50 km/h)."
),
"AlwaysOnDM": "Enable driver monitoring even when openpilot is not engaged.",
'RecordFront': "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
"IsMetric": "Display speed in km/h instead of mph.",
+ "RecordAudio": "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect.",
}
-class TogglesLayout:
+class TogglesLayout(Widget):
def __init__(self):
+ super().__init__()
self._params = Params()
items = [
toggle_item(
@@ -39,6 +48,15 @@ class TogglesLayout:
self._params.get_bool("DisengageOnAccelerator"),
icon="disengage_on_accelerator.png",
),
+ multiple_button_item(
+ "Driving Personality",
+ DESCRIPTIONS["LongitudinalPersonality"],
+ buttons=["Aggressive", "Standard", "Relaxed"],
+ button_width=255,
+ callback=self._set_longitudinal_personality,
+ selected_index=int(self._params.get("LongitudinalPersonality") or 0),
+ icon="speed_limit.png"
+ ),
toggle_item(
"Enable Lane Departure Warnings",
DESCRIPTIONS["IsLdwEnabled"],
@@ -57,12 +75,21 @@ class TogglesLayout:
self._params.get_bool("RecordFront"),
icon="monitoring.png",
),
+ toggle_item(
+ "Record Microphone Audio",
+ DESCRIPTIONS["RecordAudio"],
+ self._params.get_bool("RecordAudio"),
+ icon="microphone.png",
+ ),
toggle_item(
"Use Metric System", DESCRIPTIONS["IsMetric"], self._params.get_bool("IsMetric"), icon="monitoring.png"
),
]
- self._list_widget = ListView(items)
+ self._scroller = Scroller(items, line_separator=True, spacing=0)
- def render(self, rect):
- self._list_widget.render(rect)
+ def _render(self, rect):
+ self._scroller.render(rect)
+
+ def _set_longitudinal_personality(self, button_index: int):
+ self._params.put("LongitudinalPersonality", str(button_index))
diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py
index 343efe5851..cb8c6d472e 100644
--- a/selfdrive/ui/layouts/sidebar.py
+++ b/selfdrive/ui/layouts/sidebar.py
@@ -4,8 +4,9 @@ from dataclasses import dataclass
from collections.abc import Callable
from cereal import log
from openpilot.selfdrive.ui.ui_state import ui_state
-from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.text_measure import measure_text_cached
+from openpilot.system.ui.lib.widget import Widget
SIDEBAR_WIDTH = 300
METRIC_HEIGHT = 126
@@ -18,6 +19,7 @@ HOME_BTN = rl.Rectangle(60, 860, 180, 180)
ThermalStatus = log.DeviceState.ThermalStatus
NetworkType = log.DeviceState.NetworkType
+
# Color scheme
class Colors:
SIDEBAR_BG = rl.Color(57, 57, 57, 255)
@@ -35,6 +37,7 @@ class Colors:
BUTTON_NORMAL = rl.Color(255, 255, 255, 255)
BUTTON_PRESSED = rl.Color(255, 255, 255, 166)
+
NETWORK_TYPES = {
NetworkType.none: "Offline",
NetworkType.wifi: "WiFi",
@@ -57,8 +60,10 @@ class MetricData:
self.value = value
self.color = color
-class Sidebar:
+
+class Sidebar(Widget):
def __init__(self):
+ super().__init__()
self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0
@@ -72,7 +77,7 @@ class Sidebar:
self._font_regular = gui_app.font(FontWeight.NORMAL)
self._font_bold = gui_app.font(FontWeight.SEMI_BOLD)
- # Callbacks
+ # Callbacks
self._on_settings_click: Callable | None = None
self._on_flag_click: Callable | None = None
@@ -80,9 +85,7 @@ class Sidebar:
self._on_settings_click = on_settings
self._on_flag_click = on_flag
- def render(self, rect: rl.Rectangle):
- self.update_state()
-
+ def _render(self, rect: rl.Rectangle):
# Background
rl.draw_rectangle_rec(rect, Colors.SIDEBAR_BG)
@@ -90,9 +93,7 @@ class Sidebar:
self._draw_network_indicator(rect)
self._draw_metrics(rect)
- self._handle_mouse_release()
-
- def update_state(self):
+ def _update_state(self):
sm = ui_state.sm
if not sm.updated['deviceState']:
return
@@ -134,11 +135,7 @@ class Sidebar:
else:
self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD)
- def _handle_mouse_release(self):
- if not rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
- return
-
- mouse_pos = rl.get_mouse_position()
+ def _handle_mouse_release(self, mouse_pos: MousePos):
if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN):
if self._on_settings_click:
self._on_settings_click()
@@ -148,8 +145,7 @@ class Sidebar:
def _draw_buttons(self, rect: rl.Rectangle):
mouse_pos = rl.get_mouse_position()
- mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
-
+ mouse_down = self._is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
# Settings button
settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN)
diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py
new file mode 100644
index 0000000000..6b87540f65
--- /dev/null
+++ b/selfdrive/ui/lib/prime_state.py
@@ -0,0 +1,99 @@
+from enum import IntEnum
+import os
+import threading
+import time
+
+from openpilot.common.api import Api, api_get
+from openpilot.common.params import Params
+from openpilot.common.swaglog import cloudlog
+from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
+
+
+class PrimeType(IntEnum):
+ UNKNOWN = -2,
+ UNPAIRED = -1,
+ NONE = 0,
+ MAGENTA = 1,
+ LITE = 2,
+ BLUE = 3,
+ MAGENTA_NEW = 4,
+ PURPLE = 5,
+
+
+class PrimeState:
+ FETCH_INTERVAL = 5.0 # seconds between API calls
+ API_TIMEOUT = 10.0 # seconds for API requests
+ SLEEP_INTERVAL = 0.5 # seconds to sleep between checks in the worker thread
+
+ def __init__(self):
+ self._params = Params()
+ self._lock = threading.Lock()
+ self.prime_type: PrimeType = self._load_initial_state()
+
+ self._running = False
+ self._thread = None
+ self.start()
+
+ def _load_initial_state(self) -> PrimeType:
+ prime_type_str = os.getenv("PRIME_TYPE") or self._params.get("PrimeType", encoding='utf8')
+ try:
+ if prime_type_str is not None:
+ return PrimeType(int(prime_type_str))
+ except (ValueError, TypeError):
+ pass
+ return PrimeType.UNKNOWN
+
+ def _fetch_prime_status(self) -> None:
+ dongle_id = self._params.get("DongleId", encoding='utf8')
+ if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID:
+ return
+
+ try:
+ identity_token = Api(dongle_id).get_token()
+ response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token)
+ if response.status_code == 200:
+ data = response.json()
+ is_paired = data.get("is_paired", False)
+ prime_type = data.get("prime_type", 0)
+ self.set_type(PrimeType(prime_type) if is_paired else PrimeType.UNPAIRED)
+ except Exception as e:
+ cloudlog.error(f"Failed to fetch prime status: {e}")
+
+ def set_type(self, prime_type: PrimeType) -> None:
+ with self._lock:
+ if prime_type != self.prime_type:
+ self.prime_type = prime_type
+ self._params.put("PrimeType", str(int(prime_type)))
+ cloudlog.info(f"Prime type updated to {prime_type}")
+
+ def _worker_thread(self) -> None:
+ while self._running:
+ self._fetch_prime_status()
+
+ for _ in range(int(self.FETCH_INTERVAL / self.SLEEP_INTERVAL)):
+ if not self._running:
+ break
+ time.sleep(self.SLEEP_INTERVAL)
+
+ def start(self) -> None:
+ if self._thread and self._thread.is_alive():
+ return
+ self._running = True
+ self._thread = threading.Thread(target=self._worker_thread, daemon=True)
+ self._thread.start()
+
+ def stop(self) -> None:
+ self._running = False
+ if self._thread and self._thread.is_alive():
+ self._thread.join(timeout=1.0)
+
+ def get_type(self) -> PrimeType:
+ with self._lock:
+ return self.prime_type
+
+ def is_prime(self) -> bool:
+ with self._lock:
+ return bool(self.prime_type > PrimeType.NONE)
+
+ def __del__(self):
+ self.stop()
diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py
index 7b0128271e..7a17291bbe 100644
--- a/selfdrive/ui/onroad/alert_renderer.py
+++ b/selfdrive/ui/onroad/alert_renderer.py
@@ -6,9 +6,9 @@ from openpilot.system.hardware import TICI
from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_FPS
from openpilot.system.ui.lib.label import gui_text_box
from openpilot.system.ui.lib.text_measure import measure_text_cached
+from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.ui.ui_state import ui_state
-
ALERT_MARGIN = 40
ALERT_PADDING = 60
ALERT_LINE_SPACING = 45
@@ -21,7 +21,6 @@ ALERT_FONT_BIG = 88
SELFDRIVE_STATE_TIMEOUT = 5 # Seconds
SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds
-
# Constants
ALERT_COLORS = {
log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 235), # Black
@@ -61,8 +60,9 @@ ALERT_CRITICAL_REBOOT = Alert(
)
-class AlertRenderer:
+class AlertRenderer(Widget):
def __init__(self):
+ super().__init__()
self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL)
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
@@ -73,18 +73,20 @@ class AlertRenderer:
# Check if selfdriveState messages have stopped arriving
if not sm.updated['selfdriveState']:
recv_frame = sm.recv_frame['selfdriveState']
- if (sm.frame - recv_frame) > 5 * DEFAULT_FPS:
- # Check if waiting to start
- if recv_frame < ui_state.started_frame:
- return ALERT_STARTUP_PENDING
+ time_since_onroad = (sm.frame - ui_state.started_frame) / DEFAULT_FPS
- # Handle selfdrive timeout
- if TICI:
- ss_missing = time.monotonic() - sm.recv_time['selfdriveState']
- if ss_missing > SELFDRIVE_STATE_TIMEOUT:
- if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT:
- return ALERT_CRITICAL_TIMEOUT
- return ALERT_CRITICAL_REBOOT
+ # 1. Never received selfdriveState since going onroad
+ waiting_for_startup = recv_frame < ui_state.started_frame
+ if waiting_for_startup and time_since_onroad > 5:
+ return ALERT_STARTUP_PENDING
+
+ # 2. Lost communication with selfdriveState after receiving it
+ if TICI and not waiting_for_startup:
+ ss_missing = time.monotonic() - sm.recv_time['selfdriveState']
+ if ss_missing > SELFDRIVE_STATE_TIMEOUT:
+ if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT:
+ return ALERT_CRITICAL_TIMEOUT
+ return ALERT_CRITICAL_REBOOT
# No alert if size is none
if ss.alertSize == 0:
@@ -93,10 +95,10 @@ class AlertRenderer:
# Return current alert
return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize, status=ss.alertStatus)
- def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster) -> None:
- alert = self.get_alert(sm)
+ def _render(self, rect: rl.Rectangle) -> bool:
+ alert = self.get_alert(ui_state.sm)
if not alert:
- return
+ return False
alert_rect = self._get_alert_rect(rect, alert.size)
self._draw_background(alert_rect, alert)
@@ -108,13 +110,14 @@ class AlertRenderer:
alert_rect.height - 2 * ALERT_PADDING
)
self._draw_text(text_rect, alert)
+ return True
def _get_alert_rect(self, rect: rl.Rectangle, size: int) -> rl.Rectangle:
if size == log.SelfdriveState.AlertSize.full:
return rect
height = (ALERT_FONT_MEDIUM + 2 * ALERT_PADDING if size == log.SelfdriveState.AlertSize.small else
- ALERT_FONT_BIG + ALERT_LINE_SPACING + ALERT_FONT_SMALL + 2 * ALERT_PADDING)
+ ALERT_FONT_BIG + ALERT_LINE_SPACING + ALERT_FONT_SMALL + 2 * ALERT_PADDING)
return rl.Rectangle(
rect.x + ALERT_MARGIN,
diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py
index 47769f268e..dbc1919221 100644
--- a/selfdrive/ui/onroad/augmented_road_view.py
+++ b/selfdrive/ui/onroad/augmented_road_view.py
@@ -1,6 +1,6 @@
import numpy as np
import pyray as rl
-
+from collections.abc import Callable
from cereal import log
from msgq.visionipc import VisionStreamType
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus, UI_BORDER_SIZE
@@ -13,7 +13,6 @@ from openpilot.system.ui.lib.application import gui_app
from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame
from openpilot.common.transformations.orientation import rot_from_euler
-
OpState = log.SelfdriveState.OpenpilotState
CALIBRATED = log.LiveCalibrationData.Status.calibrated
ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD
@@ -26,6 +25,9 @@ BORDER_COLORS = {
UIStatus.ENGAGED: rl.Color(0x17, 0x86, 0x44, 0xF1), # Green for engaged state
}
+WIDE_CAM_MAX_SPEED = 10.0 # m/s (22 mph)
+ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph)
+
class AugmentedRoadView(CameraView):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
@@ -47,11 +49,19 @@ class AugmentedRoadView(CameraView):
self.alert_renderer = AlertRenderer()
self.driver_state_renderer = DriverStateRenderer()
- def render(self, rect):
+ # Callbacks
+ self._click_callback: Callable | None = None
+
+ def set_callbacks(self, on_click: Callable | None = None):
+ self._click_callback = on_click
+
+ def _render(self, rect):
# Only render when system is started to avoid invalid data access
if not ui_state.started:
return
+ self._switch_stream_if_needed(ui_state.sm)
+
# Update calibration before rendering
self._update_calibration()
@@ -76,13 +86,13 @@ class AugmentedRoadView(CameraView):
)
# Render the base camera view
- super().render(rect)
+ super()._render(rect)
# Draw all UI overlays
- self.model_renderer.draw(self._content_rect, ui_state.sm)
- self._hud_renderer.draw(self._content_rect, ui_state.sm)
- self.alert_renderer.draw(self._content_rect, ui_state.sm)
- self.driver_state_renderer.draw(self._content_rect, ui_state.sm)
+ self.model_renderer.render(self._content_rect)
+ self._hud_renderer.render(self._content_rect)
+ if not self.alert_renderer.render(self._content_rect):
+ self.driver_state_renderer.render(self._content_rect)
# Custom UI extension point - add custom overlays here
# Use self._content_rect for positioning within camera bounds
@@ -90,10 +100,32 @@ class AugmentedRoadView(CameraView):
# End clipping region
rl.end_scissor_mode()
+ # Handle click events if no HUD interaction occurred
+ if not self._hud_renderer.handle_mouse_event():
+ if self._click_callback and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ if rl.check_collision_point_rec(rl.get_mouse_position(), self._content_rect):
+ self._click_callback()
+
def _draw_border(self, rect: rl.Rectangle):
border_color = BORDER_COLORS.get(ui_state.status, BORDER_COLORS[UIStatus.DISENGAGED])
rl.draw_rectangle_lines_ex(rect, UI_BORDER_SIZE, border_color)
+ def _switch_stream_if_needed(self, sm):
+ if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams:
+ v_ego = sm['carState'].vEgo
+ if v_ego < WIDE_CAM_MAX_SPEED:
+ target = WIDE_CAM
+ elif v_ego > ROAD_CAM_MIN_SPEED:
+ target = ROAD_CAM
+ else:
+ # Hysteresis zone - keep current stream
+ target = self.stream_type
+ else:
+ target = ROAD_CAM
+
+ if self.stream_type != target:
+ self.switch_stream(target)
+
def _update_calibration(self):
# Update device camera if not already set
sm = ui_state.sm
@@ -129,7 +161,7 @@ class AugmentedRoadView(CameraView):
# Get camera configuration
device_camera = self.device_camera or DEFAULT_DEVICE_CAMERA
- is_wide_camera = self.stream_type == VisionStreamType.VISION_STREAM_WIDE_ROAD
+ is_wide_camera = self.stream_type == WIDE_CAM
intrinsic = device_camera.ecam.intrinsics if is_wide_camera else device_camera.fcam.intrinsics
calibration = self.view_from_wide_calib if is_wide_camera else self.view_from_calib
zoom = 2.0 if is_wide_camera else 1.1
@@ -170,9 +202,9 @@ class AugmentedRoadView(CameraView):
])
video_transform = np.array([
- [zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)],
- [0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)],
- [0.0, 0.0, 1.0]
+ [zoom, 0.0, (w / 2 + x - x_offset) - (cx * zoom)],
+ [0.0, zoom, (h / 2 + y - y_offset) - (cy * zoom)],
+ [0.0, 0.0, 1.0]
])
self.model_renderer.set_transform(video_transform @ calib_transform)
diff --git a/selfdrive/ui/onroad/cameraview.py b/selfdrive/ui/onroad/cameraview.py
index 9f58133abf..33f1961d2b 100644
--- a/selfdrive/ui/onroad/cameraview.py
+++ b/selfdrive/ui/onroad/cameraview.py
@@ -1,3 +1,4 @@
+import platform
import numpy as np
import pyray as rl
@@ -6,12 +7,21 @@ from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.common.swaglog import cloudlog
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage
-
+from openpilot.system.ui.lib.widget import Widget
CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts
-VERTEX_SHADER = """
+VERSION = """
#version 300 es
+precision mediump float;
+"""
+if platform.system() == "Darwin":
+ VERSION = """
+ #version 330 core
+ """
+
+
+VERTEX_SHADER = VERSION + """
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
@@ -41,9 +51,7 @@ if TICI:
}
"""
else:
- FRAME_FRAGMENT_SHADER = """
- #version 300 es
- precision mediump float;
+ FRAME_FRAGMENT_SHADER = VERSION + """
in vec2 fragTexCoord;
uniform sampler2D texture0;
uniform sampler2D texture1;
@@ -55,8 +63,10 @@ else:
}
"""
-class CameraView:
+
+class CameraView(Widget):
def __init__(self, name: str, stream_type: VisionStreamType):
+ super().__init__()
self._name = name
# Primary stream
self.client = VisionIpcClient(name, stream_type, conflate=True)
@@ -68,7 +78,6 @@ class CameraView:
self._target_stream_type: VisionStreamType | None = None
self._switching: bool = False
-
self._texture_needs_update = True
self.last_connection_attempt: float = 0.0
self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER)
@@ -82,7 +91,7 @@ class CameraView:
self.egl_images: dict[int, EGLImage] = {}
self.egl_texture: rl.Texture | None = None
- self._placeholder_color : rl.Color | None = None
+ self._placeholder_color: rl.Color | None = None
# Initialize EGL for zero-copy rendering on TICI
if TICI:
@@ -145,12 +154,12 @@ class CameraView:
zy = min(widget_aspect_ratio / frame_aspect_ratio, 1.0)
return np.array([
- [zx, 0.0, 0.0],
- [0.0, zy, 0.0],
- [0.0, 0.0, 1.0]
+ [zx, 0.0, 0.0],
+ [0.0, zy, 0.0],
+ [0.0, 0.0, 1.0]
])
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
if self._switching:
self._handle_switch()
@@ -230,7 +239,7 @@ class CameraView:
# Update textures with new frame data
if self._texture_needs_update:
y_data = self.frame.data[: self.frame.uv_offset]
- uv_data = self.frame.data[self.frame.uv_offset :]
+ uv_data = self.frame.data[self.frame.uv_offset:]
rl.update_texture(self.texture_y, rl.ffi.cast("void *", y_data.ctypes.data))
rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data))
@@ -265,7 +274,7 @@ class CameraView:
def _handle_switch(self) -> None:
"""Check if target stream is ready and switch immediately."""
if not self._target_client or not self._switching:
- return
+ return
# Try to connect target if needed
if not self._target_client.is_connected():
@@ -277,28 +286,28 @@ class CameraView:
# Check if target has frames ready
target_frame = self._target_client.recv(timeout_ms=0)
if target_frame:
- self.frame = target_frame # Update current frame to target frame
+ self.frame = target_frame # Update current frame to target frame
self._complete_switch()
def _complete_switch(self) -> None:
- """Instantly switch to target stream."""
- cloudlog.debug(f"Switching to {self._target_stream_type}")
- # Clean up current resources
- if self.client:
- del self.client
+ """Instantly switch to target stream."""
+ cloudlog.debug(f"Switching to {self._target_stream_type}")
+ # Clean up current resources
+ if self.client:
+ del self.client
- # Switch to target
- self.client = self._target_client
- self._stream_type = self._target_stream_type
- self._texture_needs_update = True
+ # Switch to target
+ self.client = self._target_client
+ self._stream_type = self._target_stream_type
+ self._texture_needs_update = True
- # Reset state
- self._target_client = None
- self._target_stream_type = None
- self._switching = False
+ # Reset state
+ self._target_client = None
+ self._target_stream_type = None
+ self._switching = False
- # Initialize textures for new stream
- self._initialize_textures()
+ # Initialize textures for new stream
+ self._initialize_textures()
def _initialize_textures(self):
self._clear_textures()
diff --git a/selfdrive/ui/onroad/driver_camera_view.py b/selfdrive/ui/onroad/driver_camera_dialog.py
similarity index 79%
rename from selfdrive/ui/onroad/driver_camera_view.py
rename to selfdrive/ui/onroad/driver_camera_dialog.py
index 2f9ff2ca7a..40c72f292b 100644
--- a/selfdrive/ui/onroad/driver_camera_view.py
+++ b/selfdrive/ui/onroad/driver_camera_dialog.py
@@ -1,20 +1,23 @@
import numpy as np
import pyray as rl
-from cereal import messaging
from msgq.visionipc import VisionStreamType
from openpilot.selfdrive.ui.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.onroad.driver_state import DriverStateRenderer
+from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.label import gui_label
-class DriverCameraView(CameraView):
- def __init__(self, stream_type: VisionStreamType):
- super().__init__("camerad", stream_type)
+class DriverCameraDialog(CameraView):
+ def __init__(self):
+ super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER)
self.driver_state_renderer = DriverStateRenderer()
- def render(self, rect, sm):
- super().render(rect)
+ def _render(self, rect):
+ super()._render(rect)
+
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ return 1
if not self.frame:
gui_label(
@@ -24,13 +27,15 @@ class DriverCameraView(CameraView):
font_weight=FontWeight.BOLD,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
)
- return
+ return -1
- self._draw_face_detection(rect, sm)
- self.driver_state_renderer.draw(rect, sm)
+ self._draw_face_detection(rect)
+ self.driver_state_renderer.render(rect)
- def _draw_face_detection(self, rect: rl.Rectangle, sm) -> None:
- driver_state = sm["driverStateV2"]
+ return -1
+
+ def _draw_face_detection(self, rect: rl.Rectangle) -> None:
+ driver_state = ui_state.sm["driverStateV2"]
is_rhd = driver_state.wheelOnRightProb > 0.5
driver_data = driver_state.rightDriverData if is_rhd else driver_state.leftDriverData
face_detect = driver_data.faceProb > 0.7
@@ -83,12 +88,11 @@ class DriverCameraView(CameraView):
if __name__ == "__main__":
gui_app.init_window("Driver Camera View")
- sm = messaging.SubMaster(["selfdriveState", "driverStateV2", "driverMonitoringState"])
- driver_camera_view = DriverCameraView(VisionStreamType.VISION_STREAM_DRIVER)
+ driver_camera_view = DriverCameraDialog()
try:
for _ in gui_app.render():
- sm.update()
- driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height), sm)
+ 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/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py
index 8e950f514e..8d5fb951d0 100644
--- a/selfdrive/ui/onroad/driver_state.py
+++ b/selfdrive/ui/onroad/driver_state.py
@@ -3,19 +3,19 @@ import pyray as rl
from dataclasses import dataclass
from openpilot.selfdrive.ui.ui_state import ui_state, UI_BORDER_SIZE
from openpilot.system.ui.lib.application import gui_app
-
+from openpilot.system.ui.lib.widget import Widget
# Default 3D coordinates for face keypoints as a NumPy array
DEFAULT_FACE_KPTS_3D = np.array([
- [-5.98, -51.20, 8.00], [-17.64, -49.14, 8.00], [-23.81, -46.40, 8.00], [-29.98, -40.91, 8.00],
- [-32.04, -37.49, 8.00], [-34.10, -32.00, 8.00], [-36.16, -21.03, 8.00], [-36.16, 6.40, 8.00],
- [-35.47, 10.51, 8.00], [-32.73, 19.43, 8.00], [-29.30, 26.29, 8.00], [-24.50, 33.83, 8.00],
- [-19.01, 41.37, 8.00], [-14.21, 46.17, 8.00], [-12.16, 47.54, 8.00], [-4.61, 49.60, 8.00],
- [4.99, 49.60, 8.00], [12.53, 47.54, 8.00], [14.59, 46.17, 8.00], [19.39, 41.37, 8.00],
- [24.87, 33.83, 8.00], [29.67, 26.29, 8.00], [33.10, 19.43, 8.00], [35.84, 10.51, 8.00],
- [36.53, 6.40, 8.00], [36.53, -21.03, 8.00], [34.47, -32.00, 8.00], [32.42, -37.49, 8.00],
- [30.36, -40.91, 8.00], [24.19, -46.40, 8.00], [18.02, -49.14, 8.00], [6.36, -51.20, 8.00],
- [-5.98, -51.20, 8.00],
+ [-5.98, -51.20, 8.00], [-17.64, -49.14, 8.00], [-23.81, -46.40, 8.00], [-29.98, -40.91, 8.00],
+ [-32.04, -37.49, 8.00], [-34.10, -32.00, 8.00], [-36.16, -21.03, 8.00], [-36.16, 6.40, 8.00],
+ [-35.47, 10.51, 8.00], [-32.73, 19.43, 8.00], [-29.30, 26.29, 8.00], [-24.50, 33.83, 8.00],
+ [-19.01, 41.37, 8.00], [-14.21, 46.17, 8.00], [-12.16, 47.54, 8.00], [-4.61, 49.60, 8.00],
+ [4.99, 49.60, 8.00], [12.53, 47.54, 8.00], [14.59, 46.17, 8.00], [19.39, 41.37, 8.00],
+ [24.87, 33.83, 8.00], [29.67, 26.29, 8.00], [33.10, 19.43, 8.00], [35.84, 10.51, 8.00],
+ [36.53, 6.40, 8.00], [36.53, -21.03, 8.00], [34.47, -32.00, 8.00], [32.42, -37.49, 8.00],
+ [30.36, -40.91, 8.00], [24.19, -46.40, 8.00], [18.02, -49.14, 8.00], [6.36, -51.20, 8.00],
+ [-5.98, -51.20, 8.00],
], dtype=np.float32)
# UI constants
@@ -31,6 +31,7 @@ SCALES_NEG = np.array([0.7, 0.4, 0.4], dtype=np.float32)
ARC_POINT_COUNT = 37 # Number of points in the arc
ARC_ANGLES = np.linspace(0.0, np.pi, ARC_POINT_COUNT, dtype=np.float32)
+
@dataclass
class ArcData:
"""Data structure for arc rendering parameters."""
@@ -40,14 +41,15 @@ class ArcData:
height: float
thickness: float
-class DriverStateRenderer:
+
+class DriverStateRenderer(Widget):
def __init__(self):
+ super().__init__()
# Initial state with NumPy arrays
self.face_kpts_draw = DEFAULT_FACE_KPTS_3D.copy()
self.is_active = False
self.is_rhd = False
self.dm_fade_state = 0.0
- self.state_updated = False
self.last_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
self.driver_pose_vals = np.zeros(3, dtype=np.float32)
self.driver_pose_diff = np.zeros(3, dtype=np.float32)
@@ -73,14 +75,10 @@ class DriverStateRenderer:
self.engaged_color = rl.Color(26, 242, 66, 255)
self.disengaged_color = rl.Color(139, 139, 139, 255)
- def draw(self, rect, sm):
- if not self._is_visible(sm):
- return
-
- self._update_state(sm, rect)
- if not self.state_updated:
- return
+ self.set_visible(lambda: (ui_state.sm.recv_frame['driverStateV2'] > ui_state.started_frame and
+ ui_state.sm.seen['driverMonitoringState']))
+ def _render(self, rect):
# Set opacity based on active state
opacity = 0.65 if self.is_active else 0.2
@@ -105,18 +103,14 @@ class DriverStateRenderer:
if self.v_arc_data:
rl.draw_spline_linear(self.v_arc_lines, len(self.v_arc_lines), self.v_arc_data.thickness, self.arc_color)
- def _is_visible(self, sm):
- """Check if the visualization should be rendered."""
- return (sm.recv_frame['driverStateV2'] > ui_state.started_frame and
- sm.seen['driverMonitoringState'] and
- sm['selfdriveState'].alertSize == 0)
-
- def _update_state(self, sm, rect):
+ def _update_state(self):
"""Update the driver monitoring state based on model data"""
- if not sm.updated["driverMonitoringState"]:
- if self.state_updated and (rect.x != self.last_rect.x or rect.y != self.last_rect.y or \
- rect.width != self.last_rect.width or rect.height != self.last_rect.height):
- self._pre_calculate_drawing_elements(rect)
+ sm = ui_state.sm
+ if not sm.updated["driverMonitoringState"]:
+ if (self._rect.x != self.last_rect.x or self._rect.y != self.last_rect.y or
+ self._rect.width != self.last_rect.width or self._rect.height != self.last_rect.height):
+ self._pre_calculate_drawing_elements()
+ self.last_rect = self._rect
return
# Get monitoring state
@@ -165,16 +159,15 @@ class DriverStateRenderer:
self.face_keypoints_transformed = self.face_kpts_draw[:, :2] * kp_depth[:, None]
# Pre-calculate all drawing elements
- self._pre_calculate_drawing_elements(rect)
- self.state_updated = True
+ self._pre_calculate_drawing_elements()
- def _pre_calculate_drawing_elements(self, rect):
+ def _pre_calculate_drawing_elements(self):
"""Pre-calculate all drawing elements based on the current rectangle"""
# Calculate icon position (bottom-left or bottom-right)
- width, height = rect.width, rect.height
+ width, height = self._rect.width, self._rect.height
offset = UI_BORDER_SIZE + BTN_SIZE // 2
- self.position_x = rect.x + (width - offset if self.is_rhd else offset)
- self.position_y = rect.y + height - offset
+ self.position_x = self._rect.x + (width - offset if self.is_rhd else offset)
+ self.position_y = self._rect.y + height - offset
# Pre-calculate the face lines positions
positioned_keypoints = self.face_keypoints_transformed + np.array([self.position_x, self.position_y])
@@ -189,15 +182,15 @@ class DriverStateRenderer:
# Horizontal arc
h_width = abs(delta_x)
self.h_arc_data = self._calculate_arc_data(
- delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2,
- self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True
+ delta_x, h_width, self.position_x, self.position_y - ARC_LENGTH / 2,
+ self.driver_pose_sins[1], self.driver_pose_diff[1], is_horizontal=True
)
# Vertical arc
v_height = abs(delta_y)
self.v_arc_data = self._calculate_arc_data(
- delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y,
- self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False
+ delta_y, v_height, self.position_x - ARC_LENGTH / 2, self.position_y,
+ self.driver_pose_sins[0], self.driver_pose_diff[0], is_horizontal=False
)
def _calculate_arc_data(
diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py
new file mode 100644
index 0000000000..8c835090d0
--- /dev/null
+++ b/selfdrive/ui/onroad/exp_button.py
@@ -0,0 +1,78 @@
+import time
+import pyray as rl
+from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.widget import Widget
+from openpilot.common.params import Params
+
+
+class ExpButton(Widget):
+ def __init__(self, button_size: int, icon_size: int):
+ super().__init__()
+ self._params = Params()
+ self._experimental_mode: bool = False
+ self._engageable: bool = False
+
+ # State hold mechanism
+ self._hold_duration = 2.0 # seconds
+ self._held_mode: bool | None = None
+ self._hold_end_time: float | None = None
+
+ self._white_color: rl.Color = rl.Color(255, 255, 255, 255)
+ self._black_bg: rl.Color = rl.Color(0, 0, 0, 166)
+ self._txt_wheel: rl.Texture = gui_app.texture('icons/chffr_wheel.png', icon_size, icon_size)
+ self._txt_exp: rl.Texture = gui_app.texture('icons/experimental.png', icon_size, icon_size)
+ self._rect = rl.Rectangle(0, 0, button_size, button_size)
+
+ def set_rect(self, rect: rl.Rectangle) -> None:
+ self._rect.x, self._rect.y = rect.x, rect.y
+
+ def _update_state(self) -> None:
+ selfdrive_state = ui_state.sm["selfdriveState"]
+ self._experimental_mode = selfdrive_state.experimentalMode
+ self._engageable = selfdrive_state.engageable or selfdrive_state.enabled
+
+ def handle_mouse_event(self) -> bool:
+ if rl.check_collision_point_rec(rl.get_mouse_position(), self._rect):
+ if (rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and
+ self._is_toggle_allowed()):
+ new_mode = not self._experimental_mode
+ self._params.put_bool("ExperimentalMode", new_mode)
+
+ # Hold new state temporarily
+ self._held_mode = new_mode
+ self._hold_end_time = time.time() + self._hold_duration
+ return True
+ return False
+
+ def _render(self, rect: rl.Rectangle) -> None:
+ center_x = int(self._rect.x + self._rect.width // 2)
+ center_y = int(self._rect.y + self._rect.height // 2)
+
+ mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect)
+ mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed
+ self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255
+
+ 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)
+
+ def _held_or_actual_mode(self):
+ now = time.time()
+ if self._hold_end_time and now < self._hold_end_time:
+ return self._held_mode
+
+ if self._hold_end_time and now >= self._hold_end_time:
+ self._hold_end_time = self._held_mode = None
+
+ return self._experimental_mode
+
+ def _is_toggle_allowed(self):
+ if not self._params.get_bool("ExperimentalModeConfirmed"):
+ return False
+
+ car_params = ui_state.sm["carParams"]
+ if car_params.alphaLongitudinalAvailable:
+ return self._params.get_bool("AlphaLongitudinalEnabled")
+ else:
+ return car_params.openpilotLongitudinalControl
diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py
index 0e893acfe9..da292d38d5 100644
--- a/selfdrive/ui/onroad/hud_renderer.py
+++ b/selfdrive/ui/onroad/hud_renderer.py
@@ -1,10 +1,11 @@
import pyray as rl
from dataclasses import dataclass
-from cereal.messaging import SubMaster
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
+from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.common.conversions import Conversions as CV
+from openpilot.system.ui.lib.widget import Widget
# Constants
SET_SPEED_NA = 255
@@ -54,21 +55,25 @@ FONT_SIZES = FontSizes()
COLORS = Colors()
-class HudRenderer:
+class HudRenderer(Widget):
def __init__(self):
+ super().__init__()
"""Initialize the HUD renderer."""
self.is_cruise_set: bool = False
self.is_cruise_available: bool = False
self.set_speed: float = SET_SPEED_NA
self.speed: float = 0.0
self.v_ego_cluster_seen: bool = False
- self._wheel_texture: rl.Texture = gui_app.texture('icons/chffr_wheel.png', UI_CONFIG.wheel_icon_size, UI_CONFIG.wheel_icon_size)
+
self._font_semi_bold: rl.Font = gui_app.font(FontWeight.SEMI_BOLD)
self._font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
- def _update_state(self, sm: SubMaster) -> None:
+ self._exp_button = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size)
+
+ def _update_state(self) -> None:
"""Update HUD state based on car state and controls state."""
+ sm = ui_state.sm
if sm.recv_frame["carState"] < ui_state.started_frame:
self.is_cruise_set = False
self.set_speed = SET_SPEED_NA
@@ -94,9 +99,9 @@ class HudRenderer:
speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH
self.speed = max(0.0, v_ego * speed_conversion)
- def draw(self, rect: rl.Rectangle, sm: SubMaster) -> None:
+ def _render(self, rect: rl.Rectangle) -> None:
"""Render HUD elements to the screen."""
- self._update_state(sm)
+ # Draw the header background
rl.draw_rectangle_gradient_v(
int(rect.x),
int(rect.y),
@@ -110,7 +115,13 @@ class HudRenderer:
self._draw_set_speed(rect)
self._draw_current_speed(rect)
- self._draw_wheel_icon(rect)
+
+ button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size
+ button_y = rect.y + UI_CONFIG.border_size
+ self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
+
+ def handle_mouse_event(self) -> bool:
+ return bool(self._exp_button.handle_mouse_event())
def _draw_set_speed(self, rect: rl.Rectangle) -> None:
"""Draw the MAX speed indicator box."""
@@ -166,13 +177,3 @@ class HudRenderer:
unit_text_size = measure_text_cached(self._font_medium, unit_text, FONT_SIZES.speed_unit)
unit_pos = rl.Vector2(rect.x + rect.width / 2 - unit_text_size.x / 2, 290 - unit_text_size.y / 2)
rl.draw_text_ex(self._font_medium, unit_text, unit_pos, FONT_SIZES.speed_unit, 0, COLORS.white_translucent)
-
- def _draw_wheel_icon(self, rect: rl.Rectangle) -> None:
- """Draw the steering wheel icon with status-based opacity."""
- center_x = int(rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size / 2)
- center_y = int(rect.y + UI_CONFIG.border_size + UI_CONFIG.button_size / 2)
- rl.draw_circle(center_x, center_y, UI_CONFIG.button_size / 2, COLORS.black_translucent)
-
- opacity = 0.7 if ui_state.status == UIStatus.DISENGAGED else 1.0
- img_pos = rl.Vector2(center_x - self._wheel_texture.width / 2, center_y - self._wheel_texture.height / 2)
- rl.draw_texture_v(self._wheel_texture, img_pos, rl.Color(255, 255, 255, int(255 * opacity)))
diff --git a/selfdrive/ui/onroad/model_renderer.py b/selfdrive/ui/onroad/model_renderer.py
index ad037f8af3..ab2b7aaefb 100644
--- a/selfdrive/ui/onroad/model_renderer.py
+++ b/selfdrive/ui/onroad/model_renderer.py
@@ -7,9 +7,9 @@ from openpilot.common.params import Params
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import DEFAULT_FPS
from openpilot.system.ui.lib.shader_polygon import draw_polygon
+from openpilot.system.ui.lib.widget import Widget
from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT
-
CLIP_MARGIN = 500
MIN_DRAW_DISTANCE = 10.0
MAX_DRAW_DISTANCE = 100.0
@@ -36,6 +36,7 @@ class ModelPoints:
raw_points: np.ndarray = field(default_factory=lambda: np.empty((0, 3), dtype=np.float32))
projected_points: np.ndarray = field(default_factory=lambda: np.empty((0, 2), dtype=np.float32))
+
@dataclass
class LeadVehicle:
glow: list[float] = field(default_factory=list)
@@ -43,8 +44,9 @@ class LeadVehicle:
fill_alpha: int = 0
-class ModelRenderer:
+class ModelRenderer(Widget):
def __init__(self):
+ super().__init__()
self._longitudinal_control = False
self._experimental_mode = False
self._blend_factor = 1.0
@@ -64,11 +66,6 @@ class ModelRenderer:
self._car_space_transform = np.zeros((3, 3), dtype=np.float32)
self._transform_dirty = True
self._clip_region = None
- self._rect = None
-
- # Pre-allocated arrays for polygon conversion
- self._temp_points_3d = np.empty((MAX_POINTS * 2, 3), dtype=np.float32)
- self._temp_proj = np.empty((3, MAX_POINTS * 2), dtype=np.float32)
self._exp_gradient = {
'start': (0.0, 1.0), # Bottom of path
@@ -86,14 +83,15 @@ class ModelRenderer:
self._car_space_transform = transform.astype(np.float32)
self._transform_dirty = True
- def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster):
+ def _render(self, rect: rl.Rectangle):
+ sm = ui_state.sm
+
# Check if data is up-to-date
if (sm.recv_frame["liveCalibration"] < ui_state.started_frame or
sm.recv_frame["modelV2"] < ui_state.started_frame):
return
# Set up clipping region
- self._rect = rect
self._clip_region = rl.Rectangle(
rect.x - CLIP_MARGIN, rect.y - CLIP_MARGIN, rect.width + 2 * CLIP_MARGIN, rect.height + 2 * CLIP_MARGIN
)
@@ -127,7 +125,6 @@ class ModelRenderer:
self._update_leads(radar_state, path_x_array)
self._transform_dirty = False
-
# Draw elements
self._draw_lane_lines()
self._draw_path(sm)
@@ -256,7 +253,7 @@ class ModelRenderer:
glow = [(x + (sz * 1.35) + g_xo, y + sz + g_yo), (x, y - g_yo), (x - (sz * 1.35) - g_xo, y + sz + g_yo)]
chevron = [(x + (sz * 1.25), y + sz), (x, y), (x - (sz * 1.25), y + sz)]
- return LeadVehicle(glow=glow,chevron=chevron, fill_alpha=int(fill_alpha))
+ return LeadVehicle(glow=glow, chevron=chevron, fill_alpha=int(fill_alpha))
def _draw_lane_lines(self):
"""Draw lane lines and road edges"""
@@ -357,54 +354,60 @@ class ModelRenderer:
if points.shape[0] == 0:
return np.empty((0, 2), dtype=np.float32)
- # Create left and right 3D points in one array
- n_points = points.shape[0]
- points_3d = self._temp_points_3d[:n_points * 2]
- points_3d[:n_points, 0] = points_3d[n_points:, 0] = points[:, 0]
- points_3d[:n_points, 1] = points[:, 1] - y_off
- points_3d[n_points:, 1] = points[:, 1] + y_off
- points_3d[:n_points, 2] = points_3d[n_points:, 2] = points[:, 2] + z_off
+ N = points.shape[0]
+ # Generate left and right 3D points in one array using broadcasting
+ offsets = np.array([[0, -y_off, z_off], [0, y_off, z_off]], dtype=np.float32)
+ points_3d = points[None, :, :] + offsets[:, None, :] # Shape: 2xNx3
+ points_3d = points_3d.reshape(2 * N, 3) # Shape: (2*N)x3
- # Single matrix multiplication for projections
- proj = np.ascontiguousarray(self._temp_proj[:, :n_points * 2]) # Slice the pre-allocated array
- np.dot(self._car_space_transform, points_3d.T, out=proj)
- valid_z = np.abs(proj[2]) > 1e-6
- if not np.any(valid_z):
+ # Transform all points to projected space in one operation
+ proj = self._car_space_transform @ points_3d.T # Shape: 3x(2*N)
+ proj = proj.reshape(3, 2, N)
+ left_proj = proj[:, 0, :]
+ right_proj = proj[:, 1, :]
+
+ # Filter points where z is sufficiently large
+ valid_proj = (np.abs(left_proj[2]) >= 1e-6) & (np.abs(right_proj[2]) >= 1e-6)
+ if not np.any(valid_proj):
return np.empty((0, 2), dtype=np.float32)
# Compute screen coordinates
- screen = proj[:2, valid_z] / proj[2, valid_z][None, :]
- left_screen = screen[:, :n_points].T
- right_screen = screen[:, n_points:].T
+ left_screen = left_proj[:2, valid_proj] / left_proj[2, valid_proj][None, :]
+ right_screen = right_proj[:2, valid_proj] / right_proj[2, valid_proj][None, :]
- # Ensure consistent shapes by re-aligning valid points
- valid_points = np.minimum(left_screen.shape[0], right_screen.shape[0])
- if valid_points == 0:
+ # Define clip region bounds
+ clip = self._clip_region
+ x_min, x_max = clip.x, clip.x + clip.width
+ y_min, y_max = clip.y, clip.y + clip.height
+
+ # Filter points within clip region
+ left_in_clip = (
+ (left_screen[0] >= x_min) & (left_screen[0] <= x_max) &
+ (left_screen[1] >= y_min) & (left_screen[1] <= y_max)
+ )
+ right_in_clip = (
+ (right_screen[0] >= x_min) & (right_screen[0] <= x_max) &
+ (right_screen[1] >= y_min) & (right_screen[1] <= y_max)
+ )
+ both_in_clip = left_in_clip & right_in_clip
+
+ if not np.any(both_in_clip):
return np.empty((0, 2), dtype=np.float32)
- left_screen = left_screen[:valid_points]
- right_screen = right_screen[:valid_points]
- if self._clip_region:
- clip = self._clip_region
- bounds_mask = (
- (left_screen[:, 0] >= clip.x) & (left_screen[:, 0] <= clip.x + clip.width) &
- (left_screen[:, 1] >= clip.y) & (left_screen[:, 1] <= clip.y + clip.height) &
- (right_screen[:, 0] >= clip.x) & (right_screen[:, 0] <= clip.x + clip.width) &
- (right_screen[:, 1] >= clip.y) & (right_screen[:, 1] <= clip.y + clip.height)
- )
- if not np.any(bounds_mask):
+ # Select valid and clipped points
+ left_screen = left_screen[:, both_in_clip]
+ right_screen = right_screen[:, both_in_clip]
+
+ # Handle Y-coordinate inversion on hills
+ if not allow_invert and left_screen.shape[1] > 1:
+ y = left_screen[1, :] # y-coordinates
+ keep = y == np.minimum.accumulate(y)
+ if not np.any(keep):
return np.empty((0, 2), dtype=np.float32)
- left_screen = left_screen[bounds_mask]
- right_screen = right_screen[bounds_mask]
+ left_screen = left_screen[:, keep]
+ right_screen = right_screen[:, keep]
- if not allow_invert and left_screen.shape[0] > 1:
- keep = np.concatenate(([True], np.diff(left_screen[:, 1]) < 0))
- left_screen = left_screen[keep]
- right_screen = right_screen[keep]
- if left_screen.shape[0] == 0:
- return np.empty((0, 2), dtype=np.float32)
-
- return np.vstack((left_screen, right_screen[::-1])).astype(np.float32)
+ return np.vstack((left_screen.T, right_screen[:, ::-1].T)).astype(np.float32)
@staticmethod
def _map_val(x, x0, x1, y0, y1):
@@ -417,10 +420,10 @@ class ModelRenderer:
def _hsla_to_color(h, s, l, a):
rgb = colorsys.hls_to_rgb(h, l, s)
return rl.Color(
- int(rgb[0] * 255),
- int(rgb[1] * 255),
- int(rgb[2] * 255),
- int(a * 255)
+ int(rgb[0] * 255),
+ int(rgb[1] * 255),
+ int(rgb[2] * 255),
+ int(a * 255)
)
@staticmethod
diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc
index 97ee539dbe..8f992bde5f 100644
--- a/selfdrive/ui/qt/offroad/developer_panel.cc
+++ b/selfdrive/ui/qt/offroad/developer_panel.cc
@@ -48,17 +48,6 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
});
addItem(experimentalLongitudinalToggle);
- enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "");
- addItem(enableGithubRunner);
-
- // error log button
- errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
- connect(errorLogBtn, &ButtonControl::clicked, [=]() {
- std::string txt = util::read_file("/data/community/crashes/error.log");
- ConfirmationDialog::rich(QString::fromStdString(txt), this);
- });
- addItem(errorLogBtn);
-
// Joystick and longitudinal maneuvers should be hidden on release branches
is_release = params.getBool("IsReleaseBranch");
@@ -107,8 +96,6 @@ void DeveloperPanel::updateToggles(bool _offroad) {
experimentalLongitudinalToggle->refresh();
// Handle specific controls visibility for release branches
- enableGithubRunner->setVisible(!is_release);
- errorLogBtn->setVisible(!is_release);
joystickToggle->setVisible(!is_release);
offroad = _offroad;
diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h
index c15b574f5a..51b1b0b6f0 100644
--- a/selfdrive/ui/qt/offroad/developer_panel.h
+++ b/selfdrive/ui/qt/offroad/developer_panel.h
@@ -16,10 +16,8 @@ private:
Params params;
ParamControl* adbToggle;
ParamControl* joystickToggle;
- ButtonControl* errorLogBtn;
ParamControl* longManeuverToggle;
ParamControl* experimentalLongitudinalToggle;
- ParamControl* enableGithubRunner;
bool is_release;
bool offroad = false;
diff --git a/selfdrive/ui/qt/offroad/firehose.cc b/selfdrive/ui/qt/offroad/firehose.cc
index c710a8bf28..17df440c36 100644
--- a/selfdrive/ui/qt/offroad/firehose.cc
+++ b/selfdrive/ui/qt/offroad/firehose.cc
@@ -22,7 +22,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent)
layout->setSpacing(20);
// header
- QLabel *title = new QLabel(tr("🔥 Firehose Mode 🔥"));
+ QLabel *title = new QLabel(tr("Firehose Mode"));
title->setStyleSheet("font-size: 100px; font-weight: 500; font-family: 'Noto Color Emoji';");
layout->addWidget(title, 0, Qt::AlignCenter);
diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc
index 4a2bfd9dd3..205a14624a 100644
--- a/selfdrive/ui/qt/offroad/settings.cc
+++ b/selfdrive/ui/qt/offroad/settings.cc
@@ -68,6 +68,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"../assets/icons/monitoring.png",
true,
},
+ {
+ "RecordAudio",
+ tr("Record and Upload Microphone Audio"),
+ tr("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."),
+ "../assets/icons/microphone.png",
+ true,
+ },
{
"IsMetric",
tr("Use Metric System"),
@@ -139,6 +146,15 @@ void TogglesPanel::expandToggleDescription(const QString ¶m) {
toggles[param.toStdString()]->showDescription();
}
+void TogglesPanel::scrollToToggle(const QString ¶m) {
+ if (auto it = toggles.find(param.toStdString()); it != toggles.end()) {
+ auto scroll_area = qobject_cast(parent()->parent());
+ if (scroll_area) {
+ scroll_area->ensureWidgetVisible(it->second);
+ }
+ }
+}
+
void TogglesPanel::showEvent(QShowEvent *event) {
updateToggles();
}
@@ -222,7 +238,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(dcamBtn);
#endif
- auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
+ resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), "");
connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription);
connect(resetCalibBtn, &ButtonControl::clicked, [&]() {
if (!uiState()->engaged()) {
@@ -235,6 +251,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
params.remove("LiveParametersV2");
params.remove("LiveDelay");
params.putBool("OnroadCycleRequested", true);
+ updateCalibDescription();
}
}
} else {
@@ -313,9 +330,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
}
void DevicePanel::updateCalibDescription() {
- QString desc =
- tr("sunnypilot requires the device to be mounted within 4° left or right and "
- "within 5° up or 9° down. sunnypilot is continuously calibrating, resetting is rarely required.");
+ QString desc = tr("sunnypilot requires the device to be mounted within 4° left or right and within 5° up or 9° down.");
std::string calib_bytes = params.get("CalibrationParams");
if (!calib_bytes.empty()) {
try {
@@ -333,8 +348,48 @@ void DevicePanel::updateCalibDescription() {
qInfo() << "invalid CalibrationParams";
}
}
- desc += tr(" Resetting calibration will restart openpilot if the car is powered on.");
- qobject_cast(sender())->setDescription(desc);
+
+ int lag_perc = 0;
+ std::string lag_bytes = params.get("LiveDelay");
+ if (!lag_bytes.empty()) {
+ try {
+ AlignedBuffer aligned_buf;
+ capnp::FlatArrayMessageReader cmsg(aligned_buf.align(lag_bytes.data(), lag_bytes.size()));
+ lag_perc = cmsg.getRoot().getLiveDelay().getCalPerc();
+ } catch (kj::Exception) {
+ qInfo() << "invalid LiveDelay";
+ }
+ }
+ if (lag_perc < 100) {
+ desc += tr("\n\nSteering lag calibration is %1% complete.").arg(lag_perc);
+ } else {
+ desc += tr("\n\nSteering lag calibration is complete.");
+ }
+
+ std::string torque_bytes = params.get("LiveTorqueParameters");
+ if (!torque_bytes.empty()) {
+ try {
+ AlignedBuffer aligned_buf;
+ capnp::FlatArrayMessageReader cmsg(aligned_buf.align(torque_bytes.data(), torque_bytes.size()));
+ auto torque = cmsg.getRoot().getLiveTorqueParameters();
+ // don't add for non-torque cars
+ if (torque.getUseParams()) {
+ int torque_perc = torque.getCalPerc();
+ if (torque_perc < 100) {
+ desc += tr(" Steering torque response calibration is %1% complete.").arg(torque_perc);
+ } else {
+ desc += tr(" Steering torque response calibration is complete.");
+ }
+ }
+ } catch (kj::Exception) {
+ qInfo() << "invalid LiveTorqueParameters";
+ }
+ }
+
+ desc += "\n\n";
+ desc += tr("openpilot is continuously calibrating, resetting is rarely required. "
+ "Resetting calibration will restart openpilot if the car is powered on.");
+ resetCalibBtn->setDescription(desc);
}
void DevicePanel::reboot() {
@@ -387,6 +442,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
}
} else {
emit expandToggleDescription(param);
+ emit scrollToToggle(param);
}
}
@@ -427,6 +483,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
TogglesPanel *toggles = new TogglesPanel(this);
QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
+ QObject::connect(this, &SettingsWindow::scrollToToggle, toggles, &TogglesPanel::scrollToToggle);
auto networking = new Networking(this);
QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &Networking::setPrimeType);
diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h
index 8b162bb843..a11a300128 100644
--- a/selfdrive/ui/qt/offroad/settings.h
+++ b/selfdrive/ui/qt/offroad/settings.h
@@ -42,6 +42,7 @@ signals:
void reviewTrainingGuide();
void showDriverView();
void expandToggleDescription(const QString ¶m);
+ void scrollToToggle(const QString ¶m);
protected:
QPushButton *sidebar_alert_widget;
@@ -67,6 +68,7 @@ protected slots:
protected:
Params params;
ButtonControl *pair_device;
+ ButtonControl *resetCalibBtn;
};
class TogglesPanel : public ListWidget {
@@ -77,6 +79,7 @@ public:
public slots:
void expandToggleDescription(const QString ¶m);
+ void scrollToToggle(const QString ¶m);
protected slots:
virtual void updateState(const UIState &s);
diff --git a/selfdrive/ui/qt/onroad/model.cc b/selfdrive/ui/qt/onroad/model.cc
index dc801e591f..a1e3756cb6 100644
--- a/selfdrive/ui/qt/onroad/model.cc
+++ b/selfdrive/ui/qt/onroad/model.cc
@@ -34,6 +34,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
}
}
+ drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
painter.restore();
}
@@ -173,6 +174,173 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
(1 - t) * start.alphaF() + t * end.alphaF());
}
+
+void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
+ auto *s = uiState();
+ auto &sm = *(s->sm);
+
+ if (!sm.alive("radarState")) return;
+
+ const auto &radar_state = sm["radarState"].getRadarState();
+ const auto &lead_one = radar_state.getLeadOne();
+ const auto &lead_two = radar_state.getLeadTwo();
+
+ // Check if we have any active leads
+ bool has_lead_one = lead_one.getStatus();
+ bool has_lead_two = lead_two.getStatus();
+
+ if (!has_lead_one && !has_lead_two) {
+ // Fade out status display
+ lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
+ if (lead_status_alpha <= 0.0f) return;
+ } else {
+ // Fade in status display
+ lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
+ }
+
+ // Draw status for each lead vehicle under its chevron
+ if (true) {
+ drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
+ }
+
+ if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
+ drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
+ }
+}
+
+void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter,
+ const cereal::RadarState::LeadData::Reader &lead_data,
+ const QPointF &chevron_pos,
+ int height, int width,
+ const QString &label) {
+
+ float d_rel = lead_data.getDRel();
+ float v_rel = lead_data.getVRel();
+ auto *s = uiState();
+ auto &sm = *(s->sm);
+ float v_ego = sm["carState"].getCarState().getVEgo();
+
+ int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
+
+ // Calculate chevron size (same logic as drawLead)
+ float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
+
+ QFont content_font = painter.font();
+ content_font.setPixelSize(35);
+ content_font.setBold(true);
+ painter.setFont(content_font);
+
+ QFontMetrics fm(content_font);
+ bool is_metric = s->scene.is_metric;
+
+ QStringList text_lines;
+
+ const int chevron_types = 3;
+ const int chevron_all = chevron_types + 1; // All metrics (value 4)
+ QStringList chevron_text[chevron_types];
+ int position;
+ float val;
+
+ // Distance display (chevron_data == 1 or all)
+ if (chevron_data == 1 || chevron_data == chevron_all) {
+ position = 0;
+ val = std::max(0.0f, d_rel);
+ QString distance_unit = is_metric ? "m" : "ft";
+ if (!is_metric) {
+ val *= 3.28084f; // Convert meters to feet
+ }
+ chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
+ }
+
+ // Absolute velocity display (chevron_data == 2 or all)
+ if (chevron_data == 2 || chevron_data == chevron_all) {
+ position = (chevron_data == 2) ? 0 : 1;
+ val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast(MS_TO_KPH) : static_cast(MS_TO_MPH)));
+ chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
+ }
+
+ // Time-to-contact display (chevron_data == 3 or all)
+ if (chevron_data == 3 || chevron_data == chevron_all) {
+ position = (chevron_data == 3) ? 0 : 2;
+ val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
+ QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
+ chevron_text[position].append(ttc_str);
+ }
+
+ // Collect all non-empty text lines
+ for (int i = 0; i < chevron_types; ++i) {
+ if (!chevron_text[i].isEmpty()) {
+ text_lines.append(chevron_text[i]);
+ }
+ }
+
+ // If no text to display, return early
+ if (text_lines.isEmpty()) {
+ return;
+ }
+
+ // Text box dimensions
+ float str_w = 150; // Width of text area
+ float str_h = 45; // Height per line
+
+ // Position text below chevron, centered horizontally
+ float text_x = chevron_pos.x() - str_w / 2;
+ float text_y = chevron_pos.y() + sz + 15;
+
+ // Clamp to screen bounds
+ text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
+
+ // Shadow offset
+ QPoint shadow_offset(2, 2);
+
+ // Draw each line of text with shadow
+ for (int i = 0; i < text_lines.size(); ++i) {
+ if (!text_lines[i].isEmpty()) {
+ QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
+
+ // Draw shadow
+ painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
+ painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
+ Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
+
+ // Determine text color based on content and danger level
+ QColor text_color;
+
+ // Check if this is a distance line (contains 'm' or 'ft')
+ if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
+ if (d_rel < 20.0f) {
+ text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
+ } else if (d_rel < 40.0f) {
+ text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
+ } else {
+ text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
+ }
+ }
+ // Enhanced color coding for time-to-contact
+ else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
+ float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
+ if (ttc_val < 3.0f) {
+ text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
+ } else if (ttc_val < 6.0f) {
+ text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
+ } else {
+ text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
+ }
+ }
+ else {
+ text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
+ }
+
+ // Draw main text
+ painter.setPen(text_color);
+ painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
+ }
+ }
+
+ // Reset pen
+ painter.setPen(Qt::NoPen);
+}
+
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
const QPointF &vd, const QRect &surface_rect) {
const float speedBuff = 10.;
diff --git a/selfdrive/ui/qt/onroad/model.h b/selfdrive/ui/qt/onroad/model.h
index 85eb236e76..e40f832c37 100644
--- a/selfdrive/ui/qt/onroad/model.h
+++ b/selfdrive/ui/qt/onroad/model.h
@@ -34,6 +34,12 @@ protected:
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
QPolygonF *pvd, int max_idx, bool allow_invert = true);
+ void drawLeadStatus(QPainter &painter, int height, int width);
+ void drawLeadStatusAtPosition(QPainter &painter,
+ const cereal::RadarState::LeadData::Reader &lead_data,
+ const QPointF &chevron_pos,
+ int height, int width,
+ const QString &label);
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
@@ -58,4 +64,9 @@ protected:
QPointF lead_vertices[2] = {};
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
QRectF clip_region;
+
+ float lead_status_alpha = 0.0f;
+ QPointF lead_status_pos;
+ QString lead_status_text;
+ QColor lead_status_color;
};
diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc
index 382a15db91..90ed973b0d 100644
--- a/selfdrive/ui/qt/setup/setup.cc
+++ b/selfdrive/ui/qt/setup/setup.cc
@@ -136,6 +136,59 @@ QWidget * Setup::low_voltage() {
return widget;
}
+QWidget * Setup::custom_software_warning() {
+ QWidget *widget = new QWidget();
+ QVBoxLayout *main_layout = new QVBoxLayout(widget);
+ main_layout->setContentsMargins(55, 0, 55, 55);
+ main_layout->setSpacing(0);
+
+ QVBoxLayout *inner_layout = new QVBoxLayout();
+ inner_layout->setContentsMargins(110, 110, 300, 0);
+ main_layout->addLayout(inner_layout);
+
+ QLabel *title = new QLabel(tr("WARNING: Custom Software"));
+ title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
+ inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
+
+ inner_layout->addSpacing(25);
+
+ QLabel *body = new QLabel(tr("Use caution when installing third-party software. Third-party software has not been tested by comma, and may cause damage to your device and/or vehicle.\n\nIf you'd like to proceed, use https://flash.comma.ai to restore your device to a factory state later."));
+ body->setWordWrap(true);
+ body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
+ body->setStyleSheet("font-size: 65px; font-weight: 300;");
+ inner_layout->addWidget(body);
+
+ inner_layout->addStretch();
+
+ QHBoxLayout *blayout = new QHBoxLayout();
+ blayout->setSpacing(50);
+ main_layout->addLayout(blayout, 0);
+
+ QPushButton *back = new QPushButton(tr("Back"));
+ back->setObjectName("navBtn");
+ blayout->addWidget(back);
+ QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
+
+ QPushButton *cont = new QPushButton(tr("Continue"));
+ cont->setObjectName("navBtn");
+ blayout->addWidget(cont);
+ QObject::connect(cont, &QPushButton::clicked, this, [=]() {
+ QTimer::singleShot(0, [=]() {
+ setCurrentWidget(downloading_widget);
+ });
+ QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
+ if (!url.isEmpty()) {
+ QTimer::singleShot(1000, this, [=]() {
+ download(url);
+ });
+ } else {
+ setCurrentWidget(software_selection_widget);
+ }
+ });
+
+ return widget;
+}
+
QWidget * Setup::getting_started() {
QWidget *widget = new QWidget();
@@ -305,20 +358,17 @@ QWidget * Setup::software_selection() {
blayout->addWidget(cont);
QObject::connect(cont, &QPushButton::clicked, [=]() {
- auto w = currentWidget();
- QTimer::singleShot(0, [=]() {
- setCurrentWidget(downloading_widget);
- });
- QString url = OPENPILOT_URL;
if (group->checkedButton() != openpilot) {
- url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
- }
- if (!url.isEmpty()) {
- QTimer::singleShot(1000, this, [=]() {
- download(url);
+ QTimer::singleShot(0, [=]() {
+ setCurrentWidget(custom_software_warning_widget);
});
} else {
- setCurrentWidget(w);
+ QTimer::singleShot(0, [=]() {
+ setCurrentWidget(downloading_widget);
+ });
+ QTimer::singleShot(1000, this, [=]() {
+ download(OPENPILOT_URL);
+ });
}
});
@@ -415,8 +465,10 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
addWidget(getting_started());
addWidget(network_setup());
- addWidget(software_selection());
-
+ software_selection_widget = software_selection();
+ addWidget(software_selection_widget);
+ custom_software_warning_widget = custom_software_warning();
+ addWidget(custom_software_warning_widget);
downloading_widget = downloading();
addWidget(downloading_widget);
diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h
index ee5cc85483..986956c902 100644
--- a/selfdrive/ui/qt/setup/setup.h
+++ b/selfdrive/ui/qt/setup/setup.h
@@ -15,6 +15,7 @@ public:
private:
void selectLanguage();
QWidget *low_voltage();
+ QWidget *custom_software_warning();
QWidget *getting_started();
QWidget *network_setup();
QWidget *software_selection();
@@ -23,6 +24,8 @@ private:
QWidget *failed_widget;
QWidget *downloading_widget;
+ QWidget *custom_software_warning_widget;
+ QWidget *software_selection_widget;
QTranslator translator;
signals:
diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc
index fd8ea5bcf5..c63cc01592 100644
--- a/selfdrive/ui/qt/sidebar.cc
+++ b/selfdrive/ui/qt/sidebar.cc
@@ -24,10 +24,11 @@ void Sidebar::drawMetric(QPainter &p, const QPair &label, QCol
p.drawText(rect.adjusted(22, 0, 0, 0), Qt::AlignCenter, label.first + "\n" + label.second);
}
-Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(false), settings_pressed(false) {
+Sidebar::Sidebar(QWidget *parent) : QFrame(parent), onroad(false), flag_pressed(false), settings_pressed(false), mic_indicator_pressed(false) {
home_img = loadPixmap("../assets/images/button_home.png", home_btn.size());
flag_img = loadPixmap("../assets/images/button_flag.png", home_btn.size());
settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio);
+ mic_img = loadPixmap("../assets/icons/microphone.png", QSize(30, 30));
connect(this, &Sidebar::valueChanged, [=] { update(); });
@@ -47,12 +48,15 @@ void Sidebar::mousePressEvent(QMouseEvent *event) {
} else if (settings_btn.contains(event->pos())) {
settings_pressed = true;
update();
+ } else if (recording_audio && mic_indicator_btn.contains(event->pos())) {
+ mic_indicator_pressed = true;
+ update();
}
}
void Sidebar::mouseReleaseEvent(QMouseEvent *event) {
- if (flag_pressed || settings_pressed) {
- flag_pressed = settings_pressed = false;
+ if (flag_pressed || settings_pressed || mic_indicator_pressed) {
+ flag_pressed = settings_pressed = mic_indicator_pressed = false;
update();
}
if (onroad && home_btn.contains(event->pos())) {
@@ -61,6 +65,8 @@ void Sidebar::mouseReleaseEvent(QMouseEvent *event) {
pm->send("userFlag", msg);
} else if (settings_btn.contains(event->pos())) {
emit openSettings();
+ } else if (recording_audio && mic_indicator_btn.contains(event->pos())) {
+ emit openSettings(2, "RecordAudio");
}
}
@@ -106,6 +112,8 @@ void Sidebar::updateState(const UIState &s) {
pandaStatus = {{tr("NO"), tr("PANDA")}, danger_color};
}
setProperty("pandaStatus", QVariant::fromValue(pandaStatus));
+
+ setProperty("recordingAudio", s.scene.recording_audio);
}
void Sidebar::paintEvent(QPaintEvent *event) {
@@ -124,6 +132,14 @@ void Sidebar::drawSidebar(QPainter &p) {
p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img);
p.setOpacity(onroad && flag_pressed ? 0.65 : 1.0);
p.drawPixmap(home_btn.x(), home_btn.y(), onroad ? flag_img : home_img);
+ if (recording_audio) {
+ p.setBrush(danger_color);
+ p.setOpacity(mic_indicator_pressed ? 0.65 : 1.0);
+ p.drawRoundedRect(mic_indicator_btn, mic_indicator_btn.height() / 2, mic_indicator_btn.height() / 2);
+ int icon_x = mic_indicator_btn.x() + (mic_indicator_btn.width() - mic_img.width()) / 2;
+ int icon_y = mic_indicator_btn.y() + (mic_indicator_btn.height() - mic_img.height()) / 2;
+ p.drawPixmap(icon_x, icon_y, mic_img);
+ }
p.setOpacity(1.0);
// network
diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h
index 3c57d10a1a..4f8409be10 100644
--- a/selfdrive/ui/qt/sidebar.h
+++ b/selfdrive/ui/qt/sidebar.h
@@ -23,6 +23,7 @@ class Sidebar : public QFrame {
Q_PROPERTY(ItemStatus tempStatus MEMBER temp_status NOTIFY valueChanged);
Q_PROPERTY(QString netType MEMBER net_type NOTIFY valueChanged);
Q_PROPERTY(int netStrength MEMBER net_strength NOTIFY valueChanged);
+ Q_PROPERTY(bool recordingAudio MEMBER recording_audio NOTIFY valueChanged);
public:
explicit Sidebar(QWidget* parent = 0);
@@ -42,8 +43,8 @@ protected:
void drawMetric(QPainter &p, const QPair &label, QColor c, int y);
virtual void drawSidebar(QPainter &p);
- QPixmap home_img, flag_img, settings_img;
- bool onroad, flag_pressed, settings_pressed;
+ QPixmap home_img, flag_img, settings_img, mic_img;
+ bool onroad, recording_audio, flag_pressed, settings_pressed, mic_indicator_pressed;
const QMap network_type = {
{cereal::DeviceState::NetworkType::NONE, tr("--")},
{cereal::DeviceState::NetworkType::WIFI, tr("Wi-Fi")},
@@ -56,6 +57,7 @@ protected:
const QRect home_btn = QRect(60, 860, 180, 180);
const QRect settings_btn = QRect(50, 35, 200, 117);
+ const QRect mic_indicator_btn = QRect(158, 252, 75, 40);
const QColor good_color = QColor(255, 255, 255);
const QColor warning_color = QColor(218, 202, 37);
const QColor danger_color = QColor(201, 34, 49);
diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc
index 0cbf14931b..4f330dca8d 100644
--- a/selfdrive/ui/qt/widgets/input.cc
+++ b/selfdrive/ui/qt/widgets/input.cc
@@ -2,6 +2,7 @@
#include
#include
+#include
#include "system/hardware/hw.h"
#include "selfdrive/ui/qt/util.h"
@@ -334,3 +335,141 @@ QString MultiOptionDialog::getSelection(const QString &prompt_text, const QStrin
}
return "";
}
+
+TreeOptionDialog::TreeOptionDialog(const QString &prompt_text, const QList> &items,
+ const QString ¤t, QWidget *parent) : DialogBase(parent) {
+ QFrame *container = new QFrame(this);
+ container->setStyleSheet(R"(
+ QFrame { background-color: #1B1B1B; }
+ #confirm_btn[enabled="false"] { background-color: #2B2B2B; }
+ #confirm_btn:enabled { background-color: #465BEA; }
+ #confirm_btn:enabled:pressed { background-color: #3049F4; }
+ QTreeWidget {
+ background-color: transparent;
+ border: none;
+ }
+ QTreeWidget::item {
+ height: 135;
+ padding: 0px 50px;
+ margin: 5px;
+ text-align: left;
+ font-size: 55px;
+ font-weight: 300;
+ border-radius: 10px;
+ background-color: #4F4F4F;
+ color: white;
+ }
+ QTreeWidget::item:selected {
+ background-color: #465BEA;
+ }
+ QTreeWidget::branch {
+ background-color: transparent;
+ }
+ )");
+
+ QVBoxLayout *main_layout = new QVBoxLayout(container);
+ main_layout->setContentsMargins(55, 50, 55, 50);
+
+ QLabel *title = new QLabel(prompt_text, this);
+ title->setStyleSheet("font-size: 70px; font-weight: 500;");
+ main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
+ main_layout->addSpacing(25);
+
+ treeWidget = new QTreeWidget(this);
+ treeWidget->setHeaderHidden(true);
+ treeWidget->setIndentation(50);
+ treeWidget->setExpandsOnDoubleClick(false); // Disable double-click expansion
+ treeWidget->setAnimated(true);
+ treeWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ treeWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+ treeWidget->setDragEnabled(false);
+ treeWidget->setMouseTracking(true);
+
+ // Connect single-click to expand/collapse
+ QObject::connect(treeWidget, &QTreeWidget::itemClicked, [=](QTreeWidgetItem *item, int) {
+ if (item->childCount() > 0) {
+ item->setExpanded(!item->isExpanded());
+ treeWidget->scrollToItem(item->child(0), QAbstractItemView::EnsureVisible);
+ }
+ });
+
+ QScroller::grabGesture(treeWidget->viewport(), QScroller::LeftMouseButtonGesture);
+
+ // Populate tree
+ QListIterator> iter(items);
+ while (iter.hasNext()) {
+ QPair currItem = iter.next();
+ if (currItem.first.isEmpty()) {
+ for (const QString &item : currItem.second) {
+ QTreeWidgetItem *topLevel = new QTreeWidgetItem();
+ topLevel->setText(0, item);
+ topLevel->setFlags(topLevel->flags() | Qt::ItemIsSelectable);
+ treeWidget->addTopLevelItem(topLevel);
+ if (item == current) {
+ topLevel->setSelected(true);
+ }
+ }
+ } else {
+ QTreeWidgetItem *folderItem = new QTreeWidgetItem(treeWidget);
+ folderItem->setIcon(0, QIcon(QPixmap("../assets/icons/menu.png")));
+ folderItem->setText(0, " " + currItem.first);
+ folderItem->setFlags(folderItem->flags() | Qt::ItemIsAutoTristate);
+ folderItem->setFlags(folderItem->flags() & ~Qt::ItemIsSelectable);
+
+ for (const QString &item : currItem.second)
+ {
+ QTreeWidgetItem *childItem = new QTreeWidgetItem(folderItem);
+ childItem->setText(0, item);
+ childItem->setFlags(childItem->flags() | Qt::ItemIsSelectable);
+
+ if (item == current) {
+ childItem->setSelected(true);
+ folderItem->setExpanded(true);
+ }
+ }
+ }
+ }
+
+ confirm_btn = new QPushButton(tr("Select"));
+ confirm_btn->setObjectName("confirm_btn");
+ confirm_btn->setEnabled(false);
+
+ QObject::connect(treeWidget, &QTreeWidget::itemSelectionChanged, [=]() {
+ QList selectedItems = treeWidget->selectedItems();
+ if (!selectedItems.isEmpty()) {
+ selection = selectedItems.first()->text(0);
+ confirm_btn->setEnabled(selection != current);
+ }
+ });
+
+ ScrollView *scroll_view = new ScrollView(treeWidget, this);
+ scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+
+ main_layout->addWidget(scroll_view);
+ main_layout->addSpacing(35);
+
+ // cancel + confirm buttons
+ QHBoxLayout *blayout = new QHBoxLayout;
+ main_layout->addLayout(blayout);
+ blayout->setSpacing(50);
+
+ QPushButton *cancel_btn = new QPushButton(tr("Cancel"));
+ QObject::connect(cancel_btn, &QPushButton::clicked, this, &ConfirmationDialog::reject);
+ QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept);
+ blayout->addWidget(cancel_btn);
+ blayout->addWidget(confirm_btn);
+
+ QVBoxLayout *outer_layout = new QVBoxLayout(this);
+ outer_layout->setContentsMargins(50, 50, 50, 50);
+ outer_layout->addWidget(container);
+}
+
+QString TreeOptionDialog::getSelection(const QString &prompt_text, const QList> &items,
+ const QString ¤t, QWidget *parent) {
+ TreeOptionDialog d(prompt_text, items, current, parent);
+ if (d.exec()) {
+ return d.selection;
+ }
+ return "";
+}
diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h
index 089e54e4a0..76f87bf32f 100644
--- a/selfdrive/ui/qt/widgets/input.h
+++ b/selfdrive/ui/qt/widgets/input.h
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include "selfdrive/ui/qt/widgets/keyboard.h"
@@ -69,3 +70,16 @@ public:
static QString getSelection(const QString &prompt_text, const QStringList &l, const QString ¤t, QWidget *parent);
QString selection;
};
+
+class TreeOptionDialog : public DialogBase {
+ Q_OBJECT
+
+public:
+ explicit TreeOptionDialog(const QString &prompt_text, const QList> &items, const QString ¤t, QWidget *parent = nullptr);
+ static QString getSelection(const QString &prompt_text, const QList> &items, const QString ¤t, QWidget *parent = nullptr);
+ QString selection;
+
+private:
+ QTreeWidget *treeWidget;
+ QPushButton *confirm_btn;
+};
diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py
index facb398339..a94456efe0 100644
--- a/selfdrive/ui/soundd.py
+++ b/selfdrive/ui/soundd.py
@@ -139,7 +139,7 @@ class Soundd(QuietMode):
# sounddevice must be imported after forking processes
import sounddevice as sd
- sm = messaging.SubMaster(['selfdriveState', 'microphone'])
+ sm = messaging.SubMaster(['selfdriveState', 'soundPressure'])
with self.get_stream(sd) as stream:
rk = Ratekeeper(20)
@@ -150,8 +150,8 @@ class Soundd(QuietMode):
self.load_param()
- if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert
- self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb)
+ if sm.updated['soundPressure'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert
+ self.spl_filter_weighted.update(sm["soundPressure"].soundPressureWeightedDb)
self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x))
self.get_audible_alert(sm)
diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript
index 5d67f4157d..810338aae8 100644
--- a/selfdrive/ui/sunnypilot/SConscript
+++ b/selfdrive/ui/sunnypilot/SConscript
@@ -21,6 +21,7 @@ qt_src = [
"sunnypilot/qt/home.cc",
"sunnypilot/qt/offroad/exit_offroad_button.cc",
"sunnypilot/qt/offroad/offroad_home.cc",
+ "sunnypilot/qt/offroad/settings/developer_panel.cc",
"sunnypilot/qt/offroad/settings/device_panel.cc",
"sunnypilot/qt/offroad/settings/lateral_panel.cc",
"sunnypilot/qt/offroad/settings/longitudinal_panel.cc",
@@ -49,6 +50,10 @@ lateral_panel_qt_src = [
"sunnypilot/qt/offroad/settings/lateral/neural_network_lateral_control.cc",
]
+longitudinal_panel_qt_src = [
+ "sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc",
+]
+
network_src = [
"sunnypilot/qt/network/sunnylink/sunnylink_client.cc",
"sunnypilot/qt/network/sunnylink/services/base_device_service.cc",
@@ -83,7 +88,7 @@ brand_settings_qt_src = [
sp_widgets_src = widgets_src + network_src
-sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + osm_panel_qt_src
+sp_qt_src = qt_src + lateral_panel_qt_src + vehicle_panel_qt_src + brand_settings_qt_src + longitudinal_panel_qt_src + osm_panel_qt_src
sp_qt_util = qt_util
Export('sp_widgets_src', 'sp_qt_src', "sp_qt_util")
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc
new file mode 100644
index 0000000000..5c9f0ff99e
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.cc
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
+
+DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(parent) {
+
+ // Advanced Controls Toggle
+ showAdvancedControls = new ParamControlSP("ShowAdvancedControls", tr("Show Advanced Controls"), tr("Toggle visibility of advanced sunnypilot controls.\nThis only toggles the visibility of the controls; it does not toggle the actual control enabled/disabled state."), "");
+ addItem(showAdvancedControls);
+
+ QObject::connect(showAdvancedControls, &ParamControlSP::toggleFlipped, this, [=](bool) {
+ AbstractControlSP::UpdateAllAdvancedControls();
+ updateToggles(!uiState()->scene.started);
+ });
+ showAdvancedControls->showDescription();
+
+ // Github Runner Toggle
+ enableGithubRunner = new ParamControlSP("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "", this, true);
+ addItem(enableGithubRunner);
+
+ // Quickboot Mode Toggle
+ prebuiltToggle = new ParamControlSP("QuickBootToggle", tr("Enable Quickboot Mode"), tr(""), "", this, true);
+ addItem(prebuiltToggle);
+
+ QObject::connect(prebuiltToggle, &ParamControl::toggleFlipped, [=](bool state) {
+ QString prebuiltPath = "/data/openpilot/prebuilt";
+ state ? QFile(prebuiltPath).open(QIODevice::WriteOnly) : QFile::remove(prebuiltPath);
+ prebuiltToggle->refresh();
+ });
+ prebuiltToggle->setVisible(false);
+
+ // Error log button
+ errorLogBtn = new ButtonControlSP(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
+ connect(errorLogBtn, &ButtonControlSP::clicked, [=]() {
+ QFileInfo file("/data/community/crashes/error.log");
+ QString text;
+ if (file.exists()) {
+ text = "" + file.lastModified().toString("dd-MMM-yyyy hh:mm:ss ").toUpper() + "
";
+ }
+ text += QString::fromStdString(util::read_file("/data/community/crashes/error.log"));
+ ConfirmationDialog::rich(text, this);
+ });
+ addItem(errorLogBtn);
+
+ QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
+}
+
+void DeveloperPanelSP::updateToggles(bool offroad) {
+ bool is_release = params.getBool("IsReleaseBranch");
+ bool is_tested = params.getBool("IsTestedBranch");
+ bool is_development = params.getBool("IsDevelopmentBranch");
+ bool disable_updates = params.getBool("DisableUpdates");
+
+ prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
+ prebuiltToggle->setEnabled(disable_updates);
+
+ params.putBool("QuickBootToggle", QFile::exists("/data/openpilot/prebuilt"));
+ prebuiltToggle->refresh();
+
+ prebuiltToggle->setDescription(disable_updates
+ ? tr("When toggled on, this creates a prebuilt file to allow accelerated boot times. When toggled off, "
+ "it immediately removes the prebuilt file so compilation of locally edited cpp files can be made. "
+ "
To edit C++ files locally on device, you MUST first turn off this toggle so the changes can recompile.")
+ : tr("Quickboot mode requires updates to be disabled.
Enable 'Disable Updates' in the Software panel first."));
+
+ enableGithubRunner->setVisible(!is_release);
+ errorLogBtn->setVisible(!is_release);
+ showAdvancedControls->setEnabled(true);
+}
+
+void DeveloperPanelSP::showEvent(QShowEvent *event) {
+ DeveloperPanel::showEvent(event);
+ updateToggles(!uiState()->scene.started);
+ AbstractControlSP::UpdateAllAdvancedControls();
+ prebuiltToggle->showDescription();
+}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h
new file mode 100644
index 0000000000..d63fe18d04
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+#pragma once
+#include
+#include
+
+#include "selfdrive/ui/qt/offroad/developer_panel.h"
+
+class DeveloperPanelSP : public DeveloperPanel {
+ Q_OBJECT
+public:
+ explicit DeveloperPanelSP(SettingsWindow *parent);
+
+private:
+ ParamControlSP *enableGithubRunner;
+ ButtonControlSP *errorLogBtn;
+ ParamControlSP *prebuiltToggle;
+ Params params;
+ ParamControlSP *showAdvancedControls;
+
+private slots:
+ void updateToggles(bool offroad);
+
+protected:
+ void showEvent(QShowEvent *event) override;
+};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc
index b89116d424..43ba7e037f 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.cc
@@ -87,6 +87,17 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
params.put("DeviceBootMode", QString::number(index).toStdString());
updateState();
});
+
+ interactivityTimeout = new OptionControlSP("InteractivityTimeout", tr("Interactivity Timeout"),
+ tr("Apply a custom timeout for settings UI."
+ "\nThis is the time after which settings UI closes automatically if user is not interacting with the screen."),
+ "", {0, 120}, 10, true, nullptr, false);
+
+ connect(interactivityTimeout, &OptionControlSP::updateLabels, [=]() {
+ updateState();
+ });
+
+ addItem(interactivityTimeout);
// Brightness
brightness = new Brightness();
@@ -198,4 +209,11 @@ void DevicePanelSP::updateState() {
currStatus = DeviceSleepModeStatus::OFFROAD;
}
toggleDeviceBootMode->setDescription(deviceSleepModeDescription(currStatus));
+
+ QString timeoutValue = QString::fromStdString(params.get("InteractivityTimeout"));
+ if (timeoutValue == "0") {
+ interactivityTimeout->setLabel("Default");
+ } else {
+ interactivityTimeout->setLabel(timeoutValue + "s");
+ }
}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h
index 2cd3281d1f..899a942dae 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h
@@ -33,6 +33,7 @@ private:
MaxTimeOffroad *maxTimeOffroad;
ButtonParamControlSP *toggleDeviceBootMode;
Brightness *brightness;
+ OptionControlSP *interactivityTimeout;
const QString alwaysOffroadStyle = R"(
PushButtonSP {
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc
new file mode 100644
index 0000000000..29cadeaa62
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.cc
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
+
+CustomAccIncrement::CustomAccIncrement(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
+ : ExpandableToggleRow(param, title, desc, icon, parent) {
+ auto *accFrame = new QFrame(this);
+ auto *accFrameLayout = new QGridLayout();
+ accFrame->setLayout(accFrameLayout);
+ accFrameLayout->setSpacing(0);
+
+ auto *shortPressControl = new AccIncrementOptionControl("CustomAccShortPressIncrement", {1, 10}, 1);
+ connect(shortPressControl, &OptionControlSP::updateLabels, shortPressControl, &AccIncrementOptionControl::refresh);
+
+ auto *longPressControl = new AccIncrementOptionControl("CustomAccLongPressIncrement", {1, 3}, 1, &customLongValues);
+ connect(longPressControl, &OptionControlSP::updateLabels, longPressControl, &AccIncrementOptionControl::refresh);
+
+ shortPressControl->setFixedWidth(280);
+ longPressControl->setFixedWidth(280);
+ accFrameLayout->addWidget(shortPressControl, 0, 0, Qt::AlignLeft);
+ accFrameLayout->addWidget(longPressControl, 0, 1, Qt::AlignRight);
+
+ addItem(accFrame);
+}
+
+AccIncrementOptionControl::AccIncrementOptionControl(const QString ¶m, const MinMaxValue &range, const int per_value_change, const QMap *valMap)
+ : OptionControlSP(param, "", "", "", range, per_value_change, true, valMap) {
+ param_name = param.toStdString();
+ refresh();
+}
+
+void AccIncrementOptionControl::refresh() {
+ std::string val = params.get(param_name);
+ std::string label = "";
+ label += param_name == "CustomAccShortPressIncrement" ? "Short Press" : "Long Press";
+ label += "
" + val;
+ label += param_name == "CustomAccShortPressIncrement"
+ ? (val == "1" ? " (Default)" : "")
+ : (val == "5" ? " (Default)" : "");
+ label += "";
+ setLabel(QString::fromStdString(label));
+}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h
new file mode 100644
index 0000000000..707d4143bc
--- /dev/null
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+ *
+ * This file is part of sunnypilot and is licensed under the MIT License.
+ * See the LICENSE.md file in the root directory for more details.
+ */
+
+#pragma once
+
+#include "selfdrive/ui/sunnypilot/ui.h"
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
+#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
+#include "selfdrive/ui/sunnypilot/qt/widgets/expandable_row.h"
+
+class CustomAccIncrement : public ExpandableToggleRow {
+ Q_OBJECT
+
+public:
+ CustomAccIncrement(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
+
+private:
+ QMap customLongValues = {
+ {"1", "1"},
+ {"2", "5"}, // Default
+ {"3", "10"}
+ };
+};
+
+class AccIncrementOptionControl : public OptionControlSP {
+ Q_OBJECT
+
+public:
+ AccIncrementOptionControl(const QString ¶m, const MinMaxValue &range, int per_value_change, const QMap *valMap = nullptr);
+ void refresh();
+
+protected:
+ std::string param_name;
+
+private:
+ Params params;
+};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc
index 9d8a4d9c19..15ed10bf9a 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.cc
@@ -8,4 +8,72 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
+ main_layout = new QStackedLayout(this);
+ ListWidget *list = new ListWidget(this, false);
+
+ cruisePanelScreen = new QWidget(this);
+ QVBoxLayout *vlayout = new QVBoxLayout(cruisePanelScreen);
+ vlayout->setContentsMargins(0, 0, 0, 0);
+
+ cruisePanelScroller = new ScrollViewSP(list, this);
+ vlayout->addWidget(cruisePanelScroller);
+
+ customAccIncrement = new CustomAccIncrement("CustomAccIncrementsEnabled", tr("Custom ACC Speed Increments"), "", "", this);
+ list->addItem(customAccIncrement);
+
+ QObject::connect(uiState(), &UIState::offroadTransition, this, &LongitudinalPanel::refresh);
+
+ main_layout->addWidget(cruisePanelScreen);
+ main_layout->setCurrentWidget(cruisePanelScreen);
+ refresh(offroad);
+}
+
+void LongitudinalPanel::showEvent(QShowEvent *event) {
+ main_layout->setCurrentWidget(cruisePanelScreen);
+ refresh(offroad);
+}
+
+void LongitudinalPanel::refresh(bool _offroad) {
+ auto cp_bytes = params.get("CarParamsPersistent");
+ if (!cp_bytes.empty()) {
+ AlignedBuffer aligned_buf;
+ capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
+ cereal::CarParams::Reader CP = cmsg.getRoot();
+
+ has_longitudinal_control = hasLongitudinalControl(CP);
+ is_pcm_cruise = CP.getPcmCruise();
+ } else {
+ has_longitudinal_control = false;
+ is_pcm_cruise = false;
+ }
+
+ QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
+ QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled.");
+ QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations.");
+ QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility.");
+
+ if (offroad) {
+ customAccIncrement->setDescription(onroadOnlyDescription);
+ customAccIncrement->showDescription();
+ } else {
+ if (has_longitudinal_control) {
+ if (is_pcm_cruise) {
+ customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
+ customAccIncrement->showDescription();
+ } else {
+ customAccIncrement->setDescription(accEnabledDescription);
+ }
+ } else {
+ params.remove("CustomAccIncrementsEnabled");
+ customAccIncrement->toggleFlipped(false);
+ customAccIncrement->setDescription(accNoLongDescription);
+ customAccIncrement->showDescription();
+ }
+ }
+
+ // enable toggle when long is available and is not PCM cruise
+ customAccIncrement->setEnabled(has_longitudinal_control && !is_pcm_cruise && !offroad);
+ customAccIncrement->refresh();
+
+ offroad = _offroad;
}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h
index cd68e3ec2f..58e94f333b 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h
@@ -7,11 +7,26 @@
#pragma once
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/custom_acc_increment.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
+#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
class LongitudinalPanel : public QWidget {
Q_OBJECT
public:
explicit LongitudinalPanel(QWidget *parent = nullptr);
+ void showEvent(QShowEvent *event) override;
+ void refresh(bool _offroad);
+
+private:
+ Params params;
+ bool has_longitudinal_control = false;
+ bool is_pcm_cruise = false;
+ bool offroad = false;
+
+ QStackedLayout *main_layout = nullptr;
+ ScrollViewSP *cruisePanelScroller = nullptr;
+ QWidget *cruisePanelScreen = nullptr;
+ CustomAccIncrement *customAccIncrement = nullptr;
};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc
index 44a6bd3b48..c4cb4b4767 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.cc
@@ -7,21 +7,57 @@
#include
#include
+#include
+#include
+#include
#include "common/model.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
+static const QString progressStyleActive = "QProgressBar {"
+ " font-size: 40px;"
+ " font-weight: 200;"
+ " padding: 1px;"
+ " border: 3px solid black;"
+ " border-radius: 10px;"
+ "}"
+ "QProgressBar::chunk {"
+ " background-color: #1e79e8;"
+ " border-radius: 10px;"
+ "}";
+
+static const QString progressStyleInactive = progressStyleActive +
+ "QProgressBar::chunk {"
+ " background-color: transparent;"
+ "}";
+
+static const QString progressStyleDone = progressStyleActive +
+ "QProgressBar {"
+ " color: #33ab4c;"
+ "}"
+ "QProgressBar::chunk {"
+ " background-color: transparent;"
+ "}";
+
+static const QString progressStyleError = progressStyleActive +
+ "QProgressBar {"
+ " color: red;"
+ "}"
+ "QProgressBar::chunk {"
+ " background-color: transparent;"
+ "}";
+
ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
- ListWidgetSP *list = new ListWidgetSP(this);
+ ListWidgetSP *list = new ListWidgetSP(this, false);
ScrollViewSP *scroller = new ScrollViewSP(list, this);
main_layout->addWidget(scroller);
const auto current_model = GetActiveModelName();
- currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), current_model);
+ currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), "", this);
currentModelLblBtn->setValue(current_model);
connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &ModelsPanel::handleCurrentModelLblBtnClicked);
@@ -32,23 +68,88 @@ ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn);
+ clearModelCacheBtn = new ButtonControlSP(tr("Clear Model Cache"), tr("CLEAR"), "", this);
+ connect(clearModelCacheBtn, &ButtonControlSP::clicked, this, &ModelsPanel::clearModelCache);
+
+ list->addItem(clearModelCacheBtn);
+
+ // Create progress bars for downloads
+ supercomboProgressBar = createProgressBar(this);
+ QString supercomboType = tr("Driving Model");
+ supercomboFrame = createModelDetailFrame(this, supercomboType, supercomboProgressBar);
+ list->addItem(supercomboFrame);
+
+ navigationProgressBar = createProgressBar(this);
+ QString navigationType = tr("Navigation Model");
+ navigationFrame = createModelDetailFrame(this, navigationType, navigationProgressBar);
+ list->addItem(navigationFrame);
+
+ visionProgressBar = createProgressBar(this);
+ QString visionType = tr("Vision Model");
+ visionFrame = createModelDetailFrame(this, visionType, visionProgressBar);
+ list->addItem(visionFrame);
+
+ policyProgressBar = createProgressBar(this);
+ QString policyType = tr("Policy Model");
+ policyFrame = createModelDetailFrame(this, policyType, policyProgressBar);
+ list->addItem(policyFrame);
+
+ list->addItem(horizontal_line());
+
// LiveDelay toggle
- list->addItem(new ParamControlSP("LagdToggle",
- tr("Live Learning Steer Delay"),
- tr("Enable this for the car to learn and adapt its steering response time. "
- "Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience."),
- "../assets/offroad/icon_shell.png"));
+ lagd_toggle_control = new ParamControlSP("LagdToggle", tr("Live Learning Steer Delay"), "", "../assets/offroad/icon_shell.png");
+ lagd_toggle_control->showDescription();
+ list->addItem(lagd_toggle_control);
+
+ // Software delay control
+ delay_control = new OptionControlSP("LagdToggledelay", tr("Adjust Software Delay"),
+ tr("Adjust the software delay when Live Learning Steer Delay is toggled off."
+ "\nThe default software delay value is 0.2"),
+ "", {5, 30}, 1, false, nullptr, true, true);
+
+ connect(delay_control, &OptionControlSP::updateLabels, [=]() {
+ float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat();
+ delay_control->setLabel(QString::number(value, 'f', 2) + "s");
+ });
+ connect(lagd_toggle_control, &ParamControlSP::toggleFlipped, [=](bool state) {
+ delay_control->setVisible(!state);
+ });
+ delay_control->showDescription();
+ list->addItem(delay_control);
}
+QProgressBar* ModelsPanel::createProgressBar(QWidget *parent) {
+ QProgressBar *progressBar = new QProgressBar(parent);
+ progressBar->setRange(0, 100);
+ progressBar->setValue(0);
+ progressBar->setTextVisible(true);
+ progressBar->setAlignment(Qt::AlignVCenter);
+ return progressBar;
+}
+
+QFrame* ModelsPanel::createModelDetailFrame(QWidget *parent, QString &typeName, QProgressBar *progressBar) {
+ QFrame *frame = new QFrame(parent);
+ QHBoxLayout *layout = new QHBoxLayout(frame);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(50);
+ layout->addWidget(new QLabel(typeName));
+ layout->addWidget(progressBar);
+ frame->setVisible(false);
+ return frame;
+}
/**
* @brief Updates the UI with bundle download progress information
* Reads status from modelManagerSP cereal message and displays status for all models
*/
void ModelsPanel::handleBundleDownloadProgress() {
+ supercomboFrame->setVisible(false);
+ visionFrame->setVisible(false);
+ policyFrame->setVisible(false);
+ navigationFrame->setVisible(false);
+
using DS = cereal::ModelManagerSP::DownloadStatus;
if (!model_manager.hasSelectedBundle() && !model_manager.hasActiveBundle()) {
- currentModelLblBtn->setDescription(tr("No custom model selected!"));
return;
}
@@ -61,21 +162,27 @@ void ModelsPanel::handleBundleDownloadProgress() {
// Get status for each model type in order
for (const auto &model: models) {
- QString typeName;
QString modelName = QString::fromStdString(bundle.getDisplayName());
+ QProgressBar *progressBar = nullptr;
+ QFrame *modelFrame = nullptr;
+
switch (model.getType()) {
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
- typeName = tr("Driving");
+ progressBar = supercomboProgressBar;
+ modelFrame = supercomboFrame;
break;
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
- typeName = tr("Navigation");
+ progressBar = navigationProgressBar;
+ modelFrame = navigationFrame;
break;
case cereal::ModelManagerSP::Model::Type::VISION:
- typeName = tr("Vision");
+ progressBar = visionProgressBar;
+ modelFrame = visionFrame;
break;
case cereal::ModelManagerSP::Model::Type::POLICY:
- typeName = tr("Policy");
+ progressBar = policyProgressBar;
+ modelFrame = policyFrame;
break;
}
@@ -83,31 +190,26 @@ void ModelsPanel::handleBundleDownloadProgress() {
QString line;
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
- line = tr("Downloading %1 model [%2]... (%3%)").arg(typeName, modelName).arg(progress.getProgress(), 0, 'f', 2);
+ progressBar->setStyleSheet(progressStyleActive);
+ progressBar->setValue(progress.getProgress());
+ progressBar->setFormat(QString(" %1% - %2").arg(static_cast(progress.getProgress())).arg(modelName));
+ device()->resetInteractiveTimeout();
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADED) {
- line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("downloaded") : tr("ready"));
+ progressBar->setStyleSheet(progressStyleDone);
+ progressBar->setFormat(tr(" %1 - %2").arg(modelName, download_status_changed ? tr("downloaded") : tr("ready")));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::CACHED) {
- line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("from cache") : tr("ready"));
+ progressBar->setStyleSheet(progressStyleDone);
+ progressBar->setFormat(tr(" %1 - %2").arg(modelName, download_status_changed ? tr("from cache") : tr("ready")));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::FAILED) {
- line = tr("%1 model [%2] download failed").arg(typeName, modelName);
+ progressBar->setStyleSheet(progressStyleError);
+ progressBar->setFormat(tr(" download failed - %1").arg(modelName));
} else {
- line = tr("%1 model [%2] pending...").arg(typeName, modelName);
+ progressBar->setStyleSheet(progressStyleInactive);
+ progressBar->setFormat(tr(" pending - %1").arg(modelName));
}
- status.append(line);
- }
-
- currentModelLblBtn->setDescription(status.join("\n"));
-
- if (prev_download_status != download_status) {
- switch (bundle.getStatus()) {
- case cereal::ModelManagerSP::DownloadStatus::DOWNLOADING:
- case cereal::ModelManagerSP::DownloadStatus::CACHED:
- case cereal::ModelManagerSP::DownloadStatus::DOWNLOADED:
- currentModelLblBtn->showDescription();
- break;
- case cereal::ModelManagerSP::DownloadStatus::FAILED:
- default:
- break;
+ // keep navigation hidden for now to avoid confusion
+ if (model.getType() != cereal::ModelManagerSP::Model::Type::NAVIGATION) {
+ modelFrame->setVisible(true);
}
}
prev_download_status = download_status;
@@ -125,6 +227,17 @@ QString ModelsPanel::GetActiveModelName() {
return DEFAULT_MODEL;
}
+/**
+ * @brief Gets the short name of the currently selected model bundle
+ * @return Display short name of the selected bundle or default model name
+ */
+QString ModelsPanel::GetActiveModelInternalName() {
+ if (model_manager.hasActiveBundle()) {
+ return QString::fromStdString(model_manager.getActiveBundle().getInternalName());
+ }
+ return DEFAULT_MODEL;
+}
+
void ModelsPanel::updateModelManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
model_manager = sm["modelManagerSP"].getModelManagerSP();
@@ -138,28 +251,73 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
- // Create mapping of bundle indices to display names
- QMap index_to_bundle;
+ struct ModelEntry {
+ QString folder;
+ QString displayName;
+ int index;
+ };
+ QList sortedModels;
+ QSet modelFolders;
const auto bundles = model_manager.getAvailableBundles();
- for (const auto &bundle: bundles) {
- index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName()));
+
+ for (const auto &bundle : bundles) {
+ auto overrides = bundle.getOverrides();
+ QString gen;
+ for (const auto &override : overrides) {
+ if (override.getKey() == "folder") {
+ gen = QString::fromStdString(override.getValue().cStr());
+ }
+ }
+
+ modelFolders.insert(gen);
+ sortedModels.append(ModelEntry{
+ gen,
+ QString::fromStdString(bundle.getDisplayName()),
+ static_cast(bundle.getIndex())
+ });
}
- // Sort bundles by index in descending order
- QStringList bundleNames;
- // Add "Default" as the first option
- bundleNames.append(DEFAULT_MODEL);
+ std::sort(sortedModels.begin(), sortedModels.end(),
+ [](const ModelEntry &a, const ModelEntry &b) {
+ return a.index > b.index;
+ });
- auto indices = index_to_bundle.keys();
- std::sort(indices.begin(), indices.end(), std::greater());
- for (const auto &index: indices) {
- bundleNames.append(index_to_bundle[index]);
+ // Create a list of folder-maxIndex pairs for sorting
+ QList> folderMaxIndices;
+ for (const auto &folder : modelFolders) {
+ int maxIndex = -1;
+ for (const auto &model : sortedModels) {
+ if (model.folder == folder) {
+ maxIndex = std::max(maxIndex, model.index);
+ }
+ }
+ folderMaxIndices.append(qMakePair(folder, maxIndex));
}
- currentModelLblBtn->setValue(GetActiveModelName());
+ // Sort folders by their highest model index
+ std::sort(folderMaxIndices.begin(), folderMaxIndices.end(),
+ [](const QPair &a, const QPair &b) {
+ return a.second > b.second;
+ });
- const QString selectedBundleName = MultiOptionDialog::getSelection(
- tr("Select a Model"), bundleNames, GetActiveModelName(), this);
+ // Create the final items list using sorted folders
+ QList> items;
+ for (const auto &folderPair : folderMaxIndices) {
+ QStringList folderModels;
+ for (const auto &model : sortedModels) {
+ if (model.folder == folderPair.first) {
+ folderModels.append(model.displayName);
+ }
+ }
+ items.append(qMakePair(folderPair.first, folderModels));
+ }
+
+ items.insert(0, qMakePair(QString(""), QStringList{DEFAULT_MODEL}));
+
+ currentModelLblBtn->setValue(GetActiveModelInternalName());
+
+ const QString selectedBundleName = TreeOptionDialog::getSelection(
+ tr("Select a Model"), items, GetActiveModelName(), this);
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return;
@@ -197,7 +355,27 @@ void ModelsPanel::updateLabels() {
updateModelManagerState();
handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
- currentModelLblBtn->setValue(GetActiveModelName());
+ currentModelLblBtn->setValue(GetActiveModelInternalName());
+
+ // Update lagdToggle description with current value
+ QString desc = tr("Enable this for the car to learn and adapt its steering response time. "
+ "Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience. "
+ "The Current value is updated automatically when the vehicle is Onroad.");
+ QString current = QString::fromStdString(params.get("LagdToggleDesc", false));
+ if (!current.isEmpty()) {
+ desc += "
" + tr("Current:") + " " + current + "";
+ }
+ lagd_toggle_control->setDescription(desc);
+ lagd_toggle_control->showDescription();
+
+ delay_control->setVisible(!params.getBool("LagdToggle"));
+ if (delay_control->isVisible()) {
+ float value = QString::fromStdString(params.get("LagdToggledelay")).toFloat();
+ delay_control->setLabel(QString::number(value, 'f', 2) + "s");
+ delay_control->showDescription();
+ }
+
+ clearModelCacheBtn->setValue(QString::number(calculateCacheSize(), 'f', 2) + " MB");
}
/**
@@ -218,3 +396,32 @@ void ModelsPanel::showResetParamsDialog() {
params.remove("LiveTorqueParameters");
}
}
+
+void ModelsPanel::clearModelCache() {
+ QString confirmMsg = tr("This will delete ALL downloaded models from the cache"
+ "
except the currently active model."
+ "
Are you sure you want to continue?");
+ QString content("" + tr("Driving Model Selector") + "
"
+ "" + confirmMsg + "
");
+ if (showConfirmationDialog(
+ content,
+ tr("Clear Cache"))) {
+ params.putBool("ModelManager_ClearCache", true);
+ }
+}
+
+double ModelsPanel::calculateCacheSize() {
+ QFuture future_ModelCacheSize = QtConcurrent::run([=]() {
+
+ QDir model_dir(QString::fromStdString(Path::model_root()));
+ QFileInfoList model_files = model_dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
+ qint64 totalSize = 0;
+ for (const QFileInfo &model_file : model_files) {
+ if (model_file.isFile()) {
+ totalSize += model_file.size();
+ }
+ }
+ return totalSize;
+ });
+ return static_cast(future_ModelCacheSize) / (1024.0 * 1024.0);
+}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h
index 4ba526651e..06a2380f39 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h
@@ -7,6 +7,8 @@
#pragma once
+#include
+
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
class ModelsPanel : public QWidget {
@@ -17,6 +19,7 @@ public:
private:
QString GetActiveModelName();
+ QString GetActiveModelInternalName();
void updateModelManagerState();
bool isDownloading() const {
@@ -33,9 +36,13 @@ private:
void handleCurrentModelLblBtnClicked();
void handleBundleDownloadProgress();
void showResetParamsDialog();
+ QProgressBar* createProgressBar(QWidget *parent);
+ QFrame* createModelDetailFrame(QWidget *parent, QString &typeName, QProgressBar *progressBar);
cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{};
+ void clearModelCache();
+ double calculateCacheSize();
bool canContinueOnMeteredDialog() {
if (!is_metered) return true;
@@ -59,6 +66,17 @@ private:
bool is_onroad = false;
ButtonControlSP *currentModelLblBtn;
+ ParamControlSP *lagd_toggle_control;
+ OptionControlSP *delay_control;
+ QProgressBar *supercomboProgressBar;
+ QFrame *supercomboFrame;
+ QProgressBar *navigationProgressBar;
+ QFrame *navigationFrame;
+ QProgressBar *visionProgressBar;
+ QFrame *visionFrame;
+ QProgressBar *policyProgressBar;
+ QFrame *policyFrame;
Params params;
+ ButtonControlSP *clearModelCacheBtn;
};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
index 12d7bd74f7..5415e75ea9 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
@@ -8,10 +8,10 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
-#include "selfdrive/ui/qt/offroad/developer_panel.h"
#include "selfdrive/ui/qt/offroad/firehose.h"
#include "selfdrive/ui/sunnypilot/qt/network/networking.h"
+#include "selfdrive/ui/sunnypilot/qt/offroad/settings/developer_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/device_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
@@ -72,6 +72,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
TogglesPanelSP *toggles = new TogglesPanelSP(this);
QObject::connect(this, &SettingsWindowSP::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription);
+ QObject::connect(this, &SettingsWindowSP::scrollToToggle, toggles, &TogglesPanel::scrollToToggle);
auto networking = new NetworkingSP(this);
QObject::connect(uiState()->prime_state, &PrimeState::changed, networking, &NetworkingSP::setPrimeType);
@@ -90,7 +91,7 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
- PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/icons/shell.png"),
+ PanelInfo(" " + tr("Developer"), new DeveloperPanelSP(this), "../assets/icons/shell.png"),
};
nav_btns = new QButtonGroup(this);
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc
index 9430d02ba8..a1961cb1ea 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.cc
@@ -18,6 +18,18 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
searchBranches(d.text());
}
});
+
+ // Disable Updates toggle
+ disableUpdatesToggle = new ParamControl("DisableUpdates",
+ tr("Disable Updates"),
+ tr("When enabled, software updates will be disabled. This requires a reboot to take effect."),
+ "../assets/icons/icon_warning.png",
+ this, true);
+ disableUpdatesToggle->showDescription();
+ addItem(disableUpdatesToggle);
+ connect(disableUpdatesToggle, &ParamControl::toggleFlipped, this, &SoftwarePanelSP::handleDisableUpdatesToggled);
+ connect(uiState(), &UIState::offroadTransition, this, &SoftwarePanelSP::updateDisableUpdatesToggle);
+ updateDisableUpdatesToggle(!uiState()->scene.started);
}
/**
@@ -49,3 +61,27 @@ void SoftwarePanelSP::searchBranches(const QString &query) {
checkForUpdates();
}
}
+
+void SoftwarePanelSP::handleDisableUpdatesToggled(bool state) {
+ if (ConfirmationDialog::confirm(tr("%1 updates requires a reboot.
Reboot now?")
+ .arg(state ? "Disabling" : "Enabling"), tr("Reboot"), this)) {
+ params.putBool("DoReboot", true);
+ } else {
+ params.putBool("DisableUpdates", !state);
+ disableUpdatesToggle->refresh();
+ }
+}
+
+void SoftwarePanelSP::updateDisableUpdatesToggle(bool offroad) {
+ bool enabled = offroad;
+ disableUpdatesToggle->setEnabled(enabled);
+ disableUpdatesToggle->setDescription(enabled
+ ? tr("When enabled, software updates will be disabled.
This requires a reboot to take effect.")
+ : tr("Please enable always offroad mode or turn off vehicle to adjust these toggles"));
+}
+
+void SoftwarePanelSP::showEvent(QShowEvent *event) {
+ SoftwarePanel::showEvent(event);
+ updateDisableUpdatesToggle(!uiState()->scene.started);
+ disableUpdatesToggle->showDescription();
+}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h
index 56d8b9be5b..efaa836a21 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h
@@ -19,4 +19,10 @@ public:
private:
void searchBranches(const QString &query);
+ ParamControl *disableUpdatesToggle = nullptr;
+ void handleDisableUpdatesToggled(bool state);
+private slots:
+ void updateDisableUpdatesToggle(bool offroad);
+protected:
+ void showEvent(QShowEvent *event) override;
};
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc
index 0d42bd74f4..818da9b75e 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.cc
@@ -12,7 +12,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
paramsRefresh();
});
-
+
main_layout = new QStackedLayout(this);
ListWidgetSP *list = new ListWidgetSP(this, false);
@@ -30,6 +30,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
},
};
+ // Add regular toggles first
for (auto &[param, title, desc, icon, needs_restart] : toggle_defs) {
auto toggle = new ParamControlSP(param, title, desc, icon, this);
@@ -53,9 +54,20 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
param_watcher->addParam(param);
}
+ // Visuals: Display Metrics below Chevron
+ std::vector chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")};
+ chevron_info_settings = new ButtonParamControlSP(
+ "ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."),
+ "",
+ chevron_info_settings_texts,
+ 200);
+ chevron_info_settings->showDescription();
+ list->addItem(chevron_info_settings);
+ param_watcher->addParam("ChevronInfo");
+
sunnypilotScroller = new ScrollViewSP(list, this);
vlayout->addWidget(sunnypilotScroller);
-
+
main_layout->addWidget(sunnypilotScreen);
}
@@ -67,4 +79,8 @@ void VisualsPanel::paramsRefresh() {
for (auto toggle : toggles) {
toggle.second->refresh();
}
+
+ if (chevron_info_settings) {
+ chevron_info_settings->refresh();
+ }
}
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h
index 42e0688957..f342662c22 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/visuals_panel.h
@@ -27,4 +27,5 @@ protected:
Params params;
std::map toggles;
ParamWatcher * param_watcher;
+ ButtonParamControlSP *chevron_info_settings;
};
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.cc b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc
index d873913b57..28374f3a9f 100644
--- a/selfdrive/ui/sunnypilot/qt/widgets/controls.cc
+++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.cc
@@ -30,9 +30,24 @@ QFrame *vertical_space(int height, QWidget *parent) {
}
// AbstractControlSP
+std::vector AbstractControlSP::advanced_controls_;
+AbstractControlSP::~AbstractControlSP() { UnregisterAdvancedControl(this); }
-AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
- : AbstractControl(title, desc, icon, parent) {
+void AbstractControlSP::RegisterAdvancedControl(AbstractControlSP *ctrl) { advanced_controls_.push_back(ctrl); }
+
+void AbstractControlSP::UnregisterAdvancedControl(AbstractControlSP *ctrl) {
+ advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), ctrl), advanced_controls_.end());
+}
+
+void AbstractControlSP::UpdateAllAdvancedControls() {
+ bool visibility = Params().getBool("ShowAdvancedControls");
+ advanced_controls_.erase(std::remove(advanced_controls_.begin(), advanced_controls_.end(), nullptr), advanced_controls_.end());
+ for (auto *ctrl : advanced_controls_) ctrl->setVisible(visibility);
+}
+
+AbstractControlSP::AbstractControlSP(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
+ : AbstractControl(title, desc, icon, parent), isAdvancedControl(advancedControl) {
+ if (isAdvancedControl) RegisterAdvancedControl(this);
main_layout = new QVBoxLayout(this);
main_layout->setMargin(0);
@@ -82,8 +97,8 @@ void AbstractControlSP::hideEvent(QHideEvent *e) {
}
}
-AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent)
- : AbstractControlSP(title, desc, icon, parent) {
+AbstractControlSP_SELECTOR::AbstractControlSP_SELECTOR(const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
+ : AbstractControlSP(title, desc, icon, parent, advancedControl) {
if (title_label != nullptr) {
delete title_label;
@@ -169,8 +184,8 @@ void AbstractControlSP_SELECTOR::hideEvent(QHideEvent *e) {
// controls
-ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent)
- : AbstractControlSP(title, desc, "", parent) {
+ButtonControlSP::ButtonControlSP(const QString &title, const QString &text, const QString &desc, QWidget *parent, bool advancedControl)
+ : AbstractControlSP(title, desc, "", parent, advancedControl) {
btn.setText(text);
btn.setStyleSheet(R"(
@@ -225,8 +240,8 @@ void ElidedLabelSP::paintEvent(QPaintEvent *event) {
// ParamControlSP
-ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
- : ToggleControlSP(title, desc, icon, false, parent) {
+ParamControlSP::ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent, bool advancedControl)
+ : ToggleControlSP(title, desc, icon, false, parent, advancedControl){
key = param.toStdString();
QObject::connect(this, &ParamControlSP::toggleFlipped, this, &ParamControlSP::toggleClicked);
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
index c8849faf53..a840febfe7 100644
--- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h
+++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
@@ -57,6 +57,7 @@ class AbstractControlSP : public AbstractControl {
Q_OBJECT
public:
+ ~AbstractControlSP();
void setDescription(const QString &desc) override {
if (description) description->setText(desc);
}
@@ -81,13 +82,30 @@ public slots:
description->setVisible(true);
}
+ void setVisible(bool visible) override {
+ bool _visible = visible;
+ if (isAdvancedControl && !params.getBool("ShowAdvancedControls")) {
+ _visible = false;
+ }
+ AbstractControl::setVisible(_visible);
+ }
+
+ static void RegisterAdvancedControl(AbstractControlSP *ctrl);
+ static void UnregisterAdvancedControl(AbstractControlSP *ctrl);
+ static void UpdateAllAdvancedControls();
+
protected:
- AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
+ AbstractControlSP(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false);
void hideEvent(QHideEvent *e) override;
QVBoxLayout *main_layout;
ElidedLabelSP *value;
QLabel *description = nullptr;
+ bool isAdvancedControl;
+
+private:
+ Params params;
+ static std::vector advanced_controls_;
};
// AbstractControlSP_SELECTOR
@@ -97,7 +115,7 @@ class AbstractControlSP_SELECTOR : public AbstractControlSP {
protected:
QSpacerItem *spacingItem = new QSpacerItem(44, 44, QSizePolicy::Minimum, QSizePolicy::Fixed);
- AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr);
+ AbstractControlSP_SELECTOR(const QString &title, const QString &desc = "", const QString &icon = "", QWidget *parent = nullptr, bool advancedControl = false);
void hideEvent(QHideEvent *e) override;
};
@@ -123,7 +141,7 @@ class ButtonControlSP : public AbstractControlSP {
Q_OBJECT
public:
- ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr);
+ ButtonControlSP(const QString &title, const QString &text, const QString &desc = "", QWidget *parent = nullptr, bool advancedControl = false);
inline void setText(const QString &text) { btn.setText(text); }
inline QString text() const { return btn.text(); }
inline void click() { btn.click(); }
@@ -142,7 +160,7 @@ class ToggleControlSP : public AbstractControlSP {
Q_OBJECT
public:
- ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr) : AbstractControlSP(title, desc, icon, parent) {
+ ToggleControlSP(const QString &title, const QString &desc = "", const QString &icon = "", const bool state = false, QWidget *parent = nullptr, bool advancedControl = false) : AbstractControlSP(title, desc, icon, parent, advancedControl) {
// space between toggle and title
icon_label = new QLabel(this);
hlayout->addWidget(icon_label);
@@ -173,7 +191,7 @@ class ParamControlSP : public ToggleControlSP {
Q_OBJECT
public:
- ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
+ ParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr, bool advancedControl = false);
void setConfirmation(bool _confirm, bool _store_confirm) {
confirm = _confirm;
store_confirm = _store_confirm;
@@ -219,7 +237,7 @@ class MultiButtonControlSP : public AbstractControlSP_SELECTOR {
public:
MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon,
- const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
+ const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), button_texts(button_texts), is_inline_layout(inline_layout) {
const QString style = R"(
QPushButton {
border-radius: 20px;
@@ -371,8 +389,8 @@ class ButtonParamControlSP : public MultiButtonControlSP {
Q_OBJECT
public:
ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
- const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : MultiButtonControlSP(title, desc, icon,
- button_texts, minimum_button_width, inline_layout) {
+ const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
+ button_texts, minimum_button_width, inline_layout, advancedControl) {
key = param.toStdString();
int value = atoi(params.get(key).c_str());
@@ -480,6 +498,16 @@ private:
return result.toInt();
}
+ int getParamValueScaled() {
+ const auto param_value = QString::fromStdString(params.get(key));
+ return static_cast(param_value.toFloat() * 100);
+ }
+
+ void setParamValueScaled(const int new_value) {
+ const float scaled_value = new_value / 100.0f;
+ params.put(key, QString::number(scaled_value, 'f', 2).toStdString());
+ }
+
// Although the method is not static, and thus has access to the value property, I prefer to be explicit about the value.
void setParamValue(const int new_value) {
const auto value_str = valueMap != nullptr ? valueMap->value(QString::number(new_value)) : QString::number(new_value);
@@ -488,7 +516,8 @@ private:
public:
OptionControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
- const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false, const QMap *valMap = nullptr) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr), _title(title), valueMap(valMap), is_inline_layout(inline_layout) {
+ const MinMaxValue &range, const int per_value_change = 1, const bool inline_layout = false,
+ const QMap *valMap = nullptr, bool scale_float = false, bool advancedControl = false) : AbstractControlSP_SELECTOR(title, desc, icon, nullptr, advancedControl), _title(title), valueMap(valMap), is_inline_layout(inline_layout), use_float_scaling(scale_float) {
const QString style = R"(
QPushButton {
border-radius: 20px;
@@ -528,7 +557,7 @@ public:
const std::vector button_texts{"-", "+"};
key = param.toStdString();
- value = getParamValue();
+ value = use_float_scaling ? getParamValueScaled() : getParamValue();
button_group = new QButtonGroup(this);
button_group->setExclusive(true);
@@ -546,10 +575,15 @@ public:
QObject::connect(button, &QPushButton::clicked, [=]() {
int change_value = (i == 0) ? -per_value_change : per_value_change;
- value = getParamValue(); // in case it changed externally, we need to get the latest value.
+ value = use_float_scaling ? getParamValueScaled() : getParamValue();
value += change_value;
value = std::clamp(value, range.min_value, range.max_value);
- setParamValue(value);
+
+ if (use_float_scaling) {
+ setParamValueScaled(value);
+ } else {
+ setParamValue(value);
+ }
button_group->button(0)->setEnabled(!(value <= range.min_value));
button_group->button(1)->setEnabled(!(value >= range.max_value));
@@ -642,6 +676,7 @@ private:
const QString label_disabled_style = "font-size: 50px; font-weight: 450; color: #5C5C5C;";
bool button_enabled = true;
+ bool use_float_scaling = false;
};
class PushButtonSP : public QPushButton {
diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py
index 536917a1a6..b263e28297 100755
--- a/selfdrive/ui/tests/test_ui/run.py
+++ b/selfdrive/ui/tests/test_ui/run.py
@@ -246,10 +246,10 @@ def setup_settings_steering_alc(click, pm: PubMaster, scroll=None):
click(970, 534)
time.sleep(UI_DELAY)
-def setup_settings_driving(click, pm: PubMaster, scroll=None):
+def setup_settings_cruise(click, pm: PubMaster, scroll=None):
setup_settings_device(click, pm)
- scroll(-1, 278, 962)
- click(278, 962)
+ scroll(-400, 278, 962)
+ click(278, 324)
time.sleep(UI_DELAY)
def setup_settings_visuals(click, pm: PubMaster, scroll=None):
@@ -312,7 +312,7 @@ CASES.update({
"settings_steering": setup_settings_steering,
"settings_steering_mads": setup_settings_steering_mads,
"settings_steering_alc": setup_settings_steering_alc,
- "settings_driving": setup_settings_driving,
+ "settings_cruise": setup_settings_cruise,
"settings_visuals": setup_settings_visuals,
"settings_trips": setup_settings_trips,
"settings_vehicle": setup_settings_vehicle,
@@ -391,9 +391,9 @@ def create_screenshots():
driver_img = frames[2]
else:
with open(frames_cache, 'wb') as f:
- road_img = FrameReader(route.camera_paths()[segnum]).get(0, pix_fmt="nv12")[0]
- wide_road_img = FrameReader(route.ecamera_paths()[segnum]).get(0, pix_fmt="nv12")[0]
- driver_img = FrameReader(route.dcamera_paths()[segnum]).get(0, pix_fmt="nv12")[0]
+ road_img = FrameReader(route.camera_paths()[segnum], pix_fmt="nv12").get(0)
+ wide_road_img = FrameReader(route.ecamera_paths()[segnum], pix_fmt="nv12").get(0)
+ driver_img = FrameReader(route.dcamera_paths()[segnum], pix_fmt="nv12").get(0)
pickle.dump([road_img, wide_road_img, driver_img], f)
STREAMS.append((VisionStreamType.VISION_STREAM_ROAD, cam.fcam, road_img.flatten().tobytes()))
diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc
index c9dc70cdff..4b8f79785b 100644
--- a/selfdrive/ui/ui.cc
+++ b/selfdrive/ui/ui.cc
@@ -61,6 +61,9 @@ void update_state(UIState *s) {
scene.light_sensor = -1;
}
scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition;
+
+ auto params = Params();
+ scene.recording_audio = params.getBool("RecordAudio") && scene.started;
}
void ui_update_params(UIState *s) {
@@ -164,8 +167,9 @@ void Device::setAwake(bool on) {
}
void Device::resetInteractiveTimeout(int timeout) {
+ int customTimeout = QString::fromStdString(Params().get("InteractivityTimeout")).toInt();
if (timeout == -1) {
- timeout = (ignition_on ? 10 : 30);
+ timeout = customTimeout == 0 ? (ignition_on ? 10 : 30) : customTimeout;
}
interactive_timeout = timeout * UI_FREQ;
}
diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h
index 4ead8aecb9..e78b573b66 100644
--- a/selfdrive/ui/ui.h
+++ b/selfdrive/ui/ui.h
@@ -62,7 +62,7 @@ typedef struct UIScene {
cereal::LongitudinalPersonality personality;
float light_sensor = -1;
- bool started, ignition, is_metric;
+ bool started, ignition, is_metric, recording_audio;
uint64_t started_frame;
} UIScene;
diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py
index e77e053cb7..bb2b431e03 100755
--- a/selfdrive/ui/ui.py
+++ b/selfdrive/ui/ui.py
@@ -6,16 +6,16 @@ from openpilot.selfdrive.ui.layouts.main import MainLayout
from openpilot.selfdrive.ui.ui_state import ui_state
-
def main():
gui_app.init_window("UI")
main_layout = MainLayout()
+ main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
for _ in gui_app.render():
ui_state.update()
- #TODO handle brigntness and awake state here
+ # TODO handle brigntness and awake state here
- main_layout.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
+ main_layout.render()
kick_watchdog()
diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py
index d9a8c40597..c2bb5f77a4 100644
--- a/selfdrive/ui/ui_state.py
+++ b/selfdrive/ui/ui_state.py
@@ -1,10 +1,20 @@
import pyray as rl
+import numpy as np
+import time
+import threading
+from collections.abc import Callable
from enum import Enum
from cereal import messaging, log
+from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params, UnknownKeyName
-
+from openpilot.common.swaglog import cloudlog
+from openpilot.selfdrive.ui.lib.prime_state import PrimeState
+from openpilot.system.ui.lib.application import DEFAULT_FPS
+from openpilot.system.hardware import HARDWARE
+from openpilot.system.ui.lib.application import gui_app
UI_BORDER_SIZE = 30
+BACKLIGHT_OFFROAD = 50
class UIStatus(Enum):
@@ -44,6 +54,8 @@ class UIState:
]
)
+ self.prime_state = PrimeState()
+
# UI Status tracking
self.status: UIStatus = UIStatus.DISENGAGED
self.started_frame: int = 0
@@ -64,10 +76,17 @@ class UIState:
def engaged(self) -> bool:
return self.started and self.sm["selfdriveState"].enabled
+ def is_onroad(self) -> bool:
+ return self.started
+
+ def is_offroad(self) -> bool:
+ return not self.started
+
def update(self) -> None:
self.sm.update(0)
self._update_state()
self._update_status()
+ device.update()
def _update_state(self) -> None:
# Handle panda states updates
@@ -125,5 +144,87 @@ class UIState:
self.is_metric = False
+class Device:
+ def __init__(self):
+ self._ignition = False
+ self._interaction_time: float = -1
+ self._interactive_timeout_callbacks: list[Callable] = []
+ self._prev_timed_out = False
+ self._awake = False
+
+ self._offroad_brightness: int = BACKLIGHT_OFFROAD
+ self._last_brightness: int = 0
+ self._brightness_filter = FirstOrderFilter(BACKLIGHT_OFFROAD, 10.00, 1 / DEFAULT_FPS)
+ self._brightness_thread: threading.Thread | None = None
+
+ def reset_interactive_timeout(self, timeout: int = -1) -> None:
+ if timeout == -1:
+ timeout = 10 if ui_state.ignition else 30
+ self._interaction_time = time.monotonic() + timeout
+
+ def add_interactive_timeout_callback(self, callback: Callable):
+ self._interactive_timeout_callbacks.append(callback)
+
+ def update(self):
+ # do initial reset
+ if self._interaction_time <= 0:
+ self.reset_interactive_timeout()
+
+ self._update_brightness()
+ self._update_wakefulness()
+
+ def set_offroad_brightness(self, brightness: int):
+ # TODO: not yet used, should be used in prime widget for QR code, etc.
+ self._offroad_brightness = min(max(brightness, 0), 100)
+
+ def _update_brightness(self):
+ clipped_brightness = self._offroad_brightness
+
+ if ui_state.started and ui_state.light_sensor >= 0:
+ clipped_brightness = ui_state.light_sensor
+
+ # CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm
+ if clipped_brightness <= 8:
+ clipped_brightness = clipped_brightness / 903.3
+ else:
+ clipped_brightness = ((clipped_brightness + 16.0) / 116.0) ** 3.0
+
+ clipped_brightness = float(np.clip(100 * clipped_brightness, 10, 100))
+
+ brightness = round(self._brightness_filter.update(clipped_brightness))
+ if not self._awake:
+ brightness = 0
+
+ if brightness != self._last_brightness:
+ if self._brightness_thread is None or not self._brightness_thread.is_alive():
+ cloudlog.debug(f"setting display brightness {brightness}")
+ self._brightness_thread = threading.Thread(target=HARDWARE.set_screen_brightness, args=(brightness,))
+ self._brightness_thread.start()
+ self._last_brightness = brightness
+
+ def _update_wakefulness(self):
+ # Handle interactive timeout
+ ignition_just_turned_off = not ui_state.ignition and self._ignition
+ self._ignition = ui_state.ignition
+
+ if ignition_just_turned_off or any(ev.left_down for ev in gui_app.mouse_events):
+ self.reset_interactive_timeout()
+
+ interaction_timeout = time.monotonic() > self._interaction_time
+ if interaction_timeout and not self._prev_timed_out:
+ for callback in self._interactive_timeout_callbacks:
+ callback()
+ self._prev_timed_out = interaction_timeout
+
+ self._set_awake(ui_state.ignition or not interaction_timeout)
+
+ def _set_awake(self, on: bool):
+ if on != self._awake:
+ self._awake = on
+ cloudlog.debug(f"setting display power {int(on)}")
+ HARDWARE.set_display_power(on)
+
+
# Global instance
ui_state = UIState()
+device = Device()
diff --git a/selfdrive/ui/widgets/__init__.py b/selfdrive/ui/widgets/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py
new file mode 100644
index 0000000000..83caf413d1
--- /dev/null
+++ b/selfdrive/ui/widgets/exp_mode_button.py
@@ -0,0 +1,74 @@
+import pyray as rl
+from openpilot.common.params import Params
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.widget import Widget
+
+
+class ExperimentalModeButton(Widget):
+ def __init__(self):
+ super().__init__()
+
+ self.img_width = 80
+ self.horizontal_padding = 50
+ self.button_height = 125
+
+ self.params = Params()
+ self.experimental_mode = self.params.get_bool("ExperimentalMode")
+ self.is_pressed = False
+
+ self.chill_pixmap = gui_app.texture("icons/couch.png", self.img_width, self.img_width)
+ self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width)
+
+ def _get_gradient_colors(self):
+ alpha = 0xCC if self.is_pressed else 0xFF
+
+ if self.experimental_mode:
+ return rl.Color(255, 155, 63, alpha), rl.Color(219, 56, 34, alpha)
+ else:
+ return rl.Color(20, 255, 171, alpha), rl.Color(35, 149, 255, alpha)
+
+ def _draw_gradient_background(self, rect):
+ start_color, end_color = self._get_gradient_colors()
+ rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), int(rect.width), int(rect.height),
+ start_color, end_color)
+
+ def _handle_interaction(self, rect):
+ mouse_pos = rl.get_mouse_position()
+ mouse_in_rect = rl.check_collision_point_rec(mouse_pos, rect)
+
+ self.is_pressed = mouse_in_rect and rl.is_mouse_button_down(rl.MOUSE_BUTTON_LEFT)
+ return mouse_in_rect and rl.is_mouse_button_released(rl.MOUSE_BUTTON_LEFT)
+
+ def _render(self, rect):
+ if self._handle_interaction(rect):
+ self.experimental_mode = not self.experimental_mode
+ # TODO: Opening settings for ExperimentalMode
+ self.params.put_bool("ExperimentalMode", self.experimental_mode)
+
+ rl.draw_rectangle_rounded(rect, 0.08, 20, rl.Color(255, 255, 255, 255))
+
+ rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
+ self._draw_gradient_background(rect)
+ rl.end_scissor_mode()
+
+ # Draw vertical separator line
+ line_x = rect.x + rect.width - self.img_width - (2 * self.horizontal_padding)
+ separator_color = rl.Color(0, 0, 0, 77) # 0x4d = 77
+ rl.draw_line_ex(rl.Vector2(line_x, rect.y), rl.Vector2(line_x, rect.y + rect.height), 3, separator_color)
+
+ # Draw text label (left aligned)
+ text = "EXPERIMENTAL MODE ON" if self.experimental_mode else "CHILL MODE ON"
+ text_x = rect.x + self.horizontal_padding
+ text_y = rect.y + rect.height / 2 - 45 // 2 # Center vertically
+
+ rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.Color(0, 0, 0, 255))
+
+ # Draw icon (right aligned)
+ icon_x = rect.x + rect.width - self.horizontal_padding - self.img_width
+ icon_y = rect.y + (rect.height - self.img_width) / 2
+ icon_rect = rl.Rectangle(icon_x, icon_y, self.img_width, self.img_width)
+
+ # Draw current mode icon
+ current_icon = self.experimental_pixmap if self.experimental_mode else self.chill_pixmap
+ source_rect = rl.Rectangle(0, 0, current_icon.width, current_icon.height)
+ rl.draw_texture_pro(current_icon, source_rect, icon_rect, rl.Vector2(0, 0), 0, rl.Color(255, 255, 255, 255))
diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py
index df88dc8554..aba07139db 100644
--- a/selfdrive/ui/widgets/offroad_alerts.py
+++ b/selfdrive/ui/widgets/offroad_alerts.py
@@ -9,6 +9,8 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wrap_text import wrap_text
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.widget import Widget
+
class AlertColors:
HIGH_SEVERITY = rl.Color(226, 44, 44, 255)
@@ -40,8 +42,9 @@ class AlertData:
visible: bool = False
-class AbstractAlert(ABC):
+class AbstractAlert(Widget, ABC):
def __init__(self, has_reboot_btn: bool = False):
+ super().__init__()
self.params = Params()
self.has_reboot_btn = has_reboot_btn
self.dismiss_callback: Callable | None = None
@@ -67,8 +70,7 @@ class AbstractAlert(ABC):
pass
def handle_input(self, mouse_pos: rl.Vector2, mouse_clicked: bool) -> bool:
- # TODO: fix scroll_panel.is_click_valid()
- if not mouse_clicked:
+ if not mouse_clicked or not self.scroll_panel.is_touch_valid():
return False
if rl.check_collision_point_rec(mouse_pos, self.dismiss_btn_rect):
@@ -88,7 +90,7 @@ class AbstractAlert(ABC):
return False
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.width, 10, AlertColors.BACKGROUND)
footer_height = AlertConstants.BUTTON_SIZE[1] + AlertConstants.SPACING
diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py
new file mode 100644
index 0000000000..2cb1499499
--- /dev/null
+++ b/selfdrive/ui/widgets/pairing_dialog.py
@@ -0,0 +1,170 @@
+import pyray as rl
+import qrcode
+import numpy as np
+import time
+
+from openpilot.common.api import Api
+from openpilot.common.swaglog import cloudlog
+from openpilot.common.params import Params
+from openpilot.system.ui.lib.application import FontWeight, gui_app
+from openpilot.system.ui.lib.wrap_text import wrap_text
+from openpilot.system.ui.lib.text_measure import measure_text_cached
+
+
+class PairingDialog:
+ """Dialog for device pairing with QR code."""
+
+ QR_REFRESH_INTERVAL = 300 # 5 minutes in seconds
+
+ def __init__(self):
+ self.params = Params()
+ self.qr_texture: rl.Texture | None = None
+ self.last_qr_generation = 0
+
+ def _get_pairing_url(self) -> str:
+ try:
+ dongle_id = self.params.get("DongleId", encoding='utf8') or ""
+ token = Api(dongle_id).get_token()
+ except Exception as e:
+ cloudlog.warning(f"Failed to get pairing token: {e}")
+ token = ""
+ return f"https://connect.comma.ai/setup?token={token}"
+
+ def _generate_qr_code(self) -> None:
+ try:
+ qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
+ qr.add_data(self._get_pairing_url())
+ qr.make(fit=True)
+
+ pil_img = qr.make_image(fill_color="black", back_color="white").convert('RGBA')
+ img_array = np.array(pil_img, dtype=np.uint8)
+
+ if self.qr_texture and self.qr_texture.id != 0:
+ rl.unload_texture(self.qr_texture)
+
+ 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)
+ except Exception as e:
+ cloudlog.warning(f"QR code generation failed: {e}")
+ self.qr_texture = None
+
+ def _check_qr_refresh(self) -> None:
+ current_time = time.time()
+ if current_time - self.last_qr_generation >= self.QR_REFRESH_INTERVAL:
+ self._generate_qr_code()
+ self.last_qr_generation = current_time
+
+ def render(self, rect: rl.Rectangle) -> int:
+ rl.clear_background(rl.Color(224, 224, 224, 255))
+
+ self._check_qr_refresh()
+
+ margin = 70
+ content_rect = rl.Rectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin)
+ y = content_rect.y
+
+ # Close button
+ close_size = 80
+ close_icon = gui_app.texture("icons/close.png", close_size, close_size)
+ close_rect = rl.Rectangle(content_rect.x, y, close_size, close_size)
+
+ mouse_pos = rl.get_mouse_position()
+ is_hover = rl.check_collision_point_rec(mouse_pos, close_rect)
+ is_pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
+ is_released = rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
+
+ color = rl.Color(180, 180, 180, 150) if (is_hover and is_pressed) else rl.WHITE
+ rl.draw_texture(close_icon, int(content_rect.x), int(y), color)
+
+ if (is_hover and is_released) or rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
+ return 1
+
+ y += close_size + 40
+
+ # Title
+ title = "Pair your device to your comma account"
+ title_font = gui_app.font(FontWeight.NORMAL)
+ left_width = int(content_rect.width * 0.5 - 15)
+
+ title_wrapped = wrap_text(title_font, title, 75, left_width)
+ rl.draw_text_ex(title_font, "\n".join(title_wrapped), rl.Vector2(content_rect.x, y), 75, 0.0, rl.BLACK)
+ y += len(title_wrapped) * 75 + 60
+
+ # Two columns: instructions and QR code
+ remaining_height = content_rect.height - (y - content_rect.y)
+ right_width = content_rect.width // 2 - 20
+
+ # Instructions
+ self._render_instructions(rl.Rectangle(content_rect.x, y, left_width, remaining_height))
+
+ # QR code
+ qr_size = min(right_width, content_rect.height) - 40
+ qr_x = content_rect.x + left_width + 40 + (right_width - qr_size) // 2
+ qr_y = content_rect.y
+ self._render_qr_code(rl.Rectangle(qr_x, qr_y, qr_size, qr_size))
+
+ return -1
+
+ def _render_instructions(self, rect: rl.Rectangle) -> None:
+ instructions = [
+ "Go to https://connect.comma.ai on your phone",
+ "Click \"add new device\" and scan the QR code on the right",
+ "Bookmark connect.comma.ai to your home screen to use it like an app",
+ ]
+
+ font = gui_app.font(FontWeight.BOLD)
+ y = rect.y
+
+ for i, text in enumerate(instructions):
+ circle_radius = 25
+ circle_x = rect.x + circle_radius + 15
+ text_x = rect.x + circle_radius * 2 + 40
+ text_width = rect.width - (circle_radius * 2 + 40)
+
+ wrapped = wrap_text(font, text, 47, int(text_width))
+ text_height = len(wrapped) * 47
+ circle_y = y + text_height // 2
+
+ # Circle and number
+ rl.draw_circle(int(circle_x), int(circle_y), circle_radius, rl.Color(70, 70, 70, 255))
+ number = str(i + 1)
+ number_width = measure_text_cached(font, number, 30).x
+ rl.draw_text(number, int(circle_x - number_width // 2), int(circle_y - 15), 30, rl.WHITE)
+
+ # Text
+ rl.draw_text_ex(font, "\n".join(wrapped), rl.Vector2(text_x, y), 47, 0.0, rl.BLACK)
+ y += text_height + 50
+
+ def _render_qr_code(self, rect: rl.Rectangle) -> None:
+ if not self.qr_texture:
+ rl.draw_rectangle_rounded(rect, 0.1, 20, rl.Color(240, 240, 240, 255))
+ error_font = gui_app.font(FontWeight.BOLD)
+ rl.draw_text_ex(
+ error_font, "QR Code Error", rl.Vector2(rect.x + 20, rect.y + rect.height // 2 - 15), 30, 0.0, rl.RED
+ )
+ return
+
+ source = rl.Rectangle(0, 0, self.qr_texture.width, self.qr_texture.height)
+ rl.draw_texture_pro(self.qr_texture, source, rect, rl.Vector2(0, 0), 0, rl.WHITE)
+
+ def __del__(self):
+ if self.qr_texture and self.qr_texture.id != 0:
+ rl.unload_texture(self.qr_texture)
+
+
+if __name__ == "__main__":
+ gui_app.init_window("pairing device")
+ pairing = PairingDialog()
+ try:
+ for _ in gui_app.render():
+ result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
+ if result != -1:
+ break
+ finally:
+ del pairing
diff --git a/selfdrive/ui/widgets/prime.py b/selfdrive/ui/widgets/prime.py
new file mode 100644
index 0000000000..afdfa33e7a
--- /dev/null
+++ b/selfdrive/ui/widgets/prime.py
@@ -0,0 +1,62 @@
+import pyray as rl
+
+from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.label import gui_label
+from openpilot.system.ui.lib.wrap_text import wrap_text
+from openpilot.system.ui.lib.text_measure import measure_text_cached
+from openpilot.system.ui.lib.widget import Widget
+
+
+class PrimeWidget(Widget):
+ """Widget for displaying comma prime subscription status"""
+
+ PRIME_BG_COLOR = rl.Color(51, 51, 51, 255)
+
+ def _render(self, rect):
+ if ui_state.prime_state.is_prime():
+ self._render_for_prime_user(rect)
+ else:
+ self._render_for_non_prime_users(rect)
+
+ def _render_for_non_prime_users(self, rect: rl.Rectangle):
+ """Renders the advertisement for non-Prime users."""
+
+ rl.draw_rectangle_rounded(rect, 0.02, 10, self.PRIME_BG_COLOR)
+
+ # Layout
+ x, y = rect.x + 80, rect.y + 90
+ w = rect.width - 160
+
+ # Title
+ gui_label(rl.Rectangle(x, y, w, 90), "Upgrade Now", 75, font_weight=FontWeight.BOLD)
+
+ # Description with wrapping
+ desc_y = y + 140
+ font = gui_app.font(FontWeight.LIGHT)
+ wrapped_text = "\n".join(wrap_text(font, "Become a comma prime member at connect.comma.ai", 56, int(w)))
+ text_size = measure_text_cached(font, wrapped_text, 56)
+ rl.draw_text_ex(font, wrapped_text, rl.Vector2(x, desc_y), 56, 0, rl.Color(255, 255, 255, 255))
+
+ # Features section
+ features_y = desc_y + text_size.y + 50
+ gui_label(rl.Rectangle(x, features_y, w, 50), "PRIME FEATURES:", 41, font_weight=FontWeight.BOLD)
+
+ # Feature list
+ features = ["Remote access", "24/7 LTE connectivity", "1 year of drive storage", "Remote snapshots"]
+ for i, feature in enumerate(features):
+ item_y = features_y + 80 + i * 65
+ gui_label(rl.Rectangle(x, item_y, 50, 60), "✓", 50, color=rl.Color(70, 91, 234, 255))
+ gui_label(rl.Rectangle(x + 60, item_y, w - 60, 60), feature, 50)
+
+ def _render_for_prime_user(self, rect: rl.Rectangle):
+ """Renders the prime user widget with subscription status."""
+
+ rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 230), 0.02, 10, self.PRIME_BG_COLOR)
+
+ x = rect.x + 56
+ y = rect.y + 40
+
+ font = gui_app.font(FontWeight.BOLD)
+ rl.draw_text_ex(font, "✓ SUBSCRIBED", rl.Vector2(x, y), 41, 0, rl.Color(134, 255, 78, 255))
+ rl.draw_text_ex(font, "comma prime", rl.Vector2(x, y + 61), 75, 0, rl.WHITE)
diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py
new file mode 100644
index 0000000000..4fd56aa522
--- /dev/null
+++ b/selfdrive/ui/widgets/setup.py
@@ -0,0 +1,94 @@
+import pyray as rl
+from openpilot.selfdrive.ui.lib.prime_state import PrimeType
+from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog
+from openpilot.selfdrive.ui.ui_state import ui_state
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+from openpilot.system.ui.lib.wrap_text import wrap_text
+from openpilot.system.ui.lib.widget import Widget
+
+
+class SetupWidget(Widget):
+ def __init__(self):
+ super().__init__()
+ self._open_settings_callback = None
+ self._pairing_dialog: PairingDialog | None = None
+
+ def set_open_settings_callback(self, callback):
+ self._open_settings_callback = callback
+
+ def _render(self, rect: rl.Rectangle):
+ if ui_state.prime_state.get_type() == PrimeType.UNPAIRED:
+ self._render_registration(rect)
+ else:
+ self._render_firehose_prompt(rect)
+
+ def _render_registration(self, rect: rl.Rectangle):
+ """Render registration prompt."""
+
+ rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 590), 0.02, 20, rl.Color(51, 51, 51, 255))
+
+ x = rect.x + 64
+ y = rect.y + 48
+ w = rect.width - 128
+
+ # Title
+ font = gui_app.font(FontWeight.BOLD)
+ rl.draw_text_ex(font, "Finish Setup", rl.Vector2(x, y), 75, 0, rl.WHITE)
+ y += 113 # 75 + 38 spacing
+
+ # Description
+ desc = "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."
+ light_font = gui_app.font(FontWeight.LIGHT)
+ wrapped = wrap_text(light_font, desc, 50, int(w))
+ for line in wrapped:
+ rl.draw_text_ex(light_font, line, rl.Vector2(x, y), 50, 0, rl.WHITE)
+ y += 50
+
+ button_rect = rl.Rectangle(x, y + 50, w, 128)
+ if gui_button(button_rect, "Pair device", button_style=ButtonStyle.PRIMARY):
+ self._show_pairing()
+
+ def _render_firehose_prompt(self, rect: rl.Rectangle):
+ """Render firehose prompt widget."""
+
+ rl.draw_rectangle_rounded(rl.Rectangle(rect.x, rect.y, rect.width, 450), 0.02, 20, rl.Color(51, 51, 51, 255))
+
+ # Content margins (56, 40, 56, 40)
+ x = rect.x + 56
+ y = rect.y + 40
+ w = rect.width - 112
+ spacing = 42
+
+ # Title with fire emojis
+ title_font = gui_app.font(FontWeight.MEDIUM)
+ title_text = "Firehose Mode"
+ rl.draw_text_ex(title_font, title_text, rl.Vector2(x, y), 64, 0, rl.WHITE)
+ y += 64 + spacing
+
+ # Description
+ desc_font = gui_app.font(FontWeight.NORMAL)
+ desc_text = "Maximize your training data uploads to improve openpilot's driving models."
+ wrapped_desc = wrap_text(desc_font, desc_text, 40, int(w))
+
+ for line in wrapped_desc:
+ rl.draw_text_ex(desc_font, line, rl.Vector2(x, y), 40, 0, rl.WHITE)
+ y += 40
+
+ y += spacing
+
+ # Open button
+ button_height = 48 + 64 # font size + padding
+ button_rect = rl.Rectangle(x, y, w, button_height)
+ if gui_button(button_rect, "Open", button_style=ButtonStyle.PRIMARY):
+ if self._open_settings_callback:
+ self._open_settings_callback()
+
+ def _show_pairing(self):
+ 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
diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py
new file mode 100644
index 0000000000..418867c86f
--- /dev/null
+++ b/selfdrive/ui/widgets/ssh_key.py
@@ -0,0 +1,128 @@
+import pyray as rl
+import requests
+import threading
+import copy
+from enum import Enum
+
+from openpilot.common.params import Params
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+from openpilot.system.ui.lib.list_view import (
+ ItemAction,
+ ListItem,
+ BUTTON_HEIGHT,
+ BUTTON_BORDER_RADIUS,
+ BUTTON_FONT_SIZE,
+ BUTTON_WIDTH,
+)
+from openpilot.system.ui.lib.text_measure import measure_text_cached
+from openpilot.system.ui.lib.widget import DialogResult
+from openpilot.system.ui.widgets.confirm_dialog import alert_dialog
+from openpilot.system.ui.widgets.keyboard import Keyboard
+
+
+class SshKeyActionState(Enum):
+ LOADING = "LOADING"
+ ADD = "ADD"
+ REMOVE = "REMOVE"
+
+
+class SshKeyAction(ItemAction):
+ HTTP_TIMEOUT = 15 # seconds
+ MAX_WIDTH = 500
+
+ def __init__(self):
+ super().__init__(self.MAX_WIDTH, True)
+
+ self._keyboard = Keyboard()
+ self._params = Params()
+ self._error_message: str = ""
+ self._text_font = gui_app.font(FontWeight.MEDIUM)
+
+ self._refresh_state()
+
+ 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.set_modal_overlay(lambda: alert_dialog(message))
+ self._username = ""
+ self._error_message = ""
+
+ # Draw username if exists
+ if self._username:
+ text_size = measure_text_cached(self._text_font, self._username, BUTTON_FONT_SIZE)
+ rl.draw_text_ex(
+ self._text_font,
+ self._username,
+ (rect.x + rect.width - BUTTON_WIDTH - text_size.x - 30, rect.y + (rect.height - text_size.y) / 2),
+ BUTTON_FONT_SIZE,
+ 1.0,
+ rl.WHITE,
+ )
+
+ # Draw button
+ if gui_button(
+ rl.Rectangle(
+ rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT
+ ),
+ self._state.value,
+ is_enabled=self._state != SshKeyActionState.LOADING,
+ border_radius=BUTTON_BORDER_RADIUS,
+ font_size=BUTTON_FONT_SIZE,
+ button_style=ButtonStyle.LIST_ACTION,
+ ):
+ self._handle_button_click()
+ return True
+ return False
+
+ def _handle_button_click(self):
+ if self._state == SshKeyActionState.ADD:
+ self._keyboard.clear()
+ self._keyboard.set_title("Enter your GitHub username")
+ gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit)
+ elif self._state == SshKeyActionState.REMOVE:
+ self._params.remove("GithubUsername")
+ self._params.remove("GithubSshKeys")
+ self._refresh_state()
+
+ def _on_username_submit(self, result: DialogResult):
+ if result != DialogResult.CONFIRM:
+ return
+
+ username = self._keyboard.text.strip()
+ if not username:
+ return
+
+ self._state = SshKeyActionState.LOADING
+ threading.Thread(target=lambda: self._fetch_ssh_key(username), daemon=True).start()
+
+ 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("No SSH keys found")
+
+ # Success - save keys
+ self._params.put("GithubUsername", username)
+ self._params.put("GithubSshKeys", keys)
+ self._state = SshKeyActionState.REMOVE
+ self._username = username
+
+ except requests.exceptions.Timeout:
+ self._error_message = "Request timed out"
+ self._state = SshKeyActionState.ADD
+ except Exception:
+ self._error_message = f"No SSH keys found for user '{username}'"
+ self._state = SshKeyActionState.ADD
+
+
+def ssh_key_item(title: str, description: str):
+ return ListItem(title=title, description=description, action_item=SshKeyAction())
diff --git a/sunnypilot/livedelay/__init__.py b/sunnypilot/livedelay/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/sunnypilot/livedelay/lagd_toggle.py b/sunnypilot/livedelay/lagd_toggle.py
new file mode 100644
index 0000000000..cc2f1e2504
--- /dev/null
+++ b/sunnypilot/livedelay/lagd_toggle.py
@@ -0,0 +1,51 @@
+"""
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+This file is part of sunnypilot and is licensed under the MIT License.
+See the LICENSE.md file in the root directory for more details.
+"""
+from openpilot.common.params import Params
+from openpilot.common.swaglog import cloudlog
+
+
+class LagdToggle:
+ def __init__(self):
+ self.params = Params()
+ self.lag = 0.0
+ self._last_desc = None
+
+ @property
+ def software_delay(self):
+ return float(self.params.get("LagdToggledelay", encoding='utf8'))
+
+ def _maybe_update_desc(self, desc):
+ if desc != self._last_desc:
+ self.params.put_nonblocking("LagdToggleDesc", desc)
+ self._last_desc = desc
+
+ def lagd_main(self, CP, sm, model):
+ if self.params.get_bool("LagdToggle"):
+ lateral_delay = sm["liveDelay"].lateralDelay
+ lat_smooth = model.LAT_SMOOTH_SECONDS
+ result = lateral_delay + lat_smooth
+ desc = f"live steer delay learner ({lateral_delay:.3f}s) + model smoothing filter ({lat_smooth:.3f}s) = total delay ({result:.3f}s)"
+ self._maybe_update_desc(desc)
+ return result
+
+ steer_actuator_delay = CP.steerActuatorDelay
+ lat_smooth = model.LAT_SMOOTH_SECONDS
+ delay = self.software_delay
+ result = (steer_actuator_delay + delay) + lat_smooth
+ desc = (f"Vehicle steering delay ({steer_actuator_delay:.3f}s) + software delay ({delay:.3f}s) + " +
+ f"model smoothing filter ({lat_smooth:.3f}s) = total delay ({result:.3f}s)")
+ self._maybe_update_desc(desc)
+ return result
+
+ def lagd_torqued_main(self, CP, msg):
+ if self.params.get_bool("LagdToggle"):
+ self.lag = msg.lateralDelay
+ cloudlog.debug(f"TORQUED USING LIVE DELAY: {self.lag:.3f}")
+ else:
+ self.lag = CP.steerActuatorDelay + self.software_delay
+ cloudlog.debug(f"TORQUED USING STEER ACTUATOR: {self.lag:.3f}")
+ return self.lag
diff --git a/sunnypilot/mapd/mapd_installer.py b/sunnypilot/mapd/mapd_installer.py
index 0be30b5b62..a151dd4807 100755
--- a/sunnypilot/mapd/mapd_installer.py
+++ b/sunnypilot/mapd/mapd_installer.py
@@ -18,7 +18,7 @@ from cereal import messaging
from openpilot.common.params import Params
from openpilot.sunnypilot.mapd.mapd_manager import MAPD_PATH, MAPD_BIN_DIR
from openpilot.system.hardware.hw import Paths
-from openpilot.system.ui.spinner import Spinner
+from openpilot.common.spinner import Spinner
from openpilot.system.version import is_prebuilt
import openpilot.system.sentry as sentry
diff --git a/sunnypilot/modeld/fill_model_msg.py b/sunnypilot/modeld/fill_model_msg.py
index 608f24424d..dadffc8433 100644
--- a/sunnypilot/modeld/fill_model_msg.py
+++ b/sunnypilot/modeld/fill_model_msg.py
@@ -103,7 +103,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose
- temporal_pose = modelV2.temporalPose
+ temporal_pose = modelV2.temporalPoseDEPRECATED
temporal_pose.trans = net_output_data['plan'][0,0,Plan.VELOCITY].tolist()
temporal_pose.transStd = net_output_data['plan_stds'][0,0,Plan.VELOCITY].tolist()
temporal_pose.rot = net_output_data['plan'][0,0,Plan.ORIENTATION_RATE].tolist()
diff --git a/sunnypilot/modeld/modeld.py b/sunnypilot/modeld/modeld.py
index d4bd96ff66..10725efb71 100755
--- a/sunnypilot/modeld/modeld.py
+++ b/sunnypilot/modeld/modeld.py
@@ -25,7 +25,7 @@ from openpilot.sunnypilot.modeld.parse_model_outputs import Parser
from openpilot.sunnypilot.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState
from openpilot.sunnypilot.modeld.constants import ModelConstants, Plan
from openpilot.sunnypilot.models.helpers import get_active_bundle, get_model_path, load_metadata, prepare_inputs, load_meta_constants
-from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd
+from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
from openpilot.sunnypilot.modeld.models.commonmodel_pyx import ModelFrame, CLContext
@@ -62,7 +62,7 @@ class ModelState:
self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32)
bundle = get_active_bundle()
overrides = {override.key: override.value for override in bundle.overrides}
- self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".2"))
+ self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0"))
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
model_paths = get_model_path()
@@ -202,7 +202,7 @@ def main(demo=False):
cloudlog.info("modeld got CarParams: %s", CP.brand)
- modeld_lagd = ModeldLagd()
+ modeld_lagd = LagdToggle()
# Enable lagd support for sunnypilot modeld
long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS
diff --git a/sunnypilot/modeld_v2/SConscript b/sunnypilot/modeld_v2/SConscript
index 4ade1469f0..bb4c8d4a50 100644
--- a/sunnypilot/modeld_v2/SConscript
+++ b/sunnypilot/modeld_v2/SConscript
@@ -13,7 +13,6 @@ common_src = [
"transforms/transform.cc",
]
-
# OpenCL is a framework on Mac
if arch == "Darwin":
frameworks += ['OpenCL']
@@ -29,4 +28,3 @@ 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)
-
diff --git a/sunnypilot/modeld_v2/fill_model_msg.py b/sunnypilot/modeld_v2/fill_model_msg.py
index 411b8177f3..c7de698f61 100644
--- a/sunnypilot/modeld_v2/fill_model_msg.py
+++ b/sunnypilot/modeld_v2/fill_model_msg.py
@@ -10,8 +10,8 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
ConfidenceClass = log.ModelDataV2.ConfidenceClass
-def get_curvature_from_output(output, vego, lat_action_t, current_generation=None):
- if current_generation != 11:
+def get_curvature_from_output(output, vego, lat_action_t, mlsim):
+ if not mlsim:
if desired_curv := output.get('desired_curvature'): # If the model outputs the desired curvature, use that directly
return float(desired_curv[0, 0])
@@ -100,7 +100,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T)
# temporal pose
- temporal_pose = modelV2.temporalPose
+ temporal_pose = modelV2.temporalPoseDEPRECATED
if 'sim_pose' in net_output_data:
temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist()
temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist()
@@ -118,30 +118,14 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
# action (includes lateral planning now)
modelV2.action = action
- # times at X_IDXS according to model plan
- PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N
- PLAN_T_IDXS[0] = 0.0
- plan_x = net_output_data['plan'][0,:,Plan.POSITION][:,0].tolist()
- for xidx in range(1, ModelConstants.IDX_N):
- tidx = 0
- # increment tidx until we find an element that's further away than the current xidx
- while tidx < ModelConstants.IDX_N - 1 and plan_x[tidx+1] < ModelConstants.X_IDXS[xidx]:
- tidx += 1
- if tidx == ModelConstants.IDX_N - 1:
- # if the Plan doesn't extend far enough, set plan_t to the max value (10s), then break
- PLAN_T_IDXS[xidx] = ModelConstants.T_IDXS[ModelConstants.IDX_N - 1]
- break
- # interpolate to find `t` for the current xidx
- current_x_val = plan_x[tidx]
- next_x_val = plan_x[tidx+1]
- p = (ModelConstants.X_IDXS[xidx] - current_x_val) / (next_x_val - current_x_val) if abs(next_x_val - current_x_val) > 1e-9 else float('nan')
- PLAN_T_IDXS[xidx] = p * ModelConstants.T_IDXS[tidx+1] + (1 - p) * ModelConstants.T_IDXS[tidx]
+ # times at X_IDXS of edges and lines aren't used
+ LINE_T_IDXS: list[float] = []
# lane lines
modelV2.init('laneLines', 4)
for i in range(4):
lane_line = modelV2.laneLines[i]
- fill_xyzt(lane_line, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
+ fill_xyzt(lane_line, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['lane_lines'][0,i,:,0], net_output_data['lane_lines'][0,i,:,1])
modelV2.laneLineStds = net_output_data['lane_lines_stds'][0,:,0,0].tolist()
modelV2.laneLineProbs = net_output_data['lane_lines_prob'][0,1::2].tolist()
@@ -151,7 +135,7 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D
modelV2.init('roadEdges', 2)
for i in range(2):
road_edge = modelV2.roadEdges[i]
- fill_xyzt(road_edge, PLAN_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
+ fill_xyzt(road_edge, LINE_T_IDXS, np.array(ModelConstants.X_IDXS), net_output_data['road_edges'][0,i,:,0], net_output_data['road_edges'][0,i,:,1])
modelV2.roadEdgeStds = net_output_data['road_edges_stds'][0,:,0,0].tolist()
# leads
diff --git a/sunnypilot/modeld_v2/modeld.py b/sunnypilot/modeld_v2/modeld.py
index fe322f462e..93a8a3f05e 100755
--- a/sunnypilot/modeld_v2/modeld.py
+++ b/sunnypilot/modeld_v2/modeld.py
@@ -23,7 +23,7 @@ from openpilot.sunnypilot.modeld_v2.models.commonmodel_pyx import DrivingModelFr
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
from openpilot.sunnypilot.models.helpers import get_active_bundle
-from openpilot.sunnypilot.models.modeld_lagd import ModeldLagd
+from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
from openpilot.sunnypilot.models.runners.helpers import get_model_runner
PROCESS_NAME = "selfdrive.modeld.modeld"
@@ -54,15 +54,15 @@ class ModelState:
raise
model_bundle = get_active_bundle()
- self.generation = model_bundle.generation
+ self.generation = model_bundle.generation if model_bundle is not None else None
overrides = {override.key: override.value for override in model_bundle.overrides}
- self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".2"))
+ self.LAT_SMOOTH_SECONDS = float(overrides.get('lat', ".0"))
self.LONG_SMOOTH_SECONDS = float(overrides.get('long', ".0"))
self.MIN_LAT_CONTROL_SPEED = 0.3
buffer_length = 5 if self.model_runner.is_20hz else 2
- self.frames = {'input_imgs': DrivingModelFrame(context, buffer_length), 'big_input_imgs': DrivingModelFrame(context, buffer_length)}
+ self.frames = {name: DrivingModelFrame(context, buffer_length) for name in self.model_runner.vision_input_names}
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
# img buffers are managed in openCL transform code
@@ -86,7 +86,11 @@ class ModelState:
self.desire_reshape_dims = (self.numpy_inputs['desire'].shape[0], self.numpy_inputs['desire'].shape[1], -1,
self.numpy_inputs['desire'].shape[2])
- def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray,
+ @property
+ def mlsim(self) -> bool:
+ return bool(self.generation is not None and self.generation >= 11)
+
+ 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'][0] = 0
@@ -110,8 +114,7 @@ class ModelState:
if key in inputs and key not in ['desire']:
self.numpy_inputs[key][:] = inputs[key]
- imgs_cl = {'input_imgs': self.frames['input_imgs'].prepare(buf, transform.flatten()),
- 'big_input_imgs': self.frames['big_input_imgs'].prepare(wbuf, transform_wide.flatten())}
+ imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.model_runner.vision_input_names}
# Prepare inputs using the model runner
self.model_runner.prepare_inputs(imgs_cl, self.numpy_inputs, self.frames)
@@ -152,7 +155,7 @@ class ModelState:
self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:]
self.full_prev_desired_curv[0,-1,:] = outputs['desired_curvature'][0, :]
self.numpy_inputs[input_name_prev][:] = self.full_prev_desired_curv[0, self.temporal_idxs]
- if self.generation == 11:
+ if self.mlsim:
self.numpy_inputs[input_name_prev][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs]
else:
length = outputs['desired_curvature'][0].size
@@ -166,7 +169,7 @@ class ModelState:
action_t=long_action_t)
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
- desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.generation)
+ desired_curvature = get_curvature_from_output(model_output, v_ego, lat_action_t, self.mlsim)
if v_ego > self.MIN_LAT_CONTROL_SPEED:
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
else:
@@ -240,7 +243,7 @@ def main(demo=False):
cloudlog.info("modeld got CarParams: %s", CP.brand)
- modeld_lagd = ModeldLagd()
+ modeld_lagd = LagdToggle()
# TODO Move smooth seconds to action function
long_delay = CP.longitudinalActuatorDelay + model.LONG_SMOOTH_SECONDS
@@ -315,6 +318,8 @@ def main(demo=False):
if prepare_only:
cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames")
+ bufs = {name: buf_extra if 'big' in name else buf_main for name in model.model_runner.vision_input_names}
+ transforms = {name: model_transform_extra if 'big' in name else model_transform_main for name in model.model_runner.vision_input_names}
inputs:dict[str, np.ndarray] = {
'desire': vec_desire,
'traffic_convention': traffic_convention,
@@ -324,7 +329,7 @@ def main(demo=False):
inputs['lateral_control_params'] = np.array([v_ego, steer_delay], dtype=np.float32)
mt1 = time.perf_counter()
- model_output = model.run(buf_main, buf_extra, model_transform_main, model_transform_extra, inputs, prepare_only)
+ model_output = model.run(bufs, transforms, inputs, prepare_only)
mt2 = time.perf_counter()
model_execution_time = mt2 - mt1
diff --git a/sunnypilot/modeld_v2/parse_model_outputs_split.py b/sunnypilot/modeld_v2/parse_model_outputs_split.py
index 7fab66e038..79fa8ec2f2 100644
--- a/sunnypilot/modeld_v2/parse_model_outputs_split.py
+++ b/sunnypilot/modeld_v2/parse_model_outputs_split.py
@@ -1,5 +1,6 @@
import numpy as np
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
+from openpilot.sunnypilot.models.helpers import get_active_bundle
def safe_exp(x, out=None):
@@ -24,6 +25,8 @@ def softmax(x, axis=-1):
class Parser:
def __init__(self, ignore_missing=False):
self.ignore_missing = ignore_missing
+ model_bundle = get_active_bundle()
+ self.generation = model_bundle.generation if model_bundle is not None else None
def check_missing(self, outs, name):
if name not in outs and not self.ignore_missing:
@@ -88,37 +91,64 @@ class Parser:
outs[name] = pred_mu_final.reshape(final_shape)
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
+ def _parse_plan_mhp(self, outs):
+ self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
+ out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
+
+ def parse_dynamic_outputs(self, outs: dict[str, np.ndarray]) -> None:
+ if 'lead' in outs:
+ if self.generation >= 12 and \
+ outs['lead'].shape[1] == 2 * SplitModelConstants.LEAD_MHP_SELECTION * SplitModelConstants.LEAD_TRAJ_LEN * SplitModelConstants.LEAD_WIDTH:
+ self.parse_mdn('lead', outs, in_N=0, out_N=0,
+ out_shape=(SplitModelConstants.LEAD_MHP_SELECTION, SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH))
+ else:
+ self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION,
+ out_shape=(SplitModelConstants.LEAD_TRAJ_LEN, SplitModelConstants.LEAD_WIDTH))
+ if 'plan' in outs:
+ if self.generation >= 12 and \
+ outs['plan'].shape[1] > 2 * SplitModelConstants.PLAN_WIDTH * SplitModelConstants.IDX_N:
+ self._parse_plan_mhp(outs)
+ elif self.generation >= 12:
+ self.parse_mdn('plan', outs, in_N=0, out_N=0,
+ out_shape=(SplitModelConstants.IDX_N, SplitModelConstants.PLAN_WIDTH))
+ else:
+ self._parse_plan_mhp(outs)
+
def split_outputs(self, outs: dict[str, np.ndarray]) -> None:
+ if 'desired_curvature' in outs:
+ self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,))
+ if 'desire_pred' in outs:
+ self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH))
+ if 'desire_state' in outs:
+ self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
if 'lane_lines' in outs:
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0,
- out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
+ out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
+ if 'lane_lines_prob' in outs:
+ self.parse_binary_crossentropy('lane_lines_prob', outs)
+ if 'lead_prob' in outs:
+ self.parse_binary_crossentropy('lead_prob', outs)
+ if 'lat_planner_solution' in outs:
+ self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
+ if 'meta' in outs:
+ self.parse_binary_crossentropy('meta', outs)
+ if 'road_edges' in outs:
self.parse_mdn('road_edges', outs, in_N=0, out_N=0,
- out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
- self.parse_mdn('lead', outs, in_N=SplitModelConstants.LEAD_MHP_N, out_N=SplitModelConstants.LEAD_MHP_SELECTION,
- out_shape=(SplitModelConstants.LEAD_TRAJ_LEN,SplitModelConstants.LEAD_WIDTH))
- if 'sim_pose' in outs:
- self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
- for k in ['lead_prob', 'lane_lines_prob']:
- self.parse_binary_crossentropy(k, outs)
+ out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
+ if 'sim_pose' in outs:
+ self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.WIDE_FROM_DEVICE_WIDTH,))
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
+ self.parse_dynamic_outputs(outs)
self.split_outputs(outs)
- self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(SplitModelConstants.DESIRE_PRED_LEN,SplitModelConstants.DESIRE_PRED_WIDTH))
- self.parse_binary_crossentropy('meta', outs)
return outs
def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
- self.parse_mdn('plan', outs, in_N=SplitModelConstants.PLAN_MHP_N, out_N=SplitModelConstants.PLAN_MHP_SELECTION,
- out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.PLAN_WIDTH))
+ self.parse_dynamic_outputs(outs)
self.split_outputs(outs)
- if 'lat_planner_solution' in outs:
- self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.IDX_N,SplitModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
- if 'desired_curvature' in outs:
- self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.DESIRED_CURV_WIDTH,))
- self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
return outs
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py
index 87f00ea5fa..61a7e1a006 100644
--- a/sunnypilot/models/helpers.py
+++ b/sunnypilot/models/helpers.py
@@ -19,7 +19,7 @@ from openpilot.system.hardware import PC
from openpilot.system.hardware.hw import Paths
from pathlib import Path
-CURRENT_SELECTOR_VERSION = 5
+CURRENT_SELECTOR_VERSION = 7
REQUIRED_MIN_SELECTOR_VERSION = 5
USE_ONNX = os.getenv('USE_ONNX', PC)
diff --git a/sunnypilot/models/manager.py b/sunnypilot/models/manager.py
index 630d6a8d77..8913135203 100644
--- a/sunnypilot/models/manager.py
+++ b/sunnypilot/models/manager.py
@@ -178,6 +178,10 @@ class ModelManagerSP:
finally:
self.params.put("ModelManager_DownloadIndex", "")
+ if self.params.get("ModelManager_ClearCache", block=False, encoding="utf-8"):
+ self.clear_model_cache()
+ self.params.remove("ModelManager_ClearCache")
+
self._report_status()
rk.keep_time()
@@ -185,6 +189,31 @@ class ModelManagerSP:
cloudlog.exception(f"Error in main thread: {str(e)}")
rk.keep_time()
+ def clear_model_cache(self) -> None:
+ """
+ Clears the model cache directory of all files except those in the active model bundle.
+ """
+
+ # Get list of files used by active model bundle
+ active_files = []
+ if self.active_bundle is not None: # When the default model is active
+ for model in self.active_bundle.models:
+ if hasattr(model, 'artifact') and model.artifact.fileName:
+ active_files.append(model.artifact.fileName)
+ if hasattr(model, 'metadata') and model.metadata.fileName:
+ active_files.append(model.metadata.fileName)
+
+ # Remove all files except active ones
+ model_dir = Paths.model_root()
+ try:
+ for filename in os.listdir(model_dir):
+ if filename not in active_files:
+ file_path = os.path.join(model_dir, filename)
+ if os.path.isfile(file_path):
+ os.remove(file_path)
+ cloudlog.info("Model cache cleared, keeping active model files")
+ except Exception as e:
+ cloudlog.exception(f"Error clearing model cache: {str(e)}")
def main():
ModelManagerSP().main_thread()
diff --git a/sunnypilot/models/modeld_lagd.py b/sunnypilot/models/modeld_lagd.py
deleted file mode 100644
index 9e538123a8..0000000000
--- a/sunnypilot/models/modeld_lagd.py
+++ /dev/null
@@ -1,26 +0,0 @@
-"""
-Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
-
-This file is part of sunnypilot and is licensed under the MIT License.
-See the LICENSE.md file in the root directory for more details.
-"""
-from openpilot.common.params import Params
-from openpilot.common.swaglog import cloudlog
-
-
-class ModeldLagd:
- def __init__(self):
- self.params = Params()
-
- def lagd_main(self, CP, sm, model):
- if self.params.get_bool("LagdToggle"):
- lateral_delay = sm["liveDelay"].lateralDelay
- lat_smooth = model.LAT_SMOOTH_SECONDS
- result = lateral_delay + lat_smooth
- cloudlog.debug(f"LAGD USING LIVE DELAY: {lateral_delay:.3f} + {lat_smooth:.3f} = {result:.3f}")
- return result
- steer_actuator_delay = CP.steerActuatorDelay
- lat_smooth = model.LAT_SMOOTH_SECONDS
- result = (steer_actuator_delay + 0.2) + lat_smooth
- cloudlog.debug(f"LAGD USING STEER ACTUATOR: {steer_actuator_delay:.3f} + 0.2 + {lat_smooth:.3f} = {result:.3f}")
- return result
diff --git a/sunnypilot/models/runners/model_runner.py b/sunnypilot/models/runners/model_runner.py
index c210d5b87e..7124662791 100644
--- a/sunnypilot/models/runners/model_runner.py
+++ b/sunnypilot/models/runners/model_runner.py
@@ -12,8 +12,15 @@ CUSTOM_MODEL_PATH = Paths.model_root()
# Set QCOM environment variable for TICI devices, potentially enabling hardware acceleration
-if TICI:
+USBGPU = "USBGPU" in os.environ
+if USBGPU:
+ os.environ['AMD'] = '1'
+ os.environ['AMD_IFACE'] = 'USB'
+elif TICI:
os.environ['QCOM'] = '1'
+else:
+ os.environ['LLVM'] = '1'
+ os.environ['JIT'] = '2' # TODO: This may cause issues
class ModelData:
@@ -132,6 +139,13 @@ class ModelRunner(ModularRunner):
return self._model_data.output_slices
raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
+ @property
+ def vision_input_names(self) -> list[str]:
+ """Returns the list of vision input names from the input shapes."""
+ if self._model_data:
+ return list(self._model_data.input_shapes.keys())
+ raise ValueError("Model data is not available. Ensure the model is loaded correctly.")
+
@abstractmethod
def prepare_inputs(self, imgs_cl: CLMemDict, numpy_inputs: NumpyDict, frames: FrameDict) -> dict:
"""
diff --git a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py
index 2e4fd45294..270890cace 100644
--- a/sunnypilot/models/runners/tinygrad/tinygrad_runner.py
+++ b/sunnypilot/models/runners/tinygrad/tinygrad_runner.py
@@ -54,6 +54,11 @@ class TinygradRunner(ModelRunner, SupercomboTinygrad, PolicyTinygrad, VisionTiny
self.input_to_dtype[name] = info[2] # dtype
self.input_to_device[name] = info[3] # device
+ @property
+ def vision_input_names(self) -> list[str]:
+ """Returns the list of vision input names from the input shapes."""
+ return [name for name in self.input_shapes.keys() if 'img' in name]
+
def prepare_vision_inputs(self, imgs_cl: CLMemDict, frames: FrameDict):
"""Prepares vision (image) inputs as Tinygrad Tensors."""
for key in imgs_cl:
@@ -109,6 +114,11 @@ class TinygradSplitRunner(ModelRunner):
vision_output = self.vision_runner.run_model()
return {**policy_output, **vision_output} # Combine results
+ @property
+ def vision_input_names(self) -> list[str]:
+ """Returns the list of vision input names from the vision runner."""
+ return list(self.vision_runner.vision_input_names)
+
@property
def input_shapes(self) -> ShapeDict:
"""Returns the combined input shapes from both vision and policy models."""
diff --git a/sunnypilot/models/tests/model_hash b/sunnypilot/models/tests/model_hash
index 2f50b2085c..6df03c31d1 100644
--- a/sunnypilot/models/tests/model_hash
+++ b/sunnypilot/models/tests/model_hash
@@ -1 +1 @@
-dec34c57c4131f6fca5d1035201d1afbf43e5250cede7bfdc798371af008afad
\ No newline at end of file
+61bb1f3077ef8219a44c8c627d5345d1a96e7f859b850bed6215eec91090becb
\ No newline at end of file
diff --git a/sunnypilot/neural_network_data b/sunnypilot/neural_network_data
index 16946bfd9c..03cac2d30e 160000
--- a/sunnypilot/neural_network_data
+++ b/sunnypilot/neural_network_data
@@ -1 +1 @@
-Subproject commit 16946bfd9cca1a33e8b609d10ca3d1a0089d38e2
+Subproject commit 03cac2d30e111e0689c0429cb8c1fe6cb5a905af
diff --git a/sunnypilot/selfdrive/car/cruise_ext.py b/sunnypilot/selfdrive/car/cruise_ext.py
new file mode 100644
index 0000000000..6f0419ed14
--- /dev/null
+++ b/sunnypilot/selfdrive/car/cruise_ext.py
@@ -0,0 +1,47 @@
+"""
+Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
+
+This file is part of sunnypilot and is licensed under the MIT License.
+See the LICENSE.md file in the root directory for more details.
+"""
+import numpy as np
+
+from cereal import car
+from openpilot.common.params import Params
+
+ButtonType = car.CarState.ButtonEvent.Type
+
+
+class VCruiseHelperSP:
+ def __init__(self) -> None:
+ self.params = Params()
+
+ self.custom_acc_enabled = self.params.get_bool("CustomAccIncrementsEnabled")
+ self.short_increment = self.read_int_param("CustomAccShortPressIncrement", 1)
+ self.long_increment = self.read_int_param("CustomAccLongPressIncrement", 5)
+
+ def read_int_param(self, key: str, default: int = 0) -> int:
+ try:
+ return int(self.params.get(key, encoding='utf8'))
+ except (ValueError, TypeError):
+ return default
+
+ def read_custom_set_speed_params(self) -> None:
+ self.custom_acc_enabled = self.params.get_bool("CustomAccIncrementsEnabled")
+ self.short_increment = self.read_int_param("CustomAccShortPressIncrement", 1)
+ self.long_increment = self.read_int_param("CustomAccLongPressIncrement", 5)
+
+ def update_v_cruise_delta(self, long_press: bool, v_cruise_delta: float) -> tuple[bool, float]:
+ if not self.custom_acc_enabled:
+ v_cruise_delta = v_cruise_delta * (5 if long_press else 1)
+ return long_press, v_cruise_delta
+
+ # Apply user-specified multipliers to the base increment
+ short_increment = np.clip(self.short_increment, 1, 10)
+ long_increment = np.clip(self.long_increment, 1, 10)
+
+ actual_increment = long_increment if long_press else short_increment
+ round_to_nearest = actual_increment in (5, 10)
+ v_cruise_delta = v_cruise_delta * actual_increment
+
+ return round_to_nearest, v_cruise_delta
diff --git a/sunnypilot/selfdrive/car/tests/test_custom_cruise.py b/sunnypilot/selfdrive/car/tests/test_custom_cruise.py
new file mode 100644
index 0000000000..2f9d976847
--- /dev/null
+++ b/sunnypilot/selfdrive/car/tests/test_custom_cruise.py
@@ -0,0 +1,149 @@
+import pytest
+from parameterized import parameterized_class
+
+from cereal import car
+from openpilot.common.conversions import Conversions as CV
+from openpilot.common.params import Params
+from openpilot.selfdrive.car.cruise import V_CRUISE_INITIAL
+from openpilot.selfdrive.car.tests.test_cruise_speed import TestVCruiseHelper
+
+ButtonEvent = car.CarState.ButtonEvent
+ButtonType = car.CarState.ButtonEvent.Type
+
+
+@parameterized_class(('pcm_cruise',), [(False,)])
+class TestCustomAccIncrements(TestVCruiseHelper):
+ def setup_method(self):
+ TestVCruiseHelper.setup_method(self)
+ self.params = Params()
+ self.reset_custom_params()
+
+ def reset_custom_params(self) -> None:
+ """Reset to default custom ACC parameters"""
+ self.params.put_bool("CustomAccIncrementsEnabled", False)
+ self.params.put("CustomAccShortPressIncrement", "1")
+ self.params.put("CustomAccLongPressIncrement", "5")
+ self.v_cruise_helper.read_custom_set_speed_params()
+
+ def press_button_short(self, button_type: car.CarState.ButtonEvent.Type) -> None:
+ """Simulate a short button press (press + release)"""
+ CS = car.CarState(cruiseState={"available": True})
+ CS.buttonEvents = [ButtonEvent(type=button_type, pressed=True)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
+
+ CS.buttonEvents = [ButtonEvent(type=button_type, pressed=False)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
+
+ def press_button_long(self, button_type: car.CarState.ButtonEvent.Type) -> None:
+ """Simulate a long button press (50+ frames)"""
+ CS = car.CarState(cruiseState={"available": True})
+ CS.buttonEvents = [ButtonEvent(type=button_type, pressed=True)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
+
+ # Hold for 50 frames to trigger long press
+ CS.buttonEvents = []
+ for _ in range(50):
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
+
+ CS.buttonEvents = [ButtonEvent(type=button_type, pressed=False)]
+ self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=True)
+
+ def set_custom_increments(self, enabled: bool, short_inc: int, long_inc: int) -> None:
+ """Set custom ACC increment parameters"""
+ self.params.put_bool("CustomAccIncrementsEnabled", enabled)
+ self.params.put("CustomAccShortPressIncrement", str(short_inc))
+ self.params.put("CustomAccLongPressIncrement", str(long_inc))
+ self.v_cruise_helper.read_custom_set_speed_params()
+
+ def test_default_behavior_when_disabled(self):
+ """Test that default increments are used when custom ACC is disabled"""
+ self.set_custom_increments(enabled=False, short_inc=5, long_inc=10)
+ self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+
+ # Short press should increment by 1 (default)
+ self.press_button_short(ButtonType.accelCruise)
+ assert self.v_cruise_helper.v_cruise_kph == initial_speed + 1
+
+ @pytest.mark.parametrize("increment", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
+ def test_custom_short_press_increments(self, increment):
+ """Test custom short press increments (1-10)"""
+ self.set_custom_increments(enabled=True, short_inc=increment, long_inc=5)
+ self.enable(50 * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+ self.press_button_short(ButtonType.accelCruise)
+
+ if increment in (5, 10):
+ # Should round to nearest increment
+ expected_speed = ((initial_speed // increment) + 1) * increment
+ else:
+ expected_speed = initial_speed + increment
+
+ assert self.v_cruise_helper.v_cruise_kph == expected_speed
+
+ @pytest.mark.parametrize("increment", (1, 5, 10))
+ def test_custom_long_press_increments(self, increment):
+ """Test custom long press increments (1, 5, 10)"""
+ self.set_custom_increments(enabled=True, short_inc=1, long_inc=increment)
+ self.enable(50 * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+ self.press_button_long(ButtonType.accelCruise)
+
+ if increment in (5, 10):
+ # Should round to nearest increment
+ expected_speed = ((initial_speed // increment) + 1) * increment
+ else:
+ expected_speed = initial_speed + increment
+
+ assert self.v_cruise_helper.v_cruise_kph == expected_speed
+
+ @pytest.mark.parametrize("button_type", [ButtonType.accelCruise, ButtonType.decelCruise])
+ def test_accel_decel_symmetry(self, button_type):
+ """Test that acceleration and deceleration work symmetrically"""
+ self.set_custom_increments(enabled=True, short_inc=3, long_inc=5)
+ self.enable(50 * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+ self.press_button_short(button_type)
+
+ expected_change = 3 if button_type == ButtonType.accelCruise else -3
+ assert self.v_cruise_helper.v_cruise_kph == initial_speed + expected_change
+
+ def test_rounding_behavior(self):
+ """Test rounding behavior for 5 and 10 increments"""
+ test_cases = [
+ (47, 5, 50), # 47 -> 50 (round up to next 5)
+ (45, 5, 50), # 45 -> 50 (already at 5, increment by 5)
+ (43, 10, 50), # 43 -> 50 (round up to next 10)
+ (40, 10, 50), # 40 -> 50 (already at 10, increment by 10)
+ ]
+
+ for initial, increment, expected in test_cases:
+ self.set_custom_increments(enabled=True, short_inc=increment, long_inc=increment)
+ self.reset_cruise_speed_state()
+ self.enable(initial * CV.KPH_TO_MS, False, False)
+
+ self.press_button_short(ButtonType.accelCruise)
+ assert self.v_cruise_helper.v_cruise_kph == expected
+
+ def test_invalid_values_fallback(self):
+ """Test that invalid values fallback to safe defaults"""
+ # Test invalid short increment
+ self.set_custom_increments(enabled=True, short_inc=-1, long_inc=5)
+ self.enable(50 * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+ self.press_button_short(ButtonType.accelCruise)
+ assert self.v_cruise_helper.v_cruise_kph == initial_speed + 1 # Should fallback to 1
+
+ # Test invalid long increment
+ self.reset_cruise_speed_state()
+ self.set_custom_increments(enabled=True, short_inc=1, long_inc=99)
+ self.enable(50 * CV.KPH_TO_MS, False, False)
+
+ initial_speed = self.v_cruise_helper.v_cruise_kph
+ self.press_button_long(ButtonType.accelCruise)
+ assert self.v_cruise_helper.v_cruise_kph == initial_speed + 10 # Should fallback to 10
diff --git a/sunnypilot/selfdrive/controls/lib/dec/constants.py b/sunnypilot/selfdrive/controls/lib/dec/constants.py
index 48f4203e93..4586afbc9f 100644
--- a/sunnypilot/selfdrive/controls/lib/dec/constants.py
+++ b/sunnypilot/selfdrive/controls/lib/dec/constants.py
@@ -1,25 +1,17 @@
class WMACConstants:
- LEAD_WINDOW_SIZE = 5
- LEAD_PROB = 0.5
+ # Lead detection parameters
+ LEAD_WINDOW_SIZE = 6 # Stable detection window
+ LEAD_PROB = 0.45 # Balanced threshold for lead detection
- SLOW_DOWN_WINDOW_SIZE = 5
- SLOW_DOWN_PROB = 0.6
+ # Slow down detection parameters
+ SLOW_DOWN_WINDOW_SIZE = 5 # Responsive but stable
+ SLOW_DOWN_PROB = 0.3 # Balanced threshold for slow down scenarios
+ # Optimized slow down distance curve - smooth and progressive
SLOW_DOWN_BP = [0., 10., 20., 30., 40., 50., 55., 60.]
- SLOW_DOWN_DIST = [25., 38., 55., 75., 95., 115., 130., 150.]
+ SLOW_DOWN_DIST = [32., 46., 64., 86., 108., 130., 145., 165.]
- SLOWNESS_WINDOW_SIZE = 12
- SLOWNESS_PROB = 0.5
- SLOWNESS_CRUISE_OFFSET = 1.05
-
- DANGEROUS_TTC_WINDOW_SIZE = 3
- DANGEROUS_TTC = 2.3
-
- MPC_FCW_WINDOW_SIZE = 10
- MPC_FCW_PROB = 0.5
-
-
-class SNG_State:
- off = 0
- stopped = 1
- going = 2
+ # Slowness detection parameters
+ SLOWNESS_WINDOW_SIZE = 10 # Stable slowness detection
+ SLOWNESS_PROB = 0.55 # Clear threshold for slowness
+ SLOWNESS_CRUISE_OFFSET = 1.025 # Conservative cruise speed offset
diff --git a/sunnypilot/selfdrive/controls/lib/dec/dec.py b/sunnypilot/selfdrive/controls/lib/dec/dec.py
index 812e5cc15f..46cad1b048 100644
--- a/sunnypilot/selfdrive/controls/lib/dec/dec.py
+++ b/sunnypilot/selfdrive/controls/lib/dec/dec.py
@@ -1,88 +1,133 @@
-# The MIT License
-#
-# Copyright (c) 2019-, Rick Lan, dragonpilot community, and a number of other of contributors.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# 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 Software.
-#
-# THE SOFTWARE IS 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 SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# Version = 2025-1-18
+"""
+Copyright (c) 2021-, rav4kumar, Haibin Wen, sunnypilot, and a number of other contributors.
-import numpy as np
+This file is part of sunnypilot and is licensed under the MIT License.
+See the LICENSE.md file in the root directory for more details.
+"""
+# Version = 2025-6-30
from cereal import messaging
from opendbc.car import structs
from numpy import interp
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
-from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State
+from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants
+from typing import Literal
# d-e2e, from modeldata.h
TRAJECTORY_SIZE = 33
+SET_MODE_TIMEOUT = 15
-HIGHWAY_CRUISE_KPH = 70
-
-STOP_AND_GO_FRAME = 60
-
-SET_MODE_TIMEOUT = 10
-
-V_ACC_MIN = 9.72
+# Define the valid mode types
+ModeType = Literal['acc', 'blended']
-class GenericMovingAverageCalculator:
- def __init__(self, window_size):
- self.window_size = window_size
- self.data = []
- self.total = 0
+class SmoothKalmanFilter:
+ """Enhanced Kalman filter with smoothing for stable decision making."""
- def add_data(self, value: float) -> None:
- if len(self.data) == self.window_size:
- self.total -= self.data.pop(0)
- self.data.append(value)
- self.total += value
+ def __init__(self, initial_value=0, measurement_noise=0.1, process_noise=0.01,
+ alpha=1.0, smoothing_factor=0.85):
+ self.x = initial_value
+ self.P = 1.0
+ self.R = measurement_noise
+ self.Q = process_noise
+ self.alpha = alpha
+ self.smoothing_factor = smoothing_factor
+ self.initialized = False
+ self.history = []
+ self.max_history = 10
+ self.confidence = 0.0
- def get_moving_average(self) -> float | None:
- return None if len(self.data) == 0 else self.total / len(self.data)
+ def add_data(self, measurement):
+ if len(self.history) >= self.max_history:
+ self.history.pop(0)
+ self.history.append(measurement)
- def reset_data(self) -> None:
- self.data = []
- self.total = 0
+ if not self.initialized:
+ self.x = measurement
+ self.initialized = True
+ self.confidence = 0.1
+ return
+
+ self.P = self.alpha * self.P + self.Q
+
+ K = self.P / (self.P + self.R)
+ effective_K = K * (1.0 - self.smoothing_factor) + self.smoothing_factor * 0.1
+
+ innovation = measurement - self.x
+ self.x = self.x + effective_K * innovation
+ self.P = (1 - effective_K) * self.P
+
+ if abs(innovation) < 0.1:
+ self.confidence = min(1.0, self.confidence + 0.05)
+ else:
+ self.confidence = max(0.1, self.confidence - 0.02)
+
+ def get_value(self):
+ return self.x if self.initialized else None
+
+ def get_confidence(self):
+ return self.confidence
+
+ def reset_data(self):
+ self.initialized = False
+ self.history = []
+ self.confidence = 0.0
-class WeightedMovingAverageCalculator:
- def __init__(self, window_size):
- self.window_size = window_size
- self.data = []
- self.weights = np.linspace(1, 3, window_size) # Linear weights, adjust as needed
+class ModeTransitionManager:
+ """Manages smooth transitions between driving modes with hysteresis."""
- def add_data(self, value: float) -> None:
- if len(self.data) == self.window_size:
- self.data.pop(0)
- self.data.append(value)
+ def __init__(self):
+ self.current_mode: ModeType = 'acc'
+ self.mode_confidence = {'acc': 1.0, 'blended': 0.0}
+ self.transition_timeout = 0
+ self.min_mode_duration = 10
+ self.mode_duration = 0
+ self.emergency_override = False
- def get_weighted_average(self) -> float | None:
- if len(self.data) == 0:
- return None
- weighted_sum: float = float(np.dot(self.data, self.weights[-len(self.data):]))
- weight_total: float = float(np.sum(self.weights[-len(self.data):]))
- return weighted_sum / weight_total
+ def request_mode(self, mode: ModeType, confidence: float = 1.0, emergency: bool = False):
+ # Emergency override for critical situations (stops, collisions)
+ if emergency:
+ self.emergency_override = True
+ self.current_mode = mode
+ self.transition_timeout = SET_MODE_TIMEOUT
+ self.mode_duration = 0
+ return
- def reset_data(self) -> None:
- self.data = []
+ self.mode_confidence[mode] = min(1.0, self.mode_confidence[mode] + 0.1 * confidence)
+ for m in self.mode_confidence:
+ if m != mode:
+ self.mode_confidence[m] = max(0.0, self.mode_confidence[m] - 0.05)
+
+ # Require minimum duration in current mode (unless emergency)
+ if self.mode_duration < self.min_mode_duration and not self.emergency_override:
+ return
+
+ # Hysteresis: higher threshold for mode changes
+ confidence_threshold = 0.6 if mode != self.current_mode else 0.3 # Lower threshold for faster response
+
+ if self.mode_confidence[mode] > confidence_threshold:
+ if mode != self.current_mode and self.transition_timeout == 0:
+ self.transition_timeout = SET_MODE_TIMEOUT
+ self.current_mode = mode
+ self.mode_duration = 0
+
+ def update(self):
+ if self.transition_timeout > 0:
+ self.transition_timeout -= 1
+ self.mode_duration += 1
+
+ # Reset emergency override after some time
+ if self.emergency_override and self.mode_duration > 20:
+ self.emergency_override = False
+
+ # Gradual confidence decay
+ for mode in self.mode_confidence:
+ self.mode_confidence[mode] *= 0.98
+
+ def get_mode(self) -> ModeType:
+ return self.current_mode
class DynamicExperimentalController:
@@ -92,51 +137,59 @@ class DynamicExperimentalController:
self._params = params or Params()
self._enabled: bool = self._params.get_bool("DynamicExperimentalControl")
self._active: bool = False
- self._mode: str = 'acc'
self._frame: int = 0
+ self._urgency = 0.0
- # Use weighted moving average for filtering leads
- self._lead_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.LEAD_WINDOW_SIZE)
+ self._mode_manager = ModeTransitionManager()
+
+ # Smooth filters for stable decision making with faster response for critical scenarios
+ self._lead_filter = SmoothKalmanFilter(
+ measurement_noise=0.15,
+ process_noise=0.05,
+ alpha=1.02,
+ smoothing_factor=0.8
+ )
+
+ self._slow_down_filter = SmoothKalmanFilter(
+ measurement_noise=0.1,
+ process_noise=0.1,
+ alpha=1.05,
+ smoothing_factor=0.7
+ )
+
+ self._slowness_filter = SmoothKalmanFilter(
+ measurement_noise=0.1,
+ process_noise=0.06,
+ alpha=1.015,
+ smoothing_factor=0.92
+ )
+
+ self._mpc_fcw_filter = SmoothKalmanFilter(
+ measurement_noise=0.2,
+ process_noise=0.1,
+ alpha=1.1,
+ smoothing_factor=0.5
+ )
self._has_lead_filtered = False
- self._has_lead_filtered_prev = False
-
- self._slow_down_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOW_DOWN_WINDOW_SIZE)
- self._has_slow_down: bool = False
- self._slow_down_confidence: float = 0.0
-
- self._has_blinkers = False
-
- self._slowness_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.SLOWNESS_WINDOW_SIZE)
- self._has_slowness: bool = False
-
- self._has_nav_instruction = False
-
- self._dangerous_ttc_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.DANGEROUS_TTC_WINDOW_SIZE)
- self._has_dangerous_ttc: bool = False
-
- self._v_ego_kph = 0.
- self._v_cruise_kph = 0.
-
- self._has_lead = False
-
+ self._has_slow_down = False
+ self._has_slowness = False
+ self._has_mpc_fcw = False
+ self._v_ego_kph = 0.0
+ self._v_cruise_kph = 0.0
self._has_standstill = False
- self._has_standstill_prev = False
-
- self._sng_transit_frame = 0
- self._sng_state = SNG_State.off
-
- self._mpc_fcw_gmac = WeightedMovingAverageCalculator(window_size=WMACConstants.MPC_FCW_WINDOW_SIZE)
- self._has_mpc_fcw: bool = False
self._mpc_fcw_crash_cnt = 0
-
- self._set_mode_timeout = 0
+ self._standstill_count = 0
+ # debug
+ self._endpoint_x = float('inf')
+ self._expected_distance = 0.0
+ self._trajectory_valid = False
def _read_params(self) -> None:
if self._frame % int(1. / DT_MDL) == 0:
self._enabled = self._params.get_bool("DynamicExperimentalControl")
def mode(self) -> str:
- return str(self._mode)
+ return self._mode_manager.get_mode()
def enabled(self) -> bool:
return self._enabled
@@ -144,46 +197,9 @@ class DynamicExperimentalController:
def active(self) -> bool:
return self._active
- @staticmethod
- def _anomaly_detection(recent_data: list[float], threshold: float = 2.0, context_check: bool = True) -> bool:
- """
- Basic anomaly detection using standard deviation.
- """
- if len(recent_data) < 5:
- return False
- mean: float = float(np.mean(recent_data))
- std_dev: float = float(np.std(recent_data))
- anomaly: bool = bool(recent_data[-1] > mean + threshold * std_dev)
-
- # Context check to ensure repeated anomaly
- if context_check:
- return np.count_nonzero(np.array(recent_data) > mean + threshold * std_dev) > 1
- return anomaly
-
- def _adaptive_slowdown_threshold(self) -> float:
- """
- Adapts the slow-down threshold based on vehicle speed and recent behavior.
- """
- slowdown_scaling_factor: float = (1.0 + 0.03 * np.log(1 + len(self._slow_down_gmac.data)))
- adaptive_threshold: float = float(
- interp(self._v_ego_kph, WMACConstants.SLOW_DOWN_BP, WMACConstants.SLOW_DOWN_DIST) * slowdown_scaling_factor
- )
- return adaptive_threshold
-
- def _smoothed_lead_detection(self, lead_prob: float, smoothing_factor: float = 0.2):
- """
- Smoothing the lead detection to avoid erratic behavior.
- """
- lead_filtering: float = (1 - smoothing_factor) * self._has_lead_filtered + smoothing_factor * lead_prob
- return lead_filtering > WMACConstants.LEAD_PROB
-
- def _adaptive_lead_prob_threshold(self) -> float:
- """
- Adapts lead probability threshold based on driving conditions.
- """
- if self._v_ego_kph > HIGHWAY_CRUISE_KPH:
- return float(WMACConstants.LEAD_PROB + 0.1) # Increase the threshold on highways
- return float(WMACConstants.LEAD_PROB)
+ def set_mpc_fcw_crash_cnt(self) -> None:
+ """Set MPC FCW crash count"""
+ self._mpc_fcw_crash_cnt = self._mpc.crash_cnt
def _update_calculations(self, sm: messaging.SubMaster) -> None:
car_state = sm['carState']
@@ -192,186 +208,168 @@ class DynamicExperimentalController:
self._v_ego_kph = car_state.vEgo * 3.6
self._v_cruise_kph = car_state.vCruise
- self._has_lead = lead_one.status
self._has_standstill = car_state.standstill
- # fcw detection
- self._mpc_fcw_gmac.add_data(self._mpc_fcw_crash_cnt > 0)
- if _mpc_fcw_weighted_average := self._mpc_fcw_gmac.get_weighted_average():
- self._has_mpc_fcw = _mpc_fcw_weighted_average > WMACConstants.MPC_FCW_PROB
- else:
- self._has_mpc_fcw = False
-
- # nav enable detection
- # self._has_nav_instruction = md.navEnabledDEPRECATED and maneuver_distance / max(car_state.vEgo, 1) < 13
-
- # lead detection with smoothing
- self._lead_gmac.add_data(lead_one.status)
- self._has_lead_filtered = (self._lead_gmac.get_weighted_average() or -1.) > WMACConstants.LEAD_PROB
- #lead_prob = self._lead_gmac.get_weighted_average() or 0
- #self._has_lead_filtered = self._smoothed_lead_detection(lead_prob)
-
- # adaptive slow down detection
- adaptive_threshold = self._adaptive_slowdown_threshold()
- slow_down_trigger = len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE and md.position.x[TRAJECTORY_SIZE - 1] < adaptive_threshold
- self._slow_down_gmac.add_data(slow_down_trigger)
- if _has_slow_down_weighted_average := self._slow_down_gmac.get_weighted_average():
- self._has_slow_down = _has_slow_down_weighted_average > WMACConstants.SLOW_DOWN_PROB
- self._slow_down_confidence = _has_slow_down_weighted_average # Store confidence level
- else:
- self._has_slow_down = False
- self._slow_down_confidence = 0.0 # No confidence if no slowdown
-
- # anomaly detection for slow down events
- if self._anomaly_detection(self._slow_down_gmac.data):
- self._slow_down_confidence *= 0.85 # Reduce confidence
- self._has_slow_down = self._slow_down_confidence > WMACConstants.SLOW_DOWN_PROB
-
- # blinker detection
- self._has_blinkers = car_state.leftBlinker or car_state.rightBlinker
-
- # sng detection
+ # standstill detection
if self._has_standstill:
- self._sng_state = SNG_State.stopped
- self._sng_transit_frame = 0
+ self._standstill_count = min(20, self._standstill_count + 1)
else:
- if self._sng_transit_frame == 0:
- if self._sng_state == SNG_State.stopped:
- self._sng_state = SNG_State.going
- self._sng_transit_frame = STOP_AND_GO_FRAME
- elif self._sng_state == SNG_State.going:
- self._sng_state = SNG_State.off
- elif self._sng_transit_frame > 0:
- self._sng_transit_frame -= 1
+ self._standstill_count = max(0, self._standstill_count - 1)
- # slowness detection
- if not self._has_standstill:
- self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph * WMACConstants.SLOWNESS_CRUISE_OFFSET))
- if _slowness_weighted_average := self._slowness_gmac.get_weighted_average():
- self._has_slowness = _slowness_weighted_average > WMACConstants.SLOWNESS_PROB
- else:
- self._has_slowness = False
+ # Lead detection
+ self._lead_filter.add_data(float(lead_one.status))
+ lead_value = self._lead_filter.get_value() or 0.0
+ self._has_lead_filtered = lead_value > WMACConstants.LEAD_PROB
- # dangerous TTC detection
- if not self._has_lead_filtered and self._has_lead_filtered_prev:
- self._dangerous_ttc_gmac.reset_data()
- self._has_dangerous_ttc = False
+ # MPC FCW detection
+ fcw_filtered_value = self._mpc_fcw_filter.get_value() or 0.0
+ self._mpc_fcw_filter.add_data(float(self._mpc_fcw_crash_cnt > 0))
+ self._has_mpc_fcw = fcw_filtered_value > 0.5
- if self._has_lead and car_state.vEgo >= 0.01:
- self._dangerous_ttc_gmac.add_data(lead_one.dRel / car_state.vEgo)
+ # Slow down detection
+ self._calculate_slow_down(md)
- if _dangerous_ttc_weighted_average := self._dangerous_ttc_gmac.get_weighted_average():
- self._has_dangerous_ttc = _dangerous_ttc_weighted_average <= WMACConstants.DANGEROUS_TTC
- else:
- self._has_dangerous_ttc = False
+ # Slowness detection
+ if not (self._standstill_count > 5) and not self._has_slow_down:
+ current_slowness = float(self._v_ego_kph <= (self._v_cruise_kph * WMACConstants.SLOWNESS_CRUISE_OFFSET))
+ self._slowness_filter.add_data(current_slowness)
+ slowness_value = self._slowness_filter.get_value() or 0.0
- # keep prev values
- self._has_standstill_prev = self._has_standstill
- self._has_lead_filtered_prev = self._has_lead_filtered
+ # Hysteresis for slowness
+ threshold = WMACConstants.SLOWNESS_PROB * (0.8 if self._has_slowness else 1.1)
+ self._has_slowness = slowness_value > threshold
+
+ def _calculate_slow_down(self, md):
+ """Calculate urgency based on trajectory endpoint vs expected distance."""
+
+ # Reset to safe defaults
+ urgency = 0.0
+ self._endpoint_x = float('inf')
+ self._trajectory_valid = False
+
+ #Require exact trajectory size
+ position_valid = len(md.position.x) == TRAJECTORY_SIZE
+ orientation_valid = len(md.orientation.x) == TRAJECTORY_SIZE
+
+ if not (position_valid and orientation_valid):
+ # Invalid trajectory - this itself might indicate a stop scenario
+ # Apply moderate urgency for incomplete trajectories at speed
+ if self._v_ego_kph > 20.0:
+ urgency = 0.3
+
+ self._slow_down_filter.add_data(urgency)
+ urgency_filtered = self._slow_down_filter.get_value() or 0.0
+ self._has_slow_down = urgency_filtered > WMACConstants.SLOW_DOWN_PROB
+ self._urgency = urgency_filtered
+ return
+
+ # We have a valid full trajectory
+ self._trajectory_valid = True
+
+ # Use the exact endpoint (33rd point, index 32)
+ endpoint_x = md.position.x[TRAJECTORY_SIZE - 1]
+ self._endpoint_x = endpoint_x
+
+ # Get expected distance based on current speed using tuned constants
+ expected_distance = interp(self._v_ego_kph,
+ WMACConstants.SLOW_DOWN_BP,
+ WMACConstants.SLOW_DOWN_DIST)
+ self._expected_distance = expected_distance
+
+ # Calculate urgency based on trajectory shortage
+ if endpoint_x < expected_distance:
+ shortage = expected_distance - endpoint_x
+ shortage_ratio = shortage / expected_distance
+
+ # Base urgency on shortage ratio
+ urgency = min(1.0, shortage_ratio * 2.0)
+
+ # Increase urgency for very short trajectories (imminent stops)
+ critical_distance = expected_distance * 0.3
+ if endpoint_x < critical_distance:
+ urgency = min(1.0, urgency * 2.0)
+
+ # Speed-based urgency adjustment
+ if self._v_ego_kph > 25.0:
+ speed_factor = 1.0 + (self._v_ego_kph - 25.0) / 80.0
+ urgency = min(1.0, urgency * speed_factor)
+
+ # Apply filtering but with less smoothing for stops
+ self._slow_down_filter.add_data(urgency)
+ urgency_filtered = self._slow_down_filter.get_value() or 0.0
+
+ # Update state with lower threshold for better stop detection
+ self._has_slow_down = urgency_filtered > (WMACConstants.SLOW_DOWN_PROB * 0.8)
+ self._urgency = urgency_filtered
def _radarless_mode(self) -> None:
- # when mpc fcw crash prob is high
- # use blended to slow down quickly
+ """Radarless mode decision logic with emergency handling."""
+
+ # EMERGENCY: MPC FCW - immediate blended mode
if self._has_mpc_fcw:
- self._set_mode('blended')
+ self._mode_manager.request_mode('blended', confidence=1.0, emergency=True)
return
- # Nav enabled and distance to upcoming turning is 300 or below
- # if self._has_nav_instruction:
- # self._set_mode('blended')
- # return
-
- # when blinker is on and speed is driving below V_ACC_MIN: blended
- # we don't want it to switch mode at higher speed, blended may trigger hard brake
- # if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
- # self._set_mode('blended')
- # return
-
- # when at highway cruise and SNG: blended
- # ensuring blended mode is used because acc is bad at catching SNG lead car
- # especially those who accel very fast and then brake very hard.
- # if self._sng_state == SNG_State.going and self._v_cruise_kph >= V_ACC_MIN:
- # self._set_mode('blended')
- # return
-
- # when standstill: blended
- # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
- if self._has_standstill:
- self._set_mode('blended')
+ # Standstill: use blended
+ if self._standstill_count > 3:
+ self._mode_manager.request_mode('blended', confidence=0.9)
return
- # when detecting slow down scenario: blended
- # e.g. traffic light, curve, stop sign etc.
+ # Slow down scenarios: emergency for high urgency, normal for lower urgency
if self._has_slow_down:
- self._set_mode('blended')
+ if self._urgency > 0.7:
+ # Emergency: immediate blended mode for high urgency stops
+ self._mode_manager.request_mode('blended', confidence=1.0, emergency=True)
+ else:
+ # Normal: blended with urgency-based confidence
+ confidence = min(1.0, self._urgency * 1.5)
+ self._mode_manager.request_mode('blended', confidence=confidence)
return
- # when detecting lead slow down: blended
- # use blended for higher braking capability
- if self._has_dangerous_ttc:
- self._set_mode('blended')
+ # Driving slow: use ACC (but not if actively slowing down)
+ if self._has_slowness and not self._has_slow_down:
+ self._mode_manager.request_mode('acc', confidence=0.8)
return
- # car driving at speed lower than set speed: acc
- if self._has_slowness:
- self._set_mode('acc')
- return
-
- self._set_mode('acc')
+ # Default: ACC
+ self._mode_manager.request_mode('acc', confidence=0.7)
def _radar_mode(self) -> None:
- # when mpc fcw crash prob is high
- # use blended to slow down quickly
+ """Radar mode with emergency handling."""
+
+ # EMERGENCY: MPC FCW - immediate blended mode
if self._has_mpc_fcw:
- self._set_mode('blended')
+ self._mode_manager.request_mode('blended', confidence=1.0, emergency=True)
return
- # If there is a filtered lead, the vehicle is not in standstill, and the lead vehicle's yRel meets the condition,
- if self._has_lead_filtered and not self._has_standstill:
- self._set_mode('acc')
+ # If lead detected and not in standstill: always use ACC
+ if self._has_lead_filtered and not (self._standstill_count > 3):
+ self._mode_manager.request_mode('acc', confidence=1.0)
return
- # when blinker is on and speed is driving below V_ACC_MIN: blended
- # we don't want it to switch mode at higher speed, blended may trigger hard brake
- # if self._has_blinkers and self._v_ego_kph < V_ACC_MIN:
- # self._set_mode('blended')
- # return
-
- # when standstill: blended
- # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light.
- if self._has_standstill:
- self._set_mode('blended')
- return
-
- # when detecting slow down scenario: blended
- # e.g. traffic light, curve, stop sign etc.
+ # Slow down scenarios: emergency for high urgency, normal for lower urgency
if self._has_slow_down:
- self._set_mode('blended')
+ if self._urgency > 0.7:
+ # Emergency: immediate blended mode for high urgency stops
+ self._mode_manager.request_mode('blended', confidence=1.0, emergency=True)
+ else:
+ # Normal: blended with urgency-based confidence
+ confidence = min(1.0, self._urgency * 1.3)
+ self._mode_manager.request_mode('blended', confidence=confidence)
return
- # car driving at speed lower than set speed: acc
- if self._has_slowness:
- self._set_mode('acc')
+ # Standstill: use blended
+ if self._standstill_count > 3:
+ self._mode_manager.request_mode('blended', confidence=0.9)
return
- # Nav enabled and distance to upcoming turning is 300 or below
- # if self._has_nav_instruction:
- # self._set_mode('blended')
- # return
+ # Driving slow: use ACC (but not if actively slowing down)
+ if self._has_slowness and not self._has_slow_down:
+ self._mode_manager.request_mode('acc', confidence=0.8)
+ return
- self._set_mode('acc')
-
- def set_mpc_fcw_crash_cnt(self) -> None:
- self._mpc_fcw_crash_cnt = self._mpc.crash_cnt
-
- def _set_mode(self, mode: str) -> None:
- if self._set_mode_timeout == 0:
- self._mode = mode
- if mode == 'blended':
- self._set_mode_timeout = SET_MODE_TIMEOUT
-
- if self._set_mode_timeout > 0:
- self._set_mode_timeout -= 1
+ # Default: ACC
+ self._mode_manager.request_mode('acc', confidence=0.7)
def update(self, sm: messaging.SubMaster) -> None:
self._read_params()
@@ -385,6 +383,6 @@ class DynamicExperimentalController:
else:
self._radar_mode()
+ self._mode_manager.update()
self._active = sm['selfdriveState'].experimentalMode and self._enabled
-
self._frame += 1
diff --git a/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py b/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py
index d15d88aefe..f9da39c03b 100644
--- a/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py
+++ b/sunnypilot/selfdrive/controls/lib/dec/tests/pytest_dynamic_controller.py
@@ -1,248 +1,94 @@
import pytest
-import numpy as np
-from openpilot.common.params import Params
-from openpilot.sunnypilot.selfdrive.controls.lib.dec.constants import WMACConstants, SNG_State
-from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController, TRAJECTORY_SIZE, STOP_AND_GO_FRAME
-
-class MockInterp:
- def __call__(self, x, xp, fp):
- return np.interp(x, xp, fp)
-
-class MockCarState:
- def __init__(self, v_ego=0., standstill=False, left_blinker=False, right_blinker=False):
- self.vEgo = v_ego
- self.standstill = standstill
- self.leftBlinker = left_blinker
- self.rightBlinker = right_blinker
+from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
class MockLeadOne:
- def __init__(self, status=False, d_rel=0):
+ def __init__(self, status=0.0):
self.status = status
- self.dRel = d_rel
+
+class MockRadarState:
+ def __init__(self, status=0.0):
+ self.leadOne = MockLeadOne(status=status)
+
+class MockCarState:
+ def __init__(self, vEgo=0.0, vCruise=0.0, standstill=False):
+ self.vEgo = vEgo
+ self.vCruise = vCruise
+ self.standstill = standstill
class MockModelData:
- def __init__(self, x_vals=None, positions=None):
- self.orientation = type('Orientation', (), {'x': x_vals})()
- self.position = type('Position', (), {'x': positions})()
+ def __init__(self, valid=True):
+ size = 33 if valid else 10 # incomplete if invalid
+ self.position = type("Pos", (), {"x": [0.0] * size})()
+ self.orientation = type("Ori", (), {"x": [0.0] * size})()
-class MockControlState:
- def __init__(self, v_cruise=0):
- self.vCruise = v_cruise
+class MockSelfDriveState:
+ def __init__(self, experimentalMode=False):
+ self.experimentalMode = experimentalMode
+
+class MockParams:
+ def get_bool(self, name):
+ return True
@pytest.fixture
-def interp(monkeypatch):
- mock_interp = MockInterp()
- monkeypatch.setattr('numpy.interp', mock_interp)
- return mock_interp
+def default_sm():
+ sm = {
+ 'carState': MockCarState(vEgo=10.0, vCruise=20.0),
+ 'radarState': MockRadarState(status=1.0),
+ 'modelV2': MockModelData(valid=True),
+ 'selfdriveState': MockSelfDriveState(experimentalMode=True),
+ }
+ return sm
@pytest.fixture
-def controller(interp):
- params = Params()
- params.put_bool("DynamicExperimentalControl", True)
- return DynamicExperimentalController()
+def mock_cp():
+ class CP:
+ radarUnavailable = False
+ return CP()
-def test_initial_state(controller):
- """Test initial state of the controller"""
- assert controller._mode == 'acc'
- assert not controller._has_lead
- assert not controller._has_standstill
- assert controller._sng_state == SNG_State.off
- assert not controller._has_lead_filtered
- assert not controller._has_slow_down
- assert not controller._has_dangerous_ttc
- assert not controller._has_mpc_fcw
+@pytest.fixture
+def mock_mpc():
+ class MPC:
+ crash_cnt = 0
+ return MPC()
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_standstill_detection(controller, has_radar):
- """Test standstill detection and state transitions"""
- car_state = MockCarState(standstill=True)
- lead_one = MockLeadOne()
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
- controls_state = MockControlState()
+# Fake Kalman Filter that always returns a given value
+class FakeKalman:
+ def __init__(self, value=1.0):
+ self.value = value
+ def add_data(self, v): pass
+ def get_value(self): return self.value
+ def get_confidence(self): return 1.0
+ def reset_data(self): pass
- # Test transition to standstill
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
- assert controller._sng_state == SNG_State.stopped
- assert controller.get_mpc_mode() == 'blended'
+def test_initial_mode_is_acc(mock_cp, mock_mpc):
+ controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
+ assert controller.mode() == "acc"
- # Test transition from standstill to moving
- car_state.standstill = False
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
- assert controller._sng_state == SNG_State.going
+def test_standstill_triggers_blended(mock_cp, mock_mpc, default_sm):
+ controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
+ default_sm['carState'].standstill = True
+ for _ in range(10):
+ controller.update(default_sm)
+ assert controller.mode() == "blended"
- # Test complete transition to normal driving
- for _ in range(STOP_AND_GO_FRAME + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
- assert controller._sng_state == SNG_State.off
+def test_emergency_blended_on_fcw(mock_cp, mock_mpc, default_sm):
+ controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
+ mock_mpc.crash_cnt = 1 # simulate FCW
+ for _ in range(2):
+ controller.update(default_sm)
+ assert controller.mode() == "blended"
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_lead_detection(controller, has_radar):
- """Test lead vehicle detection and filtering"""
- car_state = MockCarState(v_ego=20) # 72 kph
- lead_one = MockLeadOne(status=True, d_rel=50) # Safe distance
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
- controls_state = MockControlState(v_cruise=72)
+def test_radarless_slowdown_triggers_blended(mock_cp, mock_mpc, default_sm):
+ mock_cp.radarUnavailable = True
+ controller = DynamicExperimentalController(mock_cp, mock_mpc, params=MockParams())
- # Let moving average stabilize
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
+ # Force conditions to simulate slowdown
+ controller._slow_down_filter = FakeKalman(value=1.0) # Ensure urgency triggers slowdown
+ controller._v_ego_kph = 35.0
+ default_sm['modelV2'] = MockModelData(valid=False) # Incomplete trajectory
- assert controller._has_lead_filtered
- expected_mode = 'acc' if has_radar else 'blended'
- assert controller.get_mpc_mode() == expected_mode
+ for _ in range(3):
+ controller.update(default_sm)
- # Test lead loss detection
- lead_one.status = False
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert not controller._has_lead_filtered
-
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_slow_down_detection(controller, has_radar):
- """Test slow down detection based on trajectory"""
- car_state = MockCarState(v_ego=10/3.6) # 10 kph
- lead_one = MockLeadOne()
- x_vals = [0] * TRAJECTORY_SIZE
- positions = [20] * TRAJECTORY_SIZE # Position within slow down threshold
- md = MockModelData(x_vals=x_vals, positions=positions)
- controls_state = MockControlState(v_cruise=30)
-
- # Test slow down detection
- for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_slow_down
- assert controller.get_mpc_mode() == 'blended'
-
- # Test slow down recovery
- positions = [200] * TRAJECTORY_SIZE # Position outside slow down threshold
- md = MockModelData(x_vals=x_vals, positions=positions)
- for _ in range(WMACConstants.SLOW_DOWN_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert not controller._has_slow_down
-
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_dangerous_ttc_detection(controller, has_radar):
- """Test Time-To-Collision detection and handling"""
- car_state = MockCarState(v_ego=10) # 36 kph
- lead_one = MockLeadOne(status=True)
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
- controls_state = MockControlState(v_cruise=36)
-
- # First establish normal conditions with lead
- lead_one.dRel = 100 # Safe distance
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1): # First establish lead detection
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_lead_filtered # Verify lead is detected
-
- # Now test dangerous TTC detection
- lead_one.dRel = 10 # 10m distance - should trigger dangerous TTC
- # TTC = dRel/vEgo = 10/10 = 1s (which is less than DANGEROUS_TTC = 2.3s)
-
- # Need to update multiple times to allow the weighted average to stabilize
- for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_dangerous_ttc, "TTC of 1s should be considered dangerous"
- expected_mode = 'acc' if has_radar else 'blended'
- assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC"
-
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_mode_transitions(controller, has_radar):
- """Test comprehensive mode transitions under different conditions"""
- # Initialize with normal driving conditions
- car_state = MockCarState(v_ego=25) # 90 kph
- lead_one = MockLeadOne(status=False)
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[200] * TRAJECTORY_SIZE)
- controls_state = MockControlState(v_cruise=100)
-
- def stabilize_filters():
- """Helper to let all moving averages stabilize"""
- for _ in range(max(WMACConstants.LEAD_WINDOW_SIZE, WMACConstants.SLOW_DOWN_WINDOW_SIZE,
- WMACConstants.DANGEROUS_TTC_WINDOW_SIZE, WMACConstants.MPC_FCW_WINDOW_SIZE) + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- # Test 1: Normal driving -> ACC mode
- stabilize_filters()
- assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode under normal driving conditions"
-
- # Test 2: Standstill -> Blended mode
- car_state.standstill = True
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
- assert controller.get_mpc_mode() == 'blended', "Should be in blended mode during standstill"
-
- # Test 3: Lead car appears -> ACC mode
- car_state = MockCarState(v_ego=20) # Reset car state
- lead_one.status = True
- lead_one.dRel = 50 # Safe distance
- stabilize_filters()
- assert not controller._has_dangerous_ttc, "Should not have dangerous TTC"
- assert controller.get_mpc_mode() == 'acc', "Should be in ACC mode with safe lead distance"
-
- # Test 4: Dangerous TTC -> Blended mode
- car_state = MockCarState(v_ego=20) # 72 kph
- lead_one.status = True
- lead_one.dRel = 50 # First establish normal lead detection
-
- # First establish lead detection
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_lead_filtered # Verify lead is detected
-
- # Now create dangerous TTC condition
- lead_one.dRel = 20 # This creates a TTC of 1s, well below DANGEROUS_TTC
-
- for _ in range(WMACConstants.DANGEROUS_TTC_WINDOW_SIZE * 2):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_dangerous_ttc, "Should detect dangerous TTC condition"
- expected_mode = 'acc' if has_radar else 'blended'
- assert controller.get_mpc_mode() == expected_mode, f"Should be in [{expected_mode}] mode with dangerous TTC"
-
-@pytest.mark.parametrize("has_radar", [True, False], ids=["with_radar", "without_radar"])
-def test_mpc_fcw_handling(controller, has_radar):
- """Test MPC FCW crash count handling and mode transitions"""
- car_state = MockCarState(v_ego=20)
- lead_one = MockLeadOne()
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
- controls_state = MockControlState(v_cruise=72)
-
- # Test FCW activation
- controller.set_mpc_fcw_crash_cnt(5)
- for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert controller._has_mpc_fcw
- assert controller.get_mpc_mode() == 'blended'
-
- # Test FCW recovery
- controller.set_mpc_fcw_crash_cnt(0)
- for _ in range(WMACConstants.MPC_FCW_WINDOW_SIZE + 1):
- controller.update(not has_radar, car_state, lead_one, md, controls_state)
-
- assert not controller._has_mpc_fcw
-
-def test_radar_unavailable_handling(controller):
- """Test behavior transitions between radar available and unavailable states"""
- car_state = MockCarState(v_ego=27.78) # 100 kph
- lead_one = MockLeadOne(status=True, d_rel=50)
- md = MockModelData(x_vals=[0] * TRAJECTORY_SIZE, positions=[150] * TRAJECTORY_SIZE)
- controls_state = MockControlState(v_cruise=100)
-
- # Test with radar available
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
- controller.update(False, car_state, lead_one, md, controls_state)
- radar_mode = controller.get_mpc_mode()
-
- # Test with radar unavailable
- for _ in range(WMACConstants.LEAD_WINDOW_SIZE + 1):
- controller.update(True, car_state, lead_one, md, controls_state)
- radarless_mode = controller.get_mpc_mode()
-
- assert radar_mode is not None
- assert radarless_mode is not None
+ assert controller.mode() == "blended"
diff --git a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py
index 800bbac67a..1127619221 100644
--- a/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py
+++ b/sunnypilot/selfdrive/controls/lib/latcontrol_torque_ext_base.py
@@ -10,8 +10,10 @@ import numpy as np
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.selfdrive.modeld.constants import ModelConstants
-LAT_PLAN_MIN_IDX = 5
+from openpilot.sunnypilot.livedelay.lagd_toggle import LagdToggle
+LAT_PLAN_MIN_IDX = 5
+LATERAL_LAG_MOD = 0.0 # seconds, modifies how far in the future we look ahead for the lateral plan
def get_predicted_lateral_jerk(lat_accels, t_diffs):
# compute finite difference between subsequent model_v2.acceleration.y values
@@ -41,11 +43,13 @@ def get_lookahead_value(future_vals, current_val):
return min_val
-class LatControlTorqueExtBase:
+class LatControlTorqueExtBase(LagdToggle):
def __init__(self, lac_torque, CP, CP_SP):
+ LagdToggle.__init__(self)
self.model_v2 = None
self.model_valid = False
- self.use_steering_angle = lac_torque.use_steering_angle
+ self.torque_params = lac_torque.torque_params
+ self.use_steering_angle = lac_torque.torque_params.useSteeringAngle
self.actual_lateral_jerk: float = 0.0
self.lateral_jerk_setpoint: float = 0.0
@@ -53,7 +57,6 @@ class LatControlTorqueExtBase:
self.lookahead_lateral_jerk: float = 0.0
self.torque_from_lateral_accel = lac_torque.torque_from_lateral_accel
- self.torque_params = lac_torque.torque_params
self._ff = 0.0
self._pid_log = None
@@ -85,12 +88,15 @@ class LatControlTorqueExtBase:
# precompute time differences between ModelConstants.T_IDXS
self.t_diffs = np.diff(ModelConstants.T_IDXS)
- self.desired_lat_jerk_time = CP.steerActuatorDelay + 0.3
+ self.desired_lat_jerk_time = CP.steerActuatorDelay + LATERAL_LAG_MOD
def update_model_v2(self, model_v2):
self.model_v2 = model_v2
self.model_valid = self.model_v2 is not None and len(self.model_v2.orientation.x) >= CONTROL_N
+ def update_lateral_lag(self, lag):
+ self.desired_lat_jerk_time = max(0.01, lag) + LATERAL_LAG_MOD
+
def update_friction_input(self, val_1, val_2):
_error = val_1 - val_2
_value = self.lat_accel_friction_factor * _error + self.lat_jerk_friction_factor * self.lookahead_lateral_jerk
diff --git a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py
index 56f32373d5..6952a97f1f 100644
--- a/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py
+++ b/sunnypilot/selfdrive/controls/lib/longitudinal_planner.py
@@ -8,6 +8,7 @@ See the LICENSE.md file in the root directory for more details.
from cereal import messaging, custom
from opendbc.car import structs
from openpilot.sunnypilot.selfdrive.controls.lib.dec.dec import DynamicExperimentalController
+from openpilot.sunnypilot.models.helpers import get_active_bundle
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
@@ -15,6 +16,12 @@ DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimen
class LongitudinalPlannerSP:
def __init__(self, CP: structs.CarParams, mpc):
self.dec = DynamicExperimentalController(CP, mpc)
+ self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
+
+ @property
+ def mlsim(self) -> bool:
+ # If we don't have a generation set, we assume it's default model. Which as of today are mlsim.
+ return bool(self.generation is None or self.generation >= 11)
def get_mpc_mode(self) -> str | None:
if not self.dec.active():
diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py b/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py
index 218bbb9f6a..2f89594c8f 100644
--- a/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py
+++ b/sunnypilot/selfdrive/controls/lib/nnlc/nnlc.py
@@ -45,9 +45,8 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
self.pitch_last = 0.0
# setup future time offsets
- self.nn_time_offset = CP.steerActuatorDelay + 0.2
- future_times = [0.3, 0.6, 1.0, 1.5] # seconds in the future
- self.nn_future_times = [i + self.nn_time_offset for i in future_times]
+ self.future_times = [0.3, 0.6, 1.0, 1.5] # seconds in the future
+ self.nn_future_times = [i + self.desired_lat_jerk_time for i in self.future_times]
# setup past time offsets
self.past_times = [-0.3, -0.2, -0.1]
@@ -58,6 +57,10 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
self.error_deque = deque(maxlen=history_check_frames[0])
self.past_future_len = len(self.past_times) + len(self.nn_future_times)
+ def update_lateral_lag(self, lag):
+ super().update_lateral_lag(lag)
+ self.nn_future_times = [t + self.desired_lat_jerk_time for t in self.future_times]
+
def update_neural_network_feedforward(self, CS, params, calibrated_pose) -> None:
if not self.enabled or not self.model_valid or not self.has_nn_model:
return
diff --git a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py
index 699327cd81..01ddec68ab 100644
--- a/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py
+++ b/sunnypilot/selfdrive/controls/lib/nnlc/tests/test_nnlc.py
@@ -73,17 +73,21 @@ class TestNeuralNetworkLateralControl:
controller.extension.model_v2 = model_v2
# Saturate for curvature limited and controller limited
+ test_lag = 0.3
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
+ controller.extension.update_lateral_lag(test_lag)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True)
assert lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
+ controller.extension.update_lateral_lag(test_lag)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False)
assert not lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
+ controller.extension.update_lateral_lag(test_lag)
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False)
assert lac_log.saturated
diff --git a/sunnypilot/selfdrive/controls/lib/param_store.py b/sunnypilot/selfdrive/controls/lib/param_store.py
index b2701bc010..5ec2a1c8c9 100644
--- a/sunnypilot/selfdrive/controls/lib/param_store.py
+++ b/sunnypilot/selfdrive/controls/lib/param_store.py
@@ -22,14 +22,16 @@ class ParamStore:
self.keys = universal_params + brand_params
self.values = {}
+ self.cached_params_list: list[capnp.lib.capnp._DynamicStructBuilder] | None = None
def update(self, params: Params) -> None:
+ old_values = dict(self.values)
self.values = {k: params.get(k, encoding='utf8') or "0" for k in self.keys}
+ if old_values != self.values:
+ self.cached_params_list = None
def publish(self) -> list[capnp.lib.capnp._DynamicStructBuilder]:
- params_list: list[capnp.lib.capnp._DynamicStructBuilder] = []
-
- for k in self.keys:
- params_list.append(custom.CarControlSP.Param(key=k, value=self.values[k]))
-
- return params_list
+ if self.cached_params_list is None:
+ # TODO-SP: Why are we doing a list instead of a dictionary here?
+ self.cached_params_list = [custom.CarControlSP.Param(key=k, value=self.values[k]) for k in self.keys]
+ return self.cached_params_list
diff --git a/system/athena/athenad.py b/system/athena/athenad.py
index 8679bd1090..29a51322da 100755
--- a/system/athena/athenad.py
+++ b/system/athena/athenad.py
@@ -23,6 +23,7 @@ from typing import cast
from collections.abc import Callable
import requests
+from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
from jsonrpc import JSONRPCResponseManager, dispatcher
from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException,
create_connection)
@@ -56,6 +57,11 @@ WS_FRAME_SIZE = 4096
DEVICE_STATE_UPDATE_INTERVAL = 1.0 # in seconds
DEFAULT_UPLOAD_PRIORITY = 99 # higher number = lower priority
+# https://bytesolutions.com/dscp-tos-cos-precedence-conversion-chart,
+# https://en.wikipedia.org/wiki/Differentiated_services
+UPLOAD_TOS = 0x20 # CS1, low priority background traffic
+SSH_TOS = 0x90 # AF42, DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag
+
NetworkType = log.DeviceState.NetworkType
UploadFileDict = dict[str, str | int | float | bool]
@@ -64,6 +70,17 @@ UploadItemDict = dict[str, str | bool | int | float | dict[str, str]]
UploadFilesToUrlResponse = dict[str, int | list[UploadItemDict] | list[str]]
+class UploadTOSAdapter(HTTPAdapter):
+ def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
+ pool_kwargs["socket_options"] = [(socket.IPPROTO_IP, socket.IP_TOS, UPLOAD_TOS)]
+ super().init_poolmanager(connections, maxsize, block, **pool_kwargs)
+
+
+UPLOAD_SESS = requests.Session()
+UPLOAD_SESS.mount("http://", UploadTOSAdapter())
+UPLOAD_SESS.mount("https://", UploadTOSAdapter())
+
+
@dataclass
class UploadFile:
fn: str
@@ -311,10 +328,10 @@ def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.R
stream = None
try:
stream, content_length = get_upload_stream(path, compress)
- response = requests.put(upload_item.url,
- data=CallbackReader(stream, callback, content_length) if callback else stream,
- headers={**upload_item.headers, 'Content-Length': str(content_length)},
- timeout=30)
+ response = UPLOAD_SESS.put(upload_item.url,
+ data=CallbackReader(stream, callback, content_length) if callback else stream,
+ headers={**upload_item.headers, 'Content-Length': str(content_length)},
+ timeout=30)
return response
finally:
if stream:
@@ -511,8 +528,7 @@ def start_local_proxy_shim(global_end_event: threading.Event, local_port: int, w
raise Exception("Requested local port not whitelisted")
# Set TOS to keep connection responsive while under load.
- # DSCP of 36/HDD_LINUX_AC_VI with the minimum delay flag
- ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, 0x90)
+ ws.sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, SSH_TOS)
ssock, csock = socket.socketpair()
local_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
diff --git a/system/athena/registration.py b/system/athena/registration.py
index d1e54181b8..fce984c03a 100755
--- a/system/athena/registration.py
+++ b/system/athena/registration.py
@@ -7,6 +7,7 @@ from pathlib import Path
from datetime import datetime, timedelta, UTC
from openpilot.common.api import api_get
from openpilot.common.params import Params
+from openpilot.common.spinner import Spinner
from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths
@@ -44,7 +45,6 @@ def register(show_spinner=False) -> str | None:
cloudlog.warning(f"missing public key: {pubkey}")
elif dongle_id is None:
if show_spinner:
- from openpilot.system.ui.spinner import Spinner
spinner = Spinner()
spinner.update("registering device")
diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py
index dd82325815..e559e0c05c 100644
--- a/system/athena/tests/test_athenad.py
+++ b/system/athena/tests/test_athenad.py
@@ -19,7 +19,7 @@ from cereal import messaging
from openpilot.common.params import Params
from openpilot.common.timeout import Timeout
from openpilot.system.athena import athenad
-from openpilot.system.athena.athenad import MAX_RETRY_COUNT, dispatcher
+from openpilot.system.athena.athenad import MAX_RETRY_COUNT, UPLOAD_SESS, dispatcher
from openpilot.system.athena.tests.helpers import HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket
from openpilot.selfdrive.test.helpers import http_server_context
from openpilot.system.hardware.hw import Paths
@@ -29,7 +29,7 @@ def seed_athena_server(host, port):
with Timeout(2, 'HTTP Server seeding failed'):
while True:
try:
- requests.put(f'http://{host}:{port}/qlog.zst', data='', timeout=10)
+ UPLOAD_SESS.put(f'http://{host}:{port}/qlog.zst', data='', timeout=10)
break
except requests.exceptions.ConnectionError:
time.sleep(0.1)
@@ -239,7 +239,7 @@ class TestAthenadMethods:
@pytest.mark.parametrize("status,retry", [(500,True), (412,False)])
@with_upload_handler
def test_upload_handler_retry(self, mocker, host, status, retry):
- mock_put = mocker.patch('requests.put')
+ mock_put = mocker.patch('openpilot.system.athena.athenad.UPLOAD_SESS.put')
mock_put.return_value.__enter__.return_value.status_code = status
fn = self._create_file('qlog.zst')
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.zst", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
diff --git a/system/hardware/hw.h b/system/hardware/hw.h
index d2083a5985..bc9d17dd81 100644
--- a/system/hardware/hw.h
+++ b/system/hardware/hw.h
@@ -55,4 +55,8 @@ namespace Path {
return "/dev/shm";
#endif
}
+
+ inline std::string model_root() {
+ return Hardware::PC() ? Path::comma_home() + "/media/0/models" : "/data/media/0/models";
+ }
} // namespace Path
diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json
index 7e5b9f187d..ec2557422c 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-4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42.img.xz",
- "hash": "4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42",
- "hash_raw": "4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42",
+ "url": "https://commadist.azureedge.net/agnosupdate/boot-4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef.img.xz",
+ "hash": "4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef",
+ "hash_raw": "4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef",
"size": 18479104,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "6b7b3371100ad36d8a5a9ff19a1663b9b9e2d5e99cbe3cf9255e9c3017291ce3"
+ "ondevice_hash": "8d7094d774faa4e801e36b403a31b53b913b31d086f4dc682d2f64710c557e8a"
},
{
"name": "system",
- "url": "https://commadist.azureedge.net/agnosupdate/system-c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa.img.xz",
- "hash": "993d6a1cd2b684e2b1cf6ff840f8996f02a529011372d9c1471e4c80719e7da9",
- "hash_raw": "c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39.img.xz",
+ "hash": "cccd7073d067027396f2afd49874729757db0bbbc79853a0bf2938bd356fe164",
+ "hash_raw": "4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
- "ondevice_hash": "59db25651da977eeb16a1af741fd01fc3d6b50d21544b1a7428b7c86b2cdef2d",
+ "ondevice_hash": "c7707f16ce7d977748677cc354e250943b4ff6c21b9a19a492053d32397cf9ec",
"alt": {
- "hash": "c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa",
- "url": "https://commadist.azureedge.net/agnosupdate/system-c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa.img",
+ "hash": "4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39.img",
"size": 5368709120
}
}
diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json
index bc1141f763..d28b48128b 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-4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42.img.xz",
- "hash": "4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42",
- "hash_raw": "4143170bad94968fd9be870b1498b4100bf273ed0aec2a2601c9017991d4bd42",
+ "url": "https://commadist.azureedge.net/agnosupdate/boot-4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef.img.xz",
+ "hash": "4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef",
+ "hash_raw": "4de8f892dbac3fa3fee1efe68ca76e23e75812e81a6577d00d52e2da1ef624ef",
"size": 18479104,
"sparse": false,
"full_check": true,
"has_ab": true,
- "ondevice_hash": "6b7b3371100ad36d8a5a9ff19a1663b9b9e2d5e99cbe3cf9255e9c3017291ce3"
+ "ondevice_hash": "8d7094d774faa4e801e36b403a31b53b913b31d086f4dc682d2f64710c557e8a"
},
{
"name": "system",
- "url": "https://commadist.azureedge.net/agnosupdate/system-c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa.img.xz",
- "hash": "993d6a1cd2b684e2b1cf6ff840f8996f02a529011372d9c1471e4c80719e7da9",
- "hash_raw": "c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39.img.xz",
+ "hash": "cccd7073d067027396f2afd49874729757db0bbbc79853a0bf2938bd356fe164",
+ "hash_raw": "4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39",
"size": 5368709120,
"sparse": true,
"full_check": false,
"has_ab": true,
- "ondevice_hash": "59db25651da977eeb16a1af741fd01fc3d6b50d21544b1a7428b7c86b2cdef2d",
+ "ondevice_hash": "c7707f16ce7d977748677cc354e250943b4ff6c21b9a19a492053d32397cf9ec",
"alt": {
- "hash": "c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa",
- "url": "https://commadist.azureedge.net/agnosupdate/system-c51bb5841011728f7cf108a9138ba68228ffb4232dfd91d6e082a6d8a6a8deaa.img",
+ "hash": "4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39",
+ "url": "https://commadist.azureedge.net/agnosupdate/system-4bc3951f4aa3f70c53837dc2542d8b0666d37103b353fd81417cc7de1bbebe39.img",
"size": 5368709120
}
},
{
"name": "userdata_90",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-89a161f17b86637413fe10a641550110b626b699382f5138c02267b7866a8494.img.xz",
- "hash": "99d9e6cf6755581c6879bbf442bd62212beb8a04116e965ab987135b8842188b",
- "hash_raw": "89a161f17b86637413fe10a641550110b626b699382f5138c02267b7866a8494",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f0c675e0fae420870c9ba8979fa246b170f4f1a7a04b49609b55b6bdfa8c1b21.img.xz",
+ "hash": "3d8a007bae088c5959eb9b82454013f91868946d78380fecea2b1afdfb575c02",
+ "hash_raw": "f0c675e0fae420870c9ba8979fa246b170f4f1a7a04b49609b55b6bdfa8c1b21",
"size": 96636764160,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "24ea29ab9c4ecec0568a4aa83e38790fedfce694060e90f4bde725931386ff41"
+ "ondevice_hash": "5bfbabb8ff96b149056aa75d5b7e66a7cdd9cb4bcefe23b922c292f7f3a43462"
},
{
"name": "userdata_89",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-cdd3401168819987c4840765bba1aa2217641b1a6a4165c412f44cac14ccfcbf.img.xz",
- "hash": "5fbfa008a7f6b58ab01d4d171f3185924d4c9db69b54f4bfc0f214c6f17c2435",
- "hash_raw": "cdd3401168819987c4840765bba1aa2217641b1a6a4165c412f44cac14ccfcbf",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-06fc52be37b42690ed7b4f8c66c4611309a2dea9fca37dd9d27d1eff302eb1bf.img.xz",
+ "hash": "443f136484294b210318842d09fb618d5411c8bdbab9f7421d8c89eb291a8d3f",
+ "hash_raw": "06fc52be37b42690ed7b4f8c66c4611309a2dea9fca37dd9d27d1eff302eb1bf",
"size": 95563022336,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "c07dc2e883a23d4a24d976cdf53a767a2fd699c8eeb476d60cdf18e84b417a52"
+ "ondevice_hash": "67db02b29a7e4435951c64cc962a474d048ed444aa912f3494391417cd51a074"
},
{
"name": "userdata_30",
- "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-2a8e8278b3bb545e6d7292c2417ccebdca9b47507eb5924f7c1e068737a7edfd.img.xz",
- "hash": "b3bc293c9c5e0480ef663e980c8ccb2fb83ffd230c85f8797830fb61b8f59360",
- "hash_raw": "2a8e8278b3bb545e6d7292c2417ccebdca9b47507eb5924f7c1e068737a7edfd",
+ "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-06679488f0c5c3fcfd5f351133050751cd189f705e478a979c45fc4a166d18a6.img.xz",
+ "hash": "875b580cb786f290a842e9187fd945657561886123eb3075a26f7995a18068f6",
+ "hash_raw": "06679488f0c5c3fcfd5f351133050751cd189f705e478a979c45fc4a166d18a6",
"size": 32212254720,
"sparse": true,
"full_check": true,
"has_ab": false,
- "ondevice_hash": "8dae1cda089828c750d1d646337774ccd9432f567ecefde19a06dc7feeda9cd3"
+ "ondevice_hash": "16e27ba3c5cf9f0394ce6235ba6021b8a2de293fdb08399f8ca832fa5e4d0b9d"
}
]
\ No newline at end of file
diff --git a/system/hardware/tici/esim.nmconnection b/system/hardware/tici/esim.nmconnection
new file mode 100644
index 0000000000..74f6f8e82c
--- /dev/null
+++ b/system/hardware/tici/esim.nmconnection
@@ -0,0 +1,30 @@
+[connection]
+id=esim
+uuid=fff6553c-3284-4707-a6b1-acc021caaafb
+type=gsm
+permissions=
+autoconnect=true
+autoconnect-retries=100
+autoconnect-priority=2
+metered=1
+
+[gsm]
+apn=
+home-only=false
+auto-config=true
+sim-id=
+
+[ipv4]
+route-metric=1000
+dns-priority=1000
+dns-search=
+method=auto
+
+[ipv6]
+ddr-gen-mode=stable-privacy
+dns-search=
+route-metric=1000
+dns-priority=1000
+method=auto
+
+[proxy]
diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py
index f46c94c4f0..20a23bd67e 100644
--- a/system/hardware/tici/hardware.py
+++ b/system/hardware/tici/hardware.py
@@ -3,6 +3,7 @@ import math
import os
import subprocess
import time
+import tempfile
from enum import IntEnum
from functools import cached_property, lru_cache
from pathlib import Path
@@ -414,8 +415,8 @@ class Tici(HardwareBase):
# *** GPU config ***
# https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216
- sudo_write("0", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel")
- sudo_write("0", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel")
+ sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel")
+ sudo_write("1", "/sys/class/kgsl/kgsl-3d0/max_pwrlevel")
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on")
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on")
sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on")
@@ -499,19 +500,18 @@ class Tici(HardwareBase):
except Exception:
pass
- # we use the lte connection built into AGNOS. cleanup esim connection if it exists
+ # eSIM prime
dest = "/etc/NetworkManager/system-connections/esim.nmconnection"
- if os.path.exists(dest):
- os.system(f"sudo nmcli con delete {dest}")
- self.reboot_modem()
+ if sim_id.startswith('8985235') and not os.path.exists(dest):
+ with open(Path(__file__).parent/'esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf:
+ dat = f.read()
+ dat = dat.replace("sim-id=", f"sim-id={sim_id}")
+ tf.write(dat)
+ tf.flush()
- def reboot_modem(self):
- modem = self.get_modem()
- for state in (0, 1):
- try:
- modem.Command(f'AT+CFUN={state}', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT)
- except Exception:
- pass
+ # needs to be root
+ os.system(f"sudo cp {tf.name} {dest}")
+ os.system(f"sudo nmcli con load {dest}")
def get_networks(self):
r = {}
diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc
index 2d2d4640ed..144ab9f349 100644
--- a/system/loggerd/loggerd.cc
+++ b/system/loggerd/loggerd.cc
@@ -62,6 +62,7 @@ struct RemoteEncoder {
bool recording = false;
bool marked_ready_to_rotate = false;
bool seen_first_packet = false;
+ bool audio_initialized = false;
};
size_t write_encode_data(LoggerdState *s, cereal::Event::Reader event, RemoteEncoder &re, const EncoderInfo &encoder_info) {
@@ -78,12 +79,7 @@ size_t write_encode_data(LoggerdState *s, cereal::Event::Reader event, RemoteEnc
LOGW("%s: dropped %d non iframe packets before init", encoder_info.publish_name, re.dropped_frames);
re.dropped_frames = 0;
}
- // if we aren't actually recording, don't create the writer
if (encoder_info.record) {
- assert(encoder_info.filename != NULL);
- re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(),
- encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C,
- edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType()));
// write the header
auto header = edata.getHeader();
re.writer->write((uint8_t *)header.begin(), header.size(), idx.getTimestampEof() / 1000, true, false);
@@ -138,12 +134,19 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct
// if this is a new segment, we close any possible old segments, move to the new, and process any queued packets
if (re.current_segment != s->logger.segment()) {
- if (re.recording) {
- re.writer.reset();
+ // if we aren't actually recording, don't create the writer
+ if (encoder_info.record) {
+ assert(encoder_info.filename != NULL);
+ re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(),
+ encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C,
+ edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType()));
re.recording = false;
+ re.audio_initialized = false;
}
re.current_segment = s->logger.segment();
re.marked_ready_to_rotate = false;
+ }
+ if (re.audio_initialized || !encoder_info.include_audio) {
// we are in this segment now, process any queued messages before this one
if (!re.q.empty()) {
for (auto qmsg : re.q) {
@@ -153,9 +156,14 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct
}
re.q.clear();
}
+ bytes_count += write_encode_data(s, event, re, encoder_info);
+ delete msg;
+ } else if (re.q.size() > MAIN_FPS*10) {
+ LOGE_100("%s: dropping frame waiting for audio initialization, queue is too large", name.c_str());
+ delete msg;
+ } else {
+ re.q.push_back(msg); // queue up all the new segment messages, they go in after audio is initialized
}
- bytes_count += write_encode_data(s, event, re, encoder_info);
- delete msg;
} else if (offset_segment_num > s->logger.segment()) {
// encoderd packet has a newer segment, this means encoderd has rolled over
if (!re.marked_ready_to_rotate) {
@@ -214,7 +222,7 @@ void loggerd_thread() {
typedef struct ServiceState {
std::string name;
int counter, freq;
- bool encoder, user_flag;
+ bool encoder, user_flag, record_audio;
} ServiceState;
std::unordered_map service_state;
std::unordered_map remote_encoders;
@@ -226,19 +234,22 @@ void loggerd_thread() {
for (const auto& [_, it] : services) {
const bool encoder = util::ends_with(it.name, "EncodeData");
const bool livestream_encoder = util::starts_with(it.name, "livestream");
- if (!it.should_log && (!encoder || livestream_encoder)) continue;
- LOGD("logging %s", it.name.c_str());
+ const bool record_audio = (it.name == "rawAudioData") && Params().getBool("RecordAudio");
+ if (it.should_log || (encoder && !livestream_encoder) || record_audio) {
+ LOGD("logging %s", it.name.c_str());
- SubSocket * sock = SubSocket::create(ctx.get(), it.name);
- assert(sock != NULL);
- poller->registerSocket(sock);
- service_state[sock] = {
- .name = it.name,
- .counter = 0,
- .freq = it.decimation,
- .encoder = encoder,
- .user_flag = it.name == "userFlag",
- };
+ SubSocket * sock = SubSocket::create(ctx.get(), it.name);
+ assert(sock != NULL);
+ poller->registerSocket(sock);
+ service_state[sock] = {
+ .name = it.name,
+ .counter = 0,
+ .freq = it.decimation,
+ .encoder = encoder,
+ .user_flag = it.name == "userFlag",
+ .record_audio = record_audio,
+ };
+ }
}
LoggerdState s;
@@ -247,6 +258,7 @@ void loggerd_thread() {
Params().put("CurrentRoute", s.logger.routeName());
std::map encoder_infos_dict;
+ std::vector encoders_with_audio;
for (const auto &cam : cameras_logged) {
for (const auto &encoder_info : cam.encoder_infos) {
encoder_infos_dict[encoder_info.publish_name] = encoder_info;
@@ -254,6 +266,13 @@ void loggerd_thread() {
}
}
+ for (auto &[sock, service] : service_state) {
+ auto it = encoder_infos_dict.find(service.name);
+ if (it != encoder_infos_dict.end() && it->second.include_audio) {
+ encoders_with_audio.push_back(&remote_encoders[sock]);
+ }
+ }
+
uint64_t msg_count = 0, bytes_count = 0;
double start_ts = millis_since_boot();
while (!do_exit) {
@@ -271,6 +290,20 @@ void loggerd_thread() {
Message *msg = nullptr;
while (!do_exit && (msg = sock->receive(true))) {
const bool in_qlog = service.freq != -1 && (service.counter++ % service.freq == 0);
+
+ if (service.record_audio) {
+ capnp::FlatArrayMessageReader cmsg(kj::ArrayPtr((capnp::word *)msg->getData(), msg->getSize() / sizeof(capnp::word)));
+ auto event = cmsg.getRoot();
+ auto audio_data = event.getRawAudioData().getData();
+ auto sample_rate = event.getRawAudioData().getSampleRate();
+ for (auto* encoder : encoders_with_audio) {
+ if (encoder && encoder->writer) {
+ encoder->writer->write_audio((uint8_t*)audio_data.begin(), audio_data.size(), event.getLogMonoTime() / 1000, sample_rate);
+ encoder->audio_initialized = true;
+ }
+ }
+ }
+
if (service.encoder) {
s.last_camera_seen_tms = millis_since_boot();
bytes_count += handle_encoder_msg(&s, msg, service.name, remote_encoders[sock], encoder_infos_dict[service.name]);
diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h
index 27d2d37fc4..5dfb178fd5 100644
--- a/system/loggerd/loggerd.h
+++ b/system/loggerd/loggerd.h
@@ -35,6 +35,7 @@ public:
const char *thumbnail_name = NULL;
const char *filename = NULL;
bool record = true;
+ bool include_audio = false;
int frame_width = -1;
int frame_height = -1;
int fps = MAIN_FPS;
@@ -106,6 +107,7 @@ const EncoderInfo qcam_encoder_info = {
.encode_type = cereal::EncodeIndex::Type::QCAMERA_H264,
.frame_width = 526,
.frame_height = 330,
+ .include_audio = Params().getBool("RecordAudio"),
INIT_ENCODE_FUNCTIONS(QRoadEncode),
};
diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py
index 179e237a65..8e2f52a731 100644
--- a/system/loggerd/tests/test_loggerd.py
+++ b/system/loggerd/tests/test_loggerd.py
@@ -97,6 +97,50 @@ class TestLoggerd:
return sent_msgs
+ def _publish_camera_and_audio_messages(self, num_segs=1, segment_length=5):
+ d = DEVICE_CAMERAS[("tici", "ar0231")]
+ streams = [
+ (VisionStreamType.VISION_STREAM_ROAD, (d.fcam.width, d.fcam.height, 2048 * 2346, 2048, 2048 * 1216), "roadCameraState"),
+ (VisionStreamType.VISION_STREAM_DRIVER, (d.dcam.width, d.dcam.height, 2048 * 2346, 2048, 2048 * 1216), "driverCameraState"),
+ (VisionStreamType.VISION_STREAM_WIDE_ROAD, (d.ecam.width, d.ecam.height, 2048 * 2346, 2048, 2048 * 1216), "wideRoadCameraState"),
+ ]
+
+ pm = messaging.PubMaster([s for _, _, s in streams] + ["rawAudioData"])
+ vipc_server = VisionIpcServer("camerad")
+ for stream_type, frame_spec, _ in streams:
+ vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec))
+ vipc_server.start_listener()
+
+ os.environ["LOGGERD_TEST"] = "1"
+ os.environ["LOGGERD_SEGMENT_LENGTH"] = str(segment_length)
+ managed_processes["loggerd"].start()
+ managed_processes["encoderd"].start()
+ assert pm.wait_for_readers_to_update("roadCameraState", timeout=5)
+
+ fps = 20
+ for n in range(1, int(num_segs * segment_length * fps) + 1):
+ # send video
+ for stream_type, frame_spec, state in streams:
+ dat = np.empty(frame_spec[2], dtype=np.uint8)
+ vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n / fps, n / fps)
+
+ camera_state = messaging.new_message(state)
+ frame = getattr(camera_state, state)
+ frame.frameId = n
+ pm.send(state, camera_state)
+
+ # send audio
+ msg = messaging.new_message('rawAudioData')
+ msg.rawAudioData.data = bytes(800 * 2) # 800 samples of int16
+ msg.rawAudioData.sampleRate = 16000
+ pm.send('rawAudioData', msg)
+
+ for _, _, state in streams:
+ assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001)
+
+ managed_processes["loggerd"].stop()
+ managed_processes["encoderd"].stop()
+
def test_init_data_values(self):
os.environ["CLEAN"] = random.choice(["0", "1"])
@@ -136,53 +180,23 @@ class TestLoggerd:
assert getattr(initData, initData_key) == v
assert logged_params[param_key].decode() == v
- @pytest.mark.skip("FIXME: encoderd sometimes crashes in CI when running with pytest-xdist")
+ @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing
def test_rotation(self):
- os.environ["LOGGERD_TEST"] = "1"
Params().put("RecordFront", "1")
- d = DEVICE_CAMERAS[("tici", "ar0231")]
expected_files = {"rlog.zst", "qlog.zst", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"}
- streams = [(VisionStreamType.VISION_STREAM_ROAD, (d.fcam.width, d.fcam.height, 2048*2346, 2048, 2048*1216), "roadCameraState"),
- (VisionStreamType.VISION_STREAM_DRIVER, (d.dcam.width, d.dcam.height, 2048*2346, 2048, 2048*1216), "driverCameraState"),
- (VisionStreamType.VISION_STREAM_WIDE_ROAD, (d.ecam.width, d.ecam.height, 2048*2346, 2048, 2048*1216), "wideRoadCameraState")]
- pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"])
- vipc_server = VisionIpcServer("camerad")
- for stream_type, frame_spec, _ in streams:
- vipc_server.create_buffers_with_sizes(stream_type, 40, *(frame_spec))
- vipc_server.start_listener()
+ num_segs = random.randint(2, 3)
+ length = random.randint(4, 5) # H264 encoder uses 40 lookahead frames and does B-frame reordering, so minimum 3 seconds before qcam output
- num_segs = random.randint(2, 5)
- length = random.randint(1, 3)
- os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length)
- managed_processes["loggerd"].start()
- managed_processes["encoderd"].start()
- assert pm.wait_for_readers_to_update("roadCameraState", timeout=5)
-
- fps = 20.0
- for n in range(1, int(num_segs*length*fps)+1):
- for stream_type, frame_spec, state in streams:
- dat = np.empty(frame_spec[2], dtype=np.uint8)
- vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps)
-
- camera_state = messaging.new_message(state)
- frame = getattr(camera_state, state)
- frame.frameId = n
- pm.send(state, camera_state)
-
- for _, _, state in streams:
- assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001)
-
- managed_processes["loggerd"].stop()
- managed_processes["encoderd"].stop()
+ self._publish_camera_and_audio_messages(num_segs=num_segs, segment_length=length)
route_path = str(self._get_latest_log_dir()).rsplit("--", 1)[0]
for n in range(num_segs):
p = Path(f"{route_path}--{n}")
logged = {f.name for f in p.iterdir() if f.is_file()}
diff = logged ^ expected_files
- assert len(diff) == 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}"
+ assert len(diff) == 0, f"didn't get all expected files. seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}"
def test_bootlog(self):
# generate bootlog with fake launch log
@@ -281,3 +295,30 @@ class TestLoggerd:
segment_dir = self._get_latest_log_dir()
assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None
+
+ @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing
+ @pytest.mark.parametrize("record_front", [True, False])
+ def test_record_front(self, record_front):
+ params = Params()
+ params.put_bool("RecordFront", record_front)
+
+ self._publish_camera_and_audio_messages()
+
+ dcamera_hevc_exists = os.path.exists(os.path.join(self._get_latest_log_dir(), 'dcamera.hevc'))
+ assert dcamera_hevc_exists == record_front
+
+ @pytest.mark.xdist_group("camera_encoder_tests") # setting xdist group ensures tests are run in same worker, prevents encoderd from crashing
+ @pytest.mark.parametrize("record_audio", [True, False])
+ def test_record_audio(self, record_audio):
+ params = Params()
+ params.put_bool("RecordAudio", record_audio)
+
+ self._publish_camera_and_audio_messages()
+
+ qcamera_ts_path = os.path.join(self._get_latest_log_dir(), 'qcamera.ts')
+ ffprobe_cmd = f"ffprobe -i {qcamera_ts_path} -show_streams -select_streams a -loglevel error"
+ has_audio_stream = subprocess.run(ffprobe_cmd, shell=True, capture_output=True).stdout.strip() != b''
+ assert has_audio_stream == record_audio
+
+ raw_audio_in_rlog = any(m.which() == 'rawAudioData' for m in LogReader(os.path.join(self._get_latest_log_dir(), 'rlog.zst')))
+ assert raw_audio_in_rlog == record_audio
diff --git a/system/loggerd/video_writer.cc b/system/loggerd/video_writer.cc
index 90b5f1af3d..68e870982f 100644
--- a/system/loggerd/video_writer.cc
+++ b/system/loggerd/video_writer.cc
@@ -50,6 +50,45 @@ VideoWriter::VideoWriter(const char *path, const char *filename, bool remuxing,
}
}
+void VideoWriter::initialize_audio(int sample_rate) {
+ assert(this->ofmt_ctx->oformat->audio_codec != AV_CODEC_ID_NONE); // check output format supports audio streams
+ const AVCodec *audio_avcodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
+ assert(audio_avcodec);
+ this->audio_codec_ctx = avcodec_alloc_context3(audio_avcodec);
+ assert(this->audio_codec_ctx);
+ this->audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
+ this->audio_codec_ctx->sample_rate = sample_rate;
+ #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) // FFmpeg 5.1+
+ av_channel_layout_default(&this->audio_codec_ctx->ch_layout, 1);
+ #else
+ this->audio_codec_ctx->channel_layout = AV_CH_LAYOUT_MONO;
+ #endif
+ this->audio_codec_ctx->bit_rate = 32000;
+ this->audio_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+ this->audio_codec_ctx->time_base = (AVRational){1, audio_codec_ctx->sample_rate};
+ int err = avcodec_open2(this->audio_codec_ctx, audio_avcodec, NULL);
+ assert(err >= 0);
+ av_log_set_level(AV_LOG_WARNING); // hide "QAvg" info msgs at the end of every segment
+
+ this->audio_stream = avformat_new_stream(this->ofmt_ctx, NULL);
+ assert(this->audio_stream);
+ err = avcodec_parameters_from_context(this->audio_stream->codecpar, this->audio_codec_ctx);
+ assert(err >= 0);
+
+ this->audio_frame = av_frame_alloc();
+ assert(this->audio_frame);
+ this->audio_frame->format = this->audio_codec_ctx->sample_fmt;
+ #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 28, 100) // FFmpeg 5.1+
+ av_channel_layout_copy(&this->audio_frame->ch_layout, &this->audio_codec_ctx->ch_layout);
+ #else
+ this->audio_frame->channel_layout = this->audio_codec_ctx->channel_layout;
+ #endif
+ this->audio_frame->sample_rate = this->audio_codec_ctx->sample_rate;
+ this->audio_frame->nb_samples = this->audio_codec_ctx->frame_size;
+ err = av_frame_get_buffer(this->audio_frame, 0);
+ assert(err >= 0);
+}
+
void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecconfig, bool keyframe) {
if (of && data) {
size_t written = util::safe_fwrite(data, 1, len, of);
@@ -67,8 +106,10 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc
}
int err = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
assert(err >= 0);
+ // if there is an audio stream, it must be initialized before this point
err = avformat_write_header(ofmt_ctx, NULL);
assert(err >= 0);
+ header_written = true;
} else {
// input timestamps are in microseconds
AVRational in_timebase = {1, 1000000};
@@ -77,6 +118,7 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc
av_init_packet(&pkt);
pkt.data = data;
pkt.size = len;
+ pkt.stream_index = this->out_stream->index;
enum AVRounding rnd = static_cast(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.pts = pkt.dts = av_rescale_q_rnd(timestamp, in_timebase, ofmt_ctx->streams[0]->time_base, rnd);
@@ -95,11 +137,80 @@ void VideoWriter::write(uint8_t *data, int len, long long timestamp, bool codecc
}
}
+void VideoWriter::write_audio(uint8_t *data, int len, long long timestamp, int sample_rate) {
+ if (!remuxing) return;
+ if (!audio_initialized) {
+ initialize_audio(sample_rate);
+ audio_initialized = true;
+ }
+ if (!audio_codec_ctx) return;
+ // sync logMonoTime of first audio packet with the timestampEof of first video packet
+ if (audio_pts == 0) {
+ audio_pts = (timestamp * audio_codec_ctx->sample_rate) / 1000000ULL;
+ }
+
+ // convert s16le samples to fltp and add to buffer
+ const int16_t *raw_samples = reinterpret_cast(data);
+ int sample_count = len / sizeof(int16_t);
+ constexpr float normalizer = 1.0f / 32768.0f;
+
+ const size_t max_buffer_size = sample_rate * 10; // 10 seconds
+ if (audio_buffer.size() + sample_count > max_buffer_size) {
+ size_t samples_to_drop = (audio_buffer.size() + sample_count) - max_buffer_size;
+ LOGE("Audio buffer overflow, dropping %zu oldest samples", samples_to_drop);
+ audio_buffer.erase(audio_buffer.begin(), audio_buffer.begin() + samples_to_drop);
+ audio_pts += samples_to_drop;
+ }
+
+ // Add new samples to the buffer
+ const size_t original_size = audio_buffer.size();
+ audio_buffer.resize(original_size + sample_count);
+ std::transform(raw_samples, raw_samples + sample_count, audio_buffer.begin() + original_size,
+ [](int16_t sample) { return sample * normalizer; });
+
+ if (!header_written) return; // header not written yet, process audio frame after header is written
+ while (audio_buffer.size() >= audio_codec_ctx->frame_size) {
+ audio_frame->pts = audio_pts;
+ float *f_samples = reinterpret_cast(audio_frame->data[0]);
+ std::copy(audio_buffer.begin(), audio_buffer.begin() + audio_codec_ctx->frame_size, f_samples);
+ audio_buffer.erase(audio_buffer.begin(), audio_buffer.begin() + audio_codec_ctx->frame_size);
+ encode_and_write_audio_frame(audio_frame);
+ }
+}
+
+void VideoWriter::encode_and_write_audio_frame(AVFrame* frame) {
+ if (!remuxing || !audio_codec_ctx) return;
+ int send_result = avcodec_send_frame(audio_codec_ctx, frame); // encode frame
+ if (send_result >= 0) {
+ AVPacket *pkt = av_packet_alloc();
+ while (avcodec_receive_packet(audio_codec_ctx, pkt) == 0) {
+ av_packet_rescale_ts(pkt, audio_codec_ctx->time_base, audio_stream->time_base);
+ pkt->stream_index = audio_stream->index;
+
+ int err = av_interleaved_write_frame(ofmt_ctx, pkt); // write encoded frame
+ if (err < 0) {
+ LOGW("AUDIO: Write frame failed - error: %d", err);
+ }
+ av_packet_unref(pkt);
+ }
+ av_packet_free(&pkt);
+ } else {
+ LOGW("AUDIO: Failed to send audio frame to encoder: %d", send_result);
+ }
+ audio_pts += audio_codec_ctx->frame_size;
+}
+
+
VideoWriter::~VideoWriter() {
if (this->remuxing) {
+ if (this->audio_codec_ctx) {
+ encode_and_write_audio_frame(NULL); // flush encoder
+ avcodec_free_context(&this->audio_codec_ctx);
+ }
int err = av_write_trailer(this->ofmt_ctx);
if (err != 0) LOGE("av_write_trailer failed %d", err);
avcodec_free_context(&this->codec_ctx);
+ if (this->audio_frame) av_frame_free(&this->audio_frame);
err = avio_closep(&this->ofmt_ctx->pb);
if (err != 0) LOGE("avio_closep failed %d", err);
avformat_free_context(this->ofmt_ctx);
diff --git a/system/loggerd/video_writer.h b/system/loggerd/video_writer.h
index 1aa758b42b..25e6484d58 100644
--- a/system/loggerd/video_writer.h
+++ b/system/loggerd/video_writer.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
extern "C" {
#include
@@ -13,13 +14,28 @@ class VideoWriter {
public:
VideoWriter(const char *path, const char *filename, bool remuxing, int width, int height, int fps, cereal::EncodeIndex::Type codec);
void write(uint8_t *data, int len, long long timestamp, bool codecconfig, bool keyframe);
+ void write_audio(uint8_t *data, int len, long long timestamp, int sample_rate);
+
~VideoWriter();
+
private:
+ void initialize_audio(int sample_rate);
+ void encode_and_write_audio_frame(AVFrame* frame);
+
std::string vid_path, lock_path;
FILE *of = nullptr;
AVCodecContext *codec_ctx;
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
+
+ bool audio_initialized = false;
+ bool header_written = false;
+ AVStream *audio_stream = nullptr;
+ AVCodecContext *audio_codec_ctx = nullptr;
+ AVFrame *audio_frame = nullptr;
+ uint64_t audio_pts = 0;
+ std::deque audio_buffer;
+
bool remuxing;
};
diff --git a/system/manager/build.py b/system/manager/build.py
index 49c8128660..ae130f4b04 100755
--- a/system/manager/build.py
+++ b/system/manager/build.py
@@ -5,16 +5,16 @@ from pathlib import Path
# NOTE: Do NOT import anything here that needs be built (e.g. params)
from openpilot.common.basedir import BASEDIR
+from openpilot.common.spinner import Spinner
+from openpilot.common.text_window import TextWindow
from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.hardware import HARDWARE, AGNOS
-from openpilot.system.ui.spinner import Spinner
-from openpilot.system.ui.text import TextWindow
from openpilot.system.version import get_build_metadata
MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9
CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache")
-TOTAL_SCONS_NODES = 3765
+TOTAL_SCONS_NODES = 3800
MAX_BUILD_PROGRESS = 100
def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
@@ -88,7 +88,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None:
if __name__ == "__main__":
- with Spinner() as spinner:
- spinner.update_progress(0, 100)
- build_metadata = get_build_metadata()
- build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
+ spinner = Spinner()
+ spinner.update_progress(0, 100)
+ build_metadata = get_build_metadata()
+ build(spinner, build_metadata.openpilot.is_dirty, minimal = AGNOS)
diff --git a/system/manager/manager.py b/system/manager/manager.py
index 31bb43e3cf..c59f0118aa 100755
--- a/system/manager/manager.py
+++ b/system/manager/manager.py
@@ -9,6 +9,7 @@ from cereal import log
import cereal.messaging as messaging
import openpilot.system.sentry as sentry
from openpilot.common.params import Params, ParamKeyType
+from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import HARDWARE
from openpilot.system.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog
from openpilot.system.manager.process import ensure_running
@@ -49,21 +50,30 @@ def manager_init() -> None:
("BlindSpot", "0"),
("BlinkerMinLateralControlSpeed", "20"), # MPH or km/h
("BlinkerPauseLateralControl", "0"),
+ ("Brightness", "0"),
+ ("ChevronInfo", "4"),
+ ("CustomAccIncrementsEnabled", "0"),
+ ("CustomAccLongPressIncrement", "5"),
+ ("CustomAccShortPressIncrement", "1"),
("DeviceBootMode", "0"),
+ ("DisableUpdates", "0"),
("DynamicExperimentalControl", "0"),
("HyundaiLongitudinalTuning", "0"),
+ ("InteractivityTimeout", "0"),
("LagdToggle", "1"),
+ ("LagdToggledelay", "0.2"),
("Mads", "1"),
("MadsMainCruiseAllowed", "1"),
("MadsSteeringMode", "0"),
("MadsUnifiedEngagementMode", "1"),
("MapdVersion", f"{VERSION}"),
("MaxTimeOffroad", "1800"),
- ("Brightness", "0"),
("ModelManager_LastSyncTime", "0"),
("ModelManager_ModelsCache", ""),
("NeuralNetworkLateralControl", "0"),
+ ("QuickBootToggle", "0"),
("QuietMode", "0"),
+ ("ShowAdvancedControls", "0" if build_metadata.tested_channel else "1"),
]
# device boot mode
@@ -95,6 +105,7 @@ def manager_init() -> None:
params.put("GitCommitDate", build_metadata.openpilot.git_commit_date)
params.put("GitBranch", build_metadata.channel)
params.put("GitRemote", build_metadata.openpilot.git_origin)
+ params.put_bool("IsDevelopmentBranch", build_metadata.development_channel)
params.put_bool("IsTestedBranch", build_metadata.tested_channel)
params.put_bool("IsReleaseBranch", build_metadata.release_channel)
params.put("HardwareSerial", serial)
@@ -231,8 +242,6 @@ def main() -> None:
if __name__ == "__main__":
- from openpilot.system.ui.text import TextWindow
-
unblock_stdout()
try:
diff --git a/system/micd.py b/system/micd.py
index 38f3225f55..02ef82390b 100755
--- a/system/micd.py
+++ b/system/micd.py
@@ -9,10 +9,10 @@ from openpilot.common.retry import retry
from openpilot.common.swaglog import cloudlog
RATE = 10
-FFT_SAMPLES = 4096
+FFT_SAMPLES = 1600 # 100ms
REFERENCE_SPL = 2e-5 # newtons/m^2
-SAMPLE_RATE = 44100
-SAMPLE_BUFFER = 4096 # approx 100ms
+SAMPLE_RATE = 16000
+SAMPLE_BUFFER = 800 # 50ms
@cache
@@ -45,7 +45,7 @@ def apply_a_weighting(measurements: np.ndarray) -> np.ndarray:
class Mic:
def __init__(self):
self.rk = Ratekeeper(RATE)
- self.pm = messaging.PubMaster(['microphone'])
+ self.pm = messaging.PubMaster(['soundPressure', 'rawAudioData'])
self.measurements = np.empty(0)
@@ -61,12 +61,12 @@ class Mic:
sound_pressure_weighted = self.sound_pressure_weighted
sound_pressure_level_weighted = self.sound_pressure_level_weighted
- msg = messaging.new_message('microphone', valid=True)
- msg.microphone.soundPressure = float(sound_pressure)
- msg.microphone.soundPressureWeighted = float(sound_pressure_weighted)
- msg.microphone.soundPressureWeightedDb = float(sound_pressure_level_weighted)
+ msg = messaging.new_message('soundPressure', valid=True)
+ msg.soundPressure.soundPressure = float(sound_pressure)
+ msg.soundPressure.soundPressureWeighted = float(sound_pressure_weighted)
+ msg.soundPressure.soundPressureWeightedDb = float(sound_pressure_level_weighted)
- self.pm.send('microphone', msg)
+ self.pm.send('soundPressure', msg)
self.rk.keep_time()
def callback(self, indata, frames, time, status):
@@ -76,6 +76,12 @@ class Mic:
Logged A-weighted equivalents are rough approximations of the human-perceived loudness.
"""
+ msg = messaging.new_message('rawAudioData', valid=True)
+ audio_data_int_16 = (indata[:, 0] * 32767).astype(np.int16)
+ msg.rawAudioData.data = audio_data_int_16.tobytes()
+ msg.rawAudioData.sampleRate = SAMPLE_RATE
+ self.pm.send('rawAudioData', msg)
+
with self.lock:
self.measurements = np.concatenate((self.measurements, indata[:, 0]))
diff --git a/system/sensord/sensors/lsm6ds3_temp.py b/system/sensord/sensors/lsm6ds3_temp.py
index 7d7b1c2ce1..b9bb9fe3da 100644
--- a/system/sensord/sensors/lsm6ds3_temp.py
+++ b/system/sensord/sensors/lsm6ds3_temp.py
@@ -7,10 +7,10 @@ from openpilot.system.sensord.sensors.i2c_sensor import Sensor
class LSM6DS3_Temp(Sensor):
@property
def device_address(self) -> int:
- return 0x6A # Default I2C address for LSM6DS3
+ return 0x6A
def _read_temperature(self) -> float:
- scale = 16.0 if log.SensorEventData.SensorSource.lsm6ds3 else 256.0
+ scale = 16.0 if self.source == log.SensorEventData.SensorSource.lsm6ds3 else 256.0
data = self.read(0x20, 2)
return 25 + (self.parse_16bit(data[0], data[1]) / scale)
diff --git a/system/ubloxd/pigeond.py b/system/ubloxd/pigeond.py
index 2194a4b9d2..e6a03ffbdf 100755
--- a/system/ubloxd/pigeond.py
+++ b/system/ubloxd/pigeond.py
@@ -136,6 +136,17 @@ class TTYPigeon:
return True
return False
+def save_almanac(pigeon: TTYPigeon) -> None:
+ # store almanac in flash
+ pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC")
+ try:
+ if pigeon.wait_for_ack(ack=UBLOX_SOS_ACK, nack=UBLOX_SOS_NACK):
+ cloudlog.info("Done storing almanac")
+ else:
+ cloudlog.error("Error storing almanac")
+ except TimeoutError:
+ pass
+
def init_baudrate(pigeon: TTYPigeon):
# ublox default setting on startup is 9600 baudrate
pigeon.set_baud(9600)
@@ -245,16 +256,6 @@ def deinitialize_and_exit(pigeon: TTYPigeon | None):
# controlled GNSS stop
pigeon.send(b"\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74")
- # store almanac in flash
- pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC")
- try:
- if pigeon.wait_for_ack(ack=UBLOX_SOS_ACK, nack=UBLOX_SOS_NACK):
- cloudlog.warning("Done storing almanac")
- else:
- cloudlog.error("Error storing almanac")
- except TimeoutError:
- pass
-
# turn off power and exit cleanly
set_power(False)
sys.exit(0)
@@ -281,6 +282,7 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0)
def end_condition():
return True if duration == 0 else time.monotonic() - start_time < duration
+ last_almanac_save = time.monotonic()
while end_condition():
dat = pigeon.receive()
if len(dat) > 0:
@@ -294,6 +296,11 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0)
msg = messaging.new_message('ubloxRaw', len(dat), valid=True)
msg.ubloxRaw = dat[:]
pm.send('ubloxRaw', msg)
+
+ # save almanac every 5 minutes
+ if (time.monotonic() - last_almanac_save) > 60*5:
+ save_almanac(pigeon)
+ last_almanac_save = time.monotonic()
else:
# prevent locking up a CPU core if ublox disconnects
time.sleep(0.001)
diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py
index c2d6ac8e2c..12d825b99f 100644
--- a/system/ui/lib/application.py
+++ b/system/ui/lib/application.py
@@ -1,20 +1,29 @@
import atexit
+import cffi
import os
import time
import pyray as rl
+import threading
+from collections.abc import Callable
+from collections import deque
+from dataclasses import dataclass
from enum import IntEnum
+from typing import NamedTuple
from importlib.resources import as_file, files
from openpilot.common.swaglog import cloudlog
-from openpilot.system.hardware import HARDWARE
+from openpilot.system.hardware import HARDWARE, PC
+from openpilot.common.realtime import Ratekeeper
-DEFAULT_FPS = 60
+DEFAULT_FPS = int(os.getenv("FPS", "60"))
FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops
FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning
FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions
+MOUSE_THREAD_RATE = 140 # touch controller runs at 140Hz
-ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1"
-SHOW_FPS = os.getenv("SHOW_FPS") == '1'
-STRICT_MODE = os.getenv("STRICT_MODE") == '1'
+ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1"
+SHOW_FPS = os.getenv("SHOW_FPS") == "1"
+SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1"
+STRICT_MODE = os.getenv("STRICT_MODE") == "1"
SCALE = float(os.getenv("SCALE", "1.0"))
DEFAULT_TEXT_SIZE = 60
@@ -36,6 +45,74 @@ class FontWeight(IntEnum):
BLACK = 8
+@dataclass
+class ModalOverlay:
+ overlay: object = None
+ callback: Callable | None = None
+
+
+class MousePos(NamedTuple):
+ x: float
+ y: float
+
+
+class MouseEvent(NamedTuple):
+ pos: MousePos
+ left_pressed: bool
+ left_released: bool
+ left_down: bool
+ t: float
+
+
+class MouseState:
+ def __init__(self):
+ self._events: deque[MouseEvent] = deque(maxlen=MOUSE_THREAD_RATE) # bound event list
+ self._prev_mouse_event: MouseEvent | None = None
+
+ self._rk = Ratekeeper(MOUSE_THREAD_RATE)
+ self._lock = threading.Lock()
+ self._exit_event = threading.Event()
+ self._thread = None
+
+ def get_events(self) -> list[MouseEvent]:
+ with self._lock:
+ events = list(self._events)
+ self._events.clear()
+ return events
+
+ def start(self):
+ self._exit_event.clear()
+ if self._thread is None or not self._thread.is_alive():
+ self._thread = threading.Thread(target=self._run_thread, daemon=True)
+ self._thread.start()
+
+ def stop(self):
+ self._exit_event.set()
+ if self._thread is not None and self._thread.is_alive():
+ self._thread.join()
+
+ def _run_thread(self):
+ while not self._exit_event.is_set():
+ rl.poll_input_events()
+ self._handle_mouse_event()
+ self._rk.keep_time()
+
+ def _handle_mouse_event(self):
+ mouse_pos = rl.get_mouse_position()
+ ev = MouseEvent(
+ MousePos(mouse_pos.x, mouse_pos.y),
+ rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT),
+ rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT),
+ rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT),
+ time.monotonic(),
+ )
+ # Only add changes
+ if self._prev_mouse_event is None or ev[:-1] != self._prev_mouse_event[:-1]:
+ with self._lock:
+ self._events.append(ev)
+ self._prev_mouse_event = ev
+
+
class GuiApplication:
def __init__(self, width: int, height: int):
self._fonts: dict[FontWeight, rl.Font] = {}
@@ -50,6 +127,13 @@ class GuiApplication:
self._last_fps_log_time: float = time.monotonic()
self._window_close_requested = False
self._trace_log_callback = None
+ self._modal_overlay = ModalOverlay()
+
+ self._mouse = MouseState()
+ self._mouse_events: list[MouseEvent] = []
+
+ # Debug variables
+ self._mouse_history: deque[MousePos] = deque(maxlen=MOUSE_THREAD_RATE)
def request_close(self):
self._window_close_requested = True
@@ -79,6 +163,12 @@ class GuiApplication:
self._set_styles()
self._load_fonts()
+ if not PC:
+ self._mouse.start()
+
+ def set_modal_overlay(self, overlay, callback: Callable | None = None):
+ self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback)
+
def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True):
cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}"
if cache_key in self._textures:
@@ -136,11 +226,25 @@ class GuiApplication:
rl.unload_render_texture(self._render_texture)
self._render_texture = None
+ if not PC:
+ self._mouse.stop()
+
rl.close_window()
+ @property
+ def mouse_events(self) -> list[MouseEvent]:
+ return self._mouse_events
+
def render(self):
try:
while not (self._window_close_requested or rl.window_should_close()):
+ if PC:
+ # Thread is not used on PC, need to manually add mouse events
+ self._mouse._handle_mouse_event()
+
+ # Store all mouse events for the current frame
+ self._mouse_events = self._mouse.get_events()
+
if self._render_texture:
rl.begin_texture_mode(self._render_texture)
rl.clear_background(rl.BLACK)
@@ -148,7 +252,23 @@ class GuiApplication:
rl.begin_drawing()
rl.clear_background(rl.BLACK)
- yield
+ # Handle modal overlay rendering and input processing
+ 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
+
+ if result >= 0:
+ # Execute callback with the result and clear the overlay
+ if self._modal_overlay.callback is not None:
+ self._modal_overlay.callback(result)
+
+ self._modal_overlay = ModalOverlay()
+ else:
+ yield
if self._render_texture:
rl.end_texture_mode()
@@ -161,6 +281,20 @@ class GuiApplication:
if SHOW_FPS:
rl.draw_fps(10, 10)
+ if SHOW_TOUCHES:
+ for mouse_event in self._mouse_events:
+ if mouse_event.left_pressed:
+ self._mouse_history.clear()
+ self._mouse_history.append(mouse_event.pos)
+
+ if self._mouse_history:
+ mouse_pos = self._mouse_history[-1]
+ rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 15, rl.RED)
+ for idx, mouse_pos in enumerate(self._mouse_history):
+ perc = idx / len(self._mouse_history)
+ color = rl.Color(min(int(255 * (1.5 - perc)), 255), int(min(255 * (perc + 0.5), 255)), 50, 255)
+ rl.draw_circle(int(mouse_pos.x), int(mouse_pos.y), 5, color)
+
rl.end_drawing()
self._monitor_fps()
except KeyboardInterrupt:
@@ -192,12 +326,11 @@ class GuiApplication:
# Create a character set from our keyboard layouts
from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS
- from openpilot.selfdrive.ui.onroad.hud_renderer import CRUISE_DISABLED_CHAR
all_chars = set()
for layout in KEYBOARD_LAYOUTS.values():
all_chars.update(key for row in layout for key in row)
all_chars = "".join(all_chars)
- all_chars += CRUISE_DISABLED_CHAR
+ all_chars += "–✓°"
codepoint_count = rl.ffi.new("int *", 1)
codepoints = rl.load_codepoints(all_chars, codepoint_count)
@@ -219,12 +352,29 @@ class GuiApplication:
rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255)))
def _set_log_callback(self):
+ ffi_libc = cffi.FFI()
+ ffi_libc.cdef("""
+ int vasprintf(char **strp, const char *fmt, void *ap);
+ void free(void *ptr);
+ """)
+ libc = ffi_libc.dlopen(None)
+
@rl.ffi.callback("void(int, char *, void *)")
def trace_log_callback(log_level, text, args):
try:
- text_str = rl.ffi.string(text).decode('utf-8')
- except (TypeError, UnicodeDecodeError):
- text_str = str(text)
+ text_addr = int(rl.ffi.cast("uintptr_t", text))
+ args_addr = int(rl.ffi.cast("uintptr_t", args))
+ text_libc = ffi_libc.cast("char *", text_addr)
+ args_libc = ffi_libc.cast("void *", args_addr)
+
+ out = ffi_libc.new("char **")
+ if libc.vasprintf(out, text_libc, args_libc) >= 0 and out[0] != ffi_libc.NULL:
+ text_str = ffi_libc.string(out[0]).decode("utf-8", "replace")
+ libc.free(out[0])
+ else:
+ text_str = rl.ffi.string(text).decode("utf-8", "replace")
+ except Exception as e:
+ text_str = f"[Log decode error: {e}]"
if log_level == rl.TraceLogLevel.LOG_ERROR:
cloudlog.error(f"raylib: {text_str}")
diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py
index 024e360448..121e7f86fa 100644
--- a/system/ui/lib/button.py
+++ b/system/ui/lib/button.py
@@ -10,6 +10,7 @@ class ButtonStyle(IntEnum):
DANGER = 2 # For critical actions, like reboot or delete
TRANSPARENT = 3 # For buttons with transparent background and border
ACTION = 4
+ LIST_ACTION = 5 # For list items with action buttons
class TextAlignment(IntEnum):
@@ -20,11 +21,17 @@ class TextAlignment(IntEnum):
ICON_PADDING = 15
DEFAULT_BUTTON_FONT_SIZE = 60
-BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255)
BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51)
ACTION_BUTTON_FONT_SIZE = 48
-ACTION_BUTTON_TEXT_COLOR = rl.Color(0, 0, 0, 255)
+BUTTON_TEXT_COLOR = {
+ ButtonStyle.NORMAL: rl.Color(228, 228, 228, 255),
+ ButtonStyle.PRIMARY: rl.Color(228, 228, 228, 255),
+ ButtonStyle.DANGER: rl.Color(228, 228, 228, 255),
+ ButtonStyle.TRANSPARENT: rl.BLACK,
+ ButtonStyle.ACTION: rl.Color(0, 0, 0, 255),
+ ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255),
+}
BUTTON_BACKGROUND_COLORS = {
ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255),
@@ -32,6 +39,7 @@ BUTTON_BACKGROUND_COLORS = {
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
ButtonStyle.ACTION: rl.Color(189, 189, 189, 255),
+ ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255),
}
BUTTON_PRESSED_BACKGROUND_COLORS = {
@@ -40,6 +48,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
ButtonStyle.ACTION: rl.Color(130, 130, 130, 255),
+ ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74),
}
_pressed_buttons: set[str] = set() # Track mouse press state globally
@@ -133,7 +142,7 @@ def gui_button(
# Draw the button text if any
if text:
- text_color = ACTION_BUTTON_TEXT_COLOR if button_style == ButtonStyle.ACTION else BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR
- rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color)
+ color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLOR
+ rl.draw_text_ex(font, text, text_pos, font_size, 0, color)
return result
diff --git a/system/ui/lib/egl.py b/system/ui/lib/egl.py
index d43be482b3..d119a8a832 100644
--- a/system/ui/lib/egl.py
+++ b/system/ui/lib/egl.py
@@ -4,7 +4,6 @@ from dataclasses import dataclass
from typing import Any
from openpilot.common.swaglog import cloudlog
-
# EGL constants
EGL_LINUX_DMA_BUF_EXT = 0x3270
EGL_WIDTH = 0x3057
@@ -23,6 +22,7 @@ GL_TEXTURE_EXTERNAL_OES = 0x8D65
# DRM Format for NV12
DRM_FORMAT_NV12 = 842094158
+
@dataclass
class EGLImage:
"""Container for EGL image and associated resources"""
diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py
index ae036c03c2..ccf718ba56 100644
--- a/system/ui/lib/inputbox.py
+++ b/system/ui/lib/inputbox.py
@@ -2,14 +2,15 @@ import pyray as rl
import time
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.text_measure import measure_text_cached
-
+from openpilot.system.ui.lib.widget import Widget
PASSWORD_MASK_CHAR = "•"
PASSWORD_MASK_DELAY = 1.5 # Seconds to show character before masking
-class InputBox:
+class InputBox(Widget):
def __init__(self, max_text_size=255, password_mode=False):
+ super().__init__()
self._max_text_size = max_text_size
self._input_text = ""
self._cursor_position = 0
@@ -23,7 +24,7 @@ class InputBox:
self._text_offset = 0
self._visible_width = 0
self._last_char_time = 0 # Track when last character was added
- self._masked_length = 0 # How many characters are currently masked
+ self._masked_length = 0 # How many characters are currently masked
@property
def text(self):
@@ -76,11 +77,11 @@ class InputBox:
def add_char_at_cursor(self, char):
"""Add a character at the current cursor position."""
if len(self._input_text) < self._max_text_size:
- self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position :]
+ self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position:]
self.set_cursor_position(self._cursor_position + 1)
if self._password_mode:
- self._last_char_time = time.time()
+ self._last_char_time = time.monotonic()
return True
return False
@@ -88,7 +89,7 @@ class InputBox:
def delete_char_before_cursor(self):
"""Delete the character before the cursor position (backspace)."""
if self._cursor_position > 0:
- self._input_text = self._input_text[: self._cursor_position - 1] + self._input_text[self._cursor_position :]
+ self._input_text = self._input_text[: self._cursor_position - 1] + self._input_text[self._cursor_position:]
self.set_cursor_position(self._cursor_position - 1)
return True
return False
@@ -96,12 +97,12 @@ class InputBox:
def delete_char_at_cursor(self):
"""Delete the character at the cursor position (delete)."""
if self._cursor_position < len(self._input_text):
- self._input_text = self._input_text[: self._cursor_position] + self._input_text[self._cursor_position + 1 :]
+ self._input_text = self._input_text[: self._cursor_position] + self._input_text[self._cursor_position + 1:]
self.set_cursor_position(self._cursor_position)
return True
return False
- def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
+ def _render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
# Store dimensions for text offset calculations
self._visible_width = rect.width
self._font_size = font_size
@@ -160,18 +161,18 @@ class InputBox:
# Show character at last edited position if within delay window
masked_text = PASSWORD_MASK_CHAR * len(self._input_text)
- recent_edit = time.time() - self._last_char_time < PASSWORD_MASK_DELAY
+ recent_edit = time.monotonic() - self._last_char_time < PASSWORD_MASK_DELAY
if recent_edit and self._input_text:
last_pos = max(0, self._cursor_position - 1)
if last_pos < len(self._input_text):
- return masked_text[:last_pos] + self._input_text[last_pos] + masked_text[last_pos + 1 :]
+ return masked_text[:last_pos] + self._input_text[last_pos] + masked_text[last_pos + 1:]
return masked_text
def _handle_mouse_input(self, rect, font_size):
"""Handle mouse clicks to position cursor."""
mouse_pos = rl.get_mouse_position()
- if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect):
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(mouse_pos, rect):
# Calculate cursor position from click
if len(self._input_text) > 0:
font = gui_app.font()
diff --git a/system/ui/lib/label.py b/system/ui/lib/label.py
index 82533660de..c3d0e0303a 100644
--- a/system/ui/lib/label.py
+++ b/system/ui/lib/label.py
@@ -76,4 +76,3 @@ def gui_text_box(
if font_weight != FontWeight.NORMAL:
rl.gui_set_font(gui_app.font(FontWeight.NORMAL))
-
diff --git a/system/ui/lib/list_view.py b/system/ui/lib/list_view.py
index 9ca2363bc7..4b6f434de3 100644
--- a/system/ui/lib/list_view.py
+++ b/system/ui/lib/list_view.py
@@ -1,27 +1,22 @@
import os
import pyray as rl
-from dataclasses import dataclass
from collections.abc import Callable
-from abc import ABC, abstractmethod
-from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
-from openpilot.system.ui.lib.application import gui_app, FontWeight
+from abc import ABC
+from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
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.button import gui_button
-from openpilot.system.ui.lib.toggle import Toggle
-from openpilot.system.ui.lib.toggle import WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+from openpilot.system.ui.lib.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT
+from openpilot.system.ui.lib.widget import Widget
-
-LINE_PADDING = 40
-LINE_COLOR = rl.GRAY
-ITEM_PADDING = 20
-ITEM_SPACING = 80
+ITEM_BASE_WIDTH = 600
ITEM_BASE_HEIGHT = 170
+ITEM_PADDING = 20
ITEM_TEXT_FONT_SIZE = 50
ITEM_TEXT_COLOR = rl.WHITE
ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255)
ITEM_DESC_FONT_SIZE = 40
-ITEM_DESC_V_OFFSET = 130
+ITEM_DESC_V_OFFSET = 140
RIGHT_ITEM_PADDING = 20
ICON_SIZE = 80
BUTTON_WIDTH = 250
@@ -30,38 +25,42 @@ BUTTON_BORDER_RADIUS = 50
BUTTON_FONT_SIZE = 35
BUTTON_FONT_WEIGHT = FontWeight.MEDIUM
+TEXT_PADDING = 20
+
+
+def _resolve_value(value, default=""):
+ if callable(value):
+ return value()
+ return value if value is not None else default
+
# Abstract base class for right-side items
-class RightItem(ABC):
- def __init__(self, width: int = 100):
- self.width = width
- self.enabled = True
+class ItemAction(Widget, ABC):
+ def __init__(self, width: int = BUTTON_HEIGHT, enabled: bool | Callable[[], bool] = True):
+ super().__init__()
+ self.set_rect(rl.Rectangle(0, 0, width, 0))
+ self._enabled_source = enabled
- @abstractmethod
- def draw(self, rect: rl.Rectangle) -> bool:
- pass
-
- @abstractmethod
- def get_width(self) -> int:
- pass
+ @property
+ def enabled(self):
+ return _resolve_value(self._enabled_source, False)
-class ToggleRightItem(RightItem):
- def __init__(self, initial_state: bool = False, width: int = TOGGLE_WIDTH):
- super().__init__(width)
+class ToggleAction(ItemAction):
+ def __init__(self, initial_state: bool = False, width: int = TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True):
+ super().__init__(width, enabled)
self.toggle = Toggle(initial_state=initial_state)
self.state = initial_state
- self.enabled = True
- def draw(self, rect: rl.Rectangle) -> bool:
- if self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self.width, TOGGLE_HEIGHT)):
- self.state = not self.state
- return True
+ def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
+ super().set_touch_valid_callback(touch_callback)
+ self.toggle.set_touch_valid_callback(touch_callback)
+
+ def _render(self, rect: rl.Rectangle) -> bool:
+ self.toggle.set_enabled(self.enabled)
+ self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT))
return False
- def get_width(self) -> int:
- return self.width
-
def set_state(self, state: bool):
self.state = state
self.toggle.set_state(state)
@@ -69,293 +68,269 @@ class ToggleRightItem(RightItem):
def get_state(self) -> bool:
return self.state
- def set_enabled(self, enabled: bool):
- self.enabled = enabled
+
+class ButtonAction(ItemAction):
+ def __init__(self, text: str | Callable[[], str], width: int = BUTTON_WIDTH, enabled: bool | Callable[[], bool] = True):
+ super().__init__(width, enabled)
+ self._text_source = text
+
+ @property
+ def text(self):
+ return _resolve_value(self._text_source, "Error")
+
+ def _render(self, rect: rl.Rectangle) -> bool:
+ return gui_button(
+ rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT),
+ self.text,
+ border_radius=BUTTON_BORDER_RADIUS,
+ font_weight=BUTTON_FONT_WEIGHT,
+ font_size=BUTTON_FONT_SIZE,
+ button_style=ButtonStyle.LIST_ACTION,
+ is_enabled=self.enabled,
+ ) == 1
-class ButtonRightItem(RightItem):
- def __init__(self, text: str, width: int = BUTTON_WIDTH):
- super().__init__(width)
- self.text = text
- self.enabled = True
-
- def draw(self, rect: rl.Rectangle) -> bool:
- return (
- gui_button(
- rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT),
- self.text,
- border_radius=BUTTON_BORDER_RADIUS,
- font_weight=BUTTON_FONT_WEIGHT,
- font_size=BUTTON_FONT_SIZE,
- is_enabled=self.enabled,
- )
- == 1
- )
-
- def get_width(self) -> int:
- return self.width
-
- def set_enabled(self, enabled: bool):
- self.enabled = enabled
-
-
-class TextRightItem(RightItem):
- def __init__(self, text: str, color: rl.Color = ITEM_TEXT_COLOR, font_size: int = ITEM_TEXT_FONT_SIZE):
- self.text = text
+class TextAction(ItemAction):
+ def __init__(self, text: str | Callable[[], str], color: rl.Color = ITEM_TEXT_COLOR, enabled: bool | Callable[[], bool] = True):
+ self._text_source = text
self.color = color
- self.font_size = font_size
- font = gui_app.font(FontWeight.NORMAL)
- text_width = measure_text_cached(font, text, font_size).x
- super().__init__(int(text_width + 20))
+ self._font = gui_app.font(FontWeight.NORMAL)
+ initial_text = _resolve_value(text, "")
+ text_width = measure_text_cached(self._font, initial_text, ITEM_TEXT_FONT_SIZE).x
+ super().__init__(int(text_width + TEXT_PADDING), enabled)
- def draw(self, rect: rl.Rectangle) -> bool:
- font = gui_app.font(FontWeight.NORMAL)
- text_size = measure_text_cached(font, self.text, self.font_size)
+ @property
+ def text(self):
+ return _resolve_value(self._text_source, "Error")
+
+ def _render(self, rect: rl.Rectangle) -> bool:
+ current_text = self.text
+ text_size = measure_text_cached(self._font, current_text, ITEM_TEXT_FONT_SIZE)
- # Center the text in the allocated rectangle
text_x = rect.x + (rect.width - text_size.x) / 2
text_y = rect.y + (rect.height - text_size.y) / 2
-
- rl.draw_text_ex(font, self.text, rl.Vector2(text_x, text_y), self.font_size, 0, self.color)
+ rl.draw_text_ex(self._font, current_text, rl.Vector2(text_x, text_y), ITEM_TEXT_FONT_SIZE, 0, self.color)
return False
def get_width(self) -> int:
- return self.width
-
- def set_text(self, text: str):
- self.text = text
- font = gui_app.font(FontWeight.NORMAL)
- text_width = measure_text_cached(font, text, self.font_size).x
- self.width = int(text_width + 20)
+ text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x
+ return int(text_width + TEXT_PADDING)
-@dataclass
-class ListItem:
- title: str
- icon: str | None = None
- description: str | None = None
- description_visible: bool = False
- rect: "rl.Rectangle | None" = None
- callback: Callable | None = None
- right_item: RightItem | None = None
+class DualButtonAction(ItemAction):
+ def __init__(self, left_text: str, right_text: str, left_callback: Callable = None,
+ right_callback: Callable = None, enabled: bool | Callable[[], bool] = True):
+ super().__init__(width=0, enabled=enabled) # Width 0 means use full width
+ self.left_text, self.right_text = left_text, right_text
+ self.left_callback, self.right_callback = left_callback, right_callback
- # Cached properties for performance
- _wrapped_description: str | None = None
- _description_height: float = 0
+ def _render(self, rect: rl.Rectangle) -> bool:
+ button_spacing = 30
+ button_height = 120
+ button_width = (rect.width - button_spacing) / 2
+ button_y = rect.y + (rect.height - button_height) / 2
- def get_right_item(self) -> RightItem | None:
- return self.right_item
+ left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height)
+ right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height)
- def get_item_height(self, font: rl.Font, max_width: int) -> float:
- if self.description_visible and self.description:
- if not self._wrapped_description:
- wrapped_lines = wrap_text(font, self.description, ITEM_DESC_FONT_SIZE, max_width)
- self._wrapped_description = "\n".join(wrapped_lines)
- self._description_height = len(wrapped_lines) * 20 + 10 # Line height + padding
- return ITEM_BASE_HEIGHT + self._description_height - (ITEM_BASE_HEIGHT - ITEM_DESC_V_OFFSET) + ITEM_SPACING
- return ITEM_BASE_HEIGHT
+ left_clicked = gui_button(left_rect, self.left_text, button_style=ButtonStyle.LIST_ACTION) == 1
+ right_clicked = gui_button(right_rect, self.right_text, button_style=ButtonStyle.DANGER) == 1
- def get_content_width(self, total_width: int) -> int:
- if self.right_item:
- return total_width - self.right_item.get_width() - RIGHT_ITEM_PADDING
- return total_width
-
- def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle:
- if not self.right_item:
- return rl.Rectangle(0, 0, 0, 0)
-
- right_width = self.right_item.get_width()
- right_x = item_rect.x + item_rect.width - right_width
- right_y = item_rect.y
- return rl.Rectangle(right_x, right_y, right_width, ITEM_BASE_HEIGHT)
+ if left_clicked and self.left_callback:
+ self.left_callback()
+ return True
+ if right_clicked and self.right_callback:
+ self.right_callback()
+ return True
+ return False
-class ListView:
- def __init__(self, items: list[ListItem]):
- self._items: list[ListItem] = items
- self._last_dim: tuple[float, float] = (0, 0)
- self.scroll_panel = GuiScrollPanel()
+class MultipleButtonAction(ItemAction):
+ def __init__(self, buttons: list[str], button_width: int, selected_index: int = 0, callback: Callable = None):
+ super().__init__(width=len(buttons) * (button_width + 20), enabled=True)
+ self.buttons = buttons
+ self.button_width = button_width
+ self.selected_button = selected_index
+ self.callback = callback
+ self._font = gui_app.font(FontWeight.MEDIUM)
- self._font_normal = gui_app.font(FontWeight.NORMAL)
+ def _render(self, rect: rl.Rectangle) -> bool:
+ spacing = 20
+ button_y = rect.y + (rect.height - BUTTON_HEIGHT) / 2
+ clicked = -1
- # Interaction state
- self._hovered_item: int = -1
- self._last_mouse_pos = rl.Vector2(0, 0)
+ for i, text in enumerate(self.buttons):
+ button_x = rect.x + i * (self.button_width + spacing)
+ button_rect = rl.Rectangle(button_x, button_y, self.button_width, BUTTON_HEIGHT)
- self._total_height: float = 0
- self._visible_range = (0, 0)
+ # Check button state
+ mouse_pos = rl.get_mouse_position()
+ is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect)
+ is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed
+ is_selected = i == self.selected_button
- def invalid_height_cache(self):
- self._last_dim = (0, 0)
+ # Button colors
+ if is_selected:
+ bg_color = rl.Color(51, 171, 76, 255) # Green
+ elif is_pressed:
+ bg_color = rl.Color(74, 74, 74, 255) # Dark gray
+ else:
+ bg_color = rl.Color(57, 57, 57, 255) # Gray
- def render(self, rect: rl.Rectangle):
- if self._last_dim != (rect.width, rect.height):
- self._update_item_rects(rect)
- self._last_dim = (rect.width, rect.height)
+ # Draw button
+ rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color)
- # Update layout and handle scrolling
- content_rect = rl.Rectangle(rect.x, rect.y, rect.width, self._total_height)
- scroll_offset = self.scroll_panel.handle_scroll(rect, content_rect)
+ # Draw text
+ text_size = measure_text_cached(self._font, text, 40)
+ text_x = button_x + (self.button_width - text_size.x) / 2
+ text_y = button_y + (BUTTON_HEIGHT - text_size.y) / 2
+ rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, rl.Color(228, 228, 228, 255))
- # Handle mouse interaction
- if self.scroll_panel.is_click_valid():
- self._handle_mouse_interaction(rect, scroll_offset)
+ # Handle click
+ if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed:
+ clicked = i
- # Set scissor mode for clipping
- rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
+ if clicked >= 0:
+ self.selected_button = clicked
+ if self.callback:
+ self.callback(clicked)
+ return True
+ return False
- # Calculate visible range for performance
- self._calculate_visible_range(rect, -scroll_offset.y)
- # Render only visible items
- for i in range(self._visible_range[0], min(self._visible_range[1], len(self._items))):
- item = self._items[i]
- if item.rect:
- adjusted_rect = rl.Rectangle(item.rect.x, item.rect.y + scroll_offset.y, item.rect.width, item.rect.height)
- self._render_item(item, adjusted_rect, i)
+class ListItem(Widget):
+ def __init__(self, title: str = "", icon: str | None = None, description: str | Callable[[], str] | None = None,
+ description_visible: bool = False, callback: Callable | None = None,
+ action_item: ItemAction | None = None):
+ super().__init__()
+ self.title = title
+ self.icon = icon
+ self.description = description
+ self.description_visible = description_visible
+ self.callback = callback
+ self.action_item = action_item
- if i != len(self._items) - 1:
- rl.draw_line_ex(
- rl.Vector2(adjusted_rect.x + LINE_PADDING, adjusted_rect.y + adjusted_rect.height - 1),
- rl.Vector2(
- adjusted_rect.x + adjusted_rect.width - LINE_PADDING * 2, adjusted_rect.y + adjusted_rect.height - 1
- ),
- 1.0,
- LINE_COLOR,
- )
- rl.end_scissor_mode()
+ self.set_rect(rl.Rectangle(0, 0, ITEM_BASE_WIDTH, ITEM_BASE_HEIGHT))
+ self._font = gui_app.font(FontWeight.NORMAL)
- def _render_item(self, item: ListItem, rect: rl.Rectangle, index: int):
- content_x = rect.x + ITEM_PADDING
+ # Cached properties for performance
+ self._prev_max_width: int = 0
+ self._wrapped_description: str | None = None
+ self._prev_description: str | None = None
+ self._description_height: float = 0
+
+ def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
+ super().set_touch_valid_callback(touch_callback)
+ if self.action_item:
+ self.action_item.set_touch_valid_callback(touch_callback)
+
+ def set_parent_rect(self, parent_rect: rl.Rectangle):
+ super().set_parent_rect(parent_rect)
+ self._rect.width = parent_rect.width
+
+ def _handle_mouse_release(self, mouse_pos: MousePos):
+ if not self.is_visible:
+ return
+
+ # Check not in action rect
+ if self.action_item:
+ action_rect = self.get_right_item_rect(self._rect)
+ if rl.check_collision_point_rec(mouse_pos, action_rect):
+ # Click was on right item, don't toggle description
+ return
+
+ if self.description:
+ self.description_visible = not self.description_visible
+ content_width = self.get_content_width(int(self._rect.width - ITEM_PADDING * 2))
+ self._rect.height = self.get_item_height(self._font, content_width)
+
+ def _render(self, _):
+ if not self.is_visible:
+ return
+
+ # Don't draw items that are not in parent's viewport
+ if ((self._rect.y + self.rect.height) <= self._parent_rect.y or
+ self._rect.y >= (self._parent_rect.y + self._parent_rect.height)):
+ return
+
+ content_x = self._rect.x + ITEM_PADDING
text_x = content_x
- # Calculate available width for main content
- content_width = item.get_content_width(int(rect.width - ITEM_PADDING * 2))
+ # Only draw title and icon for items that have them
+ if self.title:
+ # Draw icon if present
+ if self.icon:
+ icon_texture = gui_app.texture(os.path.join("icons", self.icon), ICON_SIZE, ICON_SIZE)
+ rl.draw_texture(icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - icon_texture.width) // 2), rl.WHITE)
+ text_x += ICON_SIZE + ITEM_PADDING
- # Draw icon if present
- if item.icon:
- icon_texture = gui_app.texture(os.path.join("icons", item.icon), ICON_SIZE, ICON_SIZE)
- rl.draw_texture(
- icon_texture, int(content_x), int(rect.y + (ITEM_BASE_HEIGHT - icon_texture.width) // 2), rl.WHITE
- )
- text_x += ICON_SIZE + ITEM_PADDING
-
- # Draw main text
- text_size = measure_text_cached(self._font_normal, item.title, ITEM_TEXT_FONT_SIZE)
- item_y = rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2
- rl.draw_text_ex(self._font_normal, item.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR)
-
- # Draw description if visible (adjust width for right item)
- if item.description_visible and item._wrapped_description:
- desc_y = rect.y + ITEM_DESC_V_OFFSET
- desc_max_width = int(content_width - (text_x - content_x))
-
- # Re-wrap description if needed due to right item
- if (item.right_item and item.description) and not item._wrapped_description:
- wrapped_lines = wrap_text(self._font_normal, item.description, ITEM_DESC_FONT_SIZE, desc_max_width)
- item._wrapped_description = "\n".join(wrapped_lines)
+ # Draw main text
+ text_size = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE)
+ item_y = self._rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2
+ rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR)
+ # Draw description if visible
+ current_description = self.get_description()
+ if self.description_visible and current_description and self._wrapped_description:
rl.draw_text_ex(
- self._font_normal,
- item._wrapped_description,
- rl.Vector2(text_x, desc_y),
+ self._font,
+ self._wrapped_description,
+ rl.Vector2(text_x, self._rect.y + ITEM_DESC_V_OFFSET),
ITEM_DESC_FONT_SIZE,
0,
ITEM_DESC_TEXT_COLOR,
)
# Draw right item if present
- if item.right_item:
- right_rect = item.get_right_item_rect(rect)
- # Adjust for scroll offset
- right_rect.y = right_rect.y
- if item.right_item.draw(right_rect):
+ if self.action_item:
+ right_rect = self.get_right_item_rect(self._rect)
+ right_rect.y = self._rect.y
+ if self.action_item.render(right_rect) and self.action_item.enabled:
# Right item was clicked/activated
- if item.callback:
- item.callback()
+ if self.callback:
+ self.callback()
- def _update_item_rects(self, container_rect: rl.Rectangle) -> None:
- current_y: float = 0.0
- self._total_height = 0
+ def get_description(self):
+ return _resolve_value(self.description, None)
- for item in self._items:
- content_width = item.get_content_width(int(container_rect.width - ITEM_PADDING * 2))
- item_height = item.get_item_height(self._font_normal, content_width)
- item.rect = rl.Rectangle(container_rect.x, container_rect.y + current_y, container_rect.width, item_height)
- current_y += item_height
- self._total_height += item_height
+ def get_item_height(self, font: rl.Font, max_width: int) -> float:
+ if not self.is_visible:
+ return 0
- def _calculate_visible_range(self, rect: rl.Rectangle, scroll_offset: float):
- if not self._items:
- self._visible_range = (0, 0)
- return
+ current_description = self.get_description()
+ if self.description_visible and current_description:
+ if (
+ not self._wrapped_description
+ or current_description != self._prev_description
+ or max_width != self._prev_max_width
+ ):
+ self._prev_max_width = max_width
+ self._prev_description = current_description
- visible_top = scroll_offset
- visible_bottom = scroll_offset + rect.height
+ wrapped_lines = wrap_text(font, current_description, ITEM_DESC_FONT_SIZE, max_width)
+ self._wrapped_description = "\n".join(wrapped_lines)
+ self._description_height = len(wrapped_lines) * ITEM_DESC_FONT_SIZE + 10
+ return ITEM_BASE_HEIGHT + self._description_height - (ITEM_BASE_HEIGHT - ITEM_DESC_V_OFFSET) + ITEM_PADDING
+ return ITEM_BASE_HEIGHT
- start_idx = 0
- end_idx = len(self._items)
+ def get_content_width(self, total_width: int) -> int:
+ if self.action_item and self.action_item.rect.width > 0:
+ return total_width - int(self.action_item.rect.width) - RIGHT_ITEM_PADDING
+ return total_width
- # Find first visible item
- for i, item in enumerate(self._items):
- if item.rect and item.rect.y + item.rect.height >= visible_top:
- start_idx = max(0, i - 1)
- break
+ def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle:
+ if not self.action_item:
+ return rl.Rectangle(0, 0, 0, 0)
- # Find last visible item
- for i in range(start_idx, len(self._items)):
- item = self._items[i]
- if item.rect and item.rect.y > visible_bottom:
- end_idx = min(len(self._items), i + 2)
- break
+ right_width = self.action_item.rect.width
+ if right_width == 0: # Full width action (like DualButtonAction)
+ return rl.Rectangle(item_rect.x + ITEM_PADDING, item_rect.y,
+ item_rect.width - (ITEM_PADDING * 2), ITEM_BASE_HEIGHT)
- self._visible_range = (start_idx, end_idx)
-
- def _handle_mouse_interaction(self, rect: rl.Rectangle, scroll_offset: rl.Vector2):
- mouse_pos = rl.get_mouse_position()
-
- self._hovered_item = -1
- if not rl.check_collision_point_rec(mouse_pos, rect):
- return
-
- content_mouse_y = mouse_pos.y - rect.y - scroll_offset.y
-
- for i, item in enumerate(self._items):
- if item.rect:
- # Check if mouse is within this item's bounds in content space
- if (
- mouse_pos.x >= rect.x
- and mouse_pos.x <= rect.x + rect.width
- and content_mouse_y >= item.rect.y
- and content_mouse_y <= item.rect.y + item.rect.height
- ):
- item_screen_y = item.rect.y + scroll_offset.y
- if item_screen_y < rect.height and item_screen_y + item.rect.height > 0:
- self._hovered_item = i
- break
-
- # Handle click on main item (not right item)
- if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._hovered_item >= 0:
- item = self._items[self._hovered_item]
-
- # Check if click was on right item area
- if item.right_item and item.rect:
- adjusted_rect = rl.Rectangle(item.rect.x, item.rect.y + scroll_offset.y, item.rect.width, item.rect.height)
- right_rect = item.get_right_item_rect(adjusted_rect)
- if rl.check_collision_point_rec(mouse_pos, right_rect):
- # Click was handled by right item, don't process main item click
- return
-
- # Toggle description visibility if item has description
- if item.description:
- item.description_visible = not item.description_visible
- # Force layout update when description visibility changes
- self._last_dim = (0, 0)
-
- # Call item callback
- if item.callback:
- item.callback()
+ right_x = item_rect.x + item_rect.width - right_width
+ right_y = item_rect.y
+ return rl.Rectangle(right_x, right_y, right_width, ITEM_BASE_HEIGHT)
# Factory functions
@@ -363,18 +338,31 @@ def simple_item(title: str, callback: Callable | None = None) -> ListItem:
return ListItem(title=title, callback=callback)
-def toggle_item(
- title: str, description: str = None, initial_state: bool = False, callback: Callable | None = None, icon: str = ""
-) -> ListItem:
- toggle = ToggleRightItem(initial_state=initial_state)
- return ListItem(title=title, description=description, right_item=toggle, icon=icon, callback=callback)
+def toggle_item(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False,
+ callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True) -> ListItem:
+ action = ToggleAction(initial_state=initial_state, enabled=enabled)
+ return ListItem(title=title, description=description, action_item=action, icon=icon, callback=callback)
-def button_item(title: str, button_text: str, description: str = None, callback: Callable | None = None) -> ListItem:
- button = ButtonRightItem(text=button_text)
- return ListItem(title=title, description=description, right_item=button, callback=callback)
+def button_item(title: str, button_text: str | Callable[[], str], description: str | Callable[[], str] | None = None,
+ callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem:
+ action = ButtonAction(text=button_text, enabled=enabled)
+ return ListItem(title=title, description=description, action_item=action, callback=callback)
-def text_item(title: str, value: str, description: str = None, callback: Callable | None = None) -> ListItem:
- text_item = TextRightItem(text=value, color=rl.Color(170, 170, 170, 255))
- return ListItem(title=title, description=description, right_item=text_item, callback=callback)
+def text_item(title: str, value: str | Callable[[], str], description: str | Callable[[], str] | None = None,
+ callback: Callable | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem:
+ action = TextAction(text=value, color=rl.Color(170, 170, 170, 255), enabled=enabled)
+ return ListItem(title=title, description=description, action_item=action, callback=callback)
+
+
+def dual_button_item(left_text: str, right_text: str, left_callback: Callable = None, right_callback: Callable = 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, description: str, buttons: list[str], selected_index: int,
+ button_width: int = BUTTON_WIDTH, callback: Callable = 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/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py
index 43111504bb..21b6795a75 100644
--- a/system/ui/lib/scroll_panel.py
+++ b/system/ui/lib/scroll_panel.py
@@ -1,11 +1,14 @@
+import time
import pyray as rl
+from collections import deque
from enum import IntEnum
+from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos
# Scroll constants for smooth scrolling behavior
MOUSE_WHEEL_SCROLL_SPEED = 30
INERTIA_FRICTION = 0.92 # The rate at which the inertia slows down
MIN_VELOCITY = 0.5 # Minimum velocity before stopping the inertia
-DRAG_THRESHOLD = 5 # Pixels of movement to consider it a drag, not a click
+DRAG_THRESHOLD = 12 # Pixels of movement to consider it a drag, not a click
BOUNCE_FACTOR = 0.2 # Elastic bounce when scrolling past boundaries
BOUNCE_RETURN_SPEED = 0.15 # How quickly it returns from the bounce
MAX_BOUNCE_DISTANCE = 150 # Maximum distance for bounce effect
@@ -31,64 +34,60 @@ class GuiScrollPanel:
self._velocity_y = 0.0 # Velocity for inertia
self._is_dragging: bool = False
self._bounce_offset: float = 0.0
- self._last_frame_time = rl.get_time()
- self._velocity_history: list[float] = []
+ self._velocity_history: deque[float] = deque(maxlen=VELOCITY_HISTORY_SIZE)
self._last_drag_time: float = 0.0
self._content_rect: rl.Rectangle | None = None
self._bounds_rect: rl.Rectangle | None = None
def handle_scroll(self, bounds: rl.Rectangle, content: rl.Rectangle) -> rl.Vector2:
+ # TODO: HACK: this class is driven by mouse events, so we need to ensure we have at least one event to process
+ for mouse_event in gui_app.mouse_events or [MouseEvent(MousePos(0, 0), False, False, False, time.monotonic())]:
+ self._handle_mouse_event(mouse_event, bounds, content)
+ return self._offset
+
+ def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, content: rl.Rectangle):
# Store rectangles for reference
self._content_rect = content
self._bounds_rect = bounds
- # Calculate time delta
- current_time = rl.get_time()
- delta_time = current_time - self._last_frame_time
- self._last_frame_time = current_time
-
- # Prevent large jumps
- delta_time = min(delta_time, 0.05)
-
- mouse_pos = rl.get_mouse_position()
max_scroll_y = max(content.height - bounds.height, 0)
# Start dragging on mouse press
- if rl.check_collision_point_rec(mouse_pos, bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ if rl.check_collision_point_rec(mouse_event.pos, bounds) and mouse_event.left_pressed:
if self._scroll_state == ScrollState.IDLE or self._scroll_state == ScrollState.BOUNCING:
self._scroll_state = ScrollState.DRAGGING_CONTENT
if self._show_vertical_scroll_bar:
scrollbar_width = rl.gui_get_style(rl.GuiControl.LISTVIEW, rl.GuiListViewProperty.SCROLLBAR_WIDTH)
scrollbar_x = bounds.x + bounds.width - scrollbar_width
- if mouse_pos.x >= scrollbar_x:
+ if mouse_event.pos.x >= scrollbar_x:
self._scroll_state = ScrollState.DRAGGING_SCROLLBAR
- self._last_mouse_y = mouse_pos.y
- self._start_mouse_y = mouse_pos.y
- self._last_drag_time = current_time
- self._velocity_history = []
+ # TODO: hacky
+ # when clicking while moving, go straight into dragging
+ self._is_dragging = abs(self._velocity_y) > MIN_VELOCITY
+ self._last_mouse_y = mouse_event.pos.y
+ self._start_mouse_y = mouse_event.pos.y
+ self._last_drag_time = mouse_event.t
+ self._velocity_history.clear()
self._velocity_y = 0.0
self._bounce_offset = 0.0
- self._is_dragging = False
# Handle active dragging
if self._scroll_state == ScrollState.DRAGGING_CONTENT or self._scroll_state == ScrollState.DRAGGING_SCROLLBAR:
- if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
- delta_y = mouse_pos.y - self._last_mouse_y
+ if mouse_event.left_down:
+ delta_y = mouse_event.pos.y - self._last_mouse_y
# Track velocity for inertia
- time_since_last_drag = current_time - self._last_drag_time
+ time_since_last_drag = mouse_event.t - self._last_drag_time
if time_since_last_drag > 0:
- drag_velocity = delta_y / time_since_last_drag / 60.0
+ # TODO: HACK: /2 since we usually get two touch events per frame
+ drag_velocity = delta_y / time_since_last_drag / 60.0 / 2 # TODO: shouldn't be hardcoded
self._velocity_history.append(drag_velocity)
- if len(self._velocity_history) > VELOCITY_HISTORY_SIZE:
- self._velocity_history.pop(0)
-
- self._last_drag_time = current_time
+ self._last_drag_time = mouse_event.t
# Detect actual dragging
- total_drag = abs(mouse_pos.y - self._start_mouse_y)
+ total_drag = abs(mouse_event.pos.y - self._start_mouse_y)
if total_drag > DRAG_THRESHOLD:
self._is_dragging = True
@@ -102,9 +101,9 @@ class GuiScrollPanel:
scroll_ratio = content.height / bounds.height
self._offset.y -= delta_y * scroll_ratio
- self._last_mouse_y = mouse_pos.y
+ self._last_mouse_y = mouse_event.pos.y
- elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ elif mouse_event.left_released:
# Calculate flick velocity
if self._velocity_history:
total_weight = 0
@@ -173,15 +172,8 @@ class GuiScrollPanel:
elif self._offset.y < -(max_scroll_y + MAX_BOUNCE_DISTANCE):
self._offset.y = -(max_scroll_y + MAX_BOUNCE_DISTANCE)
- return self._offset
-
- def is_click_valid(self) -> bool:
- # Check if this is a click rather than a drag
- return (
- self._scroll_state == ScrollState.IDLE
- and not self._is_dragging
- and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
- )
+ def is_touch_valid(self):
+ return not self._is_dragging
def get_normalized_scroll_position(self) -> float:
"""Returns the current scroll position as a value from 0.0 to 1.0"""
diff --git a/system/ui/lib/scroller.py b/system/ui/lib/scroller.py
new file mode 100644
index 0000000000..8a10221772
--- /dev/null
+++ b/system/ui/lib/scroller.py
@@ -0,0 +1,74 @@
+import pyray as rl
+from openpilot.system.ui.lib.widget import Widget
+from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+
+ITEM_SPACING = 40
+LINE_COLOR = rl.GRAY
+LINE_PADDING = 40
+
+
+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 * 2, int(self._rect.y),
+ LINE_COLOR)
+
+
+class Scroller(Widget):
+ def __init__(self, items: list[Widget], spacing: int = ITEM_SPACING, line_separator: bool = False, pad_end: bool = True):
+ super().__init__()
+ self._items: list[Widget] = []
+ self._spacing = spacing
+ self._line_separator = line_separator
+ self._pad_end = pad_end
+
+ self.scroll_panel = GuiScrollPanel()
+
+ for item in items:
+ self.add_widget(item)
+
+ def add_widget(self, item: Widget) -> None:
+ if self._line_separator and len(self._items) > 0:
+ self._items.append(LineSeparator())
+ self._items.append(item)
+ item.set_touch_valid_callback(self.scroll_panel.is_touch_valid)
+
+ def _render(self, _):
+ # TODO: don't draw items that are not in the viewport
+ visible_items = [item for item in self._items if item.is_visible]
+ content_height = sum(item.rect.height for item in visible_items) + self._spacing * (len(visible_items))
+ if not self._pad_end:
+ content_height -= self._spacing
+ scroll = self.scroll_panel.handle_scroll(self._rect, rl.Rectangle(0, 0, self._rect.width, content_height))
+
+ rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y),
+ int(self._rect.width), int(self._rect.height))
+
+ cur_height = 0
+ for idx, item in enumerate(visible_items):
+ if not item.is_visible:
+ continue
+
+ # Nicely lay out items vertically
+ x = self._rect.x
+ y = self._rect.y + cur_height + self._spacing * (idx != 0)
+ cur_height += item.rect.height + self._spacing * (idx != 0)
+
+ # Consider scroll
+ x += scroll.x
+ y += scroll.y
+
+ # Update item state
+ item.set_position(x, y)
+ item.set_parent_rect(self._rect)
+ item.render()
+
+ rl.end_scissor_mode()
diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py
index 94190abc36..cfde81c554 100644
--- a/system/ui/lib/shader_polygon.py
+++ b/system/ui/lib/shader_polygon.py
@@ -1,13 +1,20 @@
+import platform
import pyray as rl
import numpy as np
from typing import Any
MAX_GRADIENT_COLORS = 15
-FRAGMENT_SHADER = """
+VERSION = """
#version 300 es
-precision mediump float;
+precision highp float;
+"""
+if platform.system() == "Darwin":
+ VERSION = """
+ #version 330 core
+ """
+FRAGMENT_SHADER = VERSION + """
in vec2 fragTexCoord;
out vec4 finalColor;
@@ -105,14 +112,13 @@ void main() {
vec4 color = useGradient == 1 ? getGradientColor(pixel) : fillColor;
finalColor = vec4(color.rgb, color.a * alpha);
} else {
- finalColor = vec4(0.0);
+ discard;
}
}
"""
# Default vertex shader
-VERTEX_SHADER = """
-#version 300 es
+VERTEX_SHADER = VERSION + """
in vec3 vertexPosition;
in vec2 vertexTexCoord;
out vec2 fragTexCoord;
@@ -124,7 +130,6 @@ void main() {
}
"""
-
UNIFORM_INT = rl.ShaderUniformDataType.SHADER_UNIFORM_INT
UNIFORM_FLOAT = rl.ShaderUniformDataType.SHADER_UNIFORM_FLOAT
UNIFORM_VEC2 = rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2
@@ -244,6 +249,7 @@ def _configure_shader_color(state, color, gradient, clipped_rect, original_rect)
state.fill_color_ptr[0:4] = [color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0]
rl.set_shader_value(state.shader, state.locations['fillColor'], state.fill_color_ptr, UNIFORM_VEC4)
+
def draw_polygon(origin_rect: rl.Rectangle, points: np.ndarray, color=None, gradient=None):
"""
Draw a complex polygon using shader-based even-odd fill rule
diff --git a/system/ui/lib/toggle.py b/system/ui/lib/toggle.py
index 5b3fbf277c..2edf2ff11c 100644
--- a/system/ui/lib/toggle.py
+++ b/system/ui/lib/toggle.py
@@ -1,4 +1,6 @@
import pyray as rl
+from openpilot.system.ui.lib.application import MousePos
+from openpilot.system.ui.lib.widget import Widget
ON_COLOR = rl.Color(51, 171, 76, 255)
OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)
@@ -11,24 +13,23 @@ BG_HEIGHT = 60
ANIMATION_SPEED = 8.0
-class Toggle:
+class Toggle(Widget):
def __init__(self, initial_state=False):
+ super().__init__()
self._state = initial_state
self._enabled = True
- self._rect = rl.Rectangle(0, 0, WIDTH, HEIGHT)
self._progress = 1.0 if initial_state else 0.0
self._target = self._progress
- def handle_input(self):
- if not self._enabled:
- return 0
+ def set_rect(self, rect: rl.Rectangle):
+ self._rect = rl.Rectangle(rect.x, rect.y, WIDTH, HEIGHT)
- if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON):
- if rl.check_collision_point_rec(rl.get_mouse_position(), self._rect):
- self._state = not self._state
- self._target = 1.0 if self._state else 0.0
- return 1
- return 0
+ def _handle_mouse_release(self, mouse_pos: MousePos):
+ if not self._enabled:
+ return
+
+ self._state = not self._state
+ self._target = 1.0 if self._state else 0.0
def get_state(self):
return self._state
@@ -49,8 +50,7 @@ class Toggle:
self._progress += delta if self._progress < self._target else -delta
self._progress = max(0.0, min(1.0, self._progress))
- def render(self, rect: rl.Rectangle):
- self._rect.x, self._rect.y = rect.x, rect.y
+ def _render(self, rect: rl.Rectangle):
self.update()
if self._enabled:
@@ -69,7 +69,5 @@ class Toggle:
knob_y = self._rect.y + HEIGHT / 2
rl.draw_circle(int(knob_x), int(knob_y), HEIGHT / 2, knob_color)
- return self.handle_input()
-
def _blend_color(self, c1, c2, t):
return rl.Color(int(c1.r + (c2.r - c1.r) * t), int(c1.g + (c2.g - c1.g) * t), int(c1.b + (c2.b - c1.b) * t), 255)
diff --git a/system/ui/lib/widget.py b/system/ui/lib/widget.py
new file mode 100644
index 0000000000..7a436b8e9f
--- /dev/null
+++ b/system/ui/lib/widget.py
@@ -0,0 +1,97 @@
+import abc
+import pyray as rl
+from enum import IntEnum
+from collections.abc import Callable
+from openpilot.system.ui.lib.application import gui_app, MousePos
+
+
+class DialogResult(IntEnum):
+ CANCEL = 0
+ CONFIRM = 1
+ NO_ACTION = -1
+
+
+class Widget(abc.ABC):
+ def __init__(self):
+ self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
+ self._parent_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
+ self._is_pressed = False
+ self._is_visible: bool | Callable[[], bool] = True
+ self._touch_valid_callback: Callable[[], bool] | None = None
+
+ def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
+ """Set a callback to determine if the widget can be clicked."""
+ self._touch_valid_callback = touch_callback
+
+ def _touch_valid(self) -> bool:
+ """Check if the widget can be touched."""
+ return self._touch_valid_callback() if self._touch_valid_callback else True
+
+ @property
+ def is_visible(self) -> bool:
+ return self._is_visible() if callable(self._is_visible) else self._is_visible
+
+ @property
+ def rect(self) -> rl.Rectangle:
+ return self._rect
+
+ def set_visible(self, visible: bool | Callable[[], bool]) -> None:
+ self._is_visible = visible
+
+ def set_rect(self, rect: rl.Rectangle) -> None:
+ changed = (self._rect.x != rect.x or self._rect.y != rect.y or
+ self._rect.width != rect.width or self._rect.height != rect.height)
+ self._rect = rect
+ if changed:
+ self._update_layout_rects()
+
+ def set_parent_rect(self, parent_rect: rl.Rectangle) -> None:
+ """Can be used like size hint in QT"""
+ self._parent_rect = parent_rect
+
+ def set_position(self, x: float, y: float) -> None:
+ changed = (self._rect.x != x or self._rect.y != y)
+ self._rect.x, self._rect.y = x, y
+ if changed:
+ self._update_layout_rects()
+
+ def render(self, rect: rl.Rectangle = None) -> bool | int | None:
+ if rect is not None:
+ self.set_rect(rect)
+
+ self._update_state()
+
+ if not self.is_visible:
+ return None
+
+ ret = self._render(self._rect)
+
+ # Keep track of whether mouse down started within the widget's rectangle
+ for mouse_event in gui_app.mouse_events:
+ if mouse_event.left_pressed and self._touch_valid():
+ if rl.check_collision_point_rec(mouse_event.pos, self._rect):
+ self._is_pressed = True
+
+ elif not self._touch_valid():
+ self._is_pressed = False
+
+ elif mouse_event.left_released:
+ if self._is_pressed and rl.check_collision_point_rec(mouse_event.pos, self._rect):
+ self._handle_mouse_release(mouse_event.pos)
+ self._is_pressed = False
+
+ return ret
+
+ @abc.abstractmethod
+ def _render(self, rect: rl.Rectangle) -> bool | int | None:
+ """Render the widget within the given rectangle."""
+
+ def _update_state(self):
+ """Optionally update the widget's non-layout state. This is called before rendering."""
+
+ def _update_layout_rects(self) -> None:
+ """Optionally update any layout rects on Widget rect change."""
+
+ def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
+ """Optionally handle mouse release events."""
+ return False
diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py
index d8a8144fb0..75d1bdbfd3 100644
--- a/system/ui/lib/wifi_manager.py
+++ b/system/ui/lib/wifi_manager.py
@@ -13,6 +13,7 @@ from dbus_next.aio import MessageBus
from dbus_next import BusType, Variant, Message
from dbus_next.errors import DBusError
from dbus_next.constants import MessageType
+
try:
from openpilot.common.params import Params
except ImportError:
@@ -38,6 +39,7 @@ NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8
TETHERING_IP_ADDRESS = "192.168.43.1"
DEFAULT_TETHERING_PASSWORD = "12345678"
+
# NetworkManager device states
class NMDeviceState(IntEnum):
DISCONNECTED = 30
@@ -46,6 +48,7 @@ class NMDeviceState(IntEnum):
IP_CONFIG = 70
ACTIVATED = 100
+
class SecurityType(IntEnum):
OPEN = 0
WPA = 1
@@ -53,6 +56,7 @@ class SecurityType(IntEnum):
WPA3 = 3
UNSUPPORTED = 4
+
@dataclass
class NetworkInfo:
ssid: str
@@ -200,7 +204,7 @@ class WifiManager:
'connection': {
'type': Variant('s', '802-11-wireless'),
'uuid': Variant('s', str(uuid.uuid4())),
- 'id': Variant('s', ssid),
+ 'id': Variant('s', f'openpilot connection {ssid}'),
'autoconnect-retries': Variant('i', 0),
},
'802-11-wireless': {
@@ -208,7 +212,10 @@ class WifiManager:
'hidden': Variant('b', is_hidden),
'mode': Variant('s', 'infrastructure'),
},
- 'ipv4': {'method': Variant('s', 'auto')},
+ 'ipv4': {
+ 'method': Variant('s', 'auto'),
+ 'dns-priority': Variant('i', 600),
+ },
'ipv6': {'method': Variant('s', 'ignore')},
}
@@ -227,7 +234,7 @@ class WifiManager:
except Exception as e:
self._current_connection_ssid = None
cloudlog.error(f"Error connecting to network: {e}")
- # Notify UI of failure
+ # Notify UI of failure
if self.callbacks.connection_failed:
self.callbacks.connection_failed(ssid, str(e))
diff --git a/system/ui/lib/window.py b/system/ui/lib/window.py
deleted file mode 100644
index 989a3b0284..0000000000
--- a/system/ui/lib/window.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import threading
-import time
-import os
-from typing import Generic, Protocol, TypeVar
-from openpilot.common.swaglog import cloudlog
-from openpilot.system.ui.lib.application import gui_app
-
-
-class RendererProtocol(Protocol):
- def render(self): ...
-
-
-R = TypeVar("R", bound=RendererProtocol)
-
-
-class BaseWindow(Generic[R]):
- def __init__(self, title: str):
- self._title = title
- self._renderer: R | None = None
- self._stop_event = threading.Event()
- self._thread = threading.Thread(target=self._run)
- self._thread.start()
-
- # wait for the renderer to be initialized
- while self._renderer is None and self._thread.is_alive():
- time.sleep(0.01)
-
- def _create_renderer(self) -> R:
- raise NotImplementedError()
-
- def _run(self):
- if os.getenv("CI") is not None:
- return
- gui_app.init_window(self._title)
- self._renderer = self._create_renderer()
- try:
- for _ in gui_app.render():
- if self._stop_event.is_set():
- break
- self._renderer.render()
- finally:
- gui_app.close()
-
- def __enter__(self):
- return self
-
- def close(self):
- if self._thread.is_alive():
- self._stop_event.set()
- self._thread.join(timeout=2.0)
- if self._thread.is_alive():
- cloudlog.warning(f"Failed to join {self._title} thread")
-
- def __del__(self):
- self.close()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- self.close()
diff --git a/system/ui/lib/wrap_text.py b/system/ui/lib/wrap_text.py
index fdfb1970aa..35dc5ac401 100644
--- a/system/ui/lib/wrap_text.py
+++ b/system/ui/lib/wrap_text.py
@@ -1,6 +1,7 @@
import pyray as rl
from openpilot.system.ui.lib.text_measure import measure_text_cached
+
def _break_long_word(font: rl.Font, word: str, font_size: int, max_width: int) -> list[str]:
if not word:
return []
@@ -39,49 +40,64 @@ def wrap_text(font: rl.Font, text: str, font_size: int, max_width: int) -> list[
if not text or max_width <= 0:
return []
- words = text.split()
- if not words:
- return []
+ # Split text by newlines first to preserve explicit line breaks
+ paragraphs = text.split('\n')
+ all_lines: list[str] = []
- lines: list[str] = []
- current_line: list[str] = []
- current_width = 0
- space_width = int(measure_text_cached(font, " ", font_size).x)
-
- for word in words:
- word_width = int(measure_text_cached(font, word, font_size).x)
-
- # Check if word alone exceeds max width (need to break the word)
- if word_width > max_width:
- # Finish current line if it has content
- if current_line:
- lines.append(" ".join(current_line))
- current_line = []
- current_width = 0
-
- # Break the long word into parts
- lines.extend(_break_long_word(font, word, font_size, max_width))
+ for paragraph in paragraphs:
+ # Handle empty paragraphs (preserve empty lines)
+ if not paragraph.strip():
+ all_lines.append("")
continue
- # Calculate width if we add this word
- needed_width = current_width
- if current_line: # Need space before word
- needed_width += space_width
- needed_width += word_width
+ # Process each paragraph separately
+ words = paragraph.split()
+ if not words:
+ all_lines.append("")
+ continue
- # Check if word fits on current line
- if needed_width <= max_width:
- current_line.append(word)
- current_width = needed_width
- else:
- # Start new line with this word
- if current_line:
- lines.append(" ".join(current_line))
- current_line = [word]
- current_width = word_width
+ lines: list[str] = []
+ current_line: list[str] = []
+ current_width = 0
+ space_width = int(measure_text_cached(font, " ", font_size).x)
- # Add remaining words
- if current_line:
- lines.append(" ".join(current_line))
+ for word in words:
+ word_width = int(measure_text_cached(font, word, font_size).x)
- return lines
+ # Check if word alone exceeds max width (need to break the word)
+ if word_width > max_width:
+ # Finish current line if it has content
+ if current_line:
+ lines.append(" ".join(current_line))
+ current_line = []
+ current_width = 0
+
+ # Break the long word into parts
+ lines.extend(_break_long_word(font, word, font_size, max_width))
+ continue
+
+ # Calculate width if we add this word
+ needed_width = current_width
+ if current_line: # Need space before word
+ needed_width += space_width
+ needed_width += word_width
+
+ # Check if word fits on current line
+ if needed_width <= max_width:
+ current_line.append(word)
+ current_width = needed_width
+ else:
+ # Start new line with this word
+ if current_line:
+ lines.append(" ".join(current_line))
+ current_line = [word]
+ current_width = word_width
+
+ # Add remaining words
+ if current_line:
+ lines.append(" ".join(current_line))
+
+ # Add all lines from this paragraph
+ all_lines.extend(lines)
+
+ return all_lines
diff --git a/system/ui/reset.py b/system/ui/reset.py
index 20b689934b..c0dbd1d956 100755
--- a/system/ui/reset.py
+++ b/system/ui/reset.py
@@ -9,6 +9,7 @@ from openpilot.system.hardware import PC
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_label, gui_text_box
+from openpilot.system.ui.lib.widget import Widget
NVME = "/dev/nvme0n1"
USERDATA = "/dev/disk/by-partlabel/userdata"
@@ -27,8 +28,9 @@ class ResetState(IntEnum):
FAILED = 3
-class Reset:
+class Reset(Widget):
def __init__(self, mode):
+ super().__init__()
self.mode = mode
self.reset_state = ResetState.NONE
@@ -54,7 +56,7 @@ class Reset:
self.reset_state = ResetState.RESETTING
threading.Timer(0.1, self._do_erase).start()
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100)
gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD)
@@ -76,7 +78,7 @@ class Reset:
if self.reset_state != ResetState.FAILED:
if gui_button(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height),
- "Confirm", button_style=ButtonStyle.PRIMARY):
+ "Confirm", button_style=ButtonStyle.PRIMARY):
self.confirm()
return True
diff --git a/system/ui/setup.py b/system/ui/setup.py
index 0ec5d6395e..4d9d269480 100755
--- a/system/ui/setup.py
+++ b/system/ui/setup.py
@@ -12,6 +12,7 @@ from openpilot.system.hardware import HARDWARE
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_label, gui_text_box
+from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManagerWrapper
from openpilot.system.ui.widgets.keyboard import Keyboard
@@ -39,8 +40,9 @@ class SetupState(IntEnum):
DOWNLOAD_FAILED = 6
-class Setup:
+class Setup(Widget):
def __init__(self):
+ super().__init__()
self.state = SetupState.GETTING_STARTED
self.network_check_thread = None
self.network_connected = threading.Event()
@@ -67,7 +69,7 @@ class Setup:
except (FileNotFoundError, ValueError):
self.state = SetupState.LOW_VOLTAGE
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
if self.state == SetupState.LOW_VOLTAGE:
self.render_low_voltage(rect)
elif self.state == SetupState.GETTING_STARTED:
@@ -116,7 +118,6 @@ class Setup:
if ret:
self.state = SetupState.NETWORK_SETUP
- self.wifi_manager.request_scan()
self.start_network_check()
def check_network_connectivity(self):
@@ -144,10 +145,6 @@ class Setup:
self.network_check_thread.join()
def render_network_setup(self, rect: rl.Rectangle):
- if self.wifi_ui.require_full_screen:
- self.wifi_ui.render(rect)
- return
-
title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE)
gui_label(title_rect, "Connect to Wi-Fi", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM)
@@ -256,18 +253,20 @@ class Setup:
self.state = SetupState.GETTING_STARTED
def render_custom_url(self):
- result = self.keyboard.render("Enter URL", "for Custom Software")
+ def handle_keyboard_result(result):
+ # Enter pressed
+ if result == 1:
+ url = self.keyboard.text
+ self.keyboard.clear()
+ if url:
+ self.download(url)
- # Enter pressed
- if result == 1:
- url = self.keyboard.text
- self.keyboard.clear()
- if url:
- self.download(url)
+ # Cancel pressed
+ elif result == 0:
+ self.state = SetupState.SOFTWARE_SELECTION
- # Cancel pressed
- elif result == 0:
- self.state = SetupState.SOFTWARE_SELECTION
+ self.keyboard.set_title("Enter URL", "for Custom Software")
+ gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result)
def download(self, url: str):
# autocomplete incomplete URLs
diff --git a/system/ui/spinner.py b/system/ui/spinner.py
index ed605885b0..279ddf034d 100755
--- a/system/ui/spinner.py
+++ b/system/ui/spinner.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python3
import pyray as rl
-import threading
-import time
+import select
+import sys
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.text_measure import measure_text_cached
-from openpilot.system.ui.lib.window import BaseWindow
+from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.text import wrap_text
from openpilot.system.ui.sunnypilot.lib.application import gui_app_sp
@@ -25,41 +25,36 @@ def clamp(value, min_value, max_value):
return max(min(value, max_value), min_value)
-class SpinnerRenderer:
+class Spinner(Widget):
def __init__(self):
+ super().__init__()
self._comma_texture = gui_app_sp.sp_texture("images/spinner_sunnypilot.png", TEXTURE_SIZE, TEXTURE_SIZE)
self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
self._rotation = 0.0
self._progress: int | None = None
self._wrapped_lines: list[str] = []
- self._lock = threading.Lock()
def set_text(self, text: str) -> None:
- with self._lock:
- if text.isdigit():
- self._progress = clamp(int(text), 0, 100)
- self._wrapped_lines = []
- else:
- self._progress = None
- self._wrapped_lines = wrap_text(text, FONT_SIZE, gui_app.width - MARGIN_H)
+ if text.isdigit():
+ self._progress = clamp(int(text), 0, 100)
+ self._wrapped_lines = []
+ else:
+ self._progress = None
+ self._wrapped_lines = wrap_text(text, FONT_SIZE, gui_app.width - MARGIN_H)
- def render(self):
- with self._lock:
- progress = self._progress
- wrapped_lines = self._wrapped_lines
-
- if wrapped_lines:
+ def _render(self, rect: rl.Rectangle):
+ if self._wrapped_lines:
# Calculate total height required for spinner and text
spacing = 50
- total_height = TEXTURE_SIZE + spacing + len(wrapped_lines) * LINE_HEIGHT
- center_y = (gui_app.height - total_height) / 2.0 + TEXTURE_SIZE / 2.0
+ total_height = TEXTURE_SIZE + spacing + len(self._wrapped_lines) * LINE_HEIGHT
+ center_y = (rect.height - total_height) / 2.0 + TEXTURE_SIZE / 2.0
else:
# Center spinner vertically
spacing = 150
- center_y = gui_app.height / 2.0
+ center_y = rect.height / 2.0
y_pos = center_y + TEXTURE_SIZE / 2.0 + spacing
- center = rl.Vector2(gui_app.width / 2.0, center_y)
+ center = rl.Vector2(rect.width / 2.0, center_y)
spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0)
comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0)
@@ -73,38 +68,42 @@ class SpinnerRenderer:
rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE)
# Display the progress bar or text based on user input
- if progress is not None:
+ if self._progress is not None:
bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT)
rl.draw_rectangle_rounded(bar, 1, 10, DARKGRAY)
- bar.width *= progress / 100.0
+ bar.width *= self._progress / 100.0
rl.draw_rectangle_rounded(bar, 1, 10, rl.WHITE)
- elif wrapped_lines:
- for i, line in enumerate(wrapped_lines):
+ elif self._wrapped_lines:
+ for i, line in enumerate(self._wrapped_lines):
text_size = measure_text_cached(gui_app.font(), line, FONT_SIZE)
rl.draw_text_ex(gui_app.font(), line, rl.Vector2(center.x - text_size.x / 2, y_pos + i * LINE_HEIGHT),
FONT_SIZE, 0.0, rl.WHITE)
-class Spinner(BaseWindow[SpinnerRenderer]):
- def __init__(self):
- super().__init__("Spinner")
-
- def _create_renderer(self):
- return SpinnerRenderer()
-
- def update(self, spinner_text: str):
- if self._renderer is not None:
- self._renderer.set_text(spinner_text)
-
- def update_progress(self, cur: float, total: float):
- self.update(str(round(100 * cur / total)))
+def _read_stdin():
+ """Non-blocking read of available lines from stdin."""
+ lines = []
+ while True:
+ rlist, _, _ = select.select([sys.stdin], [], [], 0.0)
+ if not rlist:
+ break
+ line = sys.stdin.readline().strip()
+ if line == "":
+ break
+ lines.append(line)
+ return lines
def main():
- with Spinner() as s:
- s.update("Spinner text")
- time.sleep(5)
+ gui_app.init_window("Spinner")
+ spinner = Spinner()
+ for _ in gui_app.render():
+ text_list = _read_stdin()
+ if text_list:
+ spinner.set_text(text_list[-1])
+
+ spinner.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
if __name__ == "__main__":
diff --git a/system/ui/text.py b/system/ui/text.py
index 63d00fbde3..01a580a12b 100755
--- a/system/ui/text.py
+++ b/system/ui/text.py
@@ -1,13 +1,13 @@
#!/usr/bin/env python3
import re
-import time
+import sys
import pyray as rl
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.application import gui_app
-from openpilot.system.ui.lib.window import BaseWindow
+from openpilot.system.ui.lib.widget import Widget
MARGIN = 50
SPACING = 40
@@ -18,6 +18,7 @@ BUTTON_SIZE = rl.Vector2(310, 160)
DEMO_TEXT = """This is a sample text that will be wrapped and scrolled if necessary.
The text is long enough to demonstrate scrolling and word wrapping.""" * 30
+
def wrap_text(text, font_size, max_width):
lines = []
font = gui_app.font()
@@ -46,15 +47,16 @@ def wrap_text(text, font_size, max_width):
return lines
-class TextWindowRenderer:
+class TextWindow(Widget):
def __init__(self, text: str):
+ super().__init__()
self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20)
self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT)
self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
self._scroll_panel._offset.y = -max(self._content_rect.height - self._textarea_rect.height, 0)
- def render(self):
+ def _render(self, rect: rl.Rectangle):
scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect)
rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height))
for i, line in enumerate(self._wrapped_lines):
@@ -64,7 +66,7 @@ class TextWindowRenderer:
rl.draw_text_ex(gui_app.font(), line, position, FONT_SIZE, 0, rl.WHITE)
rl.end_scissor_mode()
- button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x - SPACING, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
+ button_bounds = rl.Rectangle(rect.width - MARGIN - BUTTON_SIZE.x - SPACING, rect.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
ret = gui_button(button_bounds, "Exit" if PC else "Reboot", button_style=ButtonStyle.TRANSPARENT)
if ret:
if PC:
@@ -74,19 +76,9 @@ class TextWindowRenderer:
return ret
-class TextWindow(BaseWindow[TextWindowRenderer]):
- def __init__(self, text: str):
- self._text = text
- super().__init__("Text")
-
- def _create_renderer(self):
- return TextWindowRenderer(self._text)
-
- def wait_for_exit(self):
- while self._thread.is_alive():
- time.sleep(0.01)
-
-
if __name__ == "__main__":
- with TextWindow(DEMO_TEXT):
- time.sleep(30)
+ text = sys.argv[1] if len(sys.argv) > 1 else DEMO_TEXT
+ gui_app.init_window("Text Viewer")
+ text_window = TextWindow(text)
+ for _ in gui_app.render():
+ text_window.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
diff --git a/system/ui/updater.py b/system/ui/updater.py
index 3ee02ce97c..3fd60cfb1d 100755
--- a/system/ui/updater.py
+++ b/system/ui/updater.py
@@ -10,9 +10,9 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_text_box, gui_label
from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper
+from openpilot.system.ui.lib.widget import Widget
from openpilot.system.ui.widgets.network import WifiManagerUI
-
# Constants
MARGIN = 50
BUTTON_HEIGHT = 160
@@ -31,8 +31,9 @@ class Screen(IntEnum):
PROGRESS = 2
-class Updater:
+class Updater(Widget):
def __init__(self, updater_path, manifest_path):
+ super().__init__()
self.updater = updater_path
self.manifest = manifest_path
self.current_screen = Screen.PROMPT
@@ -60,7 +61,7 @@ class Updater:
# 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)
+ text=True, bufsize=1, universal_newlines=True)
for line in self.process.stdout:
parts = line.strip().split(":")
@@ -78,21 +79,21 @@ class Updater:
self.progress_text = "Update failed"
self.show_reboot_button = True
- def render_prompt_screen(self):
+ def render_prompt_screen(self, rect: rl.Rectangle):
# Title
- title_rect = rl.Rectangle(MARGIN + 50, 250, gui_app.width - MARGIN * 2 - 100, TITLE_FONT_SIZE)
+ title_rect = rl.Rectangle(MARGIN + 50, 250, rect.width - MARGIN * 2 - 100, TITLE_FONT_SIZE)
gui_label(title_rect, "Update Required", TITLE_FONT_SIZE, font_weight=FontWeight.BOLD)
# Description
desc_text = ("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. " +
- "The download size is approximately 1GB.")
+ "The download size is approximately 1GB.")
- desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, gui_app.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3)
+ desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, rect.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3)
gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE)
# Buttons at the bottom
- button_y = gui_app.height - MARGIN - BUTTON_HEIGHT
- button_width = (gui_app.width - MARGIN * 3) // 2
+ button_y = rect.height - MARGIN - BUTTON_HEIGHT
+ button_width = (rect.width - MARGIN * 3) // 2
# WiFi button
wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT)
@@ -106,24 +107,22 @@ class Updater:
self.install_update()
return # Return to avoid further processing after action
- def render_wifi_screen(self):
+ def render_wifi_screen(self, rect: rl.Rectangle):
# Draw the Wi-Fi manager UI
- wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, gui_app.height - MARGIN * 2 - BUTTON_HEIGHT - 20)
+ wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, rect.width - MARGIN * 2 - 100, rect.height - MARGIN * 2 - BUTTON_HEIGHT - 20)
self.wifi_manager_ui.render(wifi_rect)
- if self.wifi_manager_ui.require_full_screen:
- return
- back_button_rect = rl.Rectangle(MARGIN, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
+ back_button_rect = rl.Rectangle(MARGIN, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
if gui_button(back_button_rect, "Back"):
self.current_screen = Screen.PROMPT
return # Return to avoid processing other interactions after screen change
- def render_progress_screen(self):
- title_rect = rl.Rectangle(MARGIN + 100, 330, gui_app.width - MARGIN * 2 - 200, 100)
+ def render_progress_screen(self, rect: rl.Rectangle):
+ title_rect = rl.Rectangle(MARGIN + 100, 330, rect.width - MARGIN * 2 - 200, 100)
gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD)
# Progress bar
- bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, gui_app.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT)
+ bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, rect.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT)
rl.draw_rectangle_rounded(bar_rect, 0.5, 10, PROGRESS_BG_COLOR)
# Calculate the width of the progress chunk
@@ -134,19 +133,19 @@ class Updater:
# Show reboot button if needed
if self.show_reboot_button:
- reboot_rect = rl.Rectangle(MARGIN + 100, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
+ reboot_rect = rl.Rectangle(MARGIN + 100, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
if gui_button(reboot_rect, "Reboot"):
# Return True to signal main loop to exit before rebooting
HARDWARE.reboot()
return
- def render(self):
+ def _render(self, rect: rl.Rectangle):
if self.current_screen == Screen.PROMPT:
- self.render_prompt_screen()
+ self.render_prompt_screen(rect)
elif self.current_screen == Screen.WIFI:
- self.render_wifi_screen()
+ self.render_wifi_screen(rect)
elif self.current_screen == Screen.PROGRESS:
- self.render_progress_screen()
+ self.render_progress_screen(rect)
def main():
@@ -161,7 +160,7 @@ def main():
gui_app.init_window("System Update")
updater = Updater(updater_path, manifest_path)
for _ in gui_app.render():
- updater.render()
+ updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
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 f42220053b..00361905d6 100644
--- a/system/ui/widgets/confirm_dialog.py
+++ b/system/ui/widgets/confirm_dialog.py
@@ -1,9 +1,9 @@
import pyray as rl
-from openpilot.system.ui.lib.application import gui_app
+from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
from openpilot.system.ui.lib.label import gui_text_box
+from openpilot.system.ui.lib.widget import DialogResult
-# Constants for dialog dimensions and styling
DIALOG_WIDTH = 1520
DIALOG_HEIGHT = 600
BUTTON_HEIGHT = 160
@@ -12,7 +12,7 @@ TEXT_AREA_HEIGHT_REDUCTION = 200
BACKGROUND_COLOR = rl.Color(27, 27, 27, 255)
-def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") -> int:
+def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") -> DialogResult:
dialog_x = (gui_app.width - DIALOG_WIDTH) / 2
dialog_y = (gui_app.height - DIALOG_HEIGHT) / 2
dialog_rect = rl.Rectangle(dialog_x, dialog_y, DIALOG_WIDTH, DIALOG_HEIGHT)
@@ -30,28 +30,39 @@ def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel")
rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR)
# Draw the message in the dialog, centered
- text_rect = rl.Rectangle(dialog_rect.x, dialog_rect.y, dialog_rect.width, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION)
+ text_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y, dialog_rect.width - 2 * MARGIN, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION)
gui_text_box(
text_rect,
message,
- font_size=88,
+ font_size=70,
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
+ font_weight=FontWeight.BOLD,
)
# Initialize result; -1 means no action taken yet
- result = -1
+ result = DialogResult.NO_ACTION
# Check for keyboard input for accessibility
if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER):
- result = 1 # Confirm
+ result = DialogResult.CONFIRM
elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE):
- result = 0 # Cancel
+ result = DialogResult.CANCEL
# Check for button clicks
- if gui_button(yes_button, confirm_text, button_style=ButtonStyle.PRIMARY):
- result = 1 # Confirm
- if gui_button(no_button, cancel_text):
- result = 0 # Cancel
+ if cancel_text:
+ if gui_button(yes_button, confirm_text, button_style=ButtonStyle.PRIMARY):
+ result = DialogResult.CONFIRM
+ if gui_button(no_button, cancel_text):
+ result = DialogResult.CANCEL
+ else:
+ centered_button_x = dialog_rect.x + (dialog_rect.width - button_width) / 2
+ centered_yes_button = rl.Rectangle(centered_button_x, button_y, button_width, BUTTON_HEIGHT)
+ if gui_button(centered_yes_button, confirm_text, button_style=ButtonStyle.PRIMARY):
+ result = DialogResult.CONFIRM
return result
+
+
+def alert_dialog(message: str, button_text: str = "OK") -> DialogResult:
+ return confirm_dialog(message, button_text, cancel_text="")
diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py
new file mode 100644
index 0000000000..07741af456
--- /dev/null
+++ b/system/ui/widgets/html_render.py
@@ -0,0 +1,194 @@
+import re
+import pyray as rl
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+from openpilot.system.ui.lib.wrap_text import wrap_text
+from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+from openpilot.system.ui.lib.widget import Widget, DialogResult
+from openpilot.system.ui.lib.button import gui_button, ButtonStyle
+
+
+class ElementType(Enum):
+ H1 = "h1"
+ H2 = "h2"
+ H3 = "h3"
+ H4 = "h4"
+ H5 = "h5"
+ H6 = "h6"
+ P = "p"
+ BR = "br"
+
+
+@dataclass
+class HtmlElement:
+ type: ElementType
+ content: str
+ font_size: int
+ font_weight: FontWeight
+ color: rl.Color
+ margin_top: int
+ margin_bottom: int
+ line_height: float = 1.2
+
+
+class HtmlRenderer(Widget):
+ def __init__(self, file_path: str):
+ self.elements: list[HtmlElement] = []
+ self._normal_font = gui_app.font(FontWeight.NORMAL)
+ self._bold_font = gui_app.font(FontWeight.BOLD)
+ self._scroll_panel = GuiScrollPanel()
+
+ self.styles: dict[ElementType, dict[str, Any]] = {
+ ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 16},
+ ElementType.H2: {"size": 60, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 24, "margin_bottom": 12},
+ ElementType.H3: {"size": 52, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 10},
+ ElementType.H4: {"size": 48, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 16, "margin_bottom": 8},
+ ElementType.H5: {"size": 44, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 12, "margin_bottom": 6},
+ ElementType.H6: {"size": 40, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 10, "margin_bottom": 4},
+ ElementType.P: {"size": 38, "weight": FontWeight.NORMAL, "color": rl.Color(40, 40, 40, 255), "margin_top": 8, "margin_bottom": 12},
+ ElementType.BR: {"size": 0, "weight": FontWeight.NORMAL, "color": rl.BLACK, "margin_top": 0, "margin_bottom": 12},
+ }
+
+ self.parse_html_file(file_path)
+
+ def parse_html_file(self, file_path: str) -> None:
+ with open(file_path, encoding='utf-8') as file:
+ content = file.read()
+ self.parse_html_content(content)
+
+ def parse_html_content(self, html_content: str) -> None:
+ self.elements.clear()
+
+ # Remove HTML comments
+ html_content = re.sub(r'', '', html_content, flags=re.DOTALL)
+
+ # Remove DOCTYPE, html, head, body tags but keep their content
+ html_content = re.sub(r']*>', '', html_content)
+ html_content = re.sub(r'?(?:html|head|body)[^>]*>', '', html_content)
+
+ # Find all HTML elements
+ pattern = r'<(h[1-6]|p)(?:[^>]*)>(.*?)\1>|
'
+ matches = re.finditer(pattern, html_content, re.DOTALL | re.IGNORECASE)
+
+ for match in matches:
+ if match.group(0).lower().startswith('
tags
+ self._add_element(ElementType.BR, "")
+ else:
+ tag = match.group(1).lower()
+ content = match.group(2).strip()
+
+ # Clean up content - remove extra whitespace
+ content = re.sub(r'\s+', ' ', content)
+ content = content.strip()
+
+ if content: # Only add non-empty elements
+ element_type = ElementType(tag)
+ self._add_element(element_type, content)
+
+ def _add_element(self, element_type: ElementType, content: str) -> None:
+ style = self.styles[element_type]
+
+ element = HtmlElement(
+ type=element_type,
+ content=content,
+ font_size=style["size"],
+ font_weight=style["weight"],
+ color=style["color"],
+ margin_top=style["margin_top"],
+ margin_bottom=style["margin_bottom"],
+ )
+
+ self.elements.append(element)
+
+ def _render(self, rect: rl.Rectangle):
+ margin = 50
+ content_rect = rl.Rectangle(rect.x + margin, rect.y + margin, rect.width - (margin * 2), rect.height - (margin * 2))
+
+ button_height = 160
+ button_spacing = 20
+ scrollable_height = content_rect.height - button_height - button_spacing
+
+ scrollable_rect = rl.Rectangle(content_rect.x, content_rect.y, content_rect.width, scrollable_height)
+
+ total_height = self.get_total_height(int(scrollable_rect.width))
+ scroll_content_rect = rl.Rectangle(scrollable_rect.x, scrollable_rect.y, scrollable_rect.width, total_height)
+ scroll_offset = self._scroll_panel.handle_scroll(scrollable_rect, scroll_content_rect)
+
+ rl.begin_scissor_mode(int(scrollable_rect.x), int(scrollable_rect.y), int(scrollable_rect.width), int(scrollable_rect.height))
+ self._render_content(scrollable_rect, scroll_offset.y)
+ rl.end_scissor_mode()
+
+ button_width = (rect.width - 3 * 50) // 3
+ button_x = content_rect.x + (content_rect.width - button_width) / 2
+ button_y = content_rect.y + content_rect.height - button_height
+ button_rect = rl.Rectangle(button_x, button_y, button_width, button_height)
+ if gui_button(button_rect, "OK", button_style=ButtonStyle.PRIMARY) == 1:
+ return DialogResult.CONFIRM
+
+ return DialogResult.NO_ACTION
+
+ def _render_content(self, rect: rl.Rectangle, scroll_offset: float = 0) -> float:
+ current_y = rect.y + scroll_offset
+ padding = 20
+ content_width = rect.width - (padding * 2)
+
+ for element in self.elements:
+ if element.type == ElementType.BR:
+ current_y += element.margin_bottom
+ continue
+
+ current_y += element.margin_top
+ if current_y > rect.y + rect.height:
+ break
+
+ if element.content:
+ font = self._get_font(element.font_weight)
+ wrapped_lines = wrap_text(font, element.content, element.font_size, int(content_width))
+
+ for line in wrapped_lines:
+ if current_y < rect.y - element.font_size:
+ current_y += element.font_size * element.line_height
+ continue
+
+ if current_y > rect.y + rect.height:
+ break
+
+ rl.draw_text_ex(font, line, rl.Vector2(rect.x + padding, current_y), element.font_size, 0, rl.WHITE)
+
+ current_y += element.font_size * element.line_height
+
+ # Apply bottom margin
+ current_y += element.margin_bottom
+
+ return current_y - rect.y - scroll_offset # Return total content height
+
+ def get_total_height(self, content_width: int) -> float:
+ total_height = 0.0
+ padding = 20
+ usable_width = content_width - (padding * 2)
+
+ for element in self.elements:
+ if element.type == ElementType.BR:
+ total_height += element.margin_bottom
+ continue
+
+ total_height += element.margin_top
+
+ if element.content:
+ font = self._get_font(element.font_weight)
+ wrapped_lines = wrap_text(font, element.content, element.font_size, int(usable_width))
+
+ for _ in wrapped_lines:
+ total_height += element.font_size * element.line_height
+
+ total_height += element.margin_bottom
+
+ return total_height
+
+ def _get_font(self, weight: FontWeight):
+ if weight == FontWeight.BOLD:
+ return self._bold_font
+ return self._normal_font
diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py
index 0e22f1b527..338706e73d 100644
--- a/system/ui/widgets/keyboard.py
+++ b/system/ui/widgets/keyboard.py
@@ -5,6 +5,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.button import ButtonStyle, gui_button
from openpilot.system.ui.lib.inputbox import InputBox
from openpilot.system.ui.lib.label import gui_label
+from openpilot.system.ui.lib.widget import Widget
KEY_FONT_SIZE = 96
DOUBLE_CLICK_THRESHOLD = 0.5 # seconds
@@ -52,11 +53,14 @@ KEYBOARD_LAYOUTS = {
}
-class Keyboard:
+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):
+ super().__init__()
self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase"
self._caps_lock = False
self._last_shift_press_time = 0
+ self._title = ""
+ self._sub_title = ""
self._max_text_size = max_text_size
self._min_text_size = min_text_size
@@ -67,7 +71,7 @@ class Keyboard:
# Backspace key repeat tracking
self._backspace_pressed: bool = False
self._backspace_press_time: float = 0.0
- self._backspace_last_repeat:float = 0.0
+ self._backspace_last_repeat: float = 0.0
self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54)
self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54)
@@ -89,10 +93,14 @@ class Keyboard:
self._input_box.clear()
self._backspace_pressed = False
- def render(self, title: str, sub_title: str):
- rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN)
- gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90, font_weight=FontWeight.BOLD)
- gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, font_weight=FontWeight.NORMAL)
+ def set_title(self, title: str, sub_title: str = ""):
+ self._title = title
+ self._sub_title = sub_title
+
+ def _render(self, rect: rl.Rectangle):
+ rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN)
+ gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), self._title, 90, font_weight=FontWeight.BOLD)
+ gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), self._sub_title, 55, font_weight=FontWeight.NORMAL)
if gui_button(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125), "Cancel"):
self.clear()
return 0
@@ -223,7 +231,8 @@ if __name__ == "__main__":
gui_app.init_window("Keyboard")
keyboard = Keyboard(min_text_size=8, show_password_toggle=True)
for _ in gui_app.render():
- result = keyboard.render("Keyboard", "Type here")
+ 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:
print(f"You typed: {keyboard.text}")
gui_app.request_close()
diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py
index 80f55ebf56..0613bb1f12 100644
--- a/system/ui/widgets/network.py
+++ b/system/ui/widgets/network.py
@@ -10,6 +10,7 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper, SecurityType
from openpilot.system.ui.widgets.keyboard import Keyboard
from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog
+from openpilot.system.ui.lib.widget import Widget
NM_DEVICE_STATE_NEED_AUTH = 60
MIN_PASSWORD_LENGTH = 8
@@ -24,35 +25,42 @@ STRENGTH_ICONS = [
"icons/wifi_strength_full.png",
]
+
@dataclass
class StateIdle:
action: Literal["idle"] = "idle"
+
@dataclass
class StateConnecting:
network: NetworkInfo
action: Literal["connecting"] = "connecting"
+
@dataclass
class StateNeedsAuth:
network: NetworkInfo
action: Literal["needs_auth"] = "needs_auth"
+
@dataclass
class StateShowForgetConfirm:
network: NetworkInfo
action: Literal["show_forget_confirm"] = "show_forget_confirm"
+
@dataclass
class StateForgetting:
network: NetworkInfo
action: Literal["forgetting"] = "forgetting"
+
UIState = StateIdle | StateConnecting | StateNeedsAuth | StateShowForgetConfirm | StateForgetting
-class WifiManagerUI:
+class WifiManagerUI(Widget):
def __init__(self, wifi_manager: WifiManagerWrapper):
+ super().__init__()
self.state: UIState = StateIdle()
self.btn_width: int = 200
self.scroll_panel = GuiScrollPanel()
@@ -64,17 +72,17 @@ class WifiManagerUI:
self.wifi_manager.set_callbacks(
WifiManagerCallbacks(
- need_auth = self._on_need_auth,
- activated = self._on_activated,
- forgotten = self._on_forgotten,
- networks_updated = self._on_network_updated,
- connection_failed = self._on_connection_failed
+ need_auth=self._on_need_auth,
+ activated=self._on_activated,
+ forgotten=self._on_forgotten,
+ networks_updated=self._on_network_updated,
+ connection_failed=self._on_connection_failed
)
)
self.wifi_manager.start()
self.wifi_manager.connect()
- def render(self, rect: rl.Rectangle):
+ def _render(self, rect: rl.Rectangle):
with self._lock:
if not self._networks:
gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
@@ -82,36 +90,34 @@ class WifiManagerUI:
match self.state:
case StateNeedsAuth(network):
- result = self.keyboard.render("Enter password", f"for {network.ssid}")
- if result == 1:
- password = self.keyboard.text
- self.keyboard.clear()
-
- if len(password) >= MIN_PASSWORD_LENGTH:
- self.connect_to_network(network, password)
- elif result == 0:
- self.state = StateIdle()
-
+ self.keyboard.set_title("Enter password", f"for {network.ssid}")
+ gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(network, result))
case StateShowForgetConfirm(network):
- result = confirm_dialog(f'Forget Wi-Fi Network "{network.ssid}"?', "Forget")
- if result == 1:
- self.forget_network(network)
- elif result == 0:
- self.state = StateIdle()
-
+ gui_app.set_modal_overlay(lambda: confirm_dialog(f'Forget Wi-Fi Network "{network.ssid}"?', "Forget"),
+ callback=lambda result: self.on_forgot_confirm_finished(network, result))
case _:
self._draw_network_list(rect)
- @property
- def require_full_screen(self) -> bool:
- """Check if the WiFi UI requires exclusive full-screen rendering."""
- with self._lock:
- return isinstance(self.state, (StateNeedsAuth, StateShowForgetConfirm))
+ def _on_password_entered(self, network: NetworkInfo, result: int):
+ if result == 1:
+ password = self.keyboard.text
+ self.keyboard.clear()
+
+ if len(password) >= MIN_PASSWORD_LENGTH:
+ self.connect_to_network(network, password)
+ elif result == 0:
+ self.state = StateIdle()
+
+ def on_forgot_confirm_finished(self, network, result: int):
+ if result == 1:
+ self.forget_network(network)
+ elif result == 0:
+ self.state = StateIdle()
def _draw_network_list(self, rect: rl.Rectangle):
content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT)
offset = self.scroll_panel.handle_scroll(rect, content_rect)
- clicked = self.scroll_panel.is_click_valid()
+ clicked = self.scroll_panel.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
for i, network in enumerate(self._networks):
@@ -150,7 +156,8 @@ class WifiManagerUI:
else:
# If the network is saved, show the "Forget" button
if network.is_saved:
- forget_btn_rect = rl.Rectangle(security_icon_rect.x - self.btn_width - spacing,
+ forget_btn_rect = rl.Rectangle(
+ security_icon_rect.x - self.btn_width - spacing,
rect.y + (ITEM_HEIGHT - 80) / 2,
self.btn_width,
80,
@@ -227,7 +234,6 @@ class WifiManagerUI:
self.state = StateIdle()
-
def main():
gui_app.init_window("Wi-Fi Manager")
wifi_manager = WifiManagerWrapper()
diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py
index c25c5c6e6c..a2958ee1f4 100644
--- a/system/ui/widgets/option_dialog.py
+++ b/system/ui/widgets/option_dialog.py
@@ -1,81 +1,71 @@
import pyray as rl
-
+from openpilot.system.ui.lib.application import FontWeight
from openpilot.system.ui.lib.button import gui_button, ButtonStyle, TextAlignment
from openpilot.system.ui.lib.label import gui_label
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
+from openpilot.system.ui.lib.widget import Widget
+
+# Constants
+MARGIN = 50
+TITLE_FONT_SIZE = 70
+ITEM_HEIGHT = 135
+BUTTON_SPACING = 50
+BUTTON_HEIGHT = 160
+ITEM_SPACING = 50
+LIST_ITEM_SPACING = 25
-class MultiOptionDialog:
+class MultiOptionDialog(Widget):
def __init__(self, title, options, current=""):
- self._title = title
- self._options = options
- self._current = current if current in options else ""
- self._selection = self._current
- self._option_height = 80
- self._padding = 20
- self.scroll_panel = GuiScrollPanel()
+ super().__init__()
+ self.title = title
+ self.options = options
+ self.current = current
+ self.selection = current
+ self.scroll = GuiScrollPanel()
- @property
- def selection(self):
- return self._selection
+ def _render(self, rect):
+ dialog_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - 2 * MARGIN, rect.height - 2 * MARGIN)
+ rl.draw_rectangle_rounded(dialog_rect, 0.02, 20, rl.Color(30, 30, 30, 255))
- def render(self, rect):
- title_rect = rl.Rectangle(rect.x + self._padding, rect.y + self._padding, rect.width - 2 * self._padding, 70)
- gui_label(title_rect, self._title, 70)
+ content_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + MARGIN,
+ dialog_rect.width - 2 * MARGIN, dialog_rect.height - 2 * MARGIN)
- options_y_start = rect.y + 120
- options_height = len(self._options) * (self._option_height + 10)
- options_rect = rl.Rectangle(rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, options_height)
+ gui_label(rl.Rectangle(content_rect.x, content_rect.y, content_rect.width, TITLE_FONT_SIZE), self.title, 70, font_weight=FontWeight.BOLD)
- view_rect = rl.Rectangle(
- rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, rect.height - 200 - 2 * self._padding
- )
+ # Options area
+ options_y = content_rect.y + TITLE_FONT_SIZE + ITEM_SPACING
+ options_h = content_rect.height - TITLE_FONT_SIZE - BUTTON_HEIGHT - 2 * ITEM_SPACING
+ view_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h)
+ content_h = len(self.options) * (ITEM_HEIGHT + 10)
+ list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h)
- offset = self.scroll_panel.handle_scroll(view_rect, options_rect)
- is_click_valid = self.scroll_panel.is_click_valid()
+ # Scroll and render options
+ offset = self.scroll.handle_scroll(view_rect, list_content_rect)
+ valid_click = self.scroll.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
- rl.begin_scissor_mode(int(view_rect.x), int(view_rect.y), int(view_rect.width), int(view_rect.height))
+ rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h))
+ for i, option in enumerate(self.options):
+ item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset.y
+ item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT)
- for i, option in enumerate(self._options):
- y_pos = view_rect.y + i * (self._option_height + 10) + offset.y
- item_rect = rl.Rectangle(view_rect.x, y_pos, view_rect.width, self._option_height)
-
- if not rl.check_collision_recs(item_rect, view_rect):
- continue
-
- is_selected = option == self._selection
- button_style = ButtonStyle.PRIMARY if is_selected else ButtonStyle.NORMAL
-
- if gui_button(item_rect, option, button_style=button_style, text_alignment=TextAlignment.LEFT) and is_click_valid:
- self._selection = option
+ if rl.check_collision_recs(item_rect, view_rect):
+ selected = option == self.selection
+ style = ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL
+ if gui_button(item_rect, option, button_style=style, text_alignment=TextAlignment.LEFT) and valid_click:
+ self.selection = option
rl.end_scissor_mode()
- button_y = rect.y + rect.height - 80 - self._padding
- button_width = (rect.width - 3 * self._padding) / 2
+ # Buttons
+ button_y = content_rect.y + content_rect.height - BUTTON_HEIGHT
+ button_w = (content_rect.width - BUTTON_SPACING) / 2
- cancel_rect = rl.Rectangle(rect.x + self._padding, button_y, button_width, 80)
- if gui_button(cancel_rect, "Cancel"):
- return 0 # Canceled
+ if gui_button(rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT), "Cancel"):
+ return 0
- select_rect = rl.Rectangle(rect.x + 2 * self._padding + button_width, button_y, button_width, 80)
- has_new_selection = self._selection != "" and self._selection != self._current
+ if gui_button(rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT),
+ "Select", is_enabled=self.selection != self.current, button_style=ButtonStyle.PRIMARY):
+ return 1
- if gui_button(select_rect, "Select", is_enabled=has_new_selection, button_style=ButtonStyle.PRIMARY):
- return 1 # Selected
-
- return -1 # Still active
-
-
-if __name__ == "__main__":
- from openpilot.system.ui.lib.application import gui_app
-
- gui_app.init_window("Multi Option Dialog Example")
- options = [f"Option {i}" for i in range(1, 11)]
- dialog = MultiOptionDialog("Choose an option", options, options[0])
-
- for _ in gui_app.render():
- result = dialog.render(rl.Rectangle(100, 100, 1024, 800))
- if result >= 0:
- print(f"Selected: {dialog.selection}" if result > 0 else "Canceled")
- break
+ return -1
diff --git a/system/version.py b/system/version.py
index 11c6ced2b7..6e4d0fb42d 100755
--- a/system/version.py
+++ b/system/version.py
@@ -117,9 +117,13 @@ class BuildMetadata:
def master_channel(self) -> bool:
return self.channel in MASTER_SP_BRANCHES
+ @property
+ def development_channel(self) -> bool:
+ return self.channel.startswith("dev-") or self.channel.endswith("-prebuilt")
+
@property
def channel_type(self) -> str:
- if self.channel.startswith("dev-"):
+ if self.development_channel:
return "development"
elif self.channel.startswith("staging-"):
return "staging"
diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh
index 21c9af1c0b..9e85f90df9 100755
--- a/third_party/raylib/build.sh
+++ b/third_party/raylib/build.sh
@@ -30,7 +30,7 @@ fi
cd raylib_repo
-COMMIT="66030a7de62c9e1ee8ab30a1d657a740333bb4f2"
+COMMIT=${1:-66030a7de62c9e1ee8ab30a1d657a740333bb4f2}
git fetch origin $COMMIT
git reset --hard $COMMIT
git clean -xdff .
diff --git a/tools/README.md b/tools/README.md
index 69b0e025e8..d52c8f4522 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -8,42 +8,26 @@ Most of openpilot should work natively on macOS. On Windows you can use WSL for
## Native setup on Ubuntu 24.04 and macOS
+Follow these instructions for a fully managed setup experience. If you'd like to manage the dependencies yourself, just read the setup scripts in this directory.
+
**1. Clone openpilot**
-
-NOTE: This repository uses Git LFS for large files. Ensure you have [Git LFS](https://git-lfs.com/) installed and set up before cloning or working with it.
-
-Either do a partial clone for faster download:
``` bash
-git clone --filter=blob:none --recurse-submodules --also-filter-submodules https://github.com/commaai/openpilot.git
-```
-
-or do a full clone:
-``` bash
-git clone --recurse-submodules https://github.com/commaai/openpilot.git
+git clone https://github.com/commaai/openpilot.git
```
**2. Run the setup script**
-
``` bash
cd openpilot
tools/op.sh setup
```
-**3. Git LFS**
-
-``` bash
-git lfs pull
-```
-
-**4. Activate a python shell**
-
+**3. Activate a Python shell**
Activate a shell with the Python dependencies installed:
``` bash
source .venv/bin/activate
```
-**5. Build openpilot**
-
+**4. Build openpilot**
``` bash
scons -u -j$(nproc)
```
@@ -62,8 +46,6 @@ Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md
## Directory Structure
```
-├── ubuntu_setup.sh # Setup script for Ubuntu
-├── mac_setup.sh # Setup script for macOS
├── cabana/ # View and plot CAN messages from drives or in realtime
├── camerastream/ # Cameras stream over the network
├── joystick/ # Control your car with a joystick
diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py
index 5080293e29..abae8d9f17 100755
--- a/tools/car_porting/auto_fingerprint.py
+++ b/tools/car_porting/auto_fingerprint.py
@@ -2,7 +2,7 @@
import argparse
from collections import defaultdict
-from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions
+from opendbc.car.debug.format_fingerprints import format_brand_fw_versions
from opendbc.car.fingerprints import MIGRATION
from opendbc.car.fw_versions import MODEL_TO_BRAND, match_fw_to_car
diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh
index 8d4a8b6d90..cdbaca32cf 100755
--- a/tools/install_python_dependencies.sh
+++ b/tools/install_python_dependencies.sh
@@ -23,8 +23,8 @@ echo "installing python packages..."
uv sync --frozen --all-extras
source .venv/bin/activate
-echo "PYTHONPATH=${PWD}" > "$ROOT"/.env
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
diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py
index a67baa8c27..8f7f723c28 100644
--- a/tools/lib/framereader.py
+++ b/tools/lib/framereader.py
@@ -1,62 +1,68 @@
-import json
import os
-import pickle
-import struct
import subprocess
-import threading
-from enum import IntEnum
-from functools import wraps
+import json
+from collections.abc import Iterator
+from collections import OrderedDict
import numpy as np
-from lru import LRU
-
-import _io
-from openpilot.tools.lib.cache import cache_path_for_file_path, DEFAULT_CACHE_DIR
+from openpilot.tools.lib.filereader import FileReader, resolve_name
from openpilot.tools.lib.exceptions import DataUnreadableError
from openpilot.tools.lib.vidindex import hevc_index
-from openpilot.common.file_helpers import atomic_write_in_dir
-from openpilot.tools.lib.filereader import FileReader, resolve_name
HEVC_SLICE_B = 0
HEVC_SLICE_P = 1
HEVC_SLICE_I = 2
-class GOPReader:
- def get_gop(self, num):
- # returns (start_frame_num, num_frames, frames_to_skip, gop_data)
- raise NotImplementedError
+class LRUCache:
+ def __init__(self, capacity: int):
+ self._cache: OrderedDict = OrderedDict()
+ self.capacity = capacity
+
+ def __getitem__(self, key):
+ self._cache.move_to_end(key)
+ return self._cache[key]
+
+ def __setitem__(self, key, value):
+ self._cache[key] = value
+ if len(self._cache) > self.capacity:
+ self._cache.popitem(last=False)
+
+ def __contains__(self, key):
+ return key in self._cache
-class DoNothingContextManager:
- def __enter__(self):
- return self
-
- def __exit__(self, *x):
- pass
-
-
-class FrameType(IntEnum):
- raw = 1
- h265_stream = 2
-
-
-def fingerprint_video(fn):
+def assert_hvec(fn: str) -> None:
with FileReader(fn) as f:
header = f.read(4)
if len(header) == 0:
raise DataUnreadableError(f"{fn} is empty")
- elif header == b"\x00\xc0\x12\x00":
- return FrameType.raw
elif header == b"\x00\x00\x00\x01":
- if 'hevc' in fn:
- return FrameType.h265_stream
- else:
+ if 'hevc' not in fn:
raise NotImplementedError(fn)
- else:
- raise NotImplementedError(fn)
+def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc') -> np.ndarray:
+ threads = os.getenv("FFMPEG_THREADS", "0")
+ args = ["ffmpeg", "-v", "quiet",
+ "-threads", threads,
+ "-c:v", "hevc",
+ "-vsync", "0",
+ "-f", vid_fmt,
+ "-flags2", "showall",
+ "-i", "-",
+ "-f", "rawvideo",
+ "-pix_fmt", pix_fmt,
+ "-"]
+ dat = subprocess.check_output(args, input=rawdat)
+
+ if pix_fmt == "rgb24":
+ ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, h, w, 3)
+ elif pix_fmt in ["nv12", "yuv420p"]:
+ ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, (h*w*3//2))
+ else:
+ raise NotImplementedError(f"Unsupported pixel format: {pix_fmt}")
+ return ret
def ffprobe(fn, fmt=None):
fn = resolve_name(fn)
@@ -70,42 +76,21 @@ def ffprobe(fn, fmt=None):
ffprobe_output = subprocess.check_output(cmd, input=f.read(4096))
except subprocess.CalledProcessError as e:
raise DataUnreadableError(fn) from e
-
return json.loads(ffprobe_output)
+def get_index_data(fn: str, index_data: dict|None = None):
+ if index_data is None:
+ index_data = get_video_index(fn)
+ if index_data is None:
+ raise DataUnreadableError(f"Failed to index {fn!r}")
+ stream = index_data["probe"]["streams"][0]
+ return index_data["index"], index_data["global_prefix"], stream["width"], stream["height"]
-def cache_fn(func):
- @wraps(func)
- def cache_inner(fn, *args, **kwargs):
- if kwargs.pop('no_cache', None):
- cache_path = None
- else:
- cache_dir = kwargs.pop('cache_dir', DEFAULT_CACHE_DIR)
- cache_path = cache_path_for_file_path(fn, cache_dir)
-
- if cache_path and os.path.exists(cache_path):
- with open(cache_path, "rb") as cache_file:
- cache_value = pickle.load(cache_file)
- else:
- cache_value = func(fn, *args, **kwargs)
- if cache_path:
- with atomic_write_in_dir(cache_path, mode="wb", overwrite=True) as cache_file:
- pickle.dump(cache_value, cache_file, -1)
-
- return cache_value
-
- return cache_inner
-
-
-@cache_fn
-def index_stream(fn, ft):
- if ft != FrameType.h265_stream:
- raise NotImplementedError("Only h265 supported")
-
+def get_video_index(fn):
+ assert_hvec(fn)
frame_types, dat_len, prefix = hevc_index(fn)
index = np.array(frame_types + [(0xFFFFFFFF, dat_len)], dtype=np.uint32)
probe = ffprobe(fn, "hevc")
-
return {
'index': index,
'global_prefix': prefix,
@@ -113,425 +98,75 @@ def index_stream(fn, ft):
}
-def get_video_index(fn, frame_type, cache_dir=DEFAULT_CACHE_DIR):
- return index_stream(fn, frame_type, cache_dir=cache_dir)
-
-def read_file_check_size(f, sz, cookie):
- buff = bytearray(sz)
- bytes_read = f.readinto(buff)
- assert bytes_read == sz, (bytes_read, sz)
- return buff
-
-
-def rgb24toyuv(rgb):
- yuv_from_rgb = np.array([[ 0.299 , 0.587 , 0.114 ],
- [-0.14714119, -0.28886916, 0.43601035 ],
- [ 0.61497538, -0.51496512, -0.10001026 ]])
- img = np.dot(rgb.reshape(-1, 3), yuv_from_rgb.T).reshape(rgb.shape)
-
-
-
- ys = img[:, :, 0]
- us = (img[::2, ::2, 1] + img[1::2, ::2, 1] + img[::2, 1::2, 1] + img[1::2, 1::2, 1]) / 4 + 128
- vs = (img[::2, ::2, 2] + img[1::2, ::2, 2] + img[::2, 1::2, 2] + img[1::2, 1::2, 2]) / 4 + 128
-
- return ys, us, vs
-
-
-def rgb24toyuv420(rgb):
- ys, us, vs = rgb24toyuv(rgb)
-
- y_len = rgb.shape[0] * rgb.shape[1]
- uv_len = y_len // 4
-
- yuv420 = np.empty(y_len + 2 * uv_len, dtype=rgb.dtype)
- yuv420[:y_len] = ys.reshape(-1)
- yuv420[y_len:y_len + uv_len] = us.reshape(-1)
- yuv420[y_len + uv_len:y_len + 2 * uv_len] = vs.reshape(-1)
-
- return yuv420.clip(0, 255).astype('uint8')
-
-
-def rgb24tonv12(rgb):
- ys, us, vs = rgb24toyuv(rgb)
-
- y_len = rgb.shape[0] * rgb.shape[1]
- uv_len = y_len // 4
-
- nv12 = np.empty(y_len + 2 * uv_len, dtype=rgb.dtype)
- nv12[:y_len] = ys.reshape(-1)
- nv12[y_len::2] = us.reshape(-1)
- nv12[y_len+1::2] = vs.reshape(-1)
-
- return nv12.clip(0, 255).astype('uint8')
-
-
-def decompress_video_data(rawdat, vid_fmt, w, h, pix_fmt):
- threads = os.getenv("FFMPEG_THREADS", "0")
- cuda = os.getenv("FFMPEG_CUDA", "0") == "1"
- args = ["ffmpeg", "-v", "quiet",
- "-threads", threads,
- "-hwaccel", "none" if not cuda else "cuda",
- "-c:v", "hevc",
- "-vsync", "0",
- "-f", vid_fmt,
- "-flags2", "showall",
- "-i", "-",
- "-threads", threads,
- "-f", "rawvideo",
- "-pix_fmt", pix_fmt,
- "-"]
- dat = subprocess.check_output(args, input=rawdat)
-
- if pix_fmt == "rgb24":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, h, w, 3)
- elif pix_fmt == "nv12":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, (h*w*3//2))
- elif pix_fmt == "yuv420p":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, (h*w*3//2))
- elif pix_fmt == "yuv444p":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape(-1, 3, h, w)
- else:
- raise NotImplementedError
-
- return ret
-
-
-class BaseFrameReader:
- # properties: frame_type, frame_count, w, h
-
- def __enter__(self):
- return self
-
- def __exit__(self, *args):
- self.close()
-
- def close(self):
- pass
-
- def get(self, num, count=1, pix_fmt="rgb24"):
- raise NotImplementedError
-
-
-def FrameReader(fn, cache_dir=DEFAULT_CACHE_DIR, readahead=False, readbehind=False, index_data=None):
- frame_type = fingerprint_video(fn)
- if frame_type == FrameType.raw:
- return RawFrameReader(fn)
- elif frame_type in (FrameType.h265_stream,):
- if not index_data:
- index_data = get_video_index(fn, frame_type, cache_dir)
- return StreamFrameReader(fn, frame_type, index_data, readahead=readahead, readbehind=readbehind)
- else:
- raise NotImplementedError(frame_type)
-
-
-class RawData:
- def __init__(self, f):
- self.f = _io.FileIO(f, 'rb')
- self.lenn = struct.unpack("I", self.f.read(4))[0]
- self.count = os.path.getsize(f) / (self.lenn+4)
-
- def read(self, i):
- self.f.seek((self.lenn+4)*i + 4)
- return self.f.read(self.lenn)
-
-
-class RawFrameReader(BaseFrameReader):
- def __init__(self, fn):
- # raw camera
+class FfmpegDecoder:
+ def __init__(self, fn: str, index_data: dict|None = None,
+ pix_fmt: str = "rgb24"):
self.fn = fn
- self.frame_type = FrameType.raw
- self.rawfile = RawData(self.fn)
- self.frame_count = self.rawfile.count
- self.w, self.h = 640, 480
-
- def load_and_debayer(self, img):
- img = np.frombuffer(img, dtype='uint8').reshape(960, 1280)
- cimg = np.dstack([img[0::2, 1::2], ((img[0::2, 0::2].astype("uint16") + img[1::2, 1::2].astype("uint16")) >> 1).astype("uint8"), img[1::2, 0::2]])
- return cimg
-
- def get(self, num, count=1, pix_fmt="yuv420p"):
- assert self.frame_count is not None
- assert num+count <= self.frame_count
-
- if pix_fmt not in ("nv12", "yuv420p", "rgb24"):
- raise ValueError(f"Unsupported pixel format {pix_fmt!r}")
-
- app = []
- for i in range(num, num+count):
- dat = self.rawfile.read(i)
- rgb_dat = self.load_and_debayer(dat)
- if pix_fmt == "rgb24":
- app.append(rgb_dat)
- elif pix_fmt == "nv12":
- app.append(rgb24tonv12(rgb_dat))
- elif pix_fmt == "yuv420p":
- app.append(rgb24toyuv420(rgb_dat))
- else:
- raise NotImplementedError
-
- return app
-
-
-class VideoStreamDecompressor:
- def __init__(self, fn, vid_fmt, w, h, pix_fmt):
- self.fn = fn
- self.vid_fmt = vid_fmt
- self.w = w
- self.h = h
+ 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
- if pix_fmt in ("nv12", "yuv420p"):
- self.out_size = w*h*3//2 # yuv420p
- elif pix_fmt in ("rgb24", "yuv444p"):
- self.out_size = w*h*3
- else:
- raise NotImplementedError
+ def _gop_bounds(self, frame_idx: int):
+ f_b = frame_idx
+ while f_b > 0 and self.index[f_b, 0] != HEVC_SLICE_I:
+ f_b -= 1
+ f_e = frame_idx + 1
+ while f_e < self.frame_count and self.index[f_e, 0] != HEVC_SLICE_I:
+ f_e += 1
+ return f_b, f_e, self.index[f_b, 1], self.index[f_e, 1]
- self.proc = None
- self.t = threading.Thread(target=self.write_thread)
- self.t.daemon = True
+ def _decode_gop(self, raw: bytes) -> Iterator[np.ndarray]:
+ yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt)
- def write_thread(self):
- try:
+ def get_gop_start(self, frame_idx: int):
+ return self.iframes[np.searchsorted(self.iframes, frame_idx, side="right") - 1]
+
+ def get_iterator(self, start_fidx: int = 0, end_fidx: int|None = None,
+ frame_skip: int = 1) -> Iterator[tuple[int, np.ndarray]]:
+ end_fidx = end_fidx or self.frame_count
+ fidx = start_fidx
+ while fidx < end_fidx:
+ f_b, f_e, off_b, off_e = self._gop_bounds(fidx)
with FileReader(self.fn) as f:
- while True:
- r = f.read(1024*1024)
- if len(r) == 0:
- break
- self.proc.stdin.write(r)
- except BrokenPipeError:
- pass
- finally:
- self.proc.stdin.close()
+ 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)):
+ fidx = f_b + i
+ if fidx >= end_fidx:
+ return
+ elif fidx >= start_fidx and (fidx - start_fidx) % frame_skip == 0:
+ yield fidx, frm
+ fidx += 1
- def read(self):
- threads = os.getenv("FFMPEG_THREADS", "0")
- cuda = os.getenv("FFMPEG_CUDA", "0") == "1"
- cmd = [
- "ffmpeg",
- "-threads", threads,
- "-hwaccel", "none" if not cuda else "cuda",
- "-c:v", "hevc",
- # "-avioflags", "direct",
- "-analyzeduration", "0",
- "-probesize", "32",
- "-flush_packets", "0",
- # "-fflags", "nobuffer",
- "-vsync", "0",
- "-f", self.vid_fmt,
- "-i", "pipe:0",
- "-threads", threads,
- "-f", "rawvideo",
- "-pix_fmt", self.pix_fmt,
- "pipe:1"
- ]
- self.proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
- try:
- self.t.start()
+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)
+ for _, frame in dec.get_iterator(start_fidx=start_fidx, end_fidx=end_fidx, frame_skip=frame_skip):
+ yield frame
- while True:
- dat = self.proc.stdout.read(self.out_size)
- if len(dat) == 0:
- break
- assert len(dat) == self.out_size
- if self.pix_fmt == "rgb24":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape((self.h, self.w, 3))
- elif self.pix_fmt == "yuv420p":
- ret = np.frombuffer(dat, dtype=np.uint8)
- elif self.pix_fmt == "nv12":
- ret = np.frombuffer(dat, dtype=np.uint8)
- elif self.pix_fmt == "yuv444p":
- ret = np.frombuffer(dat, dtype=np.uint8).reshape((3, self.h, self.w))
- else:
- raise RuntimeError(f"unknown pix_fmt: {self.pix_fmt}")
- yield ret
+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)
+ 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
+ self.pix_fmt = pix_fmt
- result_code = self.proc.wait()
- assert result_code == 0, result_code
- finally:
- self.proc.kill()
- self.t.join()
+ self.it: Iterator[tuple[int, np.ndarray]] | None = None
+ self.fidx = -1
-class StreamGOPReader(GOPReader):
- def __init__(self, fn, frame_type, index_data):
- assert frame_type == FrameType.h265_stream
-
- self.fn = fn
-
- self.frame_type = frame_type
- self.frame_count = None
- self.w, self.h = None, None
-
- self.prefix = None
- self.index = None
-
- self.index = index_data['index']
- self.prefix = index_data['global_prefix']
- probe = index_data['probe']
-
- self.prefix_frame_data = None
- self.num_prefix_frames = 0
- self.vid_fmt = "hevc"
-
- i = 0
- while i < self.index.shape[0] and self.index[i, 0] != HEVC_SLICE_I:
- i += 1
- self.first_iframe = i
-
- assert self.first_iframe == 0
-
- self.frame_count = len(self.index) - 1
-
- self.w = probe['streams'][0]['width']
- self.h = probe['streams'][0]['height']
-
- def _lookup_gop(self, num):
- frame_b = num
- while frame_b > 0 and self.index[frame_b, 0] != HEVC_SLICE_I:
- frame_b -= 1
-
- frame_e = num + 1
- while frame_e < (len(self.index) - 1) and self.index[frame_e, 0] != HEVC_SLICE_I:
- frame_e += 1
-
- offset_b = self.index[frame_b, 1]
- offset_e = self.index[frame_e, 1]
-
- return (frame_b, frame_e, offset_b, offset_e)
-
- def get_gop(self, num):
- frame_b, frame_e, offset_b, offset_e = self._lookup_gop(num)
- assert frame_b <= num < frame_e
-
- num_frames = frame_e - frame_b
-
- with FileReader(self.fn) as f:
- f.seek(offset_b)
- rawdat = f.read(offset_e - offset_b)
-
- if num < self.first_iframe:
- assert self.prefix_frame_data
- rawdat = self.prefix_frame_data + rawdat
-
- rawdat = self.prefix + rawdat
-
- skip_frames = 0
- if num < self.first_iframe:
- skip_frames = self.num_prefix_frames
-
- return frame_b, num_frames, skip_frames, rawdat
-
-
-class GOPFrameReader(BaseFrameReader):
- #FrameReader with caching and readahead for formats that are group-of-picture based
-
- def __init__(self, readahead=False, readbehind=False):
- self.open_ = True
-
- self.readahead = readahead
- self.readbehind = readbehind
- self.frame_cache = LRU(64)
-
- if self.readahead:
- self.cache_lock = threading.RLock()
- self.readahead_last = None
- self.readahead_len = 30
- self.readahead_c = threading.Condition()
- self.readahead_thread = threading.Thread(target=self._readahead_thread)
- self.readahead_thread.daemon = True
- self.readahead_thread.start()
- else:
- self.cache_lock = DoNothingContextManager()
-
- def close(self):
- if not self.open_:
- return
- self.open_ = False
-
- if self.readahead:
- self.readahead_c.acquire()
- self.readahead_c.notify()
- self.readahead_c.release()
- self.readahead_thread.join()
-
- def _readahead_thread(self):
- while True:
- self.readahead_c.acquire()
- try:
- if not self.open_:
- break
- self.readahead_c.wait()
- finally:
- self.readahead_c.release()
- if not self.open_:
- break
- assert self.readahead_last
- num, pix_fmt = self.readahead_last
-
- if self.readbehind:
- for k in range(num - 1, max(0, num - self.readahead_len), -1):
- self._get_one(k, pix_fmt)
- else:
- for k in range(num, min(self.frame_count, num + self.readahead_len)):
- self._get_one(k, pix_fmt)
-
- def _get_one(self, num, pix_fmt):
- assert num < self.frame_count
-
- if (num, pix_fmt) in self.frame_cache:
- return self.frame_cache[(num, pix_fmt)]
-
- with self.cache_lock:
- if (num, pix_fmt) in self.frame_cache:
- return self.frame_cache[(num, pix_fmt)]
-
- frame_b, num_frames, skip_frames, rawdat = self.get_gop(num)
-
- ret = decompress_video_data(rawdat, self.vid_fmt, self.w, self.h, pix_fmt)
- ret = ret[skip_frames:]
- # assert ret.shape[0] == num_frames
-
- for i in range(ret.shape[0]):
- self.frame_cache[(frame_b+i, pix_fmt)] = ret[i]
-
- return self.frame_cache[(num, pix_fmt)]
-
- def get(self, num, count=1, pix_fmt="rgb24"):
- assert self.frame_count is not None
-
- if num + count > self.frame_count:
- raise ValueError(f"{num + count} > {self.frame_count}")
-
- if pix_fmt not in ("nv12", "yuv420p", "rgb24", "yuv444p"):
- raise ValueError(f"Unsupported pixel format {pix_fmt!r}")
-
- ret = [self._get_one(num + i, pix_fmt) for i in range(count)]
-
- if self.readahead:
- self.readahead_last = (num+count, pix_fmt)
- self.readahead_c.acquire()
- self.readahead_c.notify()
- self.readahead_c.release()
-
- return ret
-
-
-class StreamFrameReader(StreamGOPReader, GOPFrameReader):
- def __init__(self, fn, frame_type, index_data, readahead=False, readbehind=False):
- StreamGOPReader.__init__(self, fn, frame_type, index_data)
- GOPFrameReader.__init__(self, readahead, readbehind)
-
-
-def GOPFrameIterator(gop_reader, pix_fmt='rgb24'):
- dec = VideoStreamDecompressor(gop_reader.fn, gop_reader.vid_fmt, gop_reader.w, gop_reader.h, pix_fmt)
- yield from dec.read()
-
-
-def FrameIterator(fn, pix_fmt='rgb24', **kwargs):
- fr = FrameReader(fn, **kwargs)
- if isinstance(fr, GOPReader):
- yield from GOPFrameIterator(fr, pix_fmt)
- else:
- for i in range(fr.frame_count):
- yield fr.get(i, pix_fmt=pix_fmt)[0]
+ def get(self, fidx:int):
+ if fidx in self._cache: # If frame is cached, return it
+ return self._cache[fidx]
+ read_start = self.decoder.get_gop_start(fidx)
+ if not self.it or fidx < self.fidx or read_start != self.decoder.get_gop_start(self.fidx): # If the frame is in a different GOP, reset the iterator
+ self.it = self.decoder.get_iterator(read_start)
+ self.fidx = -1
+ while self.fidx < fidx:
+ self.fidx, frame = next(self.it)
+ self._cache[self.fidx] = frame
+ return self._cache[fidx]
diff --git a/tools/lib/tests/test_readers.py b/tools/lib/tests/test_readers.py
deleted file mode 100644
index 624531a1a8..0000000000
--- a/tools/lib/tests/test_readers.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import pytest
-import requests
-import tempfile
-
-from collections import defaultdict
-import numpy as np
-from openpilot.tools.lib.framereader import FrameReader
-from openpilot.tools.lib.logreader import LogReader
-
-
-class TestReaders:
- @pytest.mark.skip("skip for bandwidth reasons")
- def test_logreader(self):
- def _check_data(lr):
- hist = defaultdict(int)
- for l in lr:
- hist[l.which()] += 1
-
- assert hist['carControl'] == 6000
- assert hist['logMessage'] == 6857
-
- with tempfile.NamedTemporaryFile(suffix=".bz2") as fp:
- r = requests.get("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/raw_log.bz2?raw=true", timeout=10)
- fp.write(r.content)
- fp.flush()
-
- lr_file = LogReader(fp.name)
- _check_data(lr_file)
-
- lr_url = LogReader("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/raw_log.bz2?raw=true")
- _check_data(lr_url)
-
- @pytest.mark.skip("skip for bandwidth reasons")
- def test_framereader(self):
- def _check_data(f):
- assert f.frame_count == 1200
- assert f.w == 1164
- assert f.h == 874
-
- frame_first_30 = f.get(0, 30)
- assert len(frame_first_30) == 30
-
- print(frame_first_30[15])
-
- print("frame_0")
- frame_0 = f.get(0, 1)
- frame_15 = f.get(15, 1)
-
- print(frame_15[0])
-
- assert np.all(frame_first_30[0] == frame_0[0])
- assert np.all(frame_first_30[15] == frame_15[0])
-
- with tempfile.NamedTemporaryFile(suffix=".hevc") as fp:
- r = requests.get("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/video.hevc?raw=true", timeout=10)
- fp.write(r.content)
- fp.flush()
-
- fr_file = FrameReader(fp.name)
- _check_data(fr_file)
-
- fr_url = FrameReader("https://github.com/commaai/comma2k19/blob/master/Example_1/b0c9d2329ad1606b%7C2018-08-02--08-34-47/40/video.hevc?raw=true")
- _check_data(fr_url)
diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py
index 39431eca10..b90cbd93b0 100644
--- a/tools/replay/lib/ui_helpers.py
+++ b/tools/replay/lib/ui_helpers.py
@@ -198,15 +198,12 @@ def maybe_update_radar_points(lt, lid_overlay):
ar_pts = {}
for track in lt:
ar_pts[track.trackId] = [track.dRel, track.yRel, track.vRel, track.aRel]
- for ids, pt in ar_pts.items():
+ for pt in ar_pts.values():
# negative here since radar is left positive
px, py = to_topdown_pt(pt[0], -pt[1])
if px != -1:
- color = 255
- if int(ids) == 1:
- lid_overlay[px - 2:px + 2, py - 10:py + 10] = 100
- else:
- lid_overlay[px - 2:px + 2, py - 2:py + 2] = color
+ 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')
diff --git a/tools/replay/unlog_ci_segment.py b/tools/replay/unlog_ci_segment.py
index adc0b19e9b..6ddc1b298a 100755
--- a/tools/replay/unlog_ci_segment.py
+++ b/tools/replay/unlog_ci_segment.py
@@ -47,8 +47,8 @@ def replay(route, segment, loop):
if w == 'roadCameraState':
try:
- img = fr.get(frame_idx[msg.roadCameraState.frameId], pix_fmt="rgb24")
- img = img[0][:, :, ::-1] # Convert RGB to BGR, which is what the camera outputs
+ img = fr.get(frame_idx[msg.roadCameraState.frameId])
+ img = img[:, ::-1] # Convert RGB to BGR, which is what the camera outputs
msg.roadCameraState.image = img.flatten().tobytes()
except (KeyError, ValueError):
pass
diff --git a/tools/scripts/extract_audio.py b/tools/scripts/extract_audio.py
new file mode 100755
index 0000000000..2a6ad9f9e3
--- /dev/null
+++ b/tools/scripts/extract_audio.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+import os
+import sys
+import wave
+import argparse
+import numpy as np
+
+from openpilot.tools.lib.logreader import LogReader, ReadMode
+
+
+def extract_audio(route_or_segment_name, output_file=None, play=False):
+ lr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACTIVE)
+ audio_messages = list(lr.filter("rawAudioData"))
+ if not audio_messages:
+ print("No rawAudioData messages found in logs")
+ return
+ sample_rate = audio_messages[0].sampleRate
+
+ audio_chunks = []
+ total_frames = 0
+ for msg in audio_messages:
+ audio_array = np.frombuffer(msg.data, dtype=np.int16)
+ audio_chunks.append(audio_array)
+ total_frames += len(audio_array)
+ full_audio = np.concatenate(audio_chunks)
+
+ print(f"Found {total_frames} frames from {len(audio_messages)} audio messages at {sample_rate} Hz")
+
+ if output_file:
+ if write_wav_file(output_file, full_audio, sample_rate):
+ print(f"Audio written to {output_file}")
+ else:
+ print("Audio extraction canceled.")
+ if play:
+ play_audio(full_audio, sample_rate)
+
+
+def write_wav_file(filename, audio_data, sample_rate):
+ if os.path.exists(filename):
+ if input(f"File '{filename}' exists. Overwrite? (y/N): ").lower() not in ['y', 'yes']:
+ return False
+
+ with wave.open(filename, 'wb') as wav_file:
+ wav_file.setnchannels(1) # Mono
+ wav_file.setsampwidth(2) # 16-bit
+ wav_file.setframerate(sample_rate)
+ wav_file.writeframes(audio_data.tobytes())
+ return True
+
+
+def play_audio(audio_data, sample_rate):
+ try:
+ import sounddevice as sd
+
+ print("Playing audio... Press Ctrl+C to stop")
+ sd.play(audio_data, sample_rate)
+ sd.wait()
+ except KeyboardInterrupt:
+ print("\nPlayback stopped")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Extract audio data from openpilot logs")
+ parser.add_argument("-o", "--output", help="Output WAV file path")
+ parser.add_argument("--play", action="store_true", help="Play audio with sounddevice")
+ parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name")
+
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit()
+ args = parser.parse_args()
+
+ output_file = args.output
+ if not args.output and not args.play:
+ output_file = "extracted_audio.wav"
+
+ extract_audio(args.route_or_segment_name.strip(), output_file, args.play)
diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py
index 521f1597ec..77bf48f946 100755
--- a/tools/scripts/fetch_image_from_route.py
+++ b/tools/scripts/fetch_image_from_route.py
@@ -37,7 +37,7 @@ fr = FrameReader(segments[segment])
if frame >= fr.frame_count:
raise Exception("frame {frame} not found, got {fr.frame_count} frames")
-im = Image.fromarray(fr.get(frame, count=1, pix_fmt="rgb24")[0])
+im = Image.fromarray(fr.get(frame))
fn = f"uxxx_{route.replace('|', '_')}_{segment}_{frame}.png"
im.save(fn)
print(f"saved {fn}")
diff --git a/tools/webcam/camera.py b/tools/webcam/camera.py
index 8358846f3a..7926766c6f 100644
--- a/tools/webcam/camera.py
+++ b/tools/webcam/camera.py
@@ -1,4 +1,5 @@
import av
+import cv2 as cv
class Camera:
def __init__(self, cam_type_state, stream_type, camera_id):
@@ -10,11 +11,16 @@ class Camera:
self.stream_type = stream_type
self.cur_frame_id = 0
- self.container = av.open(camera_id)
- assert self.container.streams.video, f"Can't open video stream for camera {camera_id}"
- self.video_stream = self.container.streams.video[0]
- self.W = self.video_stream.codec_context.width
- self.H = self.video_stream.codec_context.height
+ print(f"Opening {cam_type_state} at {camera_id}")
+
+ self.cap = cv.VideoCapture(camera_id)
+
+ self.cap.set(cv.CAP_PROP_FRAME_WIDTH, 1280.0)
+ self.cap.set(cv.CAP_PROP_FRAME_HEIGHT, 720.0)
+ self.cap.set(cv.CAP_PROP_FPS, 25.0)
+
+ self.W = self.cap.get(cv.CAP_PROP_FRAME_WIDTH)
+ self.H = self.cap.get(cv.CAP_PROP_FRAME_HEIGHT)
@classmethod
def bgr2nv12(self, bgr):
@@ -22,8 +28,12 @@ class Camera:
return frame.reformat(format='nv12').to_ndarray()
def read_frames(self):
- for frame in self.container.decode(self.video_stream):
- img = frame.to_rgb().to_ndarray()[:,:, ::-1] # convert to bgr24
- yuv = Camera.bgr2nv12(img)
+ while True:
+ ret, frame = self.cap.read()
+ if not ret:
+ break
+ # Rotate the frame 180 degrees (flip both axes)
+ frame = cv.flip(frame, -1)
+ yuv = Camera.bgr2nv12(frame)
yield yuv.data.tobytes()
- self.container.close()
+ self.cap.release()
diff --git a/tools/webcam/camerad.py b/tools/webcam/camerad.py
index cb096dc00b..f1e70d948a 100755
--- a/tools/webcam/camerad.py
+++ b/tools/webcam/camerad.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import threading
import os
+import platform
from collections import namedtuple
from msgq.visionipc import VisionIpcServer, VisionStreamType
@@ -30,8 +31,7 @@ class Camerad:
self.cameras = []
for c in CAMERAS:
- cam_device = f"/dev/video{c.cam_id}"
- print(f"opening {c.msg_name} at {cam_device}")
+ cam_device = f"/dev/video{c.cam_id}" if platform.system() != "Darwin" else c.cam_id
cam = Camera(c.msg_name, c.stream_type, cam_device)
self.cameras.append(cam)
self.vipc_server.create_buffers(c.stream_type, 20, cam.W, cam.H)
diff --git a/uv.lock b/uv.lock
index 2f8cf68094..7cd67d7959 100644
--- a/uv.lock
+++ b/uv.lock
@@ -21,7 +21,7 @@ wheels = [
[[package]]
name = "aiohttp"
-version = "3.11.18"
+version = "3.12.13"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@@ -32,40 +32,42 @@ dependencies = [
{ name = "propcache" },
{ name = "yarl" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/63/e7/fa1a8c00e2c54b05dc8cb5d1439f627f7c267874e3f7bb047146116020f9/aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a", size = 7678653, upload-time = "2025-04-21T09:43:09.191Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2f/10/fd9ee4f9e042818c3c2390054c08ccd34556a3cb209d83285616434cf93e/aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9", size = 712088, upload-time = "2025-04-21T09:40:55.776Z" },
- { url = "https://files.pythonhosted.org/packages/22/eb/6a77f055ca56f7aae2cd2a5607a3c9e7b9554f1497a069dcfcb52bfc9540/aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b", size = 471450, upload-time = "2025-04-21T09:40:57.301Z" },
- { url = "https://files.pythonhosted.org/packages/78/dc/5f3c0d27c91abf0bb5d103e9c9b0ff059f60cf6031a5f06f456c90731f42/aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66", size = 457836, upload-time = "2025-04-21T09:40:59.322Z" },
- { url = "https://files.pythonhosted.org/packages/49/7b/55b65af9ef48b9b811c91ff8b5b9de9650c71147f10523e278d297750bc8/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756", size = 1690978, upload-time = "2025-04-21T09:41:00.795Z" },
- { url = "https://files.pythonhosted.org/packages/a2/5a/3f8938c4f68ae400152b42742653477fc625d6bfe02e764f3521321c8442/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717", size = 1745307, upload-time = "2025-04-21T09:41:02.89Z" },
- { url = "https://files.pythonhosted.org/packages/b4/42/89b694a293333ef6f771c62da022163bcf44fb03d4824372d88e3dc12530/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4", size = 1780692, upload-time = "2025-04-21T09:41:04.461Z" },
- { url = "https://files.pythonhosted.org/packages/e2/ce/1a75384e01dd1bf546898b6062b1b5f7a59b6692ef802e4dd6db64fed264/aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f", size = 1676934, upload-time = "2025-04-21T09:41:06.728Z" },
- { url = "https://files.pythonhosted.org/packages/a5/31/442483276e6c368ab5169797d9873b5875213cbcf7e74b95ad1c5003098a/aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361", size = 1621190, upload-time = "2025-04-21T09:41:08.293Z" },
- { url = "https://files.pythonhosted.org/packages/7b/83/90274bf12c079457966008a58831a99675265b6a34b505243e004b408934/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1", size = 1658947, upload-time = "2025-04-21T09:41:11.054Z" },
- { url = "https://files.pythonhosted.org/packages/91/c1/da9cee47a0350b78fdc93670ebe7ad74103011d7778ab4c382ca4883098d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421", size = 1654443, upload-time = "2025-04-21T09:41:13.213Z" },
- { url = "https://files.pythonhosted.org/packages/c9/f2/73cbe18dc25d624f79a09448adfc4972f82ed6088759ddcf783cd201956c/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e", size = 1644169, upload-time = "2025-04-21T09:41:14.827Z" },
- { url = "https://files.pythonhosted.org/packages/5b/32/970b0a196c4dccb1b0cfa5b4dc3b20f63d76f1c608f41001a84b2fd23c3d/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d", size = 1728532, upload-time = "2025-04-21T09:41:17.168Z" },
- { url = "https://files.pythonhosted.org/packages/0b/50/b1dc810a41918d2ea9574e74125eb053063bc5e14aba2d98966f7d734da0/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f", size = 1750310, upload-time = "2025-04-21T09:41:19.353Z" },
- { url = "https://files.pythonhosted.org/packages/95/24/39271f5990b35ff32179cc95537e92499d3791ae82af7dcf562be785cd15/aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd", size = 1691580, upload-time = "2025-04-21T09:41:21.868Z" },
- { url = "https://files.pythonhosted.org/packages/6b/78/75d0353feb77f041460564f12fe58e456436bbc00cbbf5d676dbf0038cc2/aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d", size = 417565, upload-time = "2025-04-21T09:41:24.78Z" },
- { url = "https://files.pythonhosted.org/packages/ed/97/b912dcb654634a813f8518de359364dfc45976f822116e725dc80a688eee/aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6", size = 443652, upload-time = "2025-04-21T09:41:26.48Z" },
- { url = "https://files.pythonhosted.org/packages/b5/d2/5bc436f42bf4745c55f33e1e6a2d69e77075d3e768e3d1a34f96ee5298aa/aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2", size = 706671, upload-time = "2025-04-21T09:41:28.021Z" },
- { url = "https://files.pythonhosted.org/packages/fe/d0/2dbabecc4e078c0474abb40536bbde717fb2e39962f41c5fc7a216b18ea7/aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508", size = 466169, upload-time = "2025-04-21T09:41:29.783Z" },
- { url = "https://files.pythonhosted.org/packages/70/84/19edcf0b22933932faa6e0be0d933a27bd173da02dc125b7354dff4d8da4/aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e", size = 457554, upload-time = "2025-04-21T09:41:31.327Z" },
- { url = "https://files.pythonhosted.org/packages/32/d0/e8d1f034ae5624a0f21e4fb3feff79342ce631f3a4d26bd3e58b31ef033b/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f", size = 1690154, upload-time = "2025-04-21T09:41:33.541Z" },
- { url = "https://files.pythonhosted.org/packages/16/de/2f9dbe2ac6f38f8495562077131888e0d2897e3798a0ff3adda766b04a34/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f", size = 1733402, upload-time = "2025-04-21T09:41:35.634Z" },
- { url = "https://files.pythonhosted.org/packages/e0/04/bd2870e1e9aef990d14b6df2a695f17807baf5c85a4c187a492bda569571/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec", size = 1783958, upload-time = "2025-04-21T09:41:37.456Z" },
- { url = "https://files.pythonhosted.org/packages/23/06/4203ffa2beb5bedb07f0da0f79b7d9039d1c33f522e0d1a2d5b6218e6f2e/aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6", size = 1695288, upload-time = "2025-04-21T09:41:39.756Z" },
- { url = "https://files.pythonhosted.org/packages/30/b2/e2285dda065d9f29ab4b23d8bcc81eb881db512afb38a3f5247b191be36c/aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009", size = 1618871, upload-time = "2025-04-21T09:41:41.972Z" },
- { url = "https://files.pythonhosted.org/packages/57/e0/88f2987885d4b646de2036f7296ebea9268fdbf27476da551c1a7c158bc0/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4", size = 1646262, upload-time = "2025-04-21T09:41:44.192Z" },
- { url = "https://files.pythonhosted.org/packages/e0/19/4d2da508b4c587e7472a032290b2981f7caeca82b4354e19ab3df2f51d56/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9", size = 1677431, upload-time = "2025-04-21T09:41:46.049Z" },
- { url = "https://files.pythonhosted.org/packages/eb/ae/047473ea50150a41440f3265f53db1738870b5a1e5406ece561ca61a3bf4/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb", size = 1637430, upload-time = "2025-04-21T09:41:47.973Z" },
- { url = "https://files.pythonhosted.org/packages/11/32/c6d1e3748077ce7ee13745fae33e5cb1dac3e3b8f8787bf738a93c94a7d2/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda", size = 1703342, upload-time = "2025-04-21T09:41:50.323Z" },
- { url = "https://files.pythonhosted.org/packages/c5/1d/a3b57bfdbe285f0d45572d6d8f534fd58761da3e9cbc3098372565005606/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1", size = 1740600, upload-time = "2025-04-21T09:41:52.111Z" },
- { url = "https://files.pythonhosted.org/packages/a5/71/f9cd2fed33fa2b7ce4d412fb7876547abb821d5b5520787d159d0748321d/aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea", size = 1695131, upload-time = "2025-04-21T09:41:53.94Z" },
- { url = "https://files.pythonhosted.org/packages/97/97/d1248cd6d02b9de6aa514793d0dcb20099f0ec47ae71a933290116c070c5/aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8", size = 412442, upload-time = "2025-04-21T09:41:55.689Z" },
- { url = "https://files.pythonhosted.org/packages/33/9a/e34e65506e06427b111e19218a99abf627638a9703f4b8bcc3e3021277ed/aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8", size = 439444, upload-time = "2025-04-21T09:41:57.977Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/65/5566b49553bf20ffed6041c665a5504fb047cefdef1b701407b8ce1a47c4/aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c", size = 709401, upload-time = "2025-06-14T15:13:30.774Z" },
+ { url = "https://files.pythonhosted.org/packages/14/b5/48e4cc61b54850bdfafa8fe0b641ab35ad53d8e5a65ab22b310e0902fa42/aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358", size = 481669, upload-time = "2025-06-14T15:13:32.316Z" },
+ { url = "https://files.pythonhosted.org/packages/04/4f/e3f95c8b2a20a0437d51d41d5ccc4a02970d8ad59352efb43ea2841bd08e/aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014", size = 469933, upload-time = "2025-06-14T15:13:34.104Z" },
+ { url = "https://files.pythonhosted.org/packages/41/c9/c5269f3b6453b1cfbd2cfbb6a777d718c5f086a3727f576c51a468b03ae2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7", size = 1740128, upload-time = "2025-06-14T15:13:35.604Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/49/a3f76caa62773d33d0cfaa842bdf5789a78749dbfe697df38ab1badff369/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013", size = 1688796, upload-time = "2025-06-14T15:13:37.125Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/e4/556fccc4576dc22bf18554b64cc873b1a3e5429a5bdb7bbef7f5d0bc7664/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47", size = 1787589, upload-time = "2025-06-14T15:13:38.745Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/3d/d81b13ed48e1a46734f848e26d55a7391708421a80336e341d2aef3b6db2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a", size = 1826635, upload-time = "2025-06-14T15:13:40.733Z" },
+ { url = "https://files.pythonhosted.org/packages/75/a5/472e25f347da88459188cdaadd1f108f6292f8a25e62d226e63f860486d1/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc", size = 1729095, upload-time = "2025-06-14T15:13:42.312Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/fe/322a78b9ac1725bfc59dfc301a5342e73d817592828e4445bd8f4ff83489/aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7", size = 1666170, upload-time = "2025-06-14T15:13:44.884Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/77/ec80912270e231d5e3839dbd6c065472b9920a159ec8a1895cf868c2708e/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b", size = 1714444, upload-time = "2025-06-14T15:13:46.401Z" },
+ { url = "https://files.pythonhosted.org/packages/21/b2/fb5aedbcb2b58d4180e58500e7c23ff8593258c27c089abfbcc7db65bd40/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9", size = 1709604, upload-time = "2025-06-14T15:13:48.377Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/15/a94c05f7c4dc8904f80b6001ad6e07e035c58a8ebfcc15e6b5d58500c858/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a", size = 1689786, upload-time = "2025-06-14T15:13:50.401Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/fd/0d2e618388f7a7a4441eed578b626bda9ec6b5361cd2954cfc5ab39aa170/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d", size = 1783389, upload-time = "2025-06-14T15:13:51.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/6b/6986d0c75996ef7e64ff7619b9b7449b1d1cbbe05c6755e65d92f1784fe9/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2", size = 1803853, upload-time = "2025-06-14T15:13:53.533Z" },
+ { url = "https://files.pythonhosted.org/packages/21/65/cd37b38f6655d95dd07d496b6d2f3924f579c43fd64b0e32b547b9c24df5/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3", size = 1716909, upload-time = "2025-06-14T15:13:55.148Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/20/2de7012427dc116714c38ca564467f6143aec3d5eca3768848d62aa43e62/aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd", size = 427036, upload-time = "2025-06-14T15:13:57.076Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/b6/98518bcc615ef998a64bef371178b9afc98ee25895b4f476c428fade2220/aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9", size = 451427, upload-time = "2025-06-14T15:13:58.505Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" },
+ { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" },
+ { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" },
+ { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" },
+ { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" },
+ { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" },
]
[[package]]
@@ -217,11 +219,11 @@ wheels = [
[[package]]
name = "certifi"
-version = "2025.4.26"
+version = "2025.6.15"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" },
]
[[package]]
@@ -295,14 +297,14 @@ wheels = [
[[package]]
name = "click"
-version = "8.2.0"
+version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" },
+ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
]
[[package]]
@@ -366,41 +368,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" },
]
-[[package]]
-name = "coverage"
-version = "7.8.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" },
- { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" },
- { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" },
- { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" },
- { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" },
- { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" },
- { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" },
- { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" },
- { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" },
- { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" },
- { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" },
- { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" },
- { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" },
- { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" },
- { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" },
- { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" },
- { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" },
- { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" },
- { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" },
- { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" },
- { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" },
- { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" },
-]
-
-[package.optional-dependencies]
-toml = [
- { name = "tomli", marker = "python_full_version <= '3.11'" },
-]
-
[[package]]
name = "crcmod"
version = "1.7"
@@ -447,31 +414,31 @@ wheels = [
[[package]]
name = "cython"
-version = "3.1.1"
+version = "3.1.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5b/d3/bb000603e46144db2e5055219bbddcf7ab3b10012fcb342695694fb88141/cython-3.1.1.tar.gz", hash = "sha256:505ccd413669d5132a53834d792c707974248088c4f60c497deb1b416e366397", size = 3175446, upload-time = "2025-05-19T09:44:54.347Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/40/7b17cd866158238db704965da1b5849af261dbad393ea3ac966f934b2d39/cython-3.1.2.tar.gz", hash = "sha256:6bbf7a953fa6762dfecdec015e3b054ba51c0121a45ad851fa130f63f5331381", size = 3184825, upload-time = "2025-06-09T07:08:48.465Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/35/b3/bc75c0352214b5ced31ce5e0d051d0ad4ad916aa7a1d669d1876ad1e59aa/cython-3.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c360823e1063784efc2335617e0f28573d7a594c5a8a05d85e850a9621cccb1f", size = 2998590, upload-time = "2025-05-19T09:56:51.148Z" },
- { url = "https://files.pythonhosted.org/packages/bf/0a/5840cdd7a1e8c0d2ffeb5e09afd32b8d10321cce33a2554ef10ea832a200/cython-3.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:12e00b88147b03c148a95365f89dc1c45a0fc52f9c35aa75ff770ef65b615839", size = 2860818, upload-time = "2025-05-19T09:56:53.694Z" },
- { url = "https://files.pythonhosted.org/packages/63/2e/0fac02ce46f208af54d76c6c786b8dddeb207ca94aa85b0455f6cbaa472c/cython-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab644415458d782c16ba7252de9cec1e3125371641cafea2e53a8c1cf85dd58d", size = 3124262, upload-time = "2025-05-19T09:56:56.277Z" },
- { url = "https://files.pythonhosted.org/packages/23/04/b7ae247b83b3f98966184c1a787001964132ae2e05a989769b1a5e7325da/cython-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5cb6c054daadaf01a88c8f49f3edd9e829c9b76a82cbb4269e3f9878254540b", size = 3215216, upload-time = "2025-05-19T09:56:58.674Z" },
- { url = "https://files.pythonhosted.org/packages/b5/3b/c4e6c16b099a592dbd6cd615c4de901eb8cc2795d5445d77b8cd378de7da/cython-3.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8f62cc9339b75fe8434325083e6a7cae88c9c21efd74bbb6ba4e3623219469", size = 3282597, upload-time = "2025-05-19T09:57:00.85Z" },
- { url = "https://files.pythonhosted.org/packages/06/dd/90a8fd8508298f1f16e2cbc665774047fbc81f0370125b6e32f0a182fc10/cython-3.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:689c1aad373556bd2ab1aa1c2dad8939a2891465a1fbd2cbbdd42b488fb40ec8", size = 3175592, upload-time = "2025-05-19T09:57:03.1Z" },
- { url = "https://files.pythonhosted.org/packages/af/ab/2cd4e8d5c46499ea2ca5b7c3ae4053db86465b35a8870ce5e847fee06aff/cython-3.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:953046c190fa9ab9a09a546a909b847cdbb4c1fe34e9bfa4a15b6ee1585a86aa", size = 3378435, upload-time = "2025-05-19T09:57:05.535Z" },
- { url = "https://files.pythonhosted.org/packages/b2/99/250fb399af2edd9861774f0c8b6c4b7d862aa52d966a07b860bcd723842a/cython-3.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:755a991601b27dd3555310d0f95b19a05e622a80d7b4e7a91fa6f5f3ef3f3b80", size = 3300519, upload-time = "2025-05-19T09:57:07.512Z" },
- { url = "https://files.pythonhosted.org/packages/37/09/1c5d470580d9b92107cadedc848f43e2f2102284f8b666ea9ab82f6fc101/cython-3.1.1-cp311-cp311-win32.whl", hash = "sha256:83b2af5c327f7da4f08afc34fddfaf6d24fa0c000b6b70a527c8125e493b6080", size = 2447287, upload-time = "2025-05-19T09:57:09.958Z" },
- { url = "https://files.pythonhosted.org/packages/30/67/c99ec81380cd9d2c798eb1572f61dbe50318958925049b39029f73fe6b52/cython-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:141ffd6279411c562f6b707adc56b63e965a4fd7f21db83f5d4fcbd8c50ac546", size = 2655739, upload-time = "2025-05-19T09:57:11.938Z" },
- { url = "https://files.pythonhosted.org/packages/78/06/83ff82381319ff68ae46f9dd3024b1d5101997e81a8e955811525b6f934b/cython-3.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d7dc0e4d0cd491fac679a61e9ede348c64ca449f99a284f9a01851aa1dbc7f6", size = 3006334, upload-time = "2025-05-19T09:57:14.284Z" },
- { url = "https://files.pythonhosted.org/packages/c3/01/b4c46c6a27cd2da642bc987c1f9087265defbc96a1929d326b9034953f15/cython-3.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd689910002adfac8734f237cdea1573e38345f27ed7fd445482813b65a29457", size = 2836861, upload-time = "2025-05-19T09:57:16.129Z" },
- { url = "https://files.pythonhosted.org/packages/96/51/7936c5d01ec3c89be8de1756f284878d4a567627b7b1790455ac627fb833/cython-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f0434916994fe213ea7749268b88d77e3ebcbd1b99542cf64bb7d180f45470", size = 3074560, upload-time = "2025-05-19T09:57:18.797Z" },
- { url = "https://files.pythonhosted.org/packages/0d/81/34aeb787dcb2624a82a33e60276ed28d2da8a08c79660cf674b19be82248/cython-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:873aac4ac0b0fb197557c0ac15458b780b9221daa4a716881cbd1a9016c8459f", size = 3192645, upload-time = "2025-05-19T09:57:21.002Z" },
- { url = "https://files.pythonhosted.org/packages/e8/bf/1350ed6cb48158a4f096306a12bc4c26c6d20d3314f1f1978ea23afe0220/cython-3.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23b886a6c8a50b1101ccef2f2f3dc9c699b77633ef5bb5007090226c2ad3f9c2", size = 3241751, upload-time = "2025-05-19T09:57:23.118Z" },
- { url = "https://files.pythonhosted.org/packages/1e/f5/9ed5a898c41723e3da2317fd1f082d963ff08571caeded31cb945be589b5/cython-3.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dff0e7dd53a0ca35b64cda843253d5cac944db26663dc097b3a1adf2c49514ad", size = 3123562, upload-time = "2025-05-19T09:57:25.492Z" },
- { url = "https://files.pythonhosted.org/packages/c3/81/b5ce4393d3a0a75a8c6d9ad0b80a62263d892260b816eb3d569ef144511a/cython-3.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f7954b0b4b3302655d3caa6924261de5907a4e129bc22ace52fe9ae0cd5a758", size = 3333555, upload-time = "2025-05-19T09:57:29.232Z" },
- { url = "https://files.pythonhosted.org/packages/db/47/2c1fa4b4901f10d00e666931dd68d4bd7954d3caa900544d135424ef6178/cython-3.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfa500fd7ae95ca152a5f8062b870532fa3e27efcef6d00612e1f28b9f72615f", size = 3282112, upload-time = "2025-05-19T09:57:31.904Z" },
- { url = "https://files.pythonhosted.org/packages/ed/5b/b000d3ebff79429419d846e06c5c09f33fd010e1f6158d7ba553e1082253/cython-3.1.1-cp312-cp312-win32.whl", hash = "sha256:cd748fab8e4426dbcb2e0fa2979558333934d24365e0de5672fbabfe337d880c", size = 2462293, upload-time = "2025-05-19T09:57:34.964Z" },
- { url = "https://files.pythonhosted.org/packages/45/0e/e1370ed3216e4e164232d1891c2a2932a3874d1a8681f8c3565cafd98579/cython-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:307f216ed319ea07644f2ef9974406c830f01bc8e677e2147e9bfcdf9e3ca8ad", size = 2666710, upload-time = "2025-05-19T09:57:37.528Z" },
- { url = "https://files.pythonhosted.org/packages/a7/97/8e8637e67afc09f1b51a617b15a0d1caf0b5159b0f79d47ab101e620e491/cython-3.1.1-py3-none-any.whl", hash = "sha256:07621e044f332d18139df2ccfcc930151fd323c2f61a58c82f304cffc9eb5280", size = 1220898, upload-time = "2025-05-19T09:44:50.614Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/de/502ddebaf5fe78f13cd6361acdd74710d3a5b15c22a9edc0ea4c873a59a5/cython-3.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5548573e0912d7dc80579827493315384c462e2f15797b91a8ed177686d31eb9", size = 3007792, upload-time = "2025-06-09T07:09:28.777Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/c8/91b00bc68effba9ba1ff5b33988052ac4d98fc1ac3021ade7261661299c6/cython-3.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bf3ea5bc50d80762c490f42846820a868a6406fdb5878ae9e4cc2f11b50228a", size = 2870798, upload-time = "2025-06-09T07:09:30.745Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/4b/29d290f14607785112c00a5e1685d766f433531bbd6a11ad229ab61b7a70/cython-3.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ce53951d06ab2bca39f153d9c5add1d631c2a44d58bf67288c9d631be9724e", size = 3131280, upload-time = "2025-06-09T07:09:32.785Z" },
+ { url = "https://files.pythonhosted.org/packages/38/3c/7c61e9ce25377ec7c4aa0b7ceeed34559ebca7b5cfd384672ba64eeaa4ba/cython-3.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e05a36224e3002d48c7c1c695b3771343bd16bc57eab60d6c5d5e08f3cbbafd8", size = 3223898, upload-time = "2025-06-09T07:09:35.345Z" },
+ { url = "https://files.pythonhosted.org/packages/10/96/2d3fbe7e50e98b53ac86fefb48b64262b2e1304b3495e8e25b3cd1c3473e/cython-3.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc0fc0777c7ab82297c01c61a1161093a22a41714f62e8c35188a309bd5db8e", size = 3291527, upload-time = "2025-06-09T07:09:37.502Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/e4/4cd3624e250d86f05bdb121a567865b9cca75cdc6dce4eedd68e626ea4f8/cython-3.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18161ef3dd0e90a944daa2be468dd27696712a5f792d6289e97d2a31298ad688", size = 3184034, upload-time = "2025-06-09T07:09:40.225Z" },
+ { url = "https://files.pythonhosted.org/packages/24/de/f8c1243c3e50ec95cb81f3a7936c8cf162f28050db8683e291c3861b46a0/cython-3.1.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ca45020950cd52d82189d6dfb6225737586be6fe7b0b9d3fadd7daca62eff531", size = 3386084, upload-time = "2025-06-09T07:09:42.206Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/95/2365937da44741ef0781bb9ecc1f8f52b38b65acb7293b5fc7c3eaee5346/cython-3.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaae97d6d07610224be2b73a93e9e3dd85c09aedfd8e47054e3ef5a863387dae", size = 3309974, upload-time = "2025-06-09T07:09:44.801Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/b8/280eed114110a1a3aa9e2e76bcd06cdd5ef0df7ab77c0be9d5378ca28c57/cython-3.1.2-cp311-cp311-win32.whl", hash = "sha256:3d439d9b19e7e70f6ff745602906d282a853dd5219d8e7abbf355de680c9d120", size = 2482942, upload-time = "2025-06-09T07:09:46.583Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/50/0aa65be5a4ab65bde3224b8fd23ed795f699d1e724ac109bb0a32036b82d/cython-3.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:8efa44ee2f1876e40eb5e45f6513a19758077c56bf140623ccab43d31f873b61", size = 2686535, upload-time = "2025-06-09T07:09:48.345Z" },
+ { url = "https://files.pythonhosted.org/packages/22/86/9393ab7204d5bb65f415dd271b658c18f57b9345d06002cae069376a5a7a/cython-3.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c2c4b6f9a941c857b40168b3f3c81d514e509d985c2dcd12e1a4fea9734192e", size = 3015898, upload-time = "2025-06-09T07:09:50.79Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/b8/3d10ac37ab7b7ee60bc6bfb48f6682ebee7fddaccf56e1e135f0d46ca79f/cython-3.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bdbc115bbe1b8c1dcbcd1b03748ea87fa967eb8dfc3a1a9bb243d4a382efcff4", size = 2846204, upload-time = "2025-06-09T07:09:52.832Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/34/637771d8e10ebabc34a34cdd0d63fe797b66c334e150189955bf6442d710/cython-3.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05111f89db1ca98edc0675cfaa62be47b3ff519a29876eb095532a9f9e052b8", size = 3080671, upload-time = "2025-06-09T07:09:54.924Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/c8/383ad1851fb272920a152c5a30bb6f08c3471b5438079d9488fc3074a170/cython-3.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e7188df8709be32cfdfadc7c3782e361c929df9132f95e1bbc90a340dca3c7", size = 3199022, upload-time = "2025-06-09T07:09:56.978Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/11/20adc8f2db37a29f245e8fd4b8b8a8245fce4bbbd128185cc9a7b1065e4c/cython-3.1.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0ecc71e60a051732c2607b8eb8f2a03a5dac09b28e52b8af323c329db9987b", size = 3241337, upload-time = "2025-06-09T07:09:59.156Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/0b/491f1fd3e177cccb6bb6d52f9609f78d395edde83ac47ebb06d21717ca29/cython-3.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f27143cf88835c8bcc9bf3304953f23f377d1d991e8942982fe7be344c7cfce3", size = 3131808, upload-time = "2025-06-09T07:10:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d2/5e7053a3214c9baa7ad72940555eb87cf4750e597f10b2bb43db62c3f39f/cython-3.1.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8c43566701133f53bf13485839d8f3f309095fe0d3b9d0cd5873073394d2edc", size = 3340319, upload-time = "2025-06-09T07:10:03.485Z" },
+ { url = "https://files.pythonhosted.org/packages/95/42/4842f8ddac9b36c94ae08b23c7fcde3f930c1dd49ac8992bb5320a4d96b5/cython-3.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a3bb893e85f027a929c1764bb14db4c31cbdf8a96f59a78f608f2ba7cfbbce95", size = 3287370, upload-time = "2025-06-09T07:10:05.637Z" },
+ { url = "https://files.pythonhosted.org/packages/03/0d/417745ed75d414176e50310087b43299a3e611e75c379ff998f60f2ca1a8/cython-3.1.2-cp312-cp312-win32.whl", hash = "sha256:12c5902f105e43ca9af7874cdf87a23627f98c15d5a4f6d38bc9d334845145c0", size = 2487734, upload-time = "2025-06-09T07:10:07.591Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/82/df61d09ab81979ba171a8252af8fb8a3b26a0f19d1330c2679c11fe41667/cython-3.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:06789eb7bd2e55b38b9dd349e9309f794aee0fed99c26ea5c9562d463877763f", size = 2695542, upload-time = "2025-06-09T07:10:09.545Z" },
+ { url = "https://files.pythonhosted.org/packages/25/d6/ef8557d5e75cc57d55df579af4976935ee111a85bbee4a5b72354e257066/cython-3.1.2-py3-none-any.whl", hash = "sha256:d23fd7ffd7457205f08571a42b108a3cf993e83a59fe4d72b42e6fc592cf2639", size = 1224753, upload-time = "2025-06-09T07:08:44.849Z" },
]
[[package]]
@@ -507,7 +474,7 @@ version = "0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-xlib", marker = "sys_platform == 'linux'" },
- { name = "typing-extensions" },
+ { name = "typing-extensions", marker = "sys_platform != 'darwin'" },
]
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" },
@@ -542,70 +509,70 @@ wheels = [
[[package]]
name = "fonttools"
-version = "4.58.0"
+version = "4.58.4"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/4d037663e2a1fe30fddb655d755d76e18624be44ad467c07412c2319ab97/fonttools-4.58.0.tar.gz", hash = "sha256:27423d0606a2c7b336913254bf0b1193ebd471d5f725d665e875c5e88a011a43", size = 3514522, upload-time = "2025-05-10T17:36:35.886Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026, upload-time = "2025-06-13T17:25:15.426Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/2e/9b9bd943872a50cb182382f8f4a99af92d76e800603d5f73e4343fdce61a/fonttools-4.58.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9345b1bb994476d6034996b31891c0c728c1059c05daa59f9ab57d2a4dce0f84", size = 2751920, upload-time = "2025-05-10T17:35:16.487Z" },
- { url = "https://files.pythonhosted.org/packages/9b/8c/e8d6375da893125f610826c2e30e6d2597dfb8dad256f8ff5a54f3089fda/fonttools-4.58.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d93119ace1e2d39ff1340deb71097932f72b21c054bd3da727a3859825e24e5", size = 2313957, upload-time = "2025-05-10T17:35:18.906Z" },
- { url = "https://files.pythonhosted.org/packages/4f/1b/a29cb00c8c20164b24f88780e298fafd0bbfb25cf8bc7b10c4b69331ad5d/fonttools-4.58.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79c9e4f01bb04f19df272ae35314eb6349fdb2e9497a163cd22a21be999694bd", size = 4913808, upload-time = "2025-05-10T17:35:21.394Z" },
- { url = "https://files.pythonhosted.org/packages/d1/ab/9b9507b65b15190cbfe1ccd3c08067d79268d8312ef20948b16d9f5aa905/fonttools-4.58.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62ecda1465d38248aaf9bee1c17a21cf0b16aef7d121d7d303dbb320a6fd49c2", size = 4935876, upload-time = "2025-05-10T17:35:23.849Z" },
- { url = "https://files.pythonhosted.org/packages/15/e4/1395853bc775b0ab06a1c61cf261779afda7baff3f65cf1197bbd21aa149/fonttools-4.58.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29d0499bff12a26733c05c1bfd07e68465158201624b2fba4a40b23d96c43f94", size = 4974798, upload-time = "2025-05-10T17:35:26.189Z" },
- { url = "https://files.pythonhosted.org/packages/3c/b9/0358368ef5462f4653a198207b29885bee8d5e23c870f6125450ed88e693/fonttools-4.58.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1871abdb0af582e2d96cc12d88889e3bfa796928f491ec14d34a2e58ca298c7e", size = 5093560, upload-time = "2025-05-10T17:35:28.577Z" },
- { url = "https://files.pythonhosted.org/packages/11/00/f64bc3659980c41eccf2c371e62eb15b40858f02a41a0e9c6258ef094388/fonttools-4.58.0-cp311-cp311-win32.whl", hash = "sha256:e292485d70402093eb94f6ab7669221743838b8bd4c1f45c84ca76b63338e7bf", size = 2186330, upload-time = "2025-05-10T17:35:31.733Z" },
- { url = "https://files.pythonhosted.org/packages/c8/a0/0287be13a1ec7733abf292ffbd76417cea78752d4ce10fecf92d8b1252d6/fonttools-4.58.0-cp311-cp311-win_amd64.whl", hash = "sha256:6df3755fcf9ad70a74ad3134bd5c9738f73c9bb701a304b1c809877b11fe701c", size = 2234687, upload-time = "2025-05-10T17:35:34.015Z" },
- { url = "https://files.pythonhosted.org/packages/6a/4e/1c6b35ec7c04d739df4cf5aace4b7ec284d6af2533a65de21972e2f237d9/fonttools-4.58.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa8316798f982c751d71f0025b372151ea36405733b62d0d94d5e7b8dd674fa6", size = 2737502, upload-time = "2025-05-10T17:35:36.436Z" },
- { url = "https://files.pythonhosted.org/packages/fc/72/c6fcafa3c9ed2b69991ae25a1ba7a3fec8bf74928a96e8229c37faa8eda2/fonttools-4.58.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6db489511e867633b859b11aefe1b7c0d90281c5bdb903413edbb2ba77b97f1", size = 2307214, upload-time = "2025-05-10T17:35:38.939Z" },
- { url = "https://files.pythonhosted.org/packages/52/11/1015cedc9878da6d8d1758049749eef857b693e5828d477287a959c8650f/fonttools-4.58.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:107bdb2dacb1f627db3c4b77fb16d065a10fe88978d02b4fc327b9ecf8a62060", size = 4811136, upload-time = "2025-05-10T17:35:41.491Z" },
- { url = "https://files.pythonhosted.org/packages/32/b9/6a1bc1af6ec17eead5d32e87075e22d0dab001eace0b5a1542d38c6a9483/fonttools-4.58.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba7212068ab20f1128a0475f169068ba8e5b6e35a39ba1980b9f53f6ac9720ac", size = 4876598, upload-time = "2025-05-10T17:35:43.986Z" },
- { url = "https://files.pythonhosted.org/packages/d8/46/b14584c7ea65ad1609fb9632251016cda8a2cd66b15606753b9f888d3677/fonttools-4.58.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f95ea3b6a3b9962da3c82db73f46d6a6845a6c3f3f968f5293b3ac1864e771c2", size = 4872256, upload-time = "2025-05-10T17:35:46.617Z" },
- { url = "https://files.pythonhosted.org/packages/05/78/b2105a7812ca4ef9bf180cd741c82f4522316c652ce2a56f788e2eb54b62/fonttools-4.58.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:874f1225cc4ccfeac32009887f722d7f8b107ca5e867dcee067597eef9d4c80b", size = 5028710, upload-time = "2025-05-10T17:35:49.227Z" },
- { url = "https://files.pythonhosted.org/packages/8c/a9/a38c85ffd30d1f2c7a5460c8abfd1aa66e00c198df3ff0b08117f5c6fcd9/fonttools-4.58.0-cp312-cp312-win32.whl", hash = "sha256:5f3cde64ec99c43260e2e6c4fa70dfb0a5e2c1c1d27a4f4fe4618c16f6c9ff71", size = 2173593, upload-time = "2025-05-10T17:35:51.226Z" },
- { url = "https://files.pythonhosted.org/packages/66/48/29752962a74b7ed95da976b5a968bba1fe611a4a7e50b9fefa345e6e7025/fonttools-4.58.0-cp312-cp312-win_amd64.whl", hash = "sha256:2aee08e2818de45067109a207cbd1b3072939f77751ef05904d506111df5d824", size = 2223230, upload-time = "2025-05-10T17:35:53.653Z" },
- { url = "https://files.pythonhosted.org/packages/9b/1f/4417c26e26a1feab85a27e927f7a73d8aabc84544be8ba108ce4aa90eb1e/fonttools-4.58.0-py3-none-any.whl", hash = "sha256:c96c36880be2268be409df7b08c5b5dacac1827083461a6bc2cb07b8cbcec1d7", size = 1111440, upload-time = "2025-05-10T17:36:33.607Z" },
+ { url = "https://files.pythonhosted.org/packages/17/7b/cc6e9bb41bab223bd2dc70ba0b21386b85f604e27f4c3206b4205085a2ab/fonttools-4.58.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3841991c9ee2dc0562eb7f23d333d34ce81e8e27c903846f0487da21e0028eb", size = 2768901, upload-time = "2025-06-13T17:24:05.901Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/15/98d75df9f2b4e7605f3260359ad6e18e027c11fa549f74fce567270ac891/fonttools-4.58.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c98f91b6a9604e7ffb5ece6ea346fa617f967c2c0944228801246ed56084664", size = 2328696, upload-time = "2025-06-13T17:24:09.18Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/c8/dc92b80f5452c9c40164e01b3f78f04b835a00e673bd9355ca257008ff61/fonttools-4.58.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab9f891eb687ddf6a4e5f82901e00f992e18012ca97ab7acd15f13632acd14c1", size = 5018830, upload-time = "2025-06-13T17:24:11.282Z" },
+ { url = "https://files.pythonhosted.org/packages/19/48/8322cf177680505d6b0b6062e204f01860cb573466a88077a9b795cb70e8/fonttools-4.58.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:891c5771e8f0094b7c0dc90eda8fc75e72930b32581418f2c285a9feedfd9a68", size = 4960922, upload-time = "2025-06-13T17:24:14.9Z" },
+ { url = "https://files.pythonhosted.org/packages/14/e0/2aff149ed7eb0916de36da513d473c6fff574a7146891ce42de914899395/fonttools-4.58.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:43ba4d9646045c375d22e3473b7d82b18b31ee2ac715cd94220ffab7bc2d5c1d", size = 4997135, upload-time = "2025-06-13T17:24:16.959Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/6f/4d9829b29a64a2e63a121cb11ecb1b6a9524086eef3e35470949837a1692/fonttools-4.58.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33d19f16e6d2ffd6669bda574a6589941f6c99a8d5cfb9f464038244c71555de", size = 5108701, upload-time = "2025-06-13T17:24:18.849Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/1e/2d656ddd1b0cd0d222f44b2d008052c2689e66b702b9af1cd8903ddce319/fonttools-4.58.4-cp311-cp311-win32.whl", hash = "sha256:b59e5109b907da19dc9df1287454821a34a75f2632a491dd406e46ff432c2a24", size = 2200177, upload-time = "2025-06-13T17:24:20.823Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/83/ba71ad053fddf4157cb0697c8da8eff6718d059f2a22986fa5f312b49c92/fonttools-4.58.4-cp311-cp311-win_amd64.whl", hash = "sha256:3d471a5b567a0d1648f2e148c9a8bcf00d9ac76eb89e976d9976582044cc2509", size = 2247892, upload-time = "2025-06-13T17:24:22.927Z" },
+ { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082, upload-time = "2025-06-13T17:24:24.862Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677, upload-time = "2025-06-13T17:24:26.815Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354, upload-time = "2025-06-13T17:24:28.428Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633, upload-time = "2025-06-13T17:24:30.568Z" },
+ { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170, upload-time = "2025-06-13T17:24:32.724Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851, upload-time = "2025-06-13T17:24:35.034Z" },
+ { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428, upload-time = "2025-06-13T17:24:36.996Z" },
+ { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649, upload-time = "2025-06-13T17:24:38.985Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" },
]
[[package]]
name = "frozenlist"
-version = "1.6.0"
+version = "1.7.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ee/f4/d744cba2da59b5c1d88823cf9e8a6c74e4659e2b27604ed973be2a0bf5ab/frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68", size = 42831, upload-time = "2025-04-17T22:38:53.099Z" }
+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/53/b5/bc883b5296ec902115c00be161da93bf661199c465ec4c483feec6ea4c32/frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d", size = 160912, upload-time = "2025-04-17T22:36:17.235Z" },
- { url = "https://files.pythonhosted.org/packages/6f/93/51b058b563d0704b39c56baa222828043aafcac17fd3734bec5dbeb619b1/frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0", size = 124315, upload-time = "2025-04-17T22:36:18.735Z" },
- { url = "https://files.pythonhosted.org/packages/c9/e0/46cd35219428d350558b874d595e132d1c17a9471a1bd0d01d518a261e7c/frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe", size = 122230, upload-time = "2025-04-17T22:36:20.6Z" },
- { url = "https://files.pythonhosted.org/packages/d1/0f/7ad2ce928ad06d6dd26a61812b959ded573d3e9d0ee6109d96c2be7172e9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba", size = 314842, upload-time = "2025-04-17T22:36:22.088Z" },
- { url = "https://files.pythonhosted.org/packages/34/76/98cbbd8a20a5c3359a2004ae5e5b216af84a150ccbad67c8f8f30fb2ea91/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595", size = 304919, upload-time = "2025-04-17T22:36:24.247Z" },
- { url = "https://files.pythonhosted.org/packages/9a/fa/258e771ce3a44348c05e6b01dffc2bc67603fba95761458c238cd09a2c77/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a", size = 324074, upload-time = "2025-04-17T22:36:26.291Z" },
- { url = "https://files.pythonhosted.org/packages/d5/a4/047d861fd8c538210e12b208c0479912273f991356b6bdee7ea8356b07c9/frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626", size = 321292, upload-time = "2025-04-17T22:36:27.909Z" },
- { url = "https://files.pythonhosted.org/packages/c0/25/cfec8af758b4525676cabd36efcaf7102c1348a776c0d1ad046b8a7cdc65/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff", size = 301569, upload-time = "2025-04-17T22:36:29.448Z" },
- { url = "https://files.pythonhosted.org/packages/87/2f/0c819372fa9f0c07b153124bf58683b8d0ca7bb73ea5ccde9b9ef1745beb/frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a", size = 313625, upload-time = "2025-04-17T22:36:31.55Z" },
- { url = "https://files.pythonhosted.org/packages/50/5f/f0cf8b0fdedffdb76b3745aa13d5dbe404d63493cc211ce8250f2025307f/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0", size = 312523, upload-time = "2025-04-17T22:36:33.078Z" },
- { url = "https://files.pythonhosted.org/packages/e1/6c/38c49108491272d3e84125bbabf2c2d0b304899b52f49f0539deb26ad18d/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606", size = 322657, upload-time = "2025-04-17T22:36:34.688Z" },
- { url = "https://files.pythonhosted.org/packages/bd/4b/3bd3bad5be06a9d1b04b1c22be80b5fe65b502992d62fab4bdb25d9366ee/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584", size = 303414, upload-time = "2025-04-17T22:36:36.363Z" },
- { url = "https://files.pythonhosted.org/packages/5b/89/7e225a30bef6e85dbfe22622c24afe932e9444de3b40d58b1ea589a14ef8/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a", size = 320321, upload-time = "2025-04-17T22:36:38.16Z" },
- { url = "https://files.pythonhosted.org/packages/22/72/7e3acef4dd9e86366cb8f4d8f28e852c2b7e116927e9722b31a6f71ea4b0/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1", size = 323975, upload-time = "2025-04-17T22:36:40.289Z" },
- { url = "https://files.pythonhosted.org/packages/d8/85/e5da03d20507e13c66ce612c9792b76811b7a43e3320cce42d95b85ac755/frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e", size = 316553, upload-time = "2025-04-17T22:36:42.045Z" },
- { url = "https://files.pythonhosted.org/packages/ac/8e/6c609cbd0580ae8a0661c408149f196aade7d325b1ae7adc930501b81acb/frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860", size = 115511, upload-time = "2025-04-17T22:36:44.067Z" },
- { url = "https://files.pythonhosted.org/packages/f2/13/a84804cfde6de12d44ed48ecbf777ba62b12ff09e761f76cdd1ff9e14bb1/frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603", size = 120863, upload-time = "2025-04-17T22:36:45.465Z" },
- { url = "https://files.pythonhosted.org/packages/9c/8a/289b7d0de2fbac832ea80944d809759976f661557a38bb8e77db5d9f79b7/frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1", size = 160193, upload-time = "2025-04-17T22:36:47.382Z" },
- { url = "https://files.pythonhosted.org/packages/19/80/2fd17d322aec7f430549f0669f599997174f93ee17929ea5b92781ec902c/frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29", size = 123831, upload-time = "2025-04-17T22:36:49.401Z" },
- { url = "https://files.pythonhosted.org/packages/99/06/f5812da431273f78c6543e0b2f7de67dfd65eb0a433978b2c9c63d2205e4/frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25", size = 121862, upload-time = "2025-04-17T22:36:51.899Z" },
- { url = "https://files.pythonhosted.org/packages/d0/31/9e61c6b5fc493cf24d54881731204d27105234d09878be1a5983182cc4a5/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576", size = 316361, upload-time = "2025-04-17T22:36:53.402Z" },
- { url = "https://files.pythonhosted.org/packages/9d/55/22ca9362d4f0222324981470fd50192be200154d51509ee6eb9baa148e96/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8", size = 307115, upload-time = "2025-04-17T22:36:55.016Z" },
- { url = "https://files.pythonhosted.org/packages/ae/39/4fff42920a57794881e7bb3898dc7f5f539261711ea411b43bba3cde8b79/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9", size = 322505, upload-time = "2025-04-17T22:36:57.12Z" },
- { url = "https://files.pythonhosted.org/packages/55/f2/88c41f374c1e4cf0092a5459e5f3d6a1e17ed274c98087a76487783df90c/frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e", size = 322666, upload-time = "2025-04-17T22:36:58.735Z" },
- { url = "https://files.pythonhosted.org/packages/75/51/034eeb75afdf3fd03997856195b500722c0b1a50716664cde64e28299c4b/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590", size = 302119, upload-time = "2025-04-17T22:37:00.512Z" },
- { url = "https://files.pythonhosted.org/packages/2b/a6/564ecde55ee633270a793999ef4fd1d2c2b32b5a7eec903b1012cb7c5143/frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103", size = 316226, upload-time = "2025-04-17T22:37:02.102Z" },
- { url = "https://files.pythonhosted.org/packages/f1/c8/6c0682c32377f402b8a6174fb16378b683cf6379ab4d2827c580892ab3c7/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c", size = 312788, upload-time = "2025-04-17T22:37:03.578Z" },
- { url = "https://files.pythonhosted.org/packages/b6/b8/10fbec38f82c5d163ca1750bfff4ede69713badf236a016781cf1f10a0f0/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821", size = 325914, upload-time = "2025-04-17T22:37:05.213Z" },
- { url = "https://files.pythonhosted.org/packages/62/ca/2bf4f3a1bd40cdedd301e6ecfdbb291080d5afc5f9ce350c0739f773d6b9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70", size = 305283, upload-time = "2025-04-17T22:37:06.985Z" },
- { url = "https://files.pythonhosted.org/packages/09/64/20cc13ccf94abc2a1f482f74ad210703dc78a590d0b805af1c9aa67f76f9/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f", size = 319264, upload-time = "2025-04-17T22:37:08.618Z" },
- { url = "https://files.pythonhosted.org/packages/20/ff/86c6a2bbe98cfc231519f5e6d712a0898488ceac804a917ce014f32e68f6/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046", size = 326482, upload-time = "2025-04-17T22:37:10.196Z" },
- { url = "https://files.pythonhosted.org/packages/2f/da/8e381f66367d79adca245d1d71527aac774e30e291d41ef161ce2d80c38e/frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770", size = 318248, upload-time = "2025-04-17T22:37:12.284Z" },
- { url = "https://files.pythonhosted.org/packages/39/24/1a1976563fb476ab6f0fa9fefaac7616a4361dbe0461324f9fd7bf425dbe/frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc", size = 115161, upload-time = "2025-04-17T22:37:13.902Z" },
- { url = "https://files.pythonhosted.org/packages/80/2e/fb4ed62a65f8cd66044706b1013f0010930d8cbb0729a2219561ea075434/frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878", size = 120548, upload-time = "2025-04-17T22:37:15.326Z" },
- { url = "https://files.pythonhosted.org/packages/71/3e/b04a0adda73bd52b390d730071c0d577073d3d26740ee1bad25c3ad0f37b/frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191", size = 12404, upload-time = "2025-04-17T22:38:51.668Z" },
+ { 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" },
]
[[package]]
@@ -651,17 +618,17 @@ wheels = [
[[package]]
name = "gymnasium"
-version = "1.1.1"
+version = "1.2.0"
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/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326, upload-time = "2025-03-06T16:30:36.428Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f9/68/2bdc7b46b5f543dd865575f9d19716866bdb76e50dd33b71ed1a3dd8bb42/gymnasium-1.1.1-py3-none-any.whl", hash = "sha256:9c167ec0a2b388666e37f63b2849cd2552f7f5b71938574c637bb36487eb928a", size = 965410, upload-time = "2025-03-06T16:30:34.443Z" },
+ { 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" },
]
[[package]]
@@ -810,89 +777,51 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380, upload-time = "2025-01-20T11:14:02.442Z" },
]
-[[package]]
-name = "lru-dict"
-version = "1.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/96/e3/42c87871920602a3c8300915bd0292f76eccc66c38f782397acbf8a62088/lru-dict-1.3.0.tar.gz", hash = "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b", size = 13123, upload-time = "2023-11-06T01:40:12.951Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a8/c9/6fac0cb67160f0efa3cc76a6a7d04d5e21a516eeb991ebba08f4f8f01ec5/lru_dict-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac", size = 17750, upload-time = "2023-11-06T01:38:52.667Z" },
- { url = "https://files.pythonhosted.org/packages/61/14/f90dee4bc547ae266dbeffd4e11611234bb6af511dea48f3bc8dac1de478/lru_dict-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51", size = 11055, upload-time = "2023-11-06T01:38:53.798Z" },
- { url = "https://files.pythonhosted.org/packages/4e/63/a0ae20525f9d52f62ac0def47935f8a2b3b6fcd2c145218b9a27fc1fb910/lru_dict-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7", size = 11330, upload-time = "2023-11-06T01:38:54.847Z" },
- { url = "https://files.pythonhosted.org/packages/e9/c6/8c2b81b61e5206910c81b712500736227289aefe4ccfb36137aa21807003/lru_dict-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa", size = 31793, upload-time = "2023-11-06T01:38:56.163Z" },
- { url = "https://files.pythonhosted.org/packages/f9/d7/af9733f94df67a2e9e31ef47d4c41aff1836024f135cdbda4743eb628452/lru_dict-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890", size = 33090, upload-time = "2023-11-06T01:38:57.091Z" },
- { url = "https://files.pythonhosted.org/packages/5b/6e/5b09b069a70028bcf05dbdc57a301fbe8b3bafecf916f2ed5a3065c79a71/lru_dict-1.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd", size = 29795, upload-time = "2023-11-06T01:38:58.278Z" },
- { url = "https://files.pythonhosted.org/packages/21/92/4690daefc2602f7c3429ecf54572d37a9e3c372d370344d2185daa4d5ecc/lru_dict-1.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c", size = 31586, upload-time = "2023-11-06T01:38:59.363Z" },
- { url = "https://files.pythonhosted.org/packages/3c/67/0a29a91087196b02f278d8765120ee4e7486f1f72a4c505fd1cd3109e627/lru_dict-1.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98", size = 36662, upload-time = "2023-11-06T01:39:00.795Z" },
- { url = "https://files.pythonhosted.org/packages/36/54/8d56c514cd2333b652bd44c8f1962ab986cbe68e8ad7258c9e0f360cddb6/lru_dict-1.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242", size = 35118, upload-time = "2023-11-06T01:39:01.883Z" },
- { url = "https://files.pythonhosted.org/packages/f5/9a/c7a175d10d503b86974cb07141ca175947145dd1c7370fcda86fbbcaf326/lru_dict-1.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e", size = 38198, upload-time = "2023-11-06T01:39:03.306Z" },
- { url = "https://files.pythonhosted.org/packages/fd/59/2e5086c8e8a05a7282a824a2a37e3c45cd5714e7b83d8bc0267cb3bb5b4f/lru_dict-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a", size = 36542, upload-time = "2023-11-06T01:39:04.751Z" },
- { url = "https://files.pythonhosted.org/packages/12/52/80d0a06e5f45fe7c278dd662da6ea5b39f2ff003248f448189932f6b71c2/lru_dict-1.3.0-cp311-cp311-win32.whl", hash = "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4", size = 12533, upload-time = "2023-11-06T01:39:05.838Z" },
- { url = "https://files.pythonhosted.org/packages/ce/fe/1f12f33513310860ec6d722709ec4ad8256d9dcc3385f6ae2a244e6e66f5/lru_dict-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc", size = 13651, upload-time = "2023-11-06T01:39:06.871Z" },
- { url = "https://files.pythonhosted.org/packages/fc/5c/385f080747eb3083af87d8e4c9068f3c4cab89035f6982134889940dafd8/lru_dict-1.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d", size = 17174, upload-time = "2023-11-06T01:39:07.923Z" },
- { url = "https://files.pythonhosted.org/packages/3c/de/5ef2ed75ce55d7059d1b96177ba04fa7ee1f35564f97bdfcd28fccfbe9d2/lru_dict-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549", size = 10742, upload-time = "2023-11-06T01:39:08.871Z" },
- { url = "https://files.pythonhosted.org/packages/ca/05/f69a6abb0062d2cf2ce0aaf0284b105b97d1da024ca6d3d0730e6151242e/lru_dict-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f", size = 11079, upload-time = "2023-11-06T01:39:09.766Z" },
- { url = "https://files.pythonhosted.org/packages/ea/59/cf891143abe58a455b8eaa9175f0e80f624a146a2bf9a1ca842ee0ef930a/lru_dict-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563", size = 32469, upload-time = "2023-11-06T01:39:11.091Z" },
- { url = "https://files.pythonhosted.org/packages/59/88/d5976e9f70107ce11e45d93c6f0c2d5eaa1fc30bb3c8f57525eda4510dff/lru_dict-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2", size = 33496, upload-time = "2023-11-06T01:39:12.463Z" },
- { url = "https://files.pythonhosted.org/packages/6c/f8/94d6e910d54fc1fa05c0ee1cd608c39401866a18cf5e5aff238449b33c11/lru_dict-1.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6", size = 29914, upload-time = "2023-11-06T01:39:13.395Z" },
- { url = "https://files.pythonhosted.org/packages/ca/b9/9db79780c8a3cfd66bba6847773061e5cf8a3746950273b9985d47bbfe53/lru_dict-1.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4", size = 32241, upload-time = "2023-11-06T01:39:14.612Z" },
- { url = "https://files.pythonhosted.org/packages/9b/b6/08a623019daec22a40c4d6d2c40851dfa3d129a53b2f9469db8eb13666c1/lru_dict-1.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c", size = 37320, upload-time = "2023-11-06T01:39:15.875Z" },
- { url = "https://files.pythonhosted.org/packages/70/0b/d3717159c26155ff77679cee1b077d22e1008bf45f19921e193319cd8e46/lru_dict-1.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1", size = 35054, upload-time = "2023-11-06T01:39:17.063Z" },
- { url = "https://files.pythonhosted.org/packages/04/74/f2ae00de7c27984a19b88d2b09ac877031c525b01199d7841ec8fa657fd6/lru_dict-1.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4", size = 38613, upload-time = "2023-11-06T01:39:18.136Z" },
- { url = "https://files.pythonhosted.org/packages/5a/0b/e30236aafe31b4247aa9ae61ba8aac6dde75c3ea0e47a8fb7eef53f6d5ce/lru_dict-1.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df", size = 37143, upload-time = "2023-11-06T01:39:19.571Z" },
- { url = "https://files.pythonhosted.org/packages/1c/28/b59bcebb8d76ba8147a784a8be7eab6a4ad3395b9236e73740ff675a5a52/lru_dict-1.3.0-cp312-cp312-win32.whl", hash = "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc", size = 12653, upload-time = "2023-11-06T01:39:20.574Z" },
- { url = "https://files.pythonhosted.org/packages/bd/18/06d9710cb0a0d3634f8501e4bdcc07abe64a32e404d82895a6a36fab97f6/lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa", size = 13811, upload-time = "2023-11-06T01:39:21.599Z" },
-]
-
[[package]]
name = "lxml"
-version = "5.4.0"
+version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" },
- { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" },
- { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" },
- { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" },
- { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" },
- { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" },
- { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" },
- { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" },
- { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" },
- { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" },
- { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" },
- { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" },
- { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" },
- { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" },
- { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" },
- { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" },
- { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" },
- { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" },
- { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" },
- { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" },
- { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" },
- { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" },
- { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" },
- { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" },
- { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" },
- { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" },
- { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" },
- { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" },
- { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" },
- { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" },
- { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" },
- { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" },
- { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" },
- { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/23/828d4cc7da96c611ec0ce6147bbcea2fdbde023dc995a165afa512399bbf/lxml-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36", size = 8438217, upload-time = "2025-06-26T16:25:34.349Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/33/5ac521212c5bcb097d573145d54b2b4a3c9766cda88af5a0e91f66037c6e/lxml-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25", size = 4590317, upload-time = "2025-06-26T16:25:38.103Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/2e/45b7ca8bee304c07f54933c37afe7dd4d39ff61ba2757f519dcc71bc5d44/lxml-6.0.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3", size = 5221628, upload-time = "2025-06-26T16:25:40.878Z" },
+ { url = "https://files.pythonhosted.org/packages/32/23/526d19f7eb2b85da1f62cffb2556f647b049ebe2a5aa8d4d41b1fb2c7d36/lxml-6.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6", size = 4949429, upload-time = "2025-06-28T18:47:20.046Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/cc/f6be27a5c656a43a5344e064d9ae004d4dcb1d3c9d4f323c8189ddfe4d13/lxml-6.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b", size = 5087909, upload-time = "2025-06-28T18:47:22.834Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/e6/8ec91b5bfbe6972458bc105aeb42088e50e4b23777170404aab5dfb0c62d/lxml-6.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967", size = 5031713, upload-time = "2025-06-26T16:25:43.226Z" },
+ { url = "https://files.pythonhosted.org/packages/33/cf/05e78e613840a40e5be3e40d892c48ad3e475804db23d4bad751b8cadb9b/lxml-6.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e", size = 5232417, upload-time = "2025-06-26T16:25:46.111Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/8c/6b306b3e35c59d5f0b32e3b9b6b3b0739b32c0dc42a295415ba111e76495/lxml-6.0.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58", size = 4681443, upload-time = "2025-06-26T16:25:48.837Z" },
+ { url = "https://files.pythonhosted.org/packages/59/43/0bd96bece5f7eea14b7220476835a60d2b27f8e9ca99c175f37c085cb154/lxml-6.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2", size = 5074542, upload-time = "2025-06-26T16:25:51.65Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/3d/32103036287a8ca012d8518071f8852c68f2b3bfe048cef2a0202eb05910/lxml-6.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851", size = 4729471, upload-time = "2025-06-26T16:25:54.571Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/a8/7be5d17df12d637d81854bd8648cd329f29640a61e9a72a3f77add4a311b/lxml-6.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f", size = 5256285, upload-time = "2025-06-26T16:25:56.997Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/d0/6cb96174c25e0d749932557c8d51d60c6e292c877b46fae616afa23ed31a/lxml-6.0.0-cp311-cp311-win32.whl", hash = "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c", size = 3612004, upload-time = "2025-06-26T16:25:59.11Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/77/6ad43b165dfc6dead001410adeb45e88597b25185f4479b7ca3b16a5808f/lxml-6.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816", size = 4003470, upload-time = "2025-06-26T16:26:01.655Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/bc/4c50ec0eb14f932a18efc34fc86ee936a66c0eb5f2fe065744a2da8a68b2/lxml-6.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab", size = 3682477, upload-time = "2025-06-26T16:26:03.808Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" },
+ { url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" },
+ { url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" },
+ { url = "https://files.pythonhosted.org/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" },
+ { url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" },
+ { url = "https://files.pythonhosted.org/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" },
+ { url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" },
+ { url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" },
]
[[package]]
name = "markdown"
-version = "3.8"
+version = "3.8.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f", size = 360906, upload-time = "2025-04-11T14:42:50.928Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc", size = 106210, upload-time = "2025-04-11T14:42:49.178Z" },
+ { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" },
]
[[package]]
@@ -968,22 +897,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:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" },
@@ -1103,70 +1032,73 @@ wheels = [
[[package]]
name = "multidict"
-version = "6.4.4"
+version = "6.6.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/03/5d/d72502cd6dd64b0c5a5117b1701f05c38e94ffb4a1b4ab65ff0cd9b974e8/multidict-6.6.2.tar.gz", hash = "sha256:c1e8b8b0523c0361a78ce9b99d9850c51cf25e1fa3c5686030ce75df6fdf2918", size = 100939, upload-time = "2025-06-28T14:38:20.828Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515, upload-time = "2025-05-19T14:14:19.767Z" },
- { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609, upload-time = "2025-05-19T14:14:21.538Z" },
- { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871, upload-time = "2025-05-19T14:14:22.666Z" },
- { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661, upload-time = "2025-05-19T14:14:24.124Z" },
- { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422, upload-time = "2025-05-19T14:14:25.437Z" },
- { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447, upload-time = "2025-05-19T14:14:26.793Z" },
- { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455, upload-time = "2025-05-19T14:14:28.149Z" },
- { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666, upload-time = "2025-05-19T14:14:29.584Z" },
- { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392, upload-time = "2025-05-19T14:14:30.961Z" },
- { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969, upload-time = "2025-05-19T14:14:32.672Z" },
- { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433, upload-time = "2025-05-19T14:14:34.016Z" },
- { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418, upload-time = "2025-05-19T14:14:35.376Z" },
- { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042, upload-time = "2025-05-19T14:14:36.723Z" },
- { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280, upload-time = "2025-05-19T14:14:38.194Z" },
- { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322, upload-time = "2025-05-19T14:14:40.015Z" },
- { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070, upload-time = "2025-05-19T14:14:41.904Z" },
- { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667, upload-time = "2025-05-19T14:14:43.534Z" },
- { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" },
- { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" },
- { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" },
- { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" },
- { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" },
- { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" },
- { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" },
- { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" },
- { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" },
- { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" },
- { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" },
- { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" },
- { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" },
- { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" },
- { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" },
- { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" },
- { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" },
- { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" },
+ { url = "https://files.pythonhosted.org/packages/20/33/e9f6a8e960087721722e1bb7948f87647c12b2ed0526d8f5b062d4673cbb/multidict-6.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f89ce85a8f949e4dde3a4fb374500630cf425a32d8c53a2892b8fbfc556720dc", size = 76259, upload-time = "2025-06-28T14:36:12.137Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/41/9924607eee3b1a133e1890be63759378c086ecd34f8c6573552d1043d503/multidict-6.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d6c5bed784d70e790b6f014c1a9456e76401ab32bdacbf8bf4a6c2f1654b12", size = 44529, upload-time = "2025-06-28T14:36:13.205Z" },
+ { url = "https://files.pythonhosted.org/packages/10/43/29c7765474941cfc2739706bd0ebac056edc3ae091654a605440beabc03f/multidict-6.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a94cff417557f5a665e81c642e88d0062d19203a6b470b84fd62007d9c4c23c6", size = 44188, upload-time = "2025-06-28T14:36:14.616Z" },
+ { url = "https://files.pythonhosted.org/packages/01/24/8a016db31ca3664ddcf312c2671309068b040022366bf81f3dfa8a55e161/multidict-6.6.2-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:ebe5fc33f384c0277d3e9ecfc2f1fb3a5d2e6ac03deda39b84cf97611857241a", size = 228403, upload-time = "2025-06-28T14:36:15.697Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/15/d88e1f74daaa18be5544a718885bcbffc8b351a72fb019a12d44effb4f7c/multidict-6.6.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9723d0ba4d4eb50e7abcc235d4db5ce562c6cc20caa902967d8d915ccd73cd75", size = 248429, upload-time = "2025-06-28T14:36:16.949Z" },
+ { url = "https://files.pythonhosted.org/packages/66/83/5e23f9c457d5b0995ce161cc26afdb62b73471e56cea104c0759022e9d6c/multidict-6.6.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fcb85c7bedeaace74fbb631186789a23706bf22a44224b36340576370b3d9fd5", size = 225137, upload-time = "2025-06-28T14:36:18.609Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/3d/8c5ef319927e179193f4fead913617f9c8e83722dcd3da5fcf34c8c41948/multidict-6.6.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d037a8777504c29cb65b90b6bff6c5bf4e5129487a3f7e4517ae97c3dcf675e", size = 257370, upload-time = "2025-06-28T14:36:19.938Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/6b/68773e727bba1d3a3aa42c18e9f8000c2d928da39de979c9816d801e8952/multidict-6.6.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d082bf71da51352247b94622c9c8fd905b22bd4a7e94e252d4336492f595abe5", size = 256199, upload-time = "2025-06-28T14:36:21.262Z" },
+ { url = "https://files.pythonhosted.org/packages/32/d7/8bcf5147c3c9a6581d80778757ec2f21c1e52f13cd1e268548d6f79e43c5/multidict-6.6.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce75c8986b17bc35f22240b7a4d9561530551e1f715b6cc23e136e7c06e6922a", size = 246823, upload-time = "2025-06-28T14:36:22.688Z" },
+ { url = "https://files.pythonhosted.org/packages/49/83/2c7cfc6057ad234d5708d738718c086c17265eadcd17a42f6e6c22b494d1/multidict-6.6.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f38e7b738de36f7caa707add650555d9bc01af9276000ba0427fda08153861f7", size = 244391, upload-time = "2025-06-28T14:36:24.335Z" },
+ { url = "https://files.pythonhosted.org/packages/23/36/4a4ea1064705c4f5f1a8edecd825bd09529d8b6877283ab97f123c4eb324/multidict-6.6.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e88f5cf86bbe5303d056cec9ccc854408571a3f6d41ce90ca9fdc2df47e3810", size = 238816, upload-time = "2025-06-28T14:36:25.541Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/e5/b25ebe8dbfe577eb03b877b167446426e69ae33da10fa74fe68518f974e2/multidict-6.6.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2b62ab7680362cfaf03059f7cb680d3bacd7d8adf176b0a86045e882486bbffc", size = 246514, upload-time = "2025-06-28T14:36:27.154Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/3a/642cb671e1563013327d56821cedacb132489c89a29e72c10c74acb5575d/multidict-6.6.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5b39f3c6c22f4f227fe8994eb916ea12e4d46f65fdbc2d977d20750a85be581b", size = 254082, upload-time = "2025-06-28T14:36:28.65Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/cc/10b69b681cd2ef752506eb83295cbb151ebaf5716ae53a50626db0b6ffea/multidict-6.6.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b256569fc1f8b52d0c3280503f2b5031e90cdb4469f67bfef32f53ef2b07922d", size = 248122, upload-time = "2025-06-28T14:36:30.533Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c2/e34dc495dc7501ffa936758d69f9268868f0fa1a99d059bd3bce7c97fc90/multidict-6.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32791619e6b41cce97b6510fb68ee215c75012aa3c53778057a538ae473d3abd", size = 242454, upload-time = "2025-06-28T14:36:31.886Z" },
+ { url = "https://files.pythonhosted.org/packages/71/38/2140871da7664088d6ddbf4597d07024e29e8f88a96348fff90f0c8aaf42/multidict-6.6.2-cp311-cp311-win32.whl", hash = "sha256:7eb520abcf9f0e983fa4c666586161a2137d78096546ea19744b50ceae1bc401", size = 41247, upload-time = "2025-06-28T14:36:33.471Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b5/b913cd1eaf8ef9eb19580a9699ed86a8f7f945248bfe09ca06ce5878caea/multidict-6.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:d2f765d6793815a82e517a1095c02ccbeab47f0a98fc244fe551625c519ade4d", size = 45877, upload-time = "2025-06-28T14:36:34.912Z" },
+ { url = "https://files.pythonhosted.org/packages/90/34/8c041a172b032ec05da5476998876863b9d1eb9029925883e2e27fb48d69/multidict-6.6.2-cp311-cp311-win_arm64.whl", hash = "sha256:1d465e6cbbcadcbb8b3b08827fe9af889cc035500764b313aafb82c7e8e4b0a9", size = 42858, upload-time = "2025-06-28T14:36:36.003Z" },
+ { url = "https://files.pythonhosted.org/packages/76/67/244bc9038eb05bae87a07d494ff48e43a4be7417c3fd538e0ea65c1beebf/multidict-6.6.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6e5e426aff6b5b32167b1185909ea390e51e59c7a6adfe65de16746e5739d8c1", size = 76357, upload-time = "2025-06-28T14:36:37.071Z" },
+ { url = "https://files.pythonhosted.org/packages/61/3c/03a4d33683ffa9851a14e14cafa76130be99101b2a1b446d47967f47f68e/multidict-6.6.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c4617af352d3e03b5febd040100d1bba67ac844e0f7780c8a124358883119dd", size = 45313, upload-time = "2025-06-28T14:36:38.121Z" },
+ { url = "https://files.pythonhosted.org/packages/50/3c/5eca9c3be9ccb31c26ad144b5fb5160c29d853cd8bc52c1ce53ffd838a0a/multidict-6.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:65854da6c2f065f7e52c4385727494d72b25eaf4e901b15fb3f61e21bb0b52eb", size = 43528, upload-time = "2025-06-28T14:36:39.169Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/d8/6707b7ac3fd336b034b89e9ac5fdcca045e8f6b84ee4163c1857795366b4/multidict-6.6.2-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c477d3bc9a1aa0214f5639a8c1b4a6b3cd9faea5a861b4001a6df62294dcc952", size = 238181, upload-time = "2025-06-28T14:36:40.703Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/24/b822b9f9bceed4f22008172717d601d6209bbe7daca2d35828be60208ba9/multidict-6.6.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19a4bc7c5c1a25424812a26e8dccb18fff65a5f24515d2f3b85302ca3f3914f", size = 257172, upload-time = "2025-06-28T14:36:42.402Z" },
+ { url = "https://files.pythonhosted.org/packages/76/e6/7995824cc95a15daebb15da87fc9509cc3c35027885d534d80718c55d10e/multidict-6.6.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:54ffaf44483b082602c1e1831472e3369c005f092271dbbcad2f7b12d1e84019", size = 242147, upload-time = "2025-06-28T14:36:43.702Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/44/23c9b50461423766d9f32b013a49ce07b358a1188d43cfa977385a872d03/multidict-6.6.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a526df4fe21d2dc68265c046685042fc89187dc40754489f32f7efc05e264b0f", size = 267431, upload-time = "2025-06-28T14:36:44.964Z" },
+ { url = "https://files.pythonhosted.org/packages/28/e4/72cc549230e7d93f9eca0206fac402af239058d8a9f0fb95f348762e8fdd/multidict-6.6.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73e8763061f0a38cec6d084b696964ee7b7e50c10c89a64b20be7044dca36a74", size = 269480, upload-time = "2025-06-28T14:36:46.569Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/e3/a809cf2e624cb37f29f4569e756bd708cd96a93d3d940143464d9079a2f5/multidict-6.6.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81a13031b6831e4de5a89fe391d7f8b60a929f2d22dad403c69d60853e5ba1ca", size = 256759, upload-time = "2025-06-28T14:36:47.859Z" },
+ { url = "https://files.pythonhosted.org/packages/03/85/ad1127e662ed20d8ba2751bf67d874380a817577cd486a7309dd50d116a1/multidict-6.6.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6421f4d5acd632af56ae90906755b242e518d59f5313a7b41cd55fb59adfcd74", size = 252393, upload-time = "2025-06-28T14:36:49.169Z" },
+ { url = "https://files.pythonhosted.org/packages/36/b3/67c331269372e38c435dff4c4b3b5ca8aba958dd58936153c5e64d07a515/multidict-6.6.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3fed2cfff9d8e3316fc4c5aca40f33d7cd29b5a9a4cbf4aa17dfcae592ccb17c", size = 249848, upload-time = "2025-06-28T14:36:50.88Z" },
+ { url = "https://files.pythonhosted.org/packages/06/56/54d51eb89cdcb5518828081cb396219699468f70266ef0fcacf57a339319/multidict-6.6.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eb97a4eed516fb3d63028fc0a8a8661e1acdf7925eace9c85153ff967926331c", size = 249993, upload-time = "2025-06-28T14:36:52.562Z" },
+ { url = "https://files.pythonhosted.org/packages/78/8e/afc23d4d59ac2969743fdabb7fbd722c0b8bf333c31b02e8594e21661755/multidict-6.6.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9bbef50bfefe84213b791c9a326d3221fa31181ba39576049a55c1eef9768109", size = 262437, upload-time = "2025-06-28T14:36:54.251Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/ec/74586ce0ebb48a7394719d5d2fda019ec7cc41e3fc01cb50ecd82cf80f6e/multidict-6.6.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d7d15b9285d439c3ca80b97b0ed6cc98a2df22c481de1848b77117563ddba14", size = 259363, upload-time = "2025-06-28T14:36:55.514Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/82/1fa2fbdc85d98b6c764b4a49e22e118b8d987f1fb5936cadfbdf091f06ef/multidict-6.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5244c5c12889d84b9b7bf22f875e52c5ba4daa89c8ab92719863a14cd76dd04d", size = 252014, upload-time = "2025-06-28T14:36:56.734Z" },
+ { url = "https://files.pythonhosted.org/packages/59/bc/21e7c4bb6e7911cac9fb41d4b295abb2d98c2123196d7c692e9e6e9f1ac4/multidict-6.6.2-cp312-cp312-win32.whl", hash = "sha256:a2ec0e52d7b298d53983cc4987fe76a25e033305f58d597fbcc1ff139b5e417e", size = 41826, upload-time = "2025-06-28T14:36:57.924Z" },
+ { url = "https://files.pythonhosted.org/packages/61/57/5bd2019d7b2a5846c75372b1a5d994358739649d8863c73c37f2f7a418a6/multidict-6.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:96d2d55c01ce4ec000a1b6eadbaa22971c91ec512819abee8b5b13f4af3fd566", size = 45920, upload-time = "2025-06-28T14:36:59.352Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c1/b1038c82ccc2e2ae3c40c912b8ee6a45ed0c9349dffdd1c3fc073f733ee9/multidict-6.6.2-cp312-cp312-win_arm64.whl", hash = "sha256:a0af3b15eab84e0d4f62a365927070d7f200db7efb8bb1e17de7c14fab5183bb", size = 43207, upload-time = "2025-06-28T14:37:00.872Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/30/7b7d121f76ea3ea7561814531e5cc19e75e9b6646818491179c2c875b591/multidict-6.6.2-py3-none-any.whl", hash = "sha256:a7d14275ff2f85a8ff3c2a32e30f94b9fc8a2125b59a4ecc32271a347fad6e78", size = 12312, upload-time = "2025-06-28T14:38:19.677Z" },
]
[[package]]
name = "mypy"
-version = "1.15.0"
+version = "1.16.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
+ { name = "pathspec" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" },
- { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" },
- { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" },
- { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" },
- { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" },
- { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" },
- { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" },
- { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" },
- { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" },
- { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" },
- { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" },
- { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" },
- { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" },
+ { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" },
+ { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" },
+ { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" },
+ { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" },
]
[[package]]
@@ -1283,6 +1215,7 @@ dependencies = [
{ name = "pyopenssl" },
{ name = "pyserial" },
{ name = "pyzmq" },
+ { name = "qrcode" },
{ name = "requests" },
{ name = "scons" },
{ name = "sentry-sdk" },
@@ -1305,8 +1238,8 @@ dev = [
{ name = "azure-storage-blob" },
{ name = "dbus-next" },
{ name = "dictdiffer" },
- { name = "lru-dict" },
{ name = "matplotlib" },
+ { name = "opencv-python-headless" },
{ name = "parameterized" },
{ name = "pyautogui" },
{ name = "pygame" },
@@ -1326,13 +1259,11 @@ docs = [
]
testing = [
{ name = "codespell" },
- { name = "coverage" },
{ name = "hypothesis" },
{ name = "mypy" },
{ name = "pre-commit-hooks" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
- { name = "pytest-cov" },
{ name = "pytest-cpp" },
{ name = "pytest-mock" },
{ name = "pytest-randomly" },
@@ -1357,7 +1288,6 @@ requires-dist = [
{ name = "casadi", specifier = ">=3.6.6" },
{ name = "cffi" },
{ name = "codespell", marker = "extra == 'testing'" },
- { name = "coverage", marker = "extra == 'testing'" },
{ name = "crcmod" },
{ name = "cython" },
{ name = "dbus-next", marker = "extra == 'dev'" },
@@ -1369,7 +1299,6 @@ requires-dist = [
{ name = "json-rpc" },
{ name = "libusb1" },
{ name = "llvmlite" },
- { name = "lru-dict", marker = "extra == 'dev'" },
{ 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'" },
@@ -1377,6 +1306,7 @@ requires-dist = [
{ name = "natsort", marker = "extra == 'docs'" },
{ name = "numpy", specifier = ">=2.0,<2.2" },
{ 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" },
@@ -1392,17 +1322,17 @@ requires-dist = [
{ name = "pyserial" },
{ name = "pytest", marker = "extra == 'testing'" },
{ name = "pytest-asyncio", marker = "extra == 'testing'" },
- { name = "pytest-cov", 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'" },
- { name = "pytest-xdist", marker = "extra == 'testing'" },
+ { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=909e97b49d12401c10608f9d777bfc9dab8a4413" },
{ name = "pytools", marker = "platform_machine != 'aarch64' and extra == 'dev'", specifier = "<2024.1.11" },
{ name = "pywinctl", marker = "extra == 'dev'" },
{ name = "pyzmq" },
+ { name = "qrcode" },
{ name = "raylib", marker = "extra == 'dev'" },
{ name = "requests" },
{ name = "rerun-sdk", marker = "extra == 'tools'", specifier = ">=0.18" },
@@ -1457,8 +1387,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 = [
@@ -1470,8 +1400,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 = [
@@ -1571,57 +1501,57 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/b8e451f6cff1c99b4
[[package]]
name = "propcache"
-version = "0.3.1"
+version = "0.3.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/07/c8/fdc6686a986feae3541ea23dcaa661bd93972d3940460646c6bb96e21c40/propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf", size = 43651, upload-time = "2025-03-26T03:06:12.05Z" }
+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/90/0f/5a5319ee83bd651f75311fcb0c492c21322a7fc8f788e4eef23f44243427/propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5", size = 80243, upload-time = "2025-03-26T03:04:01.912Z" },
- { url = "https://files.pythonhosted.org/packages/ce/84/3db5537e0879942783e2256616ff15d870a11d7ac26541336fe1b673c818/propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371", size = 46503, upload-time = "2025-03-26T03:04:03.704Z" },
- { url = "https://files.pythonhosted.org/packages/e2/c8/b649ed972433c3f0d827d7f0cf9ea47162f4ef8f4fe98c5f3641a0bc63ff/propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da", size = 45934, upload-time = "2025-03-26T03:04:05.257Z" },
- { url = "https://files.pythonhosted.org/packages/59/f9/4c0a5cf6974c2c43b1a6810c40d889769cc8f84cea676cbe1e62766a45f8/propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744", size = 233633, upload-time = "2025-03-26T03:04:07.044Z" },
- { url = "https://files.pythonhosted.org/packages/e7/64/66f2f4d1b4f0007c6e9078bd95b609b633d3957fe6dd23eac33ebde4b584/propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0", size = 241124, upload-time = "2025-03-26T03:04:08.676Z" },
- { url = "https://files.pythonhosted.org/packages/aa/bf/7b8c9fd097d511638fa9b6af3d986adbdf567598a567b46338c925144c1b/propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5", size = 240283, upload-time = "2025-03-26T03:04:10.172Z" },
- { url = "https://files.pythonhosted.org/packages/fa/c9/e85aeeeaae83358e2a1ef32d6ff50a483a5d5248bc38510d030a6f4e2816/propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256", size = 232498, upload-time = "2025-03-26T03:04:11.616Z" },
- { url = "https://files.pythonhosted.org/packages/8e/66/acb88e1f30ef5536d785c283af2e62931cb934a56a3ecf39105887aa8905/propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073", size = 221486, upload-time = "2025-03-26T03:04:13.102Z" },
- { url = "https://files.pythonhosted.org/packages/f5/f9/233ddb05ffdcaee4448508ee1d70aa7deff21bb41469ccdfcc339f871427/propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d", size = 222675, upload-time = "2025-03-26T03:04:14.658Z" },
- { url = "https://files.pythonhosted.org/packages/98/b8/eb977e28138f9e22a5a789daf608d36e05ed93093ef12a12441030da800a/propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f", size = 215727, upload-time = "2025-03-26T03:04:16.207Z" },
- { url = "https://files.pythonhosted.org/packages/89/2d/5f52d9c579f67b8ee1edd9ec073c91b23cc5b7ff7951a1e449e04ed8fdf3/propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0", size = 217878, upload-time = "2025-03-26T03:04:18.11Z" },
- { url = "https://files.pythonhosted.org/packages/7a/fd/5283e5ed8a82b00c7a989b99bb6ea173db1ad750bf0bf8dff08d3f4a4e28/propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a", size = 230558, upload-time = "2025-03-26T03:04:19.562Z" },
- { url = "https://files.pythonhosted.org/packages/90/38/ab17d75938ef7ac87332c588857422ae126b1c76253f0f5b1242032923ca/propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a", size = 233754, upload-time = "2025-03-26T03:04:21.065Z" },
- { url = "https://files.pythonhosted.org/packages/06/5d/3b921b9c60659ae464137508d3b4c2b3f52f592ceb1964aa2533b32fcf0b/propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9", size = 226088, upload-time = "2025-03-26T03:04:22.718Z" },
- { url = "https://files.pythonhosted.org/packages/54/6e/30a11f4417d9266b5a464ac5a8c5164ddc9dd153dfa77bf57918165eb4ae/propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005", size = 40859, upload-time = "2025-03-26T03:04:24.039Z" },
- { url = "https://files.pythonhosted.org/packages/1d/3a/8a68dd867da9ca2ee9dfd361093e9cb08cb0f37e5ddb2276f1b5177d7731/propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7", size = 45153, upload-time = "2025-03-26T03:04:25.211Z" },
- { url = "https://files.pythonhosted.org/packages/41/aa/ca78d9be314d1e15ff517b992bebbed3bdfef5b8919e85bf4940e57b6137/propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723", size = 80430, upload-time = "2025-03-26T03:04:26.436Z" },
- { url = "https://files.pythonhosted.org/packages/1a/d8/f0c17c44d1cda0ad1979af2e593ea290defdde9eaeb89b08abbe02a5e8e1/propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976", size = 46637, upload-time = "2025-03-26T03:04:27.932Z" },
- { url = "https://files.pythonhosted.org/packages/ae/bd/c1e37265910752e6e5e8a4c1605d0129e5b7933c3dc3cf1b9b48ed83b364/propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b", size = 46123, upload-time = "2025-03-26T03:04:30.659Z" },
- { url = "https://files.pythonhosted.org/packages/d4/b0/911eda0865f90c0c7e9f0415d40a5bf681204da5fd7ca089361a64c16b28/propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f", size = 243031, upload-time = "2025-03-26T03:04:31.977Z" },
- { url = "https://files.pythonhosted.org/packages/0a/06/0da53397c76a74271621807265b6eb61fb011451b1ddebf43213df763669/propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70", size = 249100, upload-time = "2025-03-26T03:04:33.45Z" },
- { url = "https://files.pythonhosted.org/packages/f1/eb/13090e05bf6b963fc1653cdc922133ced467cb4b8dab53158db5a37aa21e/propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7", size = 250170, upload-time = "2025-03-26T03:04:35.542Z" },
- { url = "https://files.pythonhosted.org/packages/3b/4c/f72c9e1022b3b043ec7dc475a0f405d4c3e10b9b1d378a7330fecf0652da/propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25", size = 245000, upload-time = "2025-03-26T03:04:37.501Z" },
- { url = "https://files.pythonhosted.org/packages/e8/fd/970ca0e22acc829f1adf5de3724085e778c1ad8a75bec010049502cb3a86/propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277", size = 230262, upload-time = "2025-03-26T03:04:39.532Z" },
- { url = "https://files.pythonhosted.org/packages/c4/42/817289120c6b9194a44f6c3e6b2c3277c5b70bbad39e7df648f177cc3634/propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8", size = 236772, upload-time = "2025-03-26T03:04:41.109Z" },
- { url = "https://files.pythonhosted.org/packages/7c/9c/3b3942b302badd589ad6b672da3ca7b660a6c2f505cafd058133ddc73918/propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e", size = 231133, upload-time = "2025-03-26T03:04:42.544Z" },
- { url = "https://files.pythonhosted.org/packages/98/a1/75f6355f9ad039108ff000dfc2e19962c8dea0430da9a1428e7975cf24b2/propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee", size = 230741, upload-time = "2025-03-26T03:04:44.06Z" },
- { url = "https://files.pythonhosted.org/packages/67/0c/3e82563af77d1f8731132166da69fdfd95e71210e31f18edce08a1eb11ea/propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815", size = 244047, upload-time = "2025-03-26T03:04:45.983Z" },
- { url = "https://files.pythonhosted.org/packages/f7/50/9fb7cca01532a08c4d5186d7bb2da6c4c587825c0ae134b89b47c7d62628/propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5", size = 246467, upload-time = "2025-03-26T03:04:47.699Z" },
- { url = "https://files.pythonhosted.org/packages/a9/02/ccbcf3e1c604c16cc525309161d57412c23cf2351523aedbb280eb7c9094/propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7", size = 241022, upload-time = "2025-03-26T03:04:49.195Z" },
- { url = "https://files.pythonhosted.org/packages/db/19/e777227545e09ca1e77a6e21274ae9ec45de0f589f0ce3eca2a41f366220/propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b", size = 40647, upload-time = "2025-03-26T03:04:50.595Z" },
- { url = "https://files.pythonhosted.org/packages/24/bb/3b1b01da5dd04c77a204c84e538ff11f624e31431cfde7201d9110b092b1/propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3", size = 44784, upload-time = "2025-03-26T03:04:51.791Z" },
- { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" },
+ { 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" },
]
[[package]]
name = "protobuf"
-version = "6.31.0"
+version = "6.31.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/13/48/718c1e104a2e89970a8ff3b06d87e152834b576c570a6908f8c17ba88d65/protobuf-6.31.0.tar.gz", hash = "sha256:314fab1a6a316469dc2dd46f993cbbe95c861ea6807da910becfe7475bc26ffe", size = 441644, upload-time = "2025-05-14T17:58:27.862Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b6/77/8671682038b08237c927215fa3296bc1c54e4086fe542c87017c1b626663/protobuf-6.31.0-cp310-abi3-win32.whl", hash = "sha256:10bd62802dfa0588649740a59354090eaf54b8322f772fbdcca19bc78d27f0d6", size = 423437, upload-time = "2025-05-14T17:58:16.116Z" },
- { url = "https://files.pythonhosted.org/packages/e4/07/cc9b0cbf7593f6ef8cf87fa9b0e55cd74c5cb526dd89ad84aa7d6547ef8d/protobuf-6.31.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e987c99fd634be8347246a02123250f394ba20573c953de133dc8b2c107dd71", size = 435118, upload-time = "2025-05-14T17:58:18.591Z" },
- { url = "https://files.pythonhosted.org/packages/21/46/33f884aa8bc59114dc97e0d954ca4618c556483670236008c88fbb7e834f/protobuf-6.31.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c812f0f96ceb6b514448cefeb1df54ec06dde456783f5099c0e2f8a0f2caa89", size = 425439, upload-time = "2025-05-14T17:58:19.709Z" },
- { url = "https://files.pythonhosted.org/packages/9b/f2/9a676b50229ce37b12777d7b21de90ae7bc0f9505d07e72e2e8d47b8d165/protobuf-6.31.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:67ce50195e4e584275623b8e6bc6d3d3dfd93924bf6116b86b3b8975ab9e4571", size = 321950, upload-time = "2025-05-14T17:58:22.04Z" },
- { url = "https://files.pythonhosted.org/packages/a1/a7/243fa2d3c1b7675d54744b32dacf30356f4c27c0d3ad940ca8745a1c6b2c/protobuf-6.31.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:5353e38844168a327acd2b2aa440044411cd8d1b6774d5701008bd1dba067c79", size = 320904, upload-time = "2025-05-14T17:58:23.438Z" },
- { url = "https://files.pythonhosted.org/packages/ee/01/1ed1d482960a5718fd99c82f6d79120181947cfd4667ec3944d448ed44a3/protobuf-6.31.0-py3-none-any.whl", hash = "sha256:6ac2e82556e822c17a8d23aa1190bbc1d06efb9c261981da95c71c9da09e9e23", size = 168558, upload-time = "2025-05-14T17:58:26.923Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" },
+ { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" },
+ { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" },
]
[[package]]
@@ -1802,11 +1732,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/e1/70/c7a4f46dbf06048c6
[[package]]
name = "pygments"
-version = "2.19.1"
+version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
+ { 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" },
]
[[package]]
@@ -1867,2398 +1797,2366 @@ sdist = { url = "https://files.pythonhosted.org/packages/7d/ff/4c6f31a4f08979f12
[[package]]
name = "pyobjc"
-version = "11.0"
+version = "11.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-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-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-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-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-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" },
+ { 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-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-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-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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e1/d6/27b1c9a02f6cb4954984ce1a0239618e52f78c329c7e7450bf1f219b0f0a/pyobjc-11.0.tar.gz", hash = "sha256:a8f7baed65797f67afd46290b02f652c23f4b158ddf960bce0441b78f6803418", size = 11044, upload-time = "2025-01-14T19:02:12.55Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/18/55/d0971bccf8a5a347eaccf8caa4718766a68281baab83d2b5e211b2767504/pyobjc-11.0-py3-none-any.whl", hash = "sha256:3ed5e4e993192fd7fadd42a4148d266a3587af7453ea3b240bab724d02e34e64", size = 4169, upload-time = "2025-01-14T18:46:44.385Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-core"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5c/94/a111239b98260869780a5767e5d74bfd3a8c13a40457f479c28dcd91f89d/pyobjc_core-11.0.tar.gz", hash = "sha256:63bced211cb8a8fb5c8ff46473603da30e51112861bd02c438fbbbc8578d9a70", size = 994931, upload-time = "2025-01-14T19:02:13.938Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/52/05/fa97309c3b1bc1ec90d701db89902e0bd5e1024023aa2c5387b889458b1b/pyobjc_core-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:50675c0bb8696fe960a28466f9baf6943df2928a1fd85625d678fa2f428bd0bd", size = 727295, upload-time = "2025-01-14T18:46:50.208Z" },
- { url = "https://files.pythonhosted.org/packages/56/ce/bf3ff9a9347721a398c3dfb83e29b43fb166b7ef590f3f7b7ddcd283df39/pyobjc_core-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a03061d4955c62ddd7754224a80cdadfdf17b6b5f60df1d9169a3b1b02923f0b", size = 739750, upload-time = "2025-01-14T18:46:53.039Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-accessibility"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/b5/61/7484cc4ad3aa7854cd4c969379a5f044261259d08f7c20b6718493b484f9/pyobjc_framework_accessibility-11.0.tar.gz", hash = "sha256:097450c641fa9ac665199762e77867f2a82775be2f749b8fa69223b828f60656", size = 44597, upload-time = "2025-01-14T19:02:17.596Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/52/2e/babcd02cd833c0aba34e10c34a2184021b8a3c7cb45d1ae806156c2b519d/pyobjc_framework_Accessibility-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3a751d17b288bb56a98a10b52b253b3002c885fe686b604788acac1e9739437", size = 10948, upload-time = "2025-01-14T18:47:34.37Z" },
- { url = "https://files.pythonhosted.org/packages/e8/ea/da3f982eeaffb80efb480892106caa19a2c9c8b8954570837ddbcd983520/pyobjc_framework_Accessibility-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:34536f3d60aeda618b384b1207a8c6f9978de278ce229c3469ef14fd27a3befa", size = 10962, upload-time = "2025-01-14T18:47:35.313Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-accounts"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c2/fa/b64f3f02e0a8b189dc07c391546e2dbe30ef1b3515d1427cdab743545b90/pyobjc_framework_accounts-11.0.tar.gz", hash = "sha256:afc4ae277be1e3e1f90269001c2fd886093a5465e365d7f9a3a0af3e17f06210", size = 17340, upload-time = "2025-01-14T19:02:18.625Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/45/5dfc72c82087d458ce7ddb17a338a38ae1848e72620537f31ed97192c65e/pyobjc_framework_Accounts-11.0-py2.py3-none-any.whl", hash = "sha256:3e4b494e1158e3250e4b4a09e9ff33b38f82d31aefe50dd47152c4a20ecdeec4", size = 5035, upload-time = "2025-01-14T18:47:40.92Z" },
- { url = "https://files.pythonhosted.org/packages/96/96/39b0cc9ced1180a93c75924a06598f24d0a7554b3e8ddfcb0828c0957476/pyobjc_framework_Accounts-11.0-py3-none-any.whl", hash = "sha256:ad0e378bd07ca7c88b45cda63b85424bc344e81ea44c0ae7327872d91cad311a", size = 5104, upload-time = "2025-01-14T18:47:41.967Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-addressbook"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/68/ef/5b5f6b61907ae43509fbf1654e043115d9a64d97efdc28fbb90d06c199f6/pyobjc_framework_addressbook-11.0.tar.gz", hash = "sha256:87073c85bb342eb27faa6eceb7a0e8a4c1e32ad1f2b62bb12dafb5e7b9f15837", size = 97116, upload-time = "2025-01-14T19:02:19.527Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2d/7a/8b874a52ff57dad999330ac1f899e6df8e35cec2cad8a90d8002d3c5f196/pyobjc_framework_AddressBook-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af7de23aac7571a3b9dad5b2881d8f186653aa72903db8d7dbfd2c7b993156b9", size = 13010, upload-time = "2025-01-14T18:47:47.925Z" },
- { url = "https://files.pythonhosted.org/packages/0c/b4/93de1195c22cbaf4996aeb6d55e79fc7d76311cacfe8fd716c70fb20e391/pyobjc_framework_AddressBook-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3b634ef80920ab9208f2937527e4a498e7afa6e2ceb639ebb483387ab5b9accc", size = 13039, upload-time = "2025-01-14T18:47:49.509Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-adservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/51/7c/0c6e01f83b0c5c7968564a40146f4d07080df278457bdb5a982c8f26a74d/pyobjc_framework_adservices-11.0.tar.gz", hash = "sha256:d2e1a2f395e93e1bbe754ab0d76ce1d64c0d3928472634437e0382eafc6765cd", size = 12732, upload-time = "2025-01-14T19:02:20.559Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1d/10/601c9f5a07450ce75e166042d9ac5efe6286ac2d15212885a920260af9e3/pyobjc_framework_AdServices-11.0-py2.py3-none-any.whl", hash = "sha256:7cd1458f60175cd46bd88061c20e82f04b2576fc00bc5d54d67c18dcb870e27f", size = 3420, upload-time = "2025-01-14T18:47:42.812Z" },
- { url = "https://files.pythonhosted.org/packages/89/40/98a9116790e163d6c9ac0d19ce66307b03f9ac5ee64631db69899457b154/pyobjc_framework_AdServices-11.0-py3-none-any.whl", hash = "sha256:6426d4e4a43f5ee5ce7bab44d85647dbded3e17c0c62d8923cebaf927c4162ca", size = 3486, upload-time = "2025-01-14T18:47:43.845Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-adsupport"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/0c/07/b8b5f741d1e2cad97100444b255e6ecaca3668e7414039981799aa330035/pyobjc_framework_adsupport-11.0.tar.gz", hash = "sha256:20eb8a683d34fb7a6efeceaf964a24b88c3434875c44f66db5e1b609e678043a", size = 12819, upload-time = "2025-01-14T19:02:23.032Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6f/7f/2023c0a973f8823175c7e409fdbd306b275b0bb2723acf12ffade6ba5dbe/pyobjc_framework_AdSupport-11.0-py2.py3-none-any.whl", hash = "sha256:59161f5046def176d3aa6fdd6a05916029ca69ac69f836c67e0dd780a5efcf0f", size = 3334, upload-time = "2025-01-14T18:47:44.747Z" },
- { url = "https://files.pythonhosted.org/packages/cf/84/26c4275732952416603026888ca5462ed84372d412d0ccd7a1c750c01673/pyobjc_framework_AdSupport-11.0-py3-none-any.whl", hash = "sha256:91ba05eb5602911287bd04b0efefb7a485f9af255095b87c3e77bb7d1d1242ed", size = 3405, upload-time = "2025-01-14T18:47:45.767Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-applescriptkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/14/c3/d7f9a33de7ab8e3950350e0862214e66f27ed6bff1a491bc391c377ab83e/pyobjc_framework_applescriptkit-11.0.tar.gz", hash = "sha256:4bafac4a036f0fb8ba01488b8e91d3ac861ce6e61154ffbd0b26f82b99779b50", size = 12638, upload-time = "2025-01-14T19:02:25.1Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/97/4b/5e7f6a182129be6f229ee6c036d84359b46b0f5f695824315c47b19d3149/pyobjc_framework_AppleScriptKit-11.0-py2.py3-none-any.whl", hash = "sha256:e8acc5ca99f5123ec4e60cb356c7cc407d5fe533ca53e5fa341b51f65495973b", size = 4246, upload-time = "2025-01-14T18:47:59.508Z" },
- { url = "https://files.pythonhosted.org/packages/b6/ce/7965604f553c91fbd5602e17057b0935c100542abaf76291921335b6f75c/pyobjc_framework_AppleScriptKit-11.0-py3-none-any.whl", hash = "sha256:92cffd943a4d17f684bb51245744e9d0bb8992b2967125845dfeab09d26fc624", size = 4317, upload-time = "2025-01-14T18:48:02.221Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-applescriptobjc"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/fb/9f/bb4fdbcea418f8472d7a67d4d2e4a15fca11fed04648db5208b0fce84807/pyobjc_framework_applescriptobjc-11.0.tar.gz", hash = "sha256:baff9988b6e886aed0e76441358417707de9088be5733f22055fed7904ca1001", size = 12675, upload-time = "2025-01-14T19:02:25.947Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b8/7d/b3e28759df060f26a31407282e789a1a321612afcee3871134fdac8dc75f/pyobjc_framework_AppleScriptObjC-11.0-py2.py3-none-any.whl", hash = "sha256:a4c8d417fdb64180a283eadf8ddb804ba7f9e3cef149216a11b65e1d3509c55b", size = 4347, upload-time = "2025-01-14T18:48:03.193Z" },
- { url = "https://files.pythonhosted.org/packages/0d/e7/c080a1cd77ce04e3bf4079a941105d3d670b9ba0fc91a54d4a1764bea02d/pyobjc_framework_AppleScriptObjC-11.0-py3-none-any.whl", hash = "sha256:681006b0cdf0279cd06b6d0f62b542b7f3b3b9b5d2391f7aa3798d8b355d67bf", size = 4416, upload-time = "2025-01-14T18:48:04.219Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-applicationservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coretext" },
- { name = "pyobjc-framework-quartz" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ba/fb/4e42573b0d3baa3fa18ec53614cf979f951313f1451e8f2e17df9429da1f/pyobjc_framework_applicationservices-11.0.tar.gz", hash = "sha256:d6ea18dfc7d5626a3ecf4ac72d510405c0d3a648ca38cae8db841acdebecf4d2", size = 224334, upload-time = "2025-01-14T19:02:26.828Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/99/37/3d4dc6c004aaeb67bd43f7261d7c169ff45b8fc0eefbc7ba8cd6b0c881bc/pyobjc_framework_ApplicationServices-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61a99eef23abb704257310db4f5271137707e184768f6407030c01de4731b67b", size = 30846, upload-time = "2025-01-14T18:48:06.286Z" },
- { url = "https://files.pythonhosted.org/packages/74/a9/7a45a67e126d32c61ea22ffd80e87ff7e05b4acf32bede6cce071fbfffc8/pyobjc_framework_ApplicationServices-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5fbeb425897d6129471d451ec61a29ddd5b1386eb26b1dd49cb313e34616ee21", size = 30908, upload-time = "2025-01-14T18:48:07.177Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-apptrackingtransparency"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/36/40/c1c48ed49b5e55c7a635aa1e7ca41ffa1c5547e26243f26489c4768cd730/pyobjc_framework_apptrackingtransparency-11.0.tar.gz", hash = "sha256:cd5c834b5b19c21ad6c317ba5d29f30a8d0ae5d14e7cf557da22abc0850f1e91", size = 13385, upload-time = "2025-01-14T19:02:29.226Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c4/72/6e460cd763a3048c4d75769ed60a5af7832122b78224f710e40a9eb1c5cf/pyobjc_framework_AppTrackingTransparency-11.0-py2.py3-none-any.whl", hash = "sha256:1bf6d4f148d9f5d5befe90fcfd88ce988458a52719d53d5989b08e4fbed58864", size = 3805, upload-time = "2025-01-14T18:47:57.492Z" },
- { url = "https://files.pythonhosted.org/packages/33/cb/ef2622ee08349293aae6f81216cfee2423ad37d8a1d14ba4690b537d8850/pyobjc_framework_AppTrackingTransparency-11.0-py3-none-any.whl", hash = "sha256:347f876aea9d9f47d9fbf6dfa6d3f250ecd46f56a7c4616386327061e2ecc4e9", size = 3878, upload-time = "2025-01-14T18:47:58.595Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-audiovideobridging"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/89/5f/0bd5beded0415b53f443da804410eda6a53e1bc64f8779ed9a592719da8c/pyobjc_framework_audiovideobridging-11.0.tar.gz", hash = "sha256:dbc45b06418dd780c365956fdfd69d007436b5ee54c51e671196562eb8290ba6", size = 72418, upload-time = "2025-01-14T19:02:30.083Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3c/33/2ee33542febb40174d40ae8bbdf672b4e438a3fb41ba6a4d4a3e6800121b/pyobjc_framework_AudioVideoBridging-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d025e49ca6238be96d0a1c22942b548a8d445ef8eb71259b4769e119810f42c6", size = 10944, upload-time = "2025-01-14T18:48:11.978Z" },
- { url = "https://files.pythonhosted.org/packages/45/ea/db8295e17b0b544b06620e4019afcc76d7b743a8f03cb8a1024b2bc118ac/pyobjc_framework_AudioVideoBridging-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d414ecffeb23cddc8e64262af170e663c93e8d462d18aa7067d4584069967859", size = 10962, upload-time = "2025-01-14T18:48:12.953Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-authenticationservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/31/0f/2de0d941e9c9b2eb1ce8b22eb31adc7227badfe1e53f615431d3a7fdcd48/pyobjc_framework_authenticationservices-11.0.tar.gz", hash = "sha256:6a060ce651df142e8923d1383449bc6f2c7f5eb0b517152dac609bde3901064e", size = 140036, upload-time = "2025-01-14T19:02:31.115Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/45/16/6246cf10e2d245f4018c02f351f8eb4cc93823f6e7e4dd584ab292cda786/pyobjc_framework_AuthenticationServices-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e3b23478cf8995883acfe6c1a24503c84caf2f8dbe540377fe19fb787ce9b2", size = 20079, upload-time = "2025-01-14T18:48:19.023Z" },
- { url = "https://files.pythonhosted.org/packages/b7/ca/81a55a0714e73695b536bfbcbf0f5ddf68da9485b468406f6ef887a04938/pyobjc_framework_AuthenticationServices-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1779f72c264f749946fcbfba0575a985c1e297d426617739a533554dbf172f9a", size = 20105, upload-time = "2025-01-14T18:48:19.945Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-automaticassessmentconfiguration"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/09/d5/5febfee260b88e426c7e799cc95990818feeaa9f740fb9dd516559c96520/pyobjc_framework_automaticassessmentconfiguration-11.0.tar.gz", hash = "sha256:5d3691af2b94e44ca594b6791556e15a9f0a3f9432df51cb891f5f859a65e467", size = 24420, upload-time = "2025-01-14T19:02:32.101Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/50/3f/b593adce2f5b9f9427d784db56d8195adc2cfb340d4d3578914539a17faa/pyobjc_framework_AutomaticAssessmentConfiguration-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:25f2db399eb0a47e345d0471c7af930f5a3be899ba6edb40bd9125719e4b526f", size = 9015, upload-time = "2025-01-14T18:48:25.686Z" },
- { url = "https://files.pythonhosted.org/packages/4e/c3/b6b779d783dcf3667a2011d8af0d801f6639df9735cdc34c6e6b79822298/pyobjc_framework_AutomaticAssessmentConfiguration-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b6433452d2c4cdb0eef16cc78a24ba9c61efb5bb04709ee10ca94b69119e889c", size = 9034, upload-time = "2025-01-14T18:48:26.58Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-automator"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/25/1b/1ba4eb296c3915f2e367e45470cb310a9c78b4dd65a37bd522f458f245aa/pyobjc_framework_automator-11.0.tar.gz", hash = "sha256:412d330f8c6f30066cad15e1bdecdc865510bbce469cc7d9477384c4e9f2550f", size = 200905, upload-time = "2025-01-14T19:02:33.039Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7b/2b/bfe673491042849ad400bebf557b8047317757283b98ad9921fbb6f6cae9/pyobjc_framework_Automator-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:850f9641a54cc8d9a3d02c2d87a4e80aed2413b37aa6c26a7046088b77da5b42", size = 9811, upload-time = "2025-01-14T18:48:31.987Z" },
- { url = "https://files.pythonhosted.org/packages/13/00/e60db832c536fd354fab7e813ee781327358e6bcbc4cacbd9695dade7006/pyobjc_framework_Automator-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb1b9b16873ec1d2f8af9a04ca1b2fcaa324ce4a1fada0d02fa239f6fecf773b", size = 9827, upload-time = "2025-01-14T18:48:32.958Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-avfoundation"
-version = "11.0"
+version = "11.1"
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 = "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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/76/06/018ad0e2a38dbdbc5c126d7ce37488c4d581d4e2a2b9ef678162bb36d5f6/pyobjc_framework_avfoundation-11.0.tar.gz", hash = "sha256:269a592bdaf8a16948d8935f0cf7c8cb9a53e7ea609a963ada0e55f749ddb530", size = 871064, upload-time = "2025-01-14T19:02:35.757Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/44/b5/327654548fa210b4637350de016183fbb1f6f8f9213d2c6c9b492eb8b44c/pyobjc_framework_AVFoundation-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87db350311c1d7e07d68036cdde3d01c09d97b8ba502241c0c1699d7a9c6f2e4", size = 71345, upload-time = "2025-01-14T18:47:04.589Z" },
- { url = "https://files.pythonhosted.org/packages/f5/36/e09b20f280953fa7be95a9266e5ad75f2e8b184cc39260c0537b3e60b534/pyobjc_framework_AVFoundation-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bb6f4be53c0fb42bee3f46cf0bb5396a8fd13f92d47a01f6b77037a1134f26b", size = 71314, upload-time = "2025-01-14T18:47:05.616Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-avkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/de/79/5b2fcb94b051da32a24b54bb0d90b1d01b190e1402b6303747de47fb17ac/pyobjc_framework_avkit-11.0.tar.gz", hash = "sha256:5fa40919320277b820df3e4c6e84cba91ef7221a28f4eb5374e3dbd80d1e521a", size = 46311, upload-time = "2025-01-14T19:02:37.018Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/ae/aed1023150483a288922c447e1997f4f4e9d0460038e1a070a3a53b85c19/pyobjc_framework_AVKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16b5560860c1e13e692c677ad04d8e194d0b9931dd3f15e3df4dbd7217cc8ab1", size = 11960, upload-time = "2025-01-14T18:47:15.738Z" },
- { url = "https://files.pythonhosted.org/packages/01/f4/08684e5af2a2e8940e6411e96ef1d7ed1e51a121abb19c93c25c34969213/pyobjc_framework_AVKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4da468b97bb7f356024e31647619cd1cd435b543e467209da0ee0abdfdc7121", size = 11969, upload-time = "2025-01-14T18:47:16.602Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-avrouting"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d5/80/63680dc7788bc3573a20fc5421dfcf606970a0cd3b2457829d9b66603ae0/pyobjc_framework_avrouting-11.0.tar.gz", hash = "sha256:54ec9ea0b5adb5149b554e23c07c6b4f4bdb2892ca2ed7b3e88a5de936313025", size = 20561, upload-time = "2025-01-14T19:02:38.157Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/9c/ea6924de09e13f858210d6dd934f00773b1e3db6af886c72841ed545560f/pyobjc_framework_AVRouting-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:54e58cd0292f734aba035599f37a0c00f03761e9ff5cf53a0857cec7949bb39c", size = 8067, upload-time = "2025-01-14T18:47:26.354Z" },
- { url = "https://files.pythonhosted.org/packages/3f/92/774e10af5aba5742c4a2dd563fa819550d9caa755d2648b3cc87bbe30129/pyobjc_framework_AVRouting-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:779db3fb0048b22c5dcf5871930025c0fd93068f87946e8053f31a3366fa6fb0", size = 8078, upload-time = "2025-01-14T18:47:28.53Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-backgroundassets"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a3/17/83b873069b0c0763365de88648ad4a2472e9e96fcac39fa534f3633552e8/pyobjc_framework_backgroundassets-11.0.tar.gz", hash = "sha256:9488c3f86bf427898a88b7100e77200c08a487a35c75c1b5735bd69c57ba38cb", size = 23658, upload-time = "2025-01-14T19:02:42.665Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e9/9d/bea4408649199340ec7ed154bbaa1942a0b0955006b3153088b3f35e6ff6/pyobjc_framework_BackgroundAssets-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:812bcc4eaf71c1cc42e94edc2b5ad0414d16cfe1da5c421edd9382417d625f06", size = 9499, upload-time = "2025-01-14T18:48:39.156Z" },
- { url = "https://files.pythonhosted.org/packages/bd/79/726c14fd26553c8bbe8b2ed55caa45d89093e2e85b45c1b598dd04ea7589/pyobjc_framework_BackgroundAssets-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:96b3fc40c514867d4a0b3ad4d256bc5134d789e22fa306a6b21e49ecadc51698", size = 9521, upload-time = "2025-01-14T18:48:40.063Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-browserenginekit"
-version = "11.0"
+version = "11.1"
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 = "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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9f/2e/df3d2f7e53132d398c2922d331dd1d2aa352997a1a4a1390e59db51c1d13/pyobjc_framework_browserenginekit-11.0.tar.gz", hash = "sha256:51971527f5103c0e09a4ef438c352ebb037fcad8971f8420a781c72ee421f758", size = 31352, upload-time = "2025-01-14T19:02:45.499Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/da/6d/6aa929d4993453817523db9c82a4e6e2cce7104fa59e29ee857f9e926b0d/pyobjc_framework_BrowserEngineKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:58494bc3ccc21a63751b7c9f8788d0240c3f1aad84cf221c0e42c9764a069ba4", size = 10913, upload-time = "2025-01-14T18:48:44.801Z" },
- { url = "https://files.pythonhosted.org/packages/a8/2f/dd18f7ff9438ad4612febfbdb2e4bded37033347b9f0e1355df76f2c5213/pyobjc_framework_BrowserEngineKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0925edfd60a24f53819cfd11f07926fd42bc0fbeb7a4982998a08742e859dbff", size = 10933, upload-time = "2025-01-14T18:48:45.673Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-businesschat"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5a/f2/4541989f2c9c5fc3cdfc94ebf31fc6619554b6c22dafdbb57f866a392bc1/pyobjc_framework_businesschat-11.0.tar.gz", hash = "sha256:20fe1c8c848ef3c2e132172d9a007a8aa65b08875a9ca5c27afbfc4396b16dbb", size = 12953, upload-time = "2025-01-14T19:02:46.378Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d4/5b/d7313368ea4056092400c7a4ed5c705d3d21a443641d98b140054edbd930/pyobjc_framework_BusinessChat-11.0-py2.py3-none-any.whl", hash = "sha256:1f732fdace31d2abdd14b3054f27a5e0f4591c7e1bef069b6aeb4f9c8d9ec487", size = 3408, upload-time = "2025-01-14T18:48:51.116Z" },
- { url = "https://files.pythonhosted.org/packages/8a/e6/c82e2eb2b4ad4407f1ada6d41ef583eb211cce88ffcc2e05c826760f721d/pyobjc_framework_BusinessChat-11.0-py3-none-any.whl", hash = "sha256:47a2e4da9b061daa89a6367cb0e6bb8cdea0627379dd6d5095a8fd20243d8613", size = 3477, upload-time = "2025-01-14T18:48:52.723Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-calendarstore"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9f/d3/722c1b16c7d9bdd5c408735c15193e8396f2d22ab6410b0af4569f39c46e/pyobjc_framework_calendarstore-11.0.tar.gz", hash = "sha256:40173f729df56b70ec14f9680962a248c3ce7b4babb46e8b0d760a13975ef174", size = 68475, upload-time = "2025-01-14T19:02:48.544Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e3/e1/02bda98aae43957943adb09700265603f8ff8ff2197e57b082237a8e1a8f/pyobjc_framework_CalendarStore-11.0-py2.py3-none-any.whl", hash = "sha256:67ddc18c96bba42118fc92f1117b053c58c8888edb74193f0be67a10051cc9e2", size = 5183, upload-time = "2025-01-14T18:49:01.649Z" },
- { url = "https://files.pythonhosted.org/packages/a2/5b/922df21b738e8d349df27b2a73eaf8bba93c84c8c4d0d133fdd5de2ff236/pyobjc_framework_CalendarStore-11.0-py3-none-any.whl", hash = "sha256:9b310fe66ac12e0feb7c8e3166034bec357a45f7f8b8916e93eddc6f199d08c8", size = 5251, upload-time = "2025-01-14T18:49:03.224Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-callkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e4/0a/9d39ebac92006960b8059f664d8eb7b9cdb8763fe4e8102b2d24b853004f/pyobjc_framework_callkit-11.0.tar.gz", hash = "sha256:52e44a05d0357558e1479977ed2bcb325fabc8d337f641f0249178b5b491fc59", size = 39720, upload-time = "2025-01-14T19:02:50.697Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/22/86/8d7dc24702ae810b6230d8b2cebb1c31e12abc31507095b1a9655715c921/pyobjc_framework_CallKit-11.0-py2.py3-none-any.whl", hash = "sha256:f19d94b61ecd981f4691fd244f536f947687b872ac793ccc2b3122b3854e887a", size = 5248, upload-time = "2025-01-14T18:49:05.438Z" },
- { url = "https://files.pythonhosted.org/packages/25/bd/ff89f7e5438c767fc43f603bee42a447315be48a09f64b9aa4da719ecdfc/pyobjc_framework_CallKit-11.0-py3-none-any.whl", hash = "sha256:95394b7f7a50916debe4f7a884ce9135d11733a14e07a8c502171e77bd0087a4", size = 5314, upload-time = "2025-01-14T18:49:06.459Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-carbon"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/22/15/51964f36a8ae1002b16d213d2e5ba11cc861bdd9369f1e3f116350d788c5/pyobjc_framework_carbon-11.0.tar.gz", hash = "sha256:476f690f0b34aa9e4cb3923e61481aefdcf33e38ec6087b530a94871eee2b914", size = 37538, upload-time = "2025-01-14T19:02:51.62Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/93/fb/e5724934c3a2bbed4fbda4230e15a8b7b86313b39491876647300cb4fb11/pyobjc_framework_Carbon-11.0-py2.py3-none-any.whl", hash = "sha256:beef5095269d8e5427e09f9687963515c1b79fbf6927ff756a8414445892987d", size = 4700, upload-time = "2025-01-14T18:49:07.341Z" },
- { url = "https://files.pythonhosted.org/packages/1a/3d/b53c2d8949067f3f45491e250620e437569f1b4e6a028f2f5e721726283e/pyobjc_framework_Carbon-11.0-py3-none-any.whl", hash = "sha256:9a269042e8f5705897ac64d2b48515ba055462c88460cf140f5d8d4b8c806a42", size = 4768, upload-time = "2025-01-14T18:49:10.256Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-cfnetwork"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/4f/36/7cebdfb621c7d46eeab3173256bc2e1cba1bbbbe6c0ac8aeb9a4fe2a4627/pyobjc_framework_cfnetwork-11.0.tar.gz", hash = "sha256:eb742fc6a42b248886ff09c3cf247d56e65236864bbea4264e70af8377948d96", size = 78532, upload-time = "2025-01-14T19:02:52.777Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d9/85/11047cfe2d31c242694d780783f0dea81d73cbb09929c7d4b918ce2d29bf/pyobjc_framework_CFNetwork-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e6905c86ccb5608f4153aacb931758ad39af8b708fcebb497431f9741f39e6d", size = 18988, upload-time = "2025-01-14T18:48:54.869Z" },
- { url = "https://files.pythonhosted.org/packages/3e/6e/7d90c329030e7dd6ebbec217434820ff6158a3af9906e2abbb43e9b685d6/pyobjc_framework_CFNetwork-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5f61010503073e3518e29d440079a7c0b40aef91be6d3c2032e492c21bada80b", size = 19144, upload-time = "2025-01-14T18:48:55.864Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-cinematic"
-version = "11.0"
+version = "11.1"
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 = "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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/33/ef/b5857d567cd6e0366f61c381ebea52383b98d1ac03341f39e779a085812a/pyobjc_framework_cinematic-11.0.tar.gz", hash = "sha256:94a2de8bf3f38bd190311b6bf98d1e2cea7888840b3ce3aa92e464c0216a5cdb", size = 25740, upload-time = "2025-01-14T19:02:54.95Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/55/cf/a60e131bddf5cced32a3c0050d264f2255d63c45be398cede1db03ea8b51/pyobjc_framework_Cinematic-11.0-py2.py3-none-any.whl", hash = "sha256:281721969978d726ded9bae38c4acd6713495c399025ff2b4179fc02ec68b336", size = 4508, upload-time = "2025-01-14T18:49:11.202Z" },
- { url = "https://files.pythonhosted.org/packages/09/a8/4ea347c1fc5774e2bbe7bb688fc625d583103d1e212f7b896ed19d14844b/pyobjc_framework_Cinematic-11.0-py3-none-any.whl", hash = "sha256:3a24f3528d7f77637f51fd1862cc8c79e4d0da4ba6fd3dd02b54adddec365826", size = 4580, upload-time = "2025-01-14T18:49:12.251Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-classkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f5/81/126075eaf5ccf254ddb4cfd99d92a266c30803c5b4572ea3a920fd85e850/pyobjc_framework_classkit-11.0.tar.gz", hash = "sha256:dc5b3856612cafdc7071fbebc252b8908dbf2433e0e5ddb15a0bcd1ee282d27c", size = 39301, upload-time = "2025-01-14T19:02:55.779Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a8/77/2e31bcf1e9b63f6723c01329c1191ac163e79b0f548b7cd92414115c26ff/pyobjc_framework_ClassKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:723a07591e1e40380c339b58033e8491e58be4080c0f77a26be0728f1c5025c8", size = 8776, upload-time = "2025-01-14T18:49:14.05Z" },
- { url = "https://files.pythonhosted.org/packages/68/87/f566c4f1ffd1e383c7b38cd22753dfef0863f30bfdb0b3c5102293057fc2/pyobjc_framework_ClassKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7c7ff2eb8a9d87cb69618668e96c41ed9467fd4b4a8fef517c49923c0f6418e6", size = 8794, upload-time = "2025-01-14T18:49:14.985Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-cloudkit"
-version = "11.0"
+version = "11.1"
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 = "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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/89/6c/b0709fed7fc5a1e81de311b9273bb7ba3820a636f8ba880e90510bb6d460/pyobjc_framework_cloudkit-11.0.tar.gz", hash = "sha256:e3f6bf2c3358dd394174b1e69fcec6859951fcd15f6433c6fa3082e3b7e2656d", size = 123034, upload-time = "2025-01-14T19:02:56.769Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c2/db/9f914422be88eb2c917d67aebac9dde2e272ea1b510ca1e0db17a09db125/pyobjc_framework_CloudKit-11.0-py2.py3-none-any.whl", hash = "sha256:10cb153d7185dd260d21596f75fca8502236f6afd8e72e866cff8acd9c025f14", size = 10785, upload-time = "2025-01-14T18:49:21.369Z" },
- { url = "https://files.pythonhosted.org/packages/53/73/239581763a1bd56475ebd9bdde52a79cf0b6cac20b3d4442283b1ef8705c/pyobjc_framework_CloudKit-11.0-py3-none-any.whl", hash = "sha256:b2376d92d5822ce7e4feefcffdc3f4d1d230929f1735793da6d36b52b161b552", size = 10854, upload-time = "2025-01-14T18:49:23.612Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-cocoa"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c5/32/53809096ad5fc3e7a2c5ddea642590a5f2cb5b81d0ad6ea67fdb2263d9f9/pyobjc_framework_cocoa-11.0.tar.gz", hash = "sha256:00346a8cb81ad7b017b32ff7bf596000f9faa905807b1bd234644ebd47f692c5", size = 6173848, upload-time = "2025-01-14T19:03:00.125Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/23/97/81fd41ad90e9c241172110aa635a6239d56f50d75923aaedbbe351828580/pyobjc_framework_Cocoa-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3ea7be6e6dd801b297440de02d312ba3fa7fd3c322db747ae1cb237e975f5d33", size = 385534, upload-time = "2025-01-14T18:49:27.898Z" },
- { url = "https://files.pythonhosted.org/packages/5b/8d/0e2558447c26b3ba64f7c9776a5a6c9d2ae8abf9d34308b174ae0934402e/pyobjc_framework_Cocoa-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:280a577b83c68175a28b2b7138d1d2d3111f2b2b66c30e86f81a19c2b02eae71", size = 385811, upload-time = "2025-01-14T18:49:29.259Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-collaboration"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6b/ee/1f6893eb882af5ecc6a6f4182b2ec85df777c4bc6b9a20a6b42c23abff3f/pyobjc_framework_collaboration-11.0.tar.gz", hash = "sha256:9f53929dd6d5b1a5511494432bf83807041c6f8b9ab6cf6ff184eee0b6f8226f", size = 17084, upload-time = "2025-01-14T19:03:01.98Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c1/ee/95883b6fbdbeecd99217c50c415ca024db5beb1923b935189a113412203d/pyobjc_framework_Collaboration-11.0-py2.py3-none-any.whl", hash = "sha256:acf11e584e21f6342e6d7be1675f36c92804082c29d2f373d1ca623a63959e76", size = 4807, upload-time = "2025-01-14T18:49:37.145Z" },
- { url = "https://files.pythonhosted.org/packages/c0/e5/d3ba7e3e3f306ba93c021c083287c668704d84605e0f788583abcfde815f/pyobjc_framework_Collaboration-11.0-py3-none-any.whl", hash = "sha256:e7789503ea9280ba365ce2c4e6c7c8b13dfa9174b2ecf9d174bbf9773f25f97a", size = 4876, upload-time = "2025-01-14T18:49:39.887Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-colorsync"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9a/24/397a80cd2313cc9e1b73b9acb1de66b740bbece4fe87ed4ea158de8fcef8/pyobjc_framework_colorsync-11.0.tar.gz", hash = "sha256:4f531f6075d9cc4b9d426620a1b04d3aaeb56b5ff178d0a6b0e93d068a5db0d2", size = 39249, upload-time = "2025-01-14T19:03:02.887Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/78/16/d806b5c3ff5bf8f46a4770f89b2076d2596c1301c851c60bb43aea457cd3/pyobjc_framework_ColorSync-11.0-py2.py3-none-any.whl", hash = "sha256:24f5c3e0987bfdfe6a0de36f2f908e30ea52000eb649db7b0373928140518163", size = 5916, upload-time = "2025-01-14T18:49:41.273Z" },
- { url = "https://files.pythonhosted.org/packages/06/18/777bad37aab42f75d2ef2efb9240308c15c33b3a0636278111ec6c5df550/pyobjc_framework_ColorSync-11.0-py3-none-any.whl", hash = "sha256:cbee2211f64be927eb4e4717bf6e275bf28954ed86e4a4655d367c30f856494d", size = 5987, upload-time = "2025-01-14T18:49:42.286Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-contacts"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f5/a2/89053853b28c1f2f2e69092d3e81b7c26073bc8396fc87772b3b1bfb9d57/pyobjc_framework_contacts-11.0.tar.gz", hash = "sha256:fc215baa9f66dbf9ffa1cb8170d102a3546cfd708b2b42de4e9d43645aec03d9", size = 84253, upload-time = "2025-01-14T19:03:03.743Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/91/4f/b7a7b08535015494940a62fd63825eccf4cace7f8ca87050f0837470eca8/pyobjc_framework_Contacts-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b16758fc1edc40f0ec288d67b7e39b59609fb1df2523f4362c958d150619dbe5", size = 11880, upload-time = "2025-01-14T18:49:44.316Z" },
- { url = "https://files.pythonhosted.org/packages/07/4b/0d2b41a32b6432182548cb84bb6b1c3228a7ff428ea15dfaf812b39c028f/pyobjc_framework_Contacts-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:80972851e2163b94d82fd4b0d9801790ad420dffa91a37c90fa2949031881c02", size = 11957, upload-time = "2025-01-14T18:49:46.952Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-contactsui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-contacts" },
+ { 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/67/122b16fd7f2da7f0f48c1d7fcaf0f1951253ddd5489d909a1b5fb80f3925/pyobjc_framework_contactsui-11.0.tar.gz", hash = "sha256:d0f2a4afea807fbe4db1518c4f81f0dc9aa1817fe7cb16115308fc00375a70db", size = 19486, upload-time = "2025-01-14T19:03:04.72Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fc/47/b1dbe48c64e2d061bf8b4ee532413b97e6c5748fdba43598a30421086fcc/pyobjc_framework_ContactsUI-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd3efaf3f67e92704f41927c5de06ccc4aa9daa09865cba7ac476da9c6e1c3c2", size = 7734, upload-time = "2025-01-14T18:49:52.765Z" },
- { url = "https://files.pythonhosted.org/packages/5d/c5/465656c744301bfb7640e4077c57170d245843311e0e66702b53295e2534/pyobjc_framework_ContactsUI-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:da9c85dccdf518a0ac80c627daca32d56a4636e3f118359579de51a428e85ba7", size = 7739, upload-time = "2025-01-14T18:49:55.507Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coreaudio"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/31/e6/3b7a8af3defec012d6cacf277fd8d5c3e254ceace63a05447dc1119f3a7e/pyobjc_framework_coreaudio-11.0.tar.gz", hash = "sha256:38b6b531381119be6998cf704d04c9ea475aaa33f6dd460e0584351475acd0ae", size = 140507, upload-time = "2025-01-14T19:03:05.612Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/78/f8/6f583376d2ef6a6123141d310f7f7e3e93ba9129ffbbc6eb26e25c4289c5/pyobjc_framework_CoreAudio-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:143cd44d5c069aee1abc5a88794e9531250b9fe70a98f6a08e493184dcf64b3e", size = 35750, upload-time = "2025-01-14T18:50:00.665Z" },
- { url = "https://files.pythonhosted.org/packages/df/14/b33556c06529a3c54853c41c5163e30a3fb9b2ae920e0c65a42ccd82e279/pyobjc_framework_CoreAudio-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d26eac5bc325bf046fc0bfdaa3322ddc828690dab726275f1c4c118bb888cc00", size = 36584, upload-time = "2025-01-14T18:50:01.806Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coreaudiokit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coreaudio" },
+ { 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/ef/1a/604cac8d992b6e66adbb98edb1f65820116f5d74d8decd6d43898ae2929d/pyobjc_framework_coreaudiokit-11.0.tar.gz", hash = "sha256:1a4c3de4a02b0dfa7410c012c7f0939edd2e127d439fb934aeafc68450615f1d", size = 21450, upload-time = "2025-01-14T19:03:06.681Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4c/b9/d75a4da2d6a3cb75bafd363c447d45e134fe78a340dee408423a40c04aac/pyobjc_framework_CoreAudioKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6dbf01f2625689b392c2ba02f3ab8186c914d84d6bd896bdef5181a15a9463df", size = 7192, upload-time = "2025-01-14T18:50:09.524Z" },
- { url = "https://files.pythonhosted.org/packages/46/1f/5c15023665cc0476cdd7cbc054d5b06489fc09990f068768ed2fda8a02a2/pyobjc_framework_CoreAudioKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ccf2d92052a446d1d38bfd7eaa1dcd2451d59c37e73070a9a1fee394a532d9d", size = 7214, upload-time = "2025-01-14T18:50:10.399Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-corebluetooth"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/93/74/66a62a36da9db5924ee15de6fe1eb544930609b307b3bfbc021b5cf43781/pyobjc_framework_corebluetooth-11.0.tar.gz", hash = "sha256:1dcb7c039c2efa7c72dc14cdda80e677240b49fa38999941a77ee02ca142998d", size = 59797, upload-time = "2025-01-14T19:03:07.584Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/53/a8/df866e8a84fd33d29af1ee383f13715bbd98ad67d5795dfb276a3887560f/pyobjc_framework_CoreBluetooth-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:044069d63447554ba2c65cb1bf58d489d14ea279344810386392e583a2e611ef", size = 13683, upload-time = "2025-01-14T18:50:16.231Z" },
- { url = "https://files.pythonhosted.org/packages/44/fa/ad2165bc93c9d3fb174a0d8d5a4db3a7dfcf4dcaeca7913d59748ef62fdb/pyobjc_framework_CoreBluetooth-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bae8f909512d014eed85f80deae671185af4bb5a671fba19f85c7b4c973b61bb", size = 13713, upload-time = "2025-01-14T18:50:17.122Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coredata"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/84/22/6787205b91cb6d526b6b472ebaa5baff275200774050a55b4b25d2bd957a/pyobjc_framework_coredata-11.0.tar.gz", hash = "sha256:b11acb51ff31cfb69a53f4e127996bf194bcac770e8fa67cb5ba3fb16a496058", size = 260029, upload-time = "2025-01-14T19:03:08.609Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/86/b0/32c23ee168e5081391daa8737fddde79670b083e948dffb8d74308f1dd43/pyobjc_framework_CoreData-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74ac5e7658df10544708f6017a8823a100fbe41dc3aa9f61bf2fd4f8773c3dd7", size = 16194, upload-time = "2025-01-14T18:50:22.924Z" },
- { url = "https://files.pythonhosted.org/packages/6a/9e/39ca8124c6d87dc6fa85bcf850a2c23a062a408a26300062041c10363a3f/pyobjc_framework_CoreData-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c23b8c9106b0ec6f43aca80d2b2e0b0cc8fcb4ba78db4ae3c1f39a67464357d7", size = 16208, upload-time = "2025-01-14T18:50:23.838Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-corehaptics"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2a/b8/66481497362171e7ad42fc8fcc0272c04b95a707c5c1e7e8f8a8bfe58917/pyobjc_framework_corehaptics-11.0.tar.gz", hash = "sha256:1949b56ac0bd4219eb04c466cdd0f7f93d6826ed92ee61f01a4b5e98139ee039", size = 42956, upload-time = "2025-01-14T19:03:09.753Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/96/16/16d4365c8da1f708e145500237a3cdbbdde3e83b7f3f8673b038efac03b9/pyobjc_framework_CoreHaptics-11.0-py2.py3-none-any.whl", hash = "sha256:ff1d8f58dd3b29287dfad16a60bb45706c91f1910e400b632cb664eb2e56588b", size = 5307, upload-time = "2025-01-14T18:50:33.074Z" },
- { url = "https://files.pythonhosted.org/packages/12/72/b9fca92b3704af8f5f3b5507d0d9f3d0f5eb16605664de669f4468858627/pyobjc_framework_CoreHaptics-11.0-py3-none-any.whl", hash = "sha256:33f7a767efe6867fa6821ad73872ea88aec44650a22217bcdc9c1ec7c41fd9dc", size = 5377, upload-time = "2025-01-14T18:50:34.484Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-corelocation"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/0a/2d/b21ca49a34db49390420a9d7d05fd9eb89850dbec0a555c9ee408f52609c/pyobjc_framework_corelocation-11.0.tar.gz", hash = "sha256:05055c3b567f7f8f796845da43fb755d84d630909b927a39f25cf706ef52687d", size = 103955, upload-time = "2025-01-14T19:03:10.707Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/10/99/c7844f6e583f4764c6fab4a5b5ad9e949c6fce8c30f95226118bead41e01/pyobjc_framework_CoreLocation-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:046f211a23de55364c8553cfd660dc5adeff28af4d25f5ed9b5a8bfa83266b4d", size = 13075, upload-time = "2025-01-14T18:50:37.789Z" },
- { url = "https://files.pythonhosted.org/packages/88/6b/bb4fbcd259404fb60fdbfecef3c426dc23da5a0f0bc5bf96a4169b047478/pyobjc_framework_CoreLocation-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9bca9974f143bc9e93bd7ec4ef91655964d8ad0ca5ffccc8404fb6f098fa08dc", size = 13076, upload-time = "2025-01-14T18:50:38.693Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coremedia"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/02/60/7c7b9f13c94910882de6cc08f48a52cce9739e75cc3b3b6de5c857e6536a/pyobjc_framework_coremedia-11.0.tar.gz", hash = "sha256:a414db97ba30b43c9dd96213459d6efb169f9e92ce1ad7a75516a679b181ddfb", size = 249161, upload-time = "2025-01-14T19:03:12.291Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a5/b3/7baca352ddd7256840a4eb8f38fda39bc2e023b222b86d11c1a77cc0a8fa/pyobjc_framework_CoreMedia-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:057e63e533577fe5d764a5a9d307f60e8d9c58803112951ace42183abe9437e3", size = 29422, upload-time = "2025-01-14T18:50:56.964Z" },
- { url = "https://files.pythonhosted.org/packages/68/73/7ed3eba9c5a4a2071c3a64d6b1388d13474ad8d972529f3d5c950942513d/pyobjc_framework_CoreMedia-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:afd8eb59f5ce0730ff15476ad3989aa84ffb8d8d02c9b8b2c9c1248b0541dbff", size = 29297, upload-time = "2025-01-14T18:50:58.388Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coremediaio"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a1/59/904af57d302caa4c20d3bfebb9fb9300ccc3c396134460821c9f1e8ab65b/pyobjc_framework_coremediaio-11.0.tar.gz", hash = "sha256:7d652cf1a2a75c78ea6e8dbc7fc8b782bfc0f07eafc84b700598172c82f373d8", size = 107856, upload-time = "2025-01-14T19:03:14.225Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/27/02/09fda96c4727ff0c632c3cf4b09faa5b82be9f18422860dd80b5bc676ae1/pyobjc_framework_CoreMediaIO-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4182b91c72923d5c4d52eca3c221cc6f149d80a96242c0caab1d5bc9ccbcbbb", size = 17492, upload-time = "2025-01-14T18:51:04.559Z" },
- { url = "https://files.pythonhosted.org/packages/3f/db/a7b11cbf7d31964a65c5593ac30a02b0db35260845431046d467b08fc059/pyobjc_framework_CoreMediaIO-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1ad1e0f74126b6c6d25017e4ba08f66fe5422c902060d64b69e06a0c10214355", size = 17534, upload-time = "2025-01-14T18:51:06.752Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coremidi"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/90/d004cdf4c52b8b16842e15135495de882d743b4f0217946bd8ae1a920173/pyobjc_framework_coremidi-11.0.tar.gz", hash = "sha256:acace4448b3e4802ab5dd75bbf875aae5e1f6c8cab2b2f1d58af20fc8b2a5a7f", size = 107342, upload-time = "2025-01-14T19:03:15.235Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4d/b9/c67886891ad3cd21107cf1e65f1431cbdcff33acd74bf55ad3d6e10f3adf/pyobjc_framework_CoreMIDI-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78dec1bcd253a0385ac0b758a455e2a9367fc3cb9e2306d410c61bafa8d4c2eb", size = 24314, upload-time = "2025-01-14T18:50:44.817Z" },
- { url = "https://files.pythonhosted.org/packages/c0/7a/0639bc1ac35373b68f0f15fbcb9bb4f317cc4452997ea8e611ce79f623e9/pyobjc_framework_CoreMIDI-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97158830d76b999255d87191f31624d4373ee8ff662af4f4376c584cfb805573", size = 24346, upload-time = "2025-01-14T18:50:45.805Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coreml"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2e/64/4f0a990ec0955fe9b88f1fa58303c8471c551996670216527b4ac559ed8f/pyobjc_framework_coreml-11.0.tar.gz", hash = "sha256:143a1f73a0ea0a0ea103f3175cb87a61bbcb98f70f85320ed4c61302b9156d58", size = 81452, upload-time = "2025-01-14T19:03:16.283Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/50/dc/334823bb3faa490259df7611772e804eb883c56436fc69123e8a3a5ba0ea/pyobjc_framework_CoreML-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e290ad9c0ac5f057ce3885d35e33fadc115f59111f2e04f168c45e2890cb86e8", size = 11320, upload-time = "2025-01-14T18:50:50.914Z" },
- { url = "https://files.pythonhosted.org/packages/90/9f/3d053b95fbeeaf480d33fcc067504e205049591f6bee17e3a700b988d96c/pyobjc_framework_CoreML-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:48320a57589634c206d659799284a5133aaa006cf4562f772697df5b479043e4", size = 11321, upload-time = "2025-01-14T18:50:51.803Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coremotion"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/be/79/5c4ff39a48f0dc0f764d1330b2360e9f31e3a32414e8690e7f20e4574e93/pyobjc_framework_coremotion-11.0.tar.gz", hash = "sha256:d1e7ca418897e35365d07c6fd5b5d625a3c44261b6ce46dcf80787f634ad6fa5", size = 66508, upload-time = "2025-01-14T19:03:17.254Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/01/35/da29fd7350cd68bfe70f30a9e03e1350d7363c7c4fcdb5b0cd16f4bb47e2/pyobjc_framework_CoreMotion-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8e32de44e30028500e4d17c114eea69e7e74e5ae7aef6c208edc5bac34dfc21e", size = 10229, upload-time = "2025-01-14T18:51:12.825Z" },
- { url = "https://files.pythonhosted.org/packages/ca/f6/8061b58f0f3e1daf34c19511f0eeefe4ad66d10d1994b84d7fa3733b7852/pyobjc_framework_CoreMotion-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:697a3121615e95e56808f388b0882217a50e5ff6b459eccae93f2809d5ea4389", size = 10250, upload-time = "2025-01-14T18:51:13.746Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coreservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-fsevents" },
+ { 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/ca/b5/19c096b9938d6e2fdb1b436f21ad989b77dbeb4e59b3db4bd344800fa1e8/pyobjc_framework_coreservices-11.0.tar.gz", hash = "sha256:ac96954f1945a1153bdfef685611665749eaa8016b5af6f34bd56a274952b03a", size = 1244406, upload-time = "2025-01-14T19:03:19.202Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e9/cc/3899a59ed62fa36d2c1b95b94ff52e181ac48fde4011b68ca6abcbddd47a/pyobjc_framework_CoreServices-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7538ca6e22f4da0c3a70ddd9781f9240a3fe2fd7a7aa4dfb31c31f2532f008e", size = 30258, upload-time = "2025-01-14T18:51:20.487Z" },
- { url = "https://files.pythonhosted.org/packages/82/7b/8e059764951d0414f053bfebb6b1fba803a3b14397755cfd388b0a6363a7/pyobjc_framework_CoreServices-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3b175b5aa7a78484fd07b93533174b125901a6b791df2c51e05df1ea5d5badab", size = 30250, upload-time = "2025-01-14T18:51:21.448Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-corespotlight"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/fc/6a/6707d7ef339b9ad2dd0994d1df42969ee3b231f2d098f3377d40aed60b4f/pyobjc_framework_corespotlight-11.0.tar.gz", hash = "sha256:a96c9b4ba473bc3ee19afa01a9af989458e6a56e9656c2cdea1850d2b13720e6", size = 86130, upload-time = "2025-01-14T19:03:20.457Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ba/f1/54f9522d7f6ec7a6618c86abe0236869f61dd371b49df02dff7930433656/pyobjc_framework_CoreSpotlight-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:551878bfb9cc815fe2532fdf455f500dda44f8cd203dd836a6f1eb5cc0d49a9a", size = 9579, upload-time = "2025-01-14T18:51:28.889Z" },
- { url = "https://files.pythonhosted.org/packages/6c/24/dae8d0be7cb90328a8c1100c454e52faef95acc59940796f530b665b9555/pyobjc_framework_CoreSpotlight-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0c595d0a422a0f81008df93a0a2b38a1fd62434c6f61e31f1dceec927803b80", size = 9597, upload-time = "2025-01-14T18:51:30.677Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-coretext"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/9d/e8/9b68dc788828e38143a3e834e66346713751cb83d7f0955016323005c1a2/pyobjc_framework_coretext-11.0.tar.gz", hash = "sha256:a68437153e627847e3898754dd3f13ae0cb852246b016a91f9c9cbccb9f91a43", size = 274222, upload-time = "2025-01-14T19:03:21.521Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f6/20/b8a967101b585a2425ffe645135f8618edd51e1430aeb668373475a07d1f/pyobjc_framework_CoreText-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:56a4889858308b0d9f147d568b4d91c441cc0ffd332497cb4f709bb1990450c1", size = 30397, upload-time = "2025-01-14T18:51:35.844Z" },
- { url = "https://files.pythonhosted.org/packages/0d/14/d300b8bf18acd1d98d40820d2a9b5c5b6cf96325bdfc5020bc963218e001/pyobjc_framework_CoreText-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb90e7f370b3fd7cb2fb442e3dc63fedf0b4af6908db1c18df694d10dc94669d", size = 30456, upload-time = "2025-01-14T18:51:36.962Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-corewlan"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2e/a9/cda522b270adb75d62bae447b2131da62912b5eda058a07e3a433689116f/pyobjc_framework_corewlan-11.0.tar.gz", hash = "sha256:8803981d64e3eb4fa0ea56657a9b98e4004de5a84d56e32e5444815d8ed6fa6f", size = 65254, upload-time = "2025-01-14T19:03:23.938Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/da/e7/a869bf3e8673c8fdf496706672dac77fc305493db3c1057e3ca5f8d49c3f/pyobjc_framework_CoreWLAN-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4384ba68d4beb4d610ca0d661593e16efe541faf1790222b898b3f4dd389c98a", size = 9895, upload-time = "2025-01-14T18:51:43.245Z" },
- { url = "https://files.pythonhosted.org/packages/7c/d7/87626e23f010aa865eef10c796d1d87ddd87b78656f4e4ef0e808c8268f7/pyobjc_framework_CoreWLAN-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5f5c365f6ebdae4a87d534cf8af877a57d2aabe50fe5949a9334e75173291898", size = 9917, upload-time = "2025-01-14T18:51:45.362Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-cryptotokenkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b8/72/b871fa5476479e4a22a4a0e971fb4724b0eb94c721365539ad55f4dc3135/pyobjc_framework_cryptotokenkit-11.0.tar.gz", hash = "sha256:a1bbfe9170c35cb427d39167af55aefea651c5c8a45c0de60226dae04b61a6b1", size = 58734, upload-time = "2025-01-14T19:03:24.851Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ac/60/ddf022ce94f829a605992f11b9bfa861d7a1579f794e03d969c209d0de2a/pyobjc_framework_CryptoTokenKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3c42620047cc75a749fbed045d181dc76284bc27edea904b97df1ad82c2fdafc", size = 12949, upload-time = "2025-01-14T18:51:50.055Z" },
- { url = "https://files.pythonhosted.org/packages/d7/2d/9641cae1800281faace48698646f71c3de23ea1343031c12f6637d31e6f1/pyobjc_framework_CryptoTokenKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:95b05efb06b09987e23fb62dc3af378f38cfd0bd5872940cd95cf0f39dac6a57", size = 12978, upload-time = "2025-01-14T18:51:51.215Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-datadetection"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/33/6b/b896feb16e914dc81b6ed6cdbd0b6e6390eaafc80fff5297ec17eb0bd716/pyobjc_framework_datadetection-11.0.tar.gz", hash = "sha256:9967555151892f8400cffac86e8656f2cb8d7866963fdee255e0747fa1386533", size = 13738, upload-time = "2025-01-14T19:03:27.054Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/11/a1/63653827a78c8329a0106ac06e68ec0434e7f104f022dee5929bdf8fed62/pyobjc_framework_DataDetection-11.0-py2.py3-none-any.whl", hash = "sha256:0fd191ddee9bc6a491e05dfb7de780c0266fd6c90ca783e168786c4b0b5d7d7c", size = 3428, upload-time = "2025-01-14T18:51:58.111Z" },
- { url = "https://files.pythonhosted.org/packages/1b/61/ee4579efb7c02b794d26ab0458722598726678d0bb227c9aa925a34f36af/pyobjc_framework_DataDetection-11.0-py3-none-any.whl", hash = "sha256:21b4a1dbf6cb56fdc971224476453dd1a7a4bb72d2c670444e81ae96fde97cb2", size = 3501, upload-time = "2025-01-14T18:51:59.104Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-devicecheck"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/de/f8/237a92dd9ba8a88b7027f78cba83e61b0011bfc2a49351ecaa177233f639/pyobjc_framework_devicecheck-11.0.tar.gz", hash = "sha256:66cff0323dc8eef1b76d60f9c9752684f11e534ebda60ecbf6858a9c73553f64", size = 14198, upload-time = "2025-01-14T19:03:27.918Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5c/c1/d889e1c515c23b911594aa0b53a9d8ab6173e07adaaad8db89324a731fb7/pyobjc_framework_DeviceCheck-11.0-py2.py3-none-any.whl", hash = "sha256:d9252173a57dfba09ae37ccc3049f4b4990c1cbdcde338622b42c66296a8740e", size = 3612, upload-time = "2025-01-14T18:52:00.097Z" },
- { url = "https://files.pythonhosted.org/packages/65/8b/fa0cc2da2d49897f64e27a8a4e2a68f5784515f1adcea3a90f90b8ae8d44/pyobjc_framework_DeviceCheck-11.0-py3-none-any.whl", hash = "sha256:e8ed3965808963b2f0a7e069537d752bc659b75db1901cc24e5138925b9a7052", size = 3684, upload-time = "2025-01-14T18:52:02.389Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-devicediscoveryextension"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e1/48/178a1879109128f34334fdae2fe4463c7620f169593bea96704f347d945e/pyobjc_framework_devicediscoveryextension-11.0.tar.gz", hash = "sha256:576dac3f418cfc4f71020a45f06231d14e4b2a8e182ef0020dd9da3cf238d02f", size = 14511, upload-time = "2025-01-14T19:03:32.132Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/be/3353a87691796a277ff4c048c4fa9a43db6f353fd683e8bb9e297651950c/pyobjc_framework_DeviceDiscoveryExtension-11.0-py2.py3-none-any.whl", hash = "sha256:82032e567d0031839d626947368d6d3d4ca97c915f15d2779a444cf4b2ffa4a3", size = 4194, upload-time = "2025-01-14T18:52:03.253Z" },
- { url = "https://files.pythonhosted.org/packages/06/87/52137a60498c03ab0acd3b9eadafe3c371c12e0549718e6a1f0fff8b7725/pyobjc_framework_DeviceDiscoveryExtension-11.0-py3-none-any.whl", hash = "sha256:9c94057173f13472089d561b780d93b5aa244d048b4760a0e1ab54fe7c2253c5", size = 4265, upload-time = "2025-01-14T18:52:05.101Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-dictionaryservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-coreservices" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d8/cf/2913c7df737eb8519acb7ef6429127e40d6c334415e38cfa18d6481150eb/pyobjc_framework_dictionaryservices-11.0.tar.gz", hash = "sha256:6b5f27c75424860f169e7c7e182fabffdba22854fedb8023de180e8770661dce", size = 10823, upload-time = "2025-01-14T19:03:32.942Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0a/68/5ea9766a8a6301f1a2ee39d595fe03d50b84b979d3d059e3e0ff541eab45/pyobjc_framework_DictionaryServices-11.0-py2.py3-none-any.whl", hash = "sha256:7c081371855240ac8e22783a71f32393c0f1e0b94d2fd193e8fef0a8be007080", size = 3829, upload-time = "2025-01-14T18:52:07.379Z" },
- { url = "https://files.pythonhosted.org/packages/dd/c4/62b73f813c012f72a3a8e2f6326506803b45e91dc4ce6683e02a52a7f414/pyobjc_framework_DictionaryServices-11.0-py3-none-any.whl", hash = "sha256:15cdc3b64cb73713ee928cdcc0a12c845729f117bb8e73c7511f6e3f256d9d39", size = 3901, upload-time = "2025-01-14T18:52:08.403Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-discrecording"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/cc/f36612b67ca1fff7659d7933b563dce61f8c84dad0bf79fab08bb34949ad/pyobjc_framework_discrecording-11.0.tar.gz", hash = "sha256:6bdc533f067d049ea5032f65af70b5cdab68673574ac32dacb46509a9411d256", size = 122426, upload-time = "2025-01-14T19:03:35.589Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7e/0b/fbe460ccddb4c613eb04e2b81cc9c75b0e0c407fd9fb91776381416f99af/pyobjc_framework_DiscRecording-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eab79d83c2d974aa5564f3f6f4415218573dca69010026d2d000d232494a9d81", size = 14491, upload-time = "2025-01-14T18:52:10.415Z" },
- { url = "https://files.pythonhosted.org/packages/10/6f/c4c220d979771f4d7782deddef5ea9026baa177abe81cbe63d626a215de7/pyobjc_framework_DiscRecording-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e309e7394aed23d6ccce2e035f23c0c015d029c2ad531c6b1dce820b7eea8512", size = 14505, upload-time = "2025-01-14T18:52:11.414Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-discrecordingui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-discrecording" },
+ { 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/d4/6b/3c120c59a939854dd4b7a162fad47011375c5ba00a12940f7217aea90eeb/pyobjc_framework_discrecordingui-11.0.tar.gz", hash = "sha256:bec8a252fd2022dce6c58b1f3366a7295efb0c7c77817f11f9efcce70527d7a2", size = 19614, upload-time = "2025-01-14T19:03:36.695Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/de/45/4852afc5e093b76ba8f718d80fe1cc8604122a752806354379a7dbc41dc3/pyobjc_framework_DiscRecordingUI-11.0-py2.py3-none-any.whl", hash = "sha256:1af226c9350bb1d49960c02505e1e2f286e9377040dc2777a3f9a318925e081b", size = 4671, upload-time = "2025-01-14T18:52:16.645Z" },
- { url = "https://files.pythonhosted.org/packages/98/01/c5645513eeaadf0b9e387849fa656fc22524a1881f0d3a44d5b78784f836/pyobjc_framework_DiscRecordingUI-11.0-py3-none-any.whl", hash = "sha256:943df030f497a5ab73e969a04df8a653138fb67ebcf2380fedb4b4886d4ffba0", size = 4736, upload-time = "2025-01-14T18:52:17.655Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-diskarbitration"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/43/fb/5d3ff093144f499904b1e1bce18d010fe2171b9be62b4679d3dda8b3ad19/pyobjc_framework_diskarbitration-11.0.tar.gz", hash = "sha256:1c3e21398b366a1ce96cf68501a2e415f5ccad4b43a3e7cc901e09e896dfb545", size = 20096, upload-time = "2025-01-14T19:03:37.659Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/f4/f7ad86b2bb922b94745c369b90420cda984e6ad1ac9eb79ec32f5e332123/pyobjc_framework_DiskArbitration-11.0-py2.py3-none-any.whl", hash = "sha256:58823297eb09ff020ee156649170ab824fec32825bd32f2814c32e005920a72c", size = 4793, upload-time = "2025-01-14T18:52:18.561Z" },
- { url = "https://files.pythonhosted.org/packages/8e/87/bf0fc2aa781a819421e572cf6315fae7d0baf46607f9a67c86525c7e0e03/pyobjc_framework_DiskArbitration-11.0-py3-none-any.whl", hash = "sha256:7d41189a2d82045a7195c4661d8ec16195b6325a2f68f9d960e9a9f6649d1131", size = 4865, upload-time = "2025-01-14T18:52:19.786Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-dvdplayback"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c0/89/89ebee4863fd6f173bff9373b5bda4ffa87eba6197337617ab086e23c7d5/pyobjc_framework_dvdplayback-11.0.tar.gz", hash = "sha256:9a005f441afbc34aea301857e166fd650d82762a75d024253e18d1102b21b2f8", size = 64798, upload-time = "2025-01-14T19:03:38.491Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/7f/6073ef2c5170abf55a15750cd069b0c3fdd03e48f3c86761a6a8ecaa0a38/pyobjc_framework_DVDPlayback-11.0-py2.py3-none-any.whl", hash = "sha256:2013289aa38166d81bcbf25d6600ead1996e50de2bc689e5cf36f36a45346424", size = 8171, upload-time = "2025-01-14T18:51:55.282Z" },
- { url = "https://files.pythonhosted.org/packages/db/e4/97ed8d41491f366908581efb8644376fd81ede07ec2cf204cdb3c300ed1e/pyobjc_framework_DVDPlayback-11.0-py3-none-any.whl", hash = "sha256:c6be6ae410d8dce7179d6ee8c9bc421468d4b9c19af3ff0e59c93ae71cfc33e0", size = 8245, upload-time = "2025-01-14T18:51:57.205Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-eventkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/54/13/38a98e5cee62e1655d84cfb88cad54fdec4ec272b5fd0c5ac3fc21e33e49/pyobjc_framework_eventkit-11.0.tar.gz", hash = "sha256:3d412203a510b3d62a5eb0987406e0951b13ed39c3351c0ec874afd72496627c", size = 75399, upload-time = "2025-01-14T19:03:39.441Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/97/d5/e866c951237fb1b6423b85e1623a7f8cc417862261196e276ecc23141976/pyobjc_framework_EventKit-11.0-py2.py3-none-any.whl", hash = "sha256:934e31f4c82f887e1bf01f96d33de4c7c6727de3fdb55bc739e1c686c10cc151", size = 6717, upload-time = "2025-01-14T18:52:20.684Z" },
- { url = "https://files.pythonhosted.org/packages/dc/47/3c0cc7b8c95e6759804b426e78510f65b8e7409c425b85f1b0109d14cdcc/pyobjc_framework_EventKit-11.0-py3-none-any.whl", hash = "sha256:5467977c79649dac9e0183dc72511f7dd49aab0260b67c2cfa25079a5a303f11", size = 6789, upload-time = "2025-01-14T18:52:21.73Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-exceptionhandling"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/cc/46/6c2c4805697a0cfb8413eb7bc6901298e7a1febd49bb1ea960274fc33af3/pyobjc_framework_exceptionhandling-11.0.tar.gz", hash = "sha256:b11562c6eeaef5d8d43e9d817cf50feceb02396e5eb6a7f61df2c0cec93d912b", size = 18157, upload-time = "2025-01-14T19:03:40.393Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e7/9d/c25b0bc0d300dd5aedd61f0cbd94a91ec6608b550821108d554e9eea0ed7/pyobjc_framework_ExceptionHandling-11.0-py2.py3-none-any.whl", hash = "sha256:972e0a376fee4d3d4c5161f82a8e5f6305392dbf19e98c4c6486d737759ebd89", size = 6993, upload-time = "2025-01-14T18:52:22.621Z" },
- { url = "https://files.pythonhosted.org/packages/cb/04/4b75e083325313e80e66f42d9a932c3febd2db48609d5d960a319b568f7c/pyobjc_framework_ExceptionHandling-11.0-py3-none-any.whl", hash = "sha256:d7f95fdb60a2636416066d3d12fad06cbf597e038576f8ed46fd3c742cc22252", size = 7063, upload-time = "2025-01-14T18:52:24.447Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-executionpolicy"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/91/2e4cacbdabf01bc1207817edacc814b6bc486df12e857a8d86964d98fef4/pyobjc_framework_executionpolicy-11.0.tar.gz", hash = "sha256:de953a8acae98079015b19e75ec8154a311ac1a70fb6d885e17fab09464c98a9", size = 13753, upload-time = "2025-01-14T19:03:42.353Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d5/03/a433c64c21c754ed796ae5ca0bad63fcb1d51134968ce0c53d4ee806ccd8/pyobjc_framework_ExecutionPolicy-11.0-py2.py3-none-any.whl", hash = "sha256:fdf78bf22fa6ea6f27b574f73856a8a22992d0c0d5a6ed64823e00000c06ffe7", size = 3668, upload-time = "2025-01-14T18:52:28.64Z" },
- { url = "https://files.pythonhosted.org/packages/0b/47/da969dd9d56403e23cc95e68c4816563f64ed6fde7ff4e3c3710e8e8efcf/pyobjc_framework_ExecutionPolicy-11.0-py3-none-any.whl", hash = "sha256:d2dba6f3f7803d1cd0a5608a7ad75085b73097b6c3a935b7f1326c7202249751", size = 3737, upload-time = "2025-01-14T18:52:30.841Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-extensionkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/22/98/803e3cb000dac227eb0d223802a0aeb052d34a741e572d9584e7d83afca7/pyobjc_framework_extensionkit-11.0.tar.gz", hash = "sha256:82d9e79532e5a0ff0eadf1ccac236c5d3dca344e1090a0f3e88519faa24143c7", size = 19200, upload-time = "2025-01-14T19:03:43.188Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a7/1d/ed580ce024d7e9a1ea88ee592d03b34f0b688414793bf8b7be5a367ecea8/pyobjc_framework_ExtensionKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98957dd51f0a4e02aa3d9d3a184f37ca5f99f4cb9e11282a2fc793d18de02af8", size = 7781, upload-time = "2025-01-14T18:52:32.843Z" },
- { url = "https://files.pythonhosted.org/packages/fd/9e/a68989bf7bbba7b5fb1ade168d2179e37164439daaad63a27ccb790a6593/pyobjc_framework_ExtensionKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e341979ee4a7fc5978fe44d6d1d461c774411042cac4e119a32704d6c989de6f", size = 7783, upload-time = "2025-01-14T18:52:33.711Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-externalaccessory"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/67/b0/ac0a02fe26e66c33fee751a65c1ed06bbd2934db8636e08bb491e8334bad/pyobjc_framework_externalaccessory-11.0.tar.gz", hash = "sha256:39e59331ced75cdcccf23bb5ffe0fa9d67e0c190c1da8887a0e4349b7e27584f", size = 22577, upload-time = "2025-01-14T19:03:44.021Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a3/96/bddfe9f72a59a3038ec3208a7d2a62332d5e171d7e3c338ccff6bd6e76b8/pyobjc_framework_ExternalAccessory-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:319f66edb96505f833fe7fe9ba810cb3b0d0c65605b8674bea52f040e8caebd6", size = 8785, upload-time = "2025-01-14T18:52:40.529Z" },
- { url = "https://files.pythonhosted.org/packages/e7/e2/26e9cbb18723200ef71580e46c46f037b7feecc07cf50051cd6fcb426472/pyobjc_framework_ExternalAccessory-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aaae920c9241d1b35a58ba76dba761689b248250d782179526f6dea151b1fda0", size = 8808, upload-time = "2025-01-14T18:52:42.142Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-fileprovider"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/44/fc/b8593d8645b9933e60a885f451d0c12d9c0e1b00e62121d8660d95852dff/pyobjc_framework_fileprovider-11.0.tar.gz", hash = "sha256:dcc3ac3c90117c1b8027ea5f26dad6fe5045f688ce3e60d07ece12ec56e17ab3", size = 78701, upload-time = "2025-01-14T19:03:44.931Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4a/57/1f959ec54650d1afc08e89d2995a1534f44229b1371cf66429a45b27c32d/pyobjc_framework_FileProvider-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c7e803a37f7327c191a4de7dbb36e5fbf8bd08dadbcc7f626e491451c7a3849", size = 19179, upload-time = "2025-01-14T18:53:01.659Z" },
- { url = "https://files.pythonhosted.org/packages/30/79/ff4dfe06eb43c97bd723f066ef2b92b00b1020206b4dcc5abe9b49746cad/pyobjc_framework_FileProvider-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7acdc5e0f4b5488bcbf47d3eea469b22897a4b783fe3f5d4b2b1f3288e82038", size = 19154, upload-time = "2025-01-14T18:53:02.597Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-fileproviderui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-fileprovider" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/3d/9d/ca4aed36e6188623e9da633634af772f239bee74934322e1c19ae7b79a53/pyobjc_framework_fileproviderui-11.0.tar.gz", hash = "sha256:cf5c7d32b29d344b65217397eea7b1a2913ce52ce923c9e04135a7a298848d04", size = 13419, upload-time = "2025-01-14T19:03:46.016Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0b/2e/8a91cfa9485a2e9ad295da8bb5505d0dc1046dec8557d2ae17eef75f3912/pyobjc_framework_FileProviderUI-11.0-py2.py3-none-any.whl", hash = "sha256:5102651febb5a6140f99b116b73d0fd6c9822372a5203506e4904ac0ebb1313c", size = 3642, upload-time = "2025-01-14T18:53:06.378Z" },
- { url = "https://files.pythonhosted.org/packages/75/9b/a542159b1aefedb24f01440a929b7bbc6f4bbae3a74d09ad05a7f4adb9c0/pyobjc_framework_FileProviderUI-11.0-py3-none-any.whl", hash = "sha256:b75f70eef2af3696f3cb2e0de88bbb437343b53070078573ae72d64bf56fce9d", size = 3712, upload-time = "2025-01-14T18:53:07.403Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-findersync"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f6/e3/24df6e24b589073815be13f2943b93feb12afbf558f6e54c4033b57c29ee/pyobjc_framework_findersync-11.0.tar.gz", hash = "sha256:8dab3feff5debd6bc3746a21ded991716723d98713d1ba37cec1c5e2ad78ee63", size = 15295, upload-time = "2025-01-14T19:03:46.91Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/96/f1/42797ae9065e0127df4b5bb7a45e06eff8568a476edbc8d590cea9d25228/pyobjc_framework_FinderSync-11.0-py2.py3-none-any.whl", hash = "sha256:cafb262d1ad1e3a86af333f673aeda4f9bdcf528ded97c2232fd1cf440d1db5a", size = 4788, upload-time = "2025-01-14T18:53:09.559Z" },
- { url = "https://files.pythonhosted.org/packages/d8/96/2ed2ca5536f76102ea3bfb886cdc7b34ec51f53b122b9c535b4ac9b1ee03/pyobjc_framework_FinderSync-11.0-py3-none-any.whl", hash = "sha256:d00285b85038c5546e8566bec9cd3a4615708f0e6cb774d0ea804c69546ec915", size = 4860, upload-time = "2025-01-14T18:53:11.765Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-fsevents"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/82/37/4c09cc7b8678e2bb5b68ebc62e817eb88c409b1c41bdc1510d7d24a0372d/pyobjc_framework_fsevents-11.0.tar.gz", hash = "sha256:e01dab04704a518e4c3e1f7d8722819a4f228d5082978e11618aa7abba3883fe", size = 29078, upload-time = "2025-01-14T19:03:49.762Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1f/8a/75fd630865c9f9d69b1364208582872fc818b4c1a70fd9ae85a5cf7a2c5a/pyobjc_framework_FSEvents-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0b3c7835251a35453e3079cf929b9e5329d02e2f4eaac2ebabbe19e1abd18ab", size = 13209, upload-time = "2025-01-14T18:52:49.478Z" },
- { url = "https://files.pythonhosted.org/packages/19/c6/cae1a6a96ad493339e9f0f175bcf18c1526abe422b63309d873acd663dc2/pyobjc_framework_FSEvents-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb8a5a7f7b5a70e15dae80672f10ecc16b5d1c1afe62ad2ccadb17a8098876cd", size = 13274, upload-time = "2025-01-14T18:52:51.588Z" },
+ { 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" },
+]
+
+[[package]]
+name = "pyobjc-framework-fskit"
+version = "11.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" }
+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" },
]
[[package]]
name = "pyobjc-framework-gamecenter"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7f/3b/e66caebc948d9fe3b2671659caab220aff6d5e80ac25442d83331b523d23/pyobjc_framework_gamecenter-11.0.tar.gz", hash = "sha256:18a05500dbcf2cca4a0f05839ec010c76ee08ab65b65020c9538a31feb274483", size = 31459, upload-time = "2025-01-14T19:03:50.766Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/7e/8a41ab9880e415143baf771d55566e2a863ec538837480a5ee17e1ddc08b/pyobjc_framework_GameCenter-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8bff2a36cf3cb52cbe321203147766e95997f881062143171cdd8ef2fde9e53", size = 18472, upload-time = "2025-01-14T18:53:13.794Z" },
- { url = "https://files.pythonhosted.org/packages/c4/78/846aa21be2303cba955aaf781a362504a722183b8f6a030ba02f2b2073ad/pyobjc_framework_GameCenter-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de57380e3b51579a6e8bc397c2bb5be5d0f6dcd4bf5abed587700cf7f57afd4", size = 18437, upload-time = "2025-01-14T18:53:14.802Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-gamecontroller"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/fa/30/02ca5a4fb911acf3e8018abcbd29631a842aeac02958ae91fab1acb13ad1/pyobjc_framework_gamecontroller-11.0.tar.gz", hash = "sha256:6d62f4493d634eba03a43a14c4d1e4511e1e3a2ca2e9cbefa6ae9278a272c1d0", size = 115318, upload-time = "2025-01-14T19:03:52.264Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/98/ec/05f356ab2d747a385c2a68908f2f67ee1b1e7a169b1497b0771b2226a174/pyobjc_framework_GameController-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30e8f251be49ff67491df758c73149e7c7e87bee89919966ed1b2bf56fdaacf7", size = 20995, upload-time = "2025-01-14T18:53:19.979Z" },
- { url = "https://files.pythonhosted.org/packages/66/b3/38319c9232e3508297bfedde700b125676845b1e27afe2bb681e8829f34a/pyobjc_framework_GameController-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:46403f23aaaf6a2e1a51e3954c53d6e910b80058117fdcf3a0a8100f25e30f07", size = 20919, upload-time = "2025-01-14T18:53:20.943Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-gamekit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/3f/df/c161460e5736a34f9b59aa0a3f2d6ad1d1cd9a913aa63c89c41a6ba3b6ae/pyobjc_framework_gamekit-11.0.tar.gz", hash = "sha256:29b5464ca78f0de62e6b6d56e80bbeccb96dc13820b6d5b4e835ab1cc127e5b9", size = 164394, upload-time = "2025-01-14T19:03:53.762Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/88/4d/9fe843671c7b94d8e8a925662775d4b2632c138c6a0a9d1bb2c379f225c0/pyobjc_framework_GameKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dabe856c8638940d2b346abc7a1828cca12d00b47d2951d0ac9f4e27ecc8d3ec", size = 21667, upload-time = "2025-01-14T18:53:27.239Z" },
- { url = "https://files.pythonhosted.org/packages/98/b2/d4d1f123fead83bf68eb4ecfab2125933f3114eaf2ed420d7bb99238ba67/pyobjc_framework_GameKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40d506505f71ed57779c8be9b4e07ec9337c45aebe323b3f8dd8f8c75e6fce50", size = 21627, upload-time = "2025-01-14T18:53:28.228Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-gameplaykit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-spritekit" },
+ { 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/41/f0/980c4fc3c594d9726b7eb6ae83f73127b22560e1541c7d272d23d17fdf0d/pyobjc_framework_gameplaykit-11.0.tar.gz", hash = "sha256:90eeec464fba992d75a406ccbddb35ed7420a4f5226f19c018982fa3ba7bf431", size = 72837, upload-time = "2025-01-14T19:03:56.127Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/30/e7/3530071bf1897f2fe2e5f0c54620f0df9fcac586b9ba6bb5726fc9d295c2/pyobjc_framework_GameplayKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01f59bbf5beb0cfcfea17011987995f1cccf2ec081d91269f95e71283dd83c67", size = 13381, upload-time = "2025-01-14T18:53:34.217Z" },
- { url = "https://files.pythonhosted.org/packages/b9/07/075369dd9d4e3849646285d4083a9d28214fdd043b499c7929047b942c7f/pyobjc_framework_GameplayKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f2d56af0a84439b3ddc64cdec90e3fab08b1d43da97bed0fb8d60714f47c4372", size = 13382, upload-time = "2025-01-14T18:53:36.303Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-healthkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7b/2f/d79d2ec7c23bfc94bfaa7b7c6f6487a8bffdb73263eea6900aab56135889/pyobjc_framework_healthkit-11.0.tar.gz", hash = "sha256:e78ccb05f747ae3e70b5d73522030b7ba01ef2d390155fba7d50c1c614ae241f", size = 201558, upload-time = "2025-01-14T19:03:57.117Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f0/66/36a2fa7ef61b54a8e283355518ed003aa28b26e1dfad9ecbb7f543a08acd/pyobjc_framework_HealthKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee87453c28bd4040b12a3bc834f0ea1989e331400845a14079e8f4a6ede70aa2", size = 20139, upload-time = "2025-01-14T18:53:44.342Z" },
- { url = "https://files.pythonhosted.org/packages/5f/fd/95d40483d9d185317adbf8433d0c7e83ba36ec6c5a824159b87160f6cebe/pyobjc_framework_HealthKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:680da6d67b0c79d15e897f1c588a8b02d780573aef3692e982294c43727eecf3", size = 20163, upload-time = "2025-01-14T18:53:45.278Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-imagecapturecore"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/38/fe/db1fc3ffd784a9010070cd87a05d7fd2542c400395589341fab5970a01e1/pyobjc_framework_imagecapturecore-11.0.tar.gz", hash = "sha256:f5d185d8c8b564f8b4a815381bcdb424b10d203ba5bdf0fc887085e007df6f7a", size = 99935, upload-time = "2025-01-14T19:03:58.548Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e2/fb/29f20521e0df5da0110f1d6a48e4ed3530a2c0b670bf62d89ceeddd42c18/pyobjc_framework_ImageCaptureCore-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3fd78aa4a69e24caed38ae17a69b973e505324d966df86b47441318800a52db9", size = 16611, upload-time = "2025-01-14T18:54:05.308Z" },
- { url = "https://files.pythonhosted.org/packages/0e/ce/404666e27318435a0513dcf64b85d7cd99195b2e822e03796b03af549c52/pyobjc_framework_ImageCaptureCore-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5cc6c6acbfca05977adc0e339e1225d5cd314af2fa455a70baebb54f9fb2b64", size = 16636, upload-time = "2025-01-14T18:54:06.288Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-inputmethodkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e7/e9/13d007285582e598903264a7d25cc6771a2a52d6c2a96a68fe91db0844fb/pyobjc_framework_inputmethodkit-11.0.tar.gz", hash = "sha256:86cd648bf98c4e777c884b7f69ebcafba84866740430d297645bf388eee6ce52", size = 26684, upload-time = "2025-01-14T19:03:59.525Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/08/18572def66bf1e0ee6d079b45b34f8b4cbf2ab40b3024c351e4bd83cfa4c/pyobjc_framework_InputMethodKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a58273233e236cb9fa2d8d9295017c6bf26d6f47cc3a5dc9ba81f1c1e64a346", size = 9369, upload-time = "2025-01-14T18:54:12.452Z" },
- { url = "https://files.pythonhosted.org/packages/9d/c9/7793b0d7b363548e62499660899893dff2953ae3a56aa5080e9b199d1291/pyobjc_framework_InputMethodKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7869db2b08586e97181ec2b60b8f5b9d3a683097bae4ce4bb29dc3c5709c3f13", size = 9390, upload-time = "2025-01-14T18:54:13.383Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-installerplugins"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f2/f3/0379655e8ea3566002768d5e7b3ccd72ca845390632a8dabf801348af3a7/pyobjc_framework_installerplugins-11.0.tar.gz", hash = "sha256:88ec84e6999e8b2df874758b09878504a4fbfc8471cf3cd589d57e556f5b916e", size = 27687, upload-time = "2025-01-14T19:04:00.515Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/03/db/0f3334648a53c8ad663fd19d5421863cb0b711e38a2eb742798d50ed33ef/pyobjc_framework_InstallerPlugins-11.0-py2.py3-none-any.whl", hash = "sha256:cb21bfd5597233a2de3d8c0a8d50f23cf92c43e8963edf85787430ac3cadf4a3", size = 4716, upload-time = "2025-01-14T18:54:17.885Z" },
- { url = "https://files.pythonhosted.org/packages/f7/56/fe6f50d74d19b0f85035aba977db7039eedbd2de5ac991278a6a5be475a0/pyobjc_framework_InstallerPlugins-11.0-py3-none-any.whl", hash = "sha256:2221301f466d30d6fd32c7317560c85926a3ee93f1de52d320e3b3cd826a8f93", size = 4784, upload-time = "2025-01-14T18:54:19.028Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-instantmessage"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/08/4d/6810a1f2039ff24d9498858b3ebb46357d4091aa5cec9ff4e41bbcdb25de/pyobjc_framework_instantmessage-11.0.tar.gz", hash = "sha256:ec5c4c70c9b0e61ae82888067246e4f931e700d625b3c42604e54759d4fbf65c", size = 34027, upload-time = "2025-01-14T19:04:01.405Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8c/41/4c0ec3d59f9930e9c52570f7e26d79055881e0009e07466b4988c107ef7c/pyobjc_framework_InstantMessage-11.0-py2.py3-none-any.whl", hash = "sha256:ce364e4e18ec8551512b7d968c0d950ccf7de4bb470f66fe524f3bc8d23df0d1", size = 5334, upload-time = "2025-01-14T18:54:20.187Z" },
- { url = "https://files.pythonhosted.org/packages/19/d9/e3620a5316c986b27361d2f21dd74b48f70c6f7bfe580075e970ca9d7bd6/pyobjc_framework_InstantMessage-11.0-py3-none-any.whl", hash = "sha256:a2817353eaf8f37fe6063c28006b2a0889892e3de801b51b059c153a9d3f35f8", size = 5402, upload-time = "2025-01-14T18:54:21.261Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-intents"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/56/88/07e47b0c5c46fe97c23c883ae7a053c2ca6f6fd6afe851d1c2c784644f0f/pyobjc_framework_intents-11.0.tar.gz", hash = "sha256:6405c816dfed8ffa8b3f8b0fae75f61d64787dbae8db1c475bb4450cf8fdf6b5", size = 447921, upload-time = "2025-01-14T19:04:02.487Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/86/2e/cd8a4aa10a1d3808dd6f71195a28906c573345673dd92b774fbb8c93dd75/pyobjc_framework_Intents-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9a445a3d4a0622ebf96c65b0ac0be7cec1e72cf7fd9900cd5ace6acf4e84bce", size = 32021, upload-time = "2025-01-14T18:54:23.325Z" },
- { url = "https://files.pythonhosted.org/packages/4a/0e/05c457dab601e3eb5ed7243a04fede32423f08dd03a08e988611359d55b4/pyobjc_framework_Intents-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1e148accce2c7c9243ff90ab3f1a200f93d93506da9c3a2cd034fd5579cb839a", size = 32008, upload-time = "2025-01-14T18:54:24.317Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-intentsui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-intents" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ee/96/3b3b367f70a4d0a60d2c6251e4a1f4bf470945ae939e0ba20e6d56d10c7a/pyobjc_framework_intentsui-11.0.tar.gz", hash = "sha256:4ce04f926c823fbc1fba7d9c5b33d512b514396719e6bc50ef65b82774e42bc5", size = 20774, upload-time = "2025-01-14T19:04:03.648Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/89/19/f32a14585e749258bb945805da93fd71e05534b14e09fab243fb5ec507ff/pyobjc_framework_IntentsUI-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c677225d38fffc5e00df803f93a6a627c466b35a362ed27173f7901e185882e", size = 8772, upload-time = "2025-01-14T18:54:29.958Z" },
- { url = "https://files.pythonhosted.org/packages/2f/d4/e81e9cfafef63cef481ab251a961ca98e176ca244be91368e0f6b6fe8793/pyobjc_framework_IntentsUI-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b93a1d9594f471596f255db354c13d67caed7aa020afb9f4e69cde2674f4db71", size = 8789, upload-time = "2025-01-14T18:54:30.984Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-iobluetooth"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1e/46/62913f8e5ac307b154b3dd50a7a0b167c9d7ac2a579223e33208c141c387/pyobjc_framework_iobluetooth-11.0.tar.gz", hash = "sha256:869f01f573482da92674abbae4a154143e993b1fe4b2c3523f9e0f9c48b798d4", size = 300463, upload-time = "2025-01-14T19:04:04.582Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7a/2e/2037b1c3459008ccdc41d65ab236d7919eed9bbadd0f02f65dc0193bb170/pyobjc_framework_IOBluetooth-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7d2005d3eff2afed4b5930613ae3c1b50004ebabffb86c0d5dd28d54436e16e6", size = 41010, upload-time = "2025-01-14T18:53:51.62Z" },
- { url = "https://files.pythonhosted.org/packages/2a/52/c266636ff3edc98c1aaf2cc154392876a68d4167bed0351dc2933d5ccc3c/pyobjc_framework_IOBluetooth-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f86d2e675ee2a61ba3d2a446322e918e8ef2dc3e242e893ef81abfc480a6f2c2", size = 41012, upload-time = "2025-01-14T18:53:52.757Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-iobluetoothui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-iobluetooth" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/76/55/d194de8cfa63c96970e6c90c35e80ce3fceb42934a85d3728736a0e416ff/pyobjc_framework_iobluetoothui-11.0.tar.gz", hash = "sha256:a583758d3e54149ee2dcf00374685aa99e8ae407e044f7c378acc002f9f27e63", size = 23091, upload-time = "2025-01-14T19:04:05.659Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d9/75/9401ae099f32a6be2e5759f8d25c573bcf103833343457ca5981153262ab/pyobjc_framework_IOBluetoothUI-11.0-py2.py3-none-any.whl", hash = "sha256:0f94afeb5ecbde07712ea7658a38d6b0e3558154a6bc29c9a33b633f5952b2c3", size = 3972, upload-time = "2025-01-14T18:53:57.925Z" },
- { url = "https://files.pythonhosted.org/packages/11/a3/75e473de9d25084bfbfa4c0ba24edf038956a604d78219894dc0b412e501/pyobjc_framework_IOBluetoothUI-11.0-py3-none-any.whl", hash = "sha256:5bc366a9904532168ac2c49523e7f090f81b6acbb7b8929ffc7855be0b1d4cf7", size = 4043, upload-time = "2025-01-14T18:54:00.086Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-iosurface"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/fb/91/ae9ca9e1a777eb786d9d43649437d01d24386736cffe9bb2f504b57e8db6/pyobjc_framework_iosurface-11.0.tar.gz", hash = "sha256:24da8d1cf9356717b1c7e75a1c61e9a9417b62f051d13423a4a7b0978d3dcda5", size = 20555, upload-time = "2025-01-14T19:04:09.475Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b8/08/b96f84b623e2dd2ef733ccdd67a1694f51bfdb4dfd81d38e7755566ab9e5/pyobjc_framework_IOSurface-11.0-py2.py3-none-any.whl", hash = "sha256:58c6e79401a00dc63a5797cd3cc067542d4f94fcd2fc8979dc248c3b06c3b829", size = 4905, upload-time = "2025-01-14T18:54:00.978Z" },
- { url = "https://files.pythonhosted.org/packages/2d/af/4d7ece43c993369a8593c36e0f239b739b78c01e71d74553a630dadd1599/pyobjc_framework_IOSurface-11.0-py3-none-any.whl", hash = "sha256:f2bc13cbfd178396bde6e7558b05a49f69cce376885a07f645a5dd69d2b578fc", size = 4972, upload-time = "2025-01-14T18:54:03.244Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-ituneslibrary"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/41/fe/881ab1058d795fe68ccc1e14df0d5e161601dced15d3be84105ecc44bae6/pyobjc_framework_ituneslibrary-11.0.tar.gz", hash = "sha256:2e15dcfbb9d5e95634ddff153de159a28f5879f1a13fdf95504e011773056c6e", size = 47647, upload-time = "2025-01-14T19:04:11.333Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5f/d2/52d1c71ec91ec299e1324658d023954cf62ce4c275155dc66cd298517ae2/pyobjc_framework_iTunesLibrary-11.0-py2.py3-none-any.whl", hash = "sha256:3836fccec315f5186e4b029b486fd18d4b1f24a4c2e73f2d9f3e157ee66d294d", size = 5147, upload-time = "2025-01-14T19:01:49.97Z" },
- { url = "https://files.pythonhosted.org/packages/dc/97/c23c522d506ae01740c04982a1db5861888056dc65d56876a2de0fc490bc/pyobjc_framework_iTunesLibrary-11.0-py3-none-any.whl", hash = "sha256:bfd40fde3f057318329e5fb6e256051eea3f6cd2e2adb9c1f1f51fcb87deb05a", size = 5210, upload-time = "2025-01-14T19:01:51.573Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-kernelmanagement"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/4a/ea/8ef534fce78817fc577f18de2b34e363873f785894f2bbbfc694823f5088/pyobjc_framework_kernelmanagement-11.0.tar.gz", hash = "sha256:812479d5f85eae27aeeaa22f64c20b926b28b5b9b2bf31c8eab9496d3e038028", size = 12794, upload-time = "2025-01-14T19:04:14.204Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ee/fe/ad7278325d8c760d5366b08d6162193612a3bf33bb0fa98d83d7dcc41918/pyobjc_framework_KernelManagement-11.0-py2.py3-none-any.whl", hash = "sha256:e2ad0efd00c0dce90fc05efac296733282c482d54ec7c5fdcb86b4fb8dff1eb8", size = 3604, upload-time = "2025-01-14T18:54:36.643Z" },
- { url = "https://files.pythonhosted.org/packages/1e/20/8aff6699bf780c88770214f72e92b9db736de078aa1aaaea45312758116e/pyobjc_framework_KernelManagement-11.0-py3-none-any.whl", hash = "sha256:90baacf8bea2883fd62ffb5d7dc6e6ae43fcc6f444458c884da8d92170fcaa5e", size = 3675, upload-time = "2025-01-14T18:54:37.62Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-latentsemanticmapping"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/42/29/8838eefeb82da95931134b06624364812dedf7e9cc905f36d95d497f2904/pyobjc_framework_latentsemanticmapping-11.0.tar.gz", hash = "sha256:6f578c3e0a171706bdbfcfc2c572a8059bf8039d22c1475df13583749a35cec1", size = 17704, upload-time = "2025-01-14T19:04:14.972Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7f/87/a8d2f508c021afa4f8af51773ab22cbd883270bfda8368a86d473736b05a/pyobjc_framework_LatentSemanticMapping-11.0-py2.py3-none-any.whl", hash = "sha256:87fd91320fb7ce0b2c482fda41a5c38388f5a694ee2d7208725d22ff75438c00", size = 5369, upload-time = "2025-01-14T18:54:38.493Z" },
- { url = "https://files.pythonhosted.org/packages/df/f0/cea2a0d25ad20aef6eb38c432d2c93bda2cb2239c6286b6086f8687a8072/pyobjc_framework_LatentSemanticMapping-11.0-py3-none-any.whl", hash = "sha256:073b8a4e7a22e6abd58005b7d7091144aec4fc1d4b519e9f972b3aee9da30009", size = 5435, upload-time = "2025-01-14T18:54:39.643Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-launchservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-coreservices" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/da/59/eb847389224c670c885ae3d008b1ffe3b996bbe094b43e49dfa84f3947a9/pyobjc_framework_launchservices-11.0.tar.gz", hash = "sha256:7c5c8a8cec013e2cb3fa82a167ca2d61505c36a79f75c718f3f913e597f9ffee", size = 20691, upload-time = "2025-01-14T19:04:15.884Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/35/46/72937390e3eb0f31809f0d56004a388d20b49724495885e8be677707c07c/pyobjc_framework_LaunchServices-11.0-py2.py3-none-any.whl", hash = "sha256:654572e5f2997d8f802b97f619fc6c7d4f927abb03ce53b3dad89b376517b2d1", size = 3807, upload-time = "2025-01-14T18:54:40.579Z" },
- { url = "https://files.pythonhosted.org/packages/c0/12/74b96f187beb2f5605f9d487c3141ac8d25193556f2f5febff3580e8b2cb/pyobjc_framework_LaunchServices-11.0-py3-none-any.whl", hash = "sha256:dbc169442deae53f881d1d07fc79c9da6459e5f0b411e8dd1cfd1c519b3a99c8", size = 3876, upload-time = "2025-01-14T18:54:41.577Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-libdispatch"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/33/4ec96a9edd37948f09e94635852c2db695141430cc1adc7b25968e1f3a95/pyobjc_framework_libdispatch-11.0.tar.gz", hash = "sha256:d22df11b07b1c3c8e7cfc4ba9e876a95c19f44acd36cf13d40c5cccc1ffda04b", size = 53496, upload-time = "2025-01-14T19:04:16.82Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/24/1f/f3273cc8261d45a6bef1fa48ac39cd94f6a1e77b1ec70f79bae52ad54015/pyobjc_framework_libdispatch-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cebdc33a1a771c9ab03fe5c8a2b0ed9698804e7bccdbfcd3cc0045c4b4aad4f3", size = 20607, upload-time = "2025-01-14T19:01:53.577Z" },
- { url = "https://files.pythonhosted.org/packages/32/08/40638a5e916b1b94b4b29abacb18628fd47871d80fdf2fc1ef7216726d29/pyobjc_framework_libdispatch-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:999815af50ad2216e28d76893023b7839b7f1e8f22bcf7062d81d9a51ade4613", size = 15949, upload-time = "2025-01-14T19:01:54.496Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-libxpc"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b9/7e/9fa73ce6925db9cfd8a6b45d97943af8fe59f92251e7fd201b6e4608c172/pyobjc_framework_libxpc-11.0.tar.gz", hash = "sha256:e0c336913ab6a526b036915aa9038de2a5281e696ac2d3db3347b3040519c11d", size = 48627, upload-time = "2025-01-14T19:04:17.728Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/21/c2/b77019e344b3f46ca4169c19e0539cff9586c8db0a97715590696993bd00/pyobjc_framework_libxpc-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5f05f9eb3662df5832ff09ab788d6f6099f4674cb015200db317ea8c69f8c5e8", size = 19683, upload-time = "2025-01-14T19:02:02.364Z" },
- { url = "https://files.pythonhosted.org/packages/3c/b9/bf34709c2d8f62a029f4c8e7f9a58c6eb5f3a68542cbcd2a15070b66485a/pyobjc_framework_libxpc-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e000cad8588a961a3e6e5016736cd76b5d992b080cfe8b95745691db5a0ce8df", size = 19788, upload-time = "2025-01-14T19:02:03.327Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-linkpresentation"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/95/5c/dac9fe4ad0a4076c863b5ac9925e751fc18c637ae411e4891c4b7558a5b3/pyobjc_framework_linkpresentation-11.0.tar.gz", hash = "sha256:bc4ace4aab4da4a4e4df10517bd478b6d51ebf00b423268ee8d9f356f9e87be9", size = 15231, upload-time = "2025-01-14T19:04:20.763Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/12/fc/aa3f0016e2246c4574cce0e323416303992411a012266b5bdda74095ebef/pyobjc_framework_LinkPresentation-11.0-py2.py3-none-any.whl", hash = "sha256:c10ee1ac48bb7cd2d67ade7f354ec71af1f4244a8deb8530ba646fd4ba327b21", size = 3799, upload-time = "2025-01-14T18:54:43.137Z" },
- { url = "https://files.pythonhosted.org/packages/85/0b/77c16f2d4541a4490723e18c03c3bd6ecf7db789cf4988e628753e2e4526/pyobjc_framework_LinkPresentation-11.0-py3-none-any.whl", hash = "sha256:5b063900715c5bcf58f533e6c9672473cb07fe3eaa0f0454d93947defa09f13e", size = 3865, upload-time = "2025-01-14T18:54:44.287Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-localauthentication"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-security" },
+ { 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/ec/b1/bea4b5f8adbb69c0b34eddee63e052f35271cc630db43fbef6873352e21f/pyobjc_framework_localauthentication-11.0.tar.gz", hash = "sha256:eb55a3de647894092d6ed3f8f13fdc38e5dbf4850be320ea14dd2ac83176b298", size = 40020, upload-time = "2025-01-14T19:04:22.206Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d1/dd/eaa44e4fe3b5c312190c0468afcab0a4372da29535fe9f860b6b9e1e6b4a/pyobjc_framework_LocalAuthentication-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6500bb5b195799d70f2a622d89a9c2531cb13d6afe30916cf073a195dd86eb", size = 10515, upload-time = "2025-01-14T18:54:46.214Z" },
- { url = "https://files.pythonhosted.org/packages/31/86/f4e913e966a6dbefbaa95aed35e7d235ba2f172d079d3c0b4351a584357b/pyobjc_framework_LocalAuthentication-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c0291e743fb1534c1df900e9adacc809af0651744627ce8ae25cfd021e3db73b", size = 10530, upload-time = "2025-01-14T18:54:47.16Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-localauthenticationembeddedui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-localauthentication" },
+ { 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/e1/ee/821f2d2e9da4cba3dc47e50c8367c6405e91551fb7d8ec842858d5b1d45d/pyobjc_framework_localauthenticationembeddedui-11.0.tar.gz", hash = "sha256:7e9bf6df77ff12a4e827988d8578c15b4431694b2fcfd5b0dad5d7738757ee6a", size = 14204, upload-time = "2025-01-14T19:04:23.566Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/da/66/2151e5ee7fb97b34c7eda9f8b1442683cced27bcb273d34c8aa2c564e528/pyobjc_framework_LocalAuthenticationEmbeddedUI-11.0-py2.py3-none-any.whl", hash = "sha256:0ccbbdd8c7142b1670885881c803f684ee356df83a5338be9135f46462caae6c", size = 3914, upload-time = "2025-01-14T18:54:52.074Z" },
- { url = "https://files.pythonhosted.org/packages/d8/a9/c362ac3586bb2d46868b8ea9da3747c9aae3f0c9448ee09934a1be805383/pyobjc_framework_LocalAuthenticationEmbeddedUI-11.0-py3-none-any.whl", hash = "sha256:e8da98dc38a88995e344742585d3735af9b5bd9926a29774d77e2aa6dd46b7af", size = 3984, upload-time = "2025-01-14T18:54:54.974Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mailkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d8/79/9c9140f726ba14898762ddc19e7142724e0ce5930f08eb20f33f78b05be8/pyobjc_framework_mailkit-11.0.tar.gz", hash = "sha256:d08a2dcc95b5e7955c7c385fe6e018325113d02c007c4178d3fb3c9ab326c163", size = 32274, upload-time = "2025-01-14T19:04:25.086Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d2/38/f9bcd204c1ba0943365f3cc505d934ea93fe4b99d61e961ced0f0991a4f9/pyobjc_framework_MailKit-11.0-py2.py3-none-any.whl", hash = "sha256:78e54ff3988fd1af16c06e0c39dea3b7ff522e367d262f58e88962772291c7f9", size = 4803, upload-time = "2025-01-14T18:54:58.295Z" },
- { url = "https://files.pythonhosted.org/packages/64/4a/f3596583795c608838c7fa84fc4836f365c5744a3e412392d47a200a6221/pyobjc_framework_MailKit-11.0-py3-none-any.whl", hash = "sha256:0573ee0be66419130774aca36b611d0d07fcf7c756524860acba8fe17eefeec2", size = 4874, upload-time = "2025-01-14T18:55:00.648Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mapkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-corelocation" },
- { name = "pyobjc-framework-quartz" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/96/7e/ef86c6e218a58bb9497ce9754a77f12ffe01c4b3609279727b7d7e44655a/pyobjc_framework_mapkit-11.0.tar.gz", hash = "sha256:cd8a91df4c0b442fcf1b14d735e566a06b21b3f48a2a4afe269fca45bfa49117", size = 165080, upload-time = "2025-01-14T19:04:26.606Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fc/7e/f0457c7ca001a01f47aa944c1f86a24d2d04db0aa1c19f51cbf77a65cc9b/pyobjc_framework_MapKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:83128d79aa7644e5b966b32346f7da749b1dbb110dadba857b93ecf5663e24e6", size = 23045, upload-time = "2025-01-14T18:55:02.629Z" },
- { url = "https://files.pythonhosted.org/packages/d5/b0/532b4f57f8783cf6394b17e76174c393d0503ee41e026782a9950bd46279/pyobjc_framework_MapKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e6aa1d00cfe2e02b301467e24ca51e469e9a8a2ec2a9f097b73adca1a5a2a054", size = 23040, upload-time = "2025-01-14T18:55:04.055Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mediaaccessibility"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/81/8e/9fe2cb251ff6107a03bafa07f63b6593df145a2579fffb096023fb21b167/pyobjc_framework_mediaaccessibility-11.0.tar.gz", hash = "sha256:1298cc0128e1c0724e8f8e63a6167ea6809a985922c67399b997f8243de59ab4", size = 18671, upload-time = "2025-01-14T19:04:27.624Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/50/1f/36b1115cfd02d68d39cc3fe976fe3d40bad1d1a0a9c8175c66d230bb7276/pyobjc_framework_MediaAccessibility-11.0-py2.py3-none-any.whl", hash = "sha256:901961f171f7af184decbf5a3899debfa56dbd1a63a53d0ff3d93eff90f2f464", size = 4637, upload-time = "2025-01-14T18:55:08.968Z" },
- { url = "https://files.pythonhosted.org/packages/72/3f/fa350681a6599ed6756dc598fcd17fda1521249e4570a57b4a9b9c900f47/pyobjc_framework_MediaAccessibility-11.0-py3-none-any.whl", hash = "sha256:3f4b9e4d1ac8e7f8cdb7a2e9839ab75cb358dead3e6365ccd8d6017d7e93811e", size = 4708, upload-time = "2025-01-14T18:55:09.939Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mediaextension"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-avfoundation" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coremedia" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/18/1f/e31d9431bc71077b09583ea863b3c91b7de9371d0cc17a8be99be8119daa/pyobjc_framework_mediaextension-11.0.tar.gz", hash = "sha256:ecd8a64939e1c16be005690117c21fd406fc04d3036e2adea7600d2a0c53f4ea", size = 57931, upload-time = "2025-01-14T19:04:28.65Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4d/94/1e4aa67e424a043dfa886c946bb872f9653cc12ad59bd7c2c24e3d19a4f5/pyobjc_framework_MediaExtension-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f25d674f381bae800761efe1628959293009d287f7127616f75318a87e4543d", size = 39781, upload-time = "2025-01-14T18:55:13.454Z" },
- { url = "https://files.pythonhosted.org/packages/02/3c/2cbd4498950daadd111639a7b8dea2aaa6825526677b31ae49bc940f1036/pyobjc_framework_MediaExtension-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9a167725f7a6921d446084b132505392bb375a5ef91498f7be5d94c0d48d26ae", size = 39777, upload-time = "2025-01-14T18:55:14.434Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-medialibrary"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/a8/a4/8c7d1635994800dc412a5db2c4b43ed499184651efcec0c8da3cf8e2bcc7/pyobjc_framework_medialibrary-11.0.tar.gz", hash = "sha256:692889fab1e479a9c207f0ff23c900dad5f47caf47c05cc995d9bb7c1e56e8b9", size = 18975, upload-time = "2025-01-14T19:04:29.739Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/16/b6/c079b41a7a4b6b856b4ba7196500f058fb9d9f4f021269b49cf0861ace1f/pyobjc_framework_MediaLibrary-11.0-py2.py3-none-any.whl", hash = "sha256:3d273d4db7e1894fd2a95448c26eeced6e13e33555f727988aeec4b2762246fb", size = 4288, upload-time = "2025-01-14T18:55:20.473Z" },
- { url = "https://files.pythonhosted.org/packages/b0/ae/05f2ee15f5e8524b27d6e446822edfed977c1ed0d3201644ae4d5d78bdde/pyobjc_framework_MediaLibrary-11.0-py3-none-any.whl", hash = "sha256:b8b97bb9067cf81942ce69d3273e2b18d093290c3fd692172a54f012ab64c0b3", size = 4359, upload-time = "2025-01-14T18:55:21.491Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mediaplayer"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-avfoundation" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/3d2783f2f96ddf51bebcf6537a4a0f2a8a1fe4e520de218fc1b7c5b219ed/pyobjc_framework_mediaplayer-11.0.tar.gz", hash = "sha256:c61be0ba6c648db6b1d013a52f9afb8901a8d7fbabd983df2175c1b1fbff81e5", size = 94020, upload-time = "2025-01-14T19:04:30.617Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/96/b2/57b7b75bb5f2b624ce48cd48fb7d651d2f24d279918b352ae8fb03384b47/pyobjc_framework_MediaPlayer-11.0-py2.py3-none-any.whl", hash = "sha256:b124b0f18444b69b64142bad2579287d0b1a4a35cb6b14526523a822066d527d", size = 6903, upload-time = "2025-01-14T18:55:24.375Z" },
- { url = "https://files.pythonhosted.org/packages/e9/8e/4969374f0fb243dd06336f2edc8c755743a683e73a57c3253279d048a455/pyobjc_framework_MediaPlayer-11.0-py3-none-any.whl", hash = "sha256:1a051624b536666feb5fd1a4bb54000ab45dac0c8aea4cd4707cbde1773acf57", size = 6977, upload-time = "2025-01-14T18:55:25.359Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mediatoolbox"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/da/46/cf5f3bde6cad32f10095850ca44f24ba241d18b26379187c412be1260f39/pyobjc_framework_mediatoolbox-11.0.tar.gz", hash = "sha256:de949a44f10b5a15e5a7131ee53b2806b8cb753fd01a955970ec0f475952ba24", size = 23067, upload-time = "2025-01-14T19:04:32.823Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c3/d5/ee184e33bd743c363d7ab59d8412289c6ac14c78a035545a067b98704ae2/pyobjc_framework_MediaToolbox-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df09e4db52d4efeafe4a324600b9c5062fd87c1d1217ebec2df65c8b6b0ce9ef", size = 12776, upload-time = "2025-01-14T18:55:30.277Z" },
- { url = "https://files.pythonhosted.org/packages/e9/a5/c02d2c44ebcd5884d7ccf55c597c0960d14e4e8f386b65dcd76f9f50ec3d/pyobjc_framework_MediaToolbox-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e80e3057f5030fb034ac93c3e891cee346716e1669f280ebbd63ccfa52b2b7ff", size = 12937, upload-time = "2025-01-14T18:55:34.139Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metal"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/77/e0/a6d18a1183410a5d8610ca1ae6c065b8944586441f8669faee7509817246/pyobjc_framework_metal-11.0.tar.gz", hash = "sha256:cad390150aa63502d5cfe242026b55ed39ffaf816342ddf51e44a9aead6c24be", size = 446102, upload-time = "2025-01-14T19:04:34.011Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e2/fe/083727028e63ffcf7455d10288df05696737ee74a31decdc671e32624f58/pyobjc_framework_Metal-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ac5f317d52cd7523dea2e172fbe8b03e7452b907da42a0a5e5c5ab427c5e9de", size = 57321, upload-time = "2025-01-14T18:55:39.025Z" },
- { url = "https://files.pythonhosted.org/packages/78/85/396ad46929ec6e2aa554c29a3fae2f7c7ffb2e1a3fbb9c41948d5a573dc8/pyobjc_framework_Metal-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45802d48d1a35cc66fee08539c8ca9fc6a0dc4ab700cf78a81cf5f8982ed6f5b", size = 57099, upload-time = "2025-01-14T18:55:40.091Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metalfx"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-metal" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/68/cf/ff9367e4737a12ebd12a17e693ec247028cf065761acc073ebefb2b2393a/pyobjc_framework_metalfx-11.0.tar.gz", hash = "sha256:2ae41991bf7a733c44fcd5b6550cedea3accaaf0f529643975d3da113c9f0caa", size = 26436, upload-time = "2025-01-14T19:04:36.161Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/16/f1/4140b63b3128cb2f12e136c4158a082ce170e4eb979bccb628768c59fd98/pyobjc_framework_MetalFX-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3a3847812d40cb6bb7a5f0e735f9f28cba83a1e1264d4dafc630ce894e98a20", size = 10308, upload-time = "2025-01-14T18:55:47.719Z" },
- { url = "https://files.pythonhosted.org/packages/c0/85/460abd4f96a7a3efd36404a480ed4d31a51f4b3ed64dc4595502a5f725c3/pyobjc_framework_MetalFX-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a37dc271513b217fcba4a99c6cd92997ee171b49b974e0a9dd1b35feb32b7109", size = 10338, upload-time = "2025-01-14T18:55:48.593Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metalkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-metal" },
+ { 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/92/27/fb3c1b10914abf2ae6682837abf76bcd8cb7af2ba613fbc55fb9d055bb95/pyobjc_framework_metalkit-11.0.tar.gz", hash = "sha256:1bbbe35c7c6a481383d32f6eaae59a1cd8084319a65c1aa343d63a257d8b4ddb", size = 44628, upload-time = "2025-01-14T19:04:36.977Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d3/44/e7eb6746d9e1ad0ad08ab0a8ac20d264b049960363a8f28a744d1d9c319c/pyobjc_framework_MetalKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f314478a5d772d2f7b4db09957ecb63acd6e3f0cde8c18b1b6b35caa9ea7def2", size = 8598, upload-time = "2025-01-14T18:55:56.761Z" },
- { url = "https://files.pythonhosted.org/packages/a6/1c/1ae6d629065e495e8e0b7def36e1d632e461a933f616f9776a914d69b2fd/pyobjc_framework_MetalKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f2d93180e7ac5abd906e492165a72f82d308d68101eadd213bba68a4b1dc4a8", size = 8611, upload-time = "2025-01-14T18:55:58.085Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metalperformanceshaders"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-metal" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/14/c2/c08996a8c6cfef09fb9e726cc99b0bf3ad0ffcef66d5c2543e6b35dd4e2e/pyobjc_framework_metalperformanceshaders-11.0.tar.gz", hash = "sha256:41179e3a11e55325153fffd84f48946d47c1dc1944677febd871a127021e056d", size = 301444, upload-time = "2025-01-14T19:04:38.064Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e6/e9/3741ac0e745e1014961f12cf967eac1d4ec5b110d3ed13fdf9dd4ce27933/pyobjc_framework_MetalPerformanceShaders-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:460a30ff31f04bbe82bf3304949171e148e3171ba0c0773dd9bfc42dad766d2e", size = 33004, upload-time = "2025-01-14T18:56:02.993Z" },
- { url = "https://files.pythonhosted.org/packages/39/b4/51434a9a897a47f6a0d1f6079725e3de4dbc75a7004275f116a2043cf80b/pyobjc_framework_MetalPerformanceShaders-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:abd4649de32aedfa45f8535d74227ba3e1411b6426f794026e8426feab43ea8e", size = 33222, upload-time = "2025-01-14T18:56:04.78Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metalperformanceshadersgraph"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-metalperformanceshaders" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b5/b8/353852c76eb437e907ca0acf8a5b5f9255e9b9ee8c0706b69b0c17498f97/pyobjc_framework_metalperformanceshadersgraph-11.0.tar.gz", hash = "sha256:33077ebbbe1aa7787de2552a83534be6c439d7f4272de17915a85fda8fd3b72d", size = 105381, upload-time = "2025-01-14T19:04:39.831Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0d/8c/3d8f1cc6cfe7f9fd73f3911bb62256fdefc4d7f5375b8be84870d8c15650/pyobjc_framework_MetalPerformanceShadersGraph-11.0-py2.py3-none-any.whl", hash = "sha256:d48ffe401fbc8273a23e908685635a51c64d4ebfb5ad32742664ab9fac6c5194", size = 6403, upload-time = "2025-01-14T18:56:10.236Z" },
- { url = "https://files.pythonhosted.org/packages/ef/26/ca0441ac11d5ecc7814b48b3af9df467ead93622f0edc67e947f1a4afe97/pyobjc_framework_MetalPerformanceShadersGraph-11.0-py3-none-any.whl", hash = "sha256:f0702a6e91b273e552283ff2782220ce08eb65325aa45ad428e0b7f3b45cf211", size = 6474, upload-time = "2025-01-14T18:56:11.479Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-metrickit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/28/82/605ad654f40ff4480ba9366ad3726da80c98e33b73f122fb91259be1ce81/pyobjc_framework_metrickit-11.0.tar.gz", hash = "sha256:ee3da403863beec181a2d6dc7b7eeb4d07e954b88bbabac58a82523b2f83fdc7", size = 40414, upload-time = "2025-01-14T19:04:41.186Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/13/1f/cc897b07b3ed96a26a3008f43e0deefaa60e280ac13118a2ff4224fca0d8/pyobjc_framework_MetricKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:422b6ca1f082dae864df8cc4aa0bac3829be95675b72ef63cd3ee00d30966b97", size = 7958, upload-time = "2025-01-14T18:56:13.428Z" },
- { url = "https://files.pythonhosted.org/packages/19/63/f37010479670958d3c976d007d45107c3fc53b5626586527c6310821e15a/pyobjc_framework_MetricKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b94313601bbf0181c8f75712e82646261ff0e020da5c83d25914952db53a7955", size = 7966, upload-time = "2025-01-14T18:56:14.36Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-mlcompute"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c5/c9/22fe4720685724ec1444c8e5cdb41d360b1434d0971fb3e43cf3e9bf51fd/pyobjc_framework_mlcompute-11.0.tar.gz", hash = "sha256:1a1ee9ab43d1824300055ff94b042a26f38f1d18f6f0aa08be1c88278e7284d9", size = 89265, upload-time = "2025-01-14T19:04:43.326Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/75/06/a5865c0e4db4e7289bf6b40242b7149af87d5779f34ca168df5cabf2d5a4/pyobjc_framework_MLCompute-11.0-py2.py3-none-any.whl", hash = "sha256:16ec2942af9915f931df76b42e7f42348109b599faef955f5bea540735f87677", size = 6729, upload-time = "2025-01-14T18:54:55.927Z" },
- { url = "https://files.pythonhosted.org/packages/b5/15/3c69df5b5b99cea4a573e1d0e3c0b607cfe4ea1404ea1fe3a302361eb452/pyobjc_framework_MLCompute-11.0-py3-none-any.whl", hash = "sha256:bcdf94fe060fb034aed41db84af1cfcdbf3925e69b2b11df89d4546fac6cf0bf", size = 6799, upload-time = "2025-01-14T18:54:56.893Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-modelio"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/ca/7c/b75b84d41e7854ffe9c9a42846f8105227a5fd0b02b690b4a75018b2caa3/pyobjc_framework_modelio-11.0.tar.gz", hash = "sha256:c875eb6ff7f94d18362a00faaa3016ae0c28140326338d18aa03c0b62f1c6b9d", size = 122652, upload-time = "2025-01-14T19:04:44.263Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/11/98/a30e8df5624c7929dfcd9748bf859929e8aa2c7d836efe5888dafc05f729/pyobjc_framework_ModelIO-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c126318b878ffb31c39b0c7c91ca20a3b46c14c18f000e3bfb854e4541fe0147", size = 20715, upload-time = "2025-01-14T18:56:22.163Z" },
- { url = "https://files.pythonhosted.org/packages/a9/f8/bb4bc635eb16331c20731cae2e495645d0d10e25962451631eb9085a3f85/pyobjc_framework_ModelIO-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a7357f07b77f3ab0a8107d827acdbc3e1fd458ce396335c057930b6a3f225a93", size = 20715, upload-time = "2025-01-14T18:56:23.297Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-multipeerconnectivity"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/14/80/4137cb9751aa3846c4954b3e61f948aae17afeb6851e01194aa50683caef/pyobjc_framework_multipeerconnectivity-11.0.tar.gz", hash = "sha256:8278a3483c0b6b88a8888ca76c46fd85808f9df56d45708cbc4e4182a5565cd3", size = 25534, upload-time = "2025-01-14T19:04:45.211Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2d/d2/a4144f966cbe998f8da46b936783561bcd3e7e84b8f2dc45eb49ee3f6f21/pyobjc_framework_MultipeerConnectivity-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e338b22f5b0fcb398e316552398c252bedfc3375c058340861eb205e3cdf994e", size = 12423, upload-time = "2025-01-14T18:56:30.132Z" },
- { url = "https://files.pythonhosted.org/packages/7b/50/ac9213aca34d30993a36525c23d19ba5a568d3ea4e31e3bc2a6940ddafde/pyobjc_framework_MultipeerConnectivity-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:66bef15f5e5afd6b966cdadf2162082b0171f4a45af6d2cb2644f38431011911", size = 12447, upload-time = "2025-01-14T18:56:31.04Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-naturallanguage"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/62/64/63e97635fa637384bc8c980796573dc7a9e7074a6866aef073b1faf3e11d/pyobjc_framework_naturallanguage-11.0.tar.gz", hash = "sha256:4c9471fa2c48a8fd4899de4406823e66cb0292dbba7b471622017f3647d53fa4", size = 46385, upload-time = "2025-01-14T19:04:46.185Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7d/72/2246c0a6dc2d087951a626157f52c81cf88fe28393994163e9572fd1eb61/pyobjc_framework_NaturalLanguage-11.0-py2.py3-none-any.whl", hash = "sha256:0744a2871690dcc9ec9e7169023b492abdde63ef97abde46013c01477b4d047c", size = 5250, upload-time = "2025-01-14T18:56:34.675Z" },
- { url = "https://files.pythonhosted.org/packages/3a/49/f5faf3fab0f1ffb21882115878f1e5023257239aa576d6c01c31e42dd1da/pyobjc_framework_NaturalLanguage-11.0-py3-none-any.whl", hash = "sha256:7c021b270fda5469b56b9804e860cf5a80a485b817fc5fd3bb002383b2982d94", size = 5321, upload-time = "2025-01-14T18:56:36.377Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-netfs"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c7/29/eb569870b52c7581104ed2806cae2d425d60b5ab304128cd58155d5b567f/pyobjc_framework_netfs-11.0.tar.gz", hash = "sha256:3de5f627a62addf4aab8a4d2d07213e9b2b6c8adbe6cc4c332ee868075785a6a", size = 16173, upload-time = "2025-01-14T19:04:47.11Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/00/e7/4be35bc2adbebffb5ac7ede2b8459432194a82bd8f325af12b77b7c26248/pyobjc_framework_NetFS-11.0-py2.py3-none-any.whl", hash = "sha256:11e06da73a1d590b8462f3a1412604758d49b5e04d134b6e991282453b76abb8", size = 4088, upload-time = "2025-01-14T18:56:37.646Z" },
- { url = "https://files.pythonhosted.org/packages/fe/83/b7c8dfaee82c0312af25c2b31621505ce19f01fab7bb55eec69c0b4d24ad/pyobjc_framework_NetFS-11.0-py3-none-any.whl", hash = "sha256:9b69a36e3a6782ce37cd3140c584dd7d5c96f7355662d004a2927583b112b4dd", size = 4162, upload-time = "2025-01-14T18:56:38.682Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-network"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/78/8e/18e55aff83549e041484d2ee94dd91b29cec9de40508e7fe9c4afec110a7/pyobjc_framework_network-11.0.tar.gz", hash = "sha256:d4dcc02773d7d642a385c7f0d951aeb7361277446c912a49230cddab60a65ab8", size = 124160, upload-time = "2025-01-14T19:04:50.191Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/24/b5/16800524e6d8d99467f53dbafa661abb1405d08d50def7edb933504197a3/pyobjc_framework_Network-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6fc797537690a241b555475923bcee28824efacd501e235457daeb4496b4b700", size = 19507, upload-time = "2025-01-14T18:56:41.544Z" },
- { url = "https://files.pythonhosted.org/packages/36/7c/a5966976564e8e71c0e66bf68e9282c279ad0c3ce81be61fa20ca8e0ca2e/pyobjc_framework_Network-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b9bb4a0cbd01cc4acb120ce313662763bca0c5ef11c01a0a0cae64c80b120c5", size = 19532, upload-time = "2025-01-14T18:56:42.499Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-networkextension"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/59/90/97dcfac5895b07e891adf634c3a074b68992d132ccfab386c186ac1a598c/pyobjc_framework_networkextension-11.0.tar.gz", hash = "sha256:5ba2254e2c13010b6c4f1e2948047d95eff86bfddfc77716747718fa3a8cb1af", size = 188551, upload-time = "2025-01-14T19:04:51.352Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f2/a4/120aba6e1ccf473d7294c200687f500b096947fec58d94dc772b1a444ecc/pyobjc_framework_NetworkExtension-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4bba4f338748c8ad2cb4320c4dd64b64772a863c6b6f991c2636b2a2f4cb839a", size = 13945, upload-time = "2025-01-14T18:56:53.477Z" },
- { url = "https://files.pythonhosted.org/packages/d1/0f/f7039d2bae0dcd63f66aff008613860499b6014dbd272726026f6c4c768d/pyobjc_framework_NetworkExtension-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:abf63433992ff1830f42cb813d1575473f0034ca6f62827f43bb2b33cc31e095", size = 13960, upload-time = "2025-01-14T18:56:55.661Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-notificationcenter"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d7/d0/f0a602e01173531a2b639e283a092cf1f307fd873abd2ed590b9c4122337/pyobjc_framework_notificationcenter-11.0.tar.gz", hash = "sha256:f878b318c693d63d6b8bd1c3e2ad4f8097b22872f18f40142e394d84f1ead9f6", size = 22844, upload-time = "2025-01-14T19:04:52.459Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bf/f2/22f04062b772e2f47ee2d54eac3f80c5aef727ec468ef5ab9a3272dd2a73/pyobjc_framework_NotificationCenter-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:075853f3e36eb4377182589e552226b2207a575035d7e128055cfde9dcad84b7", size = 9684, upload-time = "2025-01-14T18:57:02.779Z" },
- { url = "https://files.pythonhosted.org/packages/16/22/531c2aab1639ab13aeaf3ac324afa102515b8d5eb860cb1a566018d98058/pyobjc_framework_NotificationCenter-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:093e50badfbc78edf088f9241cddba7516a58188d401f299e361f1ec85e93fce", size = 9707, upload-time = "2025-01-14T18:57:03.659Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-opendirectory"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/55/cf/ba0cf807758acdc6a19e4787fdcda2eb59034aa22c4203d04fd49b276981/pyobjc_framework_opendirectory-11.0.tar.gz", hash = "sha256:0c82594f4f0bcf2318c4641527f9243962d7b03e67d4f3fb111b899a299fc7eb", size = 189165, upload-time = "2025-01-14T19:04:53.42Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b4/0a/e5a03c46a5873db83fb89ea829e4a0c02fb3f56f3639a6053e72854f435b/pyobjc_framework_OpenDirectory-11.0-py2.py3-none-any.whl", hash = "sha256:8a0feeda5a7f34b25b72c71cd1e4dd57b636cc4103248ff91bcb8571d4915eb4", size = 11747, upload-time = "2025-01-14T18:57:17.445Z" },
- { url = "https://files.pythonhosted.org/packages/da/fd/be3815a19978ab2a3abe9563a031195b40647077fcebbee86232af260176/pyobjc_framework_OpenDirectory-11.0-py3-none-any.whl", hash = "sha256:bfac495de433a62e3934619e2f5d2254177f960b7d4e905ed4ef359127e23b24", size = 11816, upload-time = "2025-01-14T18:57:18.486Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-osakit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d3/4a/e49680f7f3ab9c0632ed9be76a0a59299e7fd797335690b3da4d117f2d7b/pyobjc_framework_osakit-11.0.tar.gz", hash = "sha256:77ac18e2660133a9eeb01c76ad3df3b4b36fd29005fc36bca00f57cca121aac3", size = 22535, upload-time = "2025-01-14T19:04:54.753Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/56/f6/1dcff2f76280946368ee75ab39c92e261a851656c5979a50513563d08cf0/pyobjc_framework_OSAKit-11.0-py2.py3-none-any.whl", hash = "sha256:3183414e345af83a2187b00356130909a7c2a41b2227dc579b662737300c3ba4", size = 4094, upload-time = "2025-01-14T18:57:08.639Z" },
- { url = "https://files.pythonhosted.org/packages/17/75/745985429f0ff4776ffb8ba261199e11f4d6977b1814ad2b39084f83bad5/pyobjc_framework_OSAKit-11.0-py3-none-any.whl", hash = "sha256:79150c47d2aeffc72fb6551060518ce472275edbad3b56aef5923a6086371c28", size = 4162, upload-time = "2025-01-14T18:57:09.71Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-oslog"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coremedia" },
- { name = "pyobjc-framework-quartz" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b0/93/0a72353d0212a815bd5e43aec528ce7b28b71d461d26e5fa3882ff96ffa3/pyobjc_framework_oslog-11.0.tar.gz", hash = "sha256:9d29eb7c89a41d7c702dffb6e2e338a2d5219387c8dae22b67754ddf9e2fcb3f", size = 24151, upload-time = "2025-01-14T19:04:55.587Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9c/54/6b507a18d0adadf8b707be9616bc9bab157963b81fa3c9928a0148d3bfd8/pyobjc_framework_OSLog-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0131851fca9b741f290ffa727dd30328dd8526b87c8cef623b79239bed99187", size = 7694, upload-time = "2025-01-14T18:57:12.713Z" },
- { url = "https://files.pythonhosted.org/packages/d1/79/81e64a55023f458aa5d99d10671fd9bcc6c0dcf8339768152fbc28c92cef/pyobjc_framework_OSLog-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:17d8b49113a476372b24ac8e544d88f6d12f878f1081dd611ab203c4484f2039", size = 7720, upload-time = "2025-01-14T18:57:13.695Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-passkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/cb/f8/ebb2bc840f87292a4f60080463ee698ca08516cc958364741dfff2858b33/pyobjc_framework_passkit-11.0.tar.gz", hash = "sha256:2044d9d634dd98b7b624ee09487b27e5f26a7729f6689abba23a4a011febe19c", size = 120495, upload-time = "2025-01-14T19:04:57.689Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/53/72/d7dae8f5a1c5b12d9cf404a71a82fd5a638bc4de2d1099bf838aee1026f0/pyobjc_framework_PassKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:710372134c3adedb9017bfc2fbc592ef0e94ae916145b58e57234239bf903b90", size = 14354, upload-time = "2025-01-14T18:57:24.436Z" },
- { url = "https://files.pythonhosted.org/packages/c3/b1/5ee2f5581877241a4fc2db4ab4a33d595a918bde1b4a59796240e2b2244b/pyobjc_framework_PassKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe0144177f7feb96577bea53841d9b9b3f63185735a1bf1b36368ab189fd6282", size = 14391, upload-time = "2025-01-14T18:57:26.67Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-pencilkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f4/8d/1e97cd72b776e5e1294cbda84325b364702617dd435d32448dcc0a80bd93/pyobjc_framework_pencilkit-11.0.tar.gz", hash = "sha256:9598c28e83f5b7f091592cc1af2b16f7ae94cf00045d8d14ed2c17cb9e4ffd50", size = 22812, upload-time = "2025-01-14T19:04:58.652Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/af/5b/24fb83a97648eaa0d231df7908532dff7b36d5f516d55c92ed9ae07c4e1b/pyobjc_framework_PencilKit-11.0-py2.py3-none-any.whl", hash = "sha256:22cbb6ed2504be4c8d631c4711b00fae48ef731c10c69861b4de1e4fcdc19279", size = 3970, upload-time = "2025-01-14T18:57:30.597Z" },
- { url = "https://files.pythonhosted.org/packages/08/fd/89a005c86b06137837952838d976ce6e39b31082392d78c382d44e03944d/pyobjc_framework_PencilKit-11.0-py3-none-any.whl", hash = "sha256:a4e606c5b69e6adb80ef30fc95fe0095971735d12ab6fc4fe4d982e4c8a3881a", size = 4045, upload-time = "2025-01-14T18:57:31.87Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-phase"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-avfoundation" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/d2/a2/65182dcb44fceb2173f4134d6cd4325dfd0731225b621aa2027d2a03d043/pyobjc_framework_phase-11.0.tar.gz", hash = "sha256:e06a0f8308ae4f3731f88b3e1239b7bdfdda3eef97023e3ce972e2f386451d80", size = 59214, upload-time = "2025-01-14T19:04:59.461Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a9/97/efb9d770ba05d285384b0c121e9e911929893356da1944a0bb03ea0df0f2/pyobjc_framework_PHASE-11.0-py2.py3-none-any.whl", hash = "sha256:d3e41c2b2fdf4b2ce39f558a08762c6864449ff87b618e42747777ad3f821323", size = 6777, upload-time = "2025-01-14T18:57:20.135Z" },
- { url = "https://files.pythonhosted.org/packages/38/85/03420927e4243d0ef8e3e8aa1ca511b5638743d7ec319a570a472a50d60f/pyobjc_framework_PHASE-11.0-py3-none-any.whl", hash = "sha256:78c0600477ea294304b51f8284a2fb299be284c33ae2c135e1c7cd26fdf4def4", size = 6846, upload-time = "2025-01-14T18:57:21.193Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-photos"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f7/c3/fc755c1f8f411433d7ba2e92f3fe3e7b417e9629675ad6baf94ac8b01e64/pyobjc_framework_photos-11.0.tar.gz", hash = "sha256:cfdfdefb0d560b091425227d5c0e24a40b445b5251ff4d37bd326cd8626b80cd", size = 92122, upload-time = "2025-01-14T19:05:01.804Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/80/27/62e5833b9629121b4b6ea8f2b2aa295cf6b719dc6316387f77ec0bd41d77/pyobjc_framework_Photos-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:71bf849888713e4a00eb44074c5000ed081c905ba35b3a55ee84c6367ce60ce8", size = 12085, upload-time = "2025-01-14T18:57:34.615Z" },
- { url = "https://files.pythonhosted.org/packages/b9/6e/54108271ea34b0fc51bf8d0bf677788e4d39a1e29ad481f8c78c100f3159/pyobjc_framework_Photos-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ea630c3abf4620b022f23167ef5f3d6b157b38697d7ffc5df0fc507e95bed655", size = 12107, upload-time = "2025-01-14T18:57:35.66Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-photosui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e4/2c/70ac99fb2b7ba14d220c78cf6401c0c7a47992269f85f699220a6a2cff09/pyobjc_framework_photosui-11.0.tar.gz", hash = "sha256:3c65342e31f6109d8229992b2712b29cab1021475969b55f4f215dd97e2a99db", size = 47898, upload-time = "2025-01-14T19:05:02.737Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/94/ec/9574692e2852d546b28bac853b2b0584c4d4f093a4befac0e105789ee9f6/pyobjc_framework_PhotosUI-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b3865d2cc4fad4d34255941fe93ce504b9d2c7a7043bd0f4c715da9f4af1cf1", size = 12165, upload-time = "2025-01-14T18:57:44.048Z" },
- { url = "https://files.pythonhosted.org/packages/90/a9/85d70fe9eee0d15a0615a3f7b2ef92120c32614e350286d347d733fcf1d0/pyobjc_framework_PhotosUI-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:66826184121cd15415750d801160721adad80b53cbb315192522229b17252ebb", size = 12176, upload-time = "2025-01-14T18:57:44.993Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-preferencepanes"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/35/01/81cc46e0a92d15f2b664b2efdcc8fd310acac570c9f63a99d446e0489784/pyobjc_framework_preferencepanes-11.0.tar.gz", hash = "sha256:ee000c351befeb81f4fa678ada85695ca4af07933b6bd9b1947164e16dd0b3e5", size = 26419, upload-time = "2025-01-14T19:05:03.787Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/70/f7/5d0d9b94563ef06fe0a9c15ba2b77922b73bcc4b6630c487936edf382e20/pyobjc_framework_PreferencePanes-11.0-py2.py3-none-any.whl", hash = "sha256:2143851549430d6bb951adae44cb65c1986662ac7c8cbe15891ed194cbe283a2", size = 4706, upload-time = "2025-01-14T18:57:51.425Z" },
- { url = "https://files.pythonhosted.org/packages/9b/0e/76d694eea953b39318249ae24c956c3e115d8222343fb01f0186f7ca0043/pyobjc_framework_PreferencePanes-11.0-py3-none-any.whl", hash = "sha256:9f1287716374338fa99445ca13dfcc6c9be5597c8a5ce06680a8ca245b4e0acc", size = 4772, upload-time = "2025-01-14T18:57:52.684Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-pushkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/17/ab/7fe55ce5b32c434142be026ec27b1801a2d4694b159b502f9ecd568eebf2/pyobjc_framework_pushkit-11.0.tar.gz", hash = "sha256:df9854ed4065c50022863b3c11c2a21c4279b36c2b5c8f08b834174aacb44e81", size = 20816, upload-time = "2025-01-14T19:05:05.468Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/17/5f/de178da22fa628cd88f599fea2a70b7d1d9ebc65576307df0bf29822a347/pyobjc_framework_PushKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0185cebcc5aad73aae50804c7a2412da6275717b8f872b830d71c484efcdea7a", size = 8010, upload-time = "2025-01-14T18:57:59.042Z" },
- { url = "https://files.pythonhosted.org/packages/5f/a5/60f93031302aba7cdff28728b8141b58c3bd5c12f4a6cef5796a8cc2e666/pyobjc_framework_PushKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43bd1ed31664982e4d8397a7e07e58a7deb85bf9c9866ea966fd7ca25796014c", size = 8032, upload-time = "2025-01-14T18:57:59.973Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-quartz"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a5/ad/f00f3f53387c23bbf4e0bb1410e11978cbf87c82fa6baff0ee86f74c5fb6/pyobjc_framework_quartz-11.0.tar.gz", hash = "sha256:3205bf7795fb9ae34747f701486b3db6dfac71924894d1f372977c4d70c3c619", size = 3952463, upload-time = "2025-01-14T19:05:07.931Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a3/6a/68957c8c5e8f0128d4d419728bac397d48fa7ad7a66e82b70e64d129ffca/pyobjc_framework_Quartz-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d251696bfd8e8ef72fbc90eb29fec95cb9d1cc409008a183d5cc3246130ae8c2", size = 212349, upload-time = "2025-01-14T18:58:08.963Z" },
- { url = "https://files.pythonhosted.org/packages/60/5d/df827b78dcb5140652ad08af8038c9ddd7e01e6bdf84462bfee644e6e661/pyobjc_framework_Quartz-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb4a9f2d9d580ea15e25e6b270f47681afb5689cafc9e25712445ce715bcd18e", size = 212061, upload-time = "2025-01-14T18:58:10.2Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-quicklookthumbnailing"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/50/a1/35ca40d2d4ab05acbc9766986d482482d466529003711c7b4e52a8df4935/pyobjc_framework_quicklookthumbnailing-11.0.tar.gz", hash = "sha256:40763284bd0f71e6a55803f5234ad9cd8e8dd3aaaf5e1fd204e6c952b3f3530d", size = 16784, upload-time = "2025-01-14T19:05:09.857Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/9d/85/1a66fefa99e7a4eb7534b2f56f9a24d33beda450dd2ca45d180307e76c74/pyobjc_framework_QuickLookThumbnailing-11.0-py2.py3-none-any.whl", hash = "sha256:6e567a764942845ce4db7ccfc0f8a9d091216bd029ecca955e618a43d64a5d84", size = 4164, upload-time = "2025-01-14T18:58:16.381Z" },
- { url = "https://files.pythonhosted.org/packages/05/d7/26decb13136b7c95a1ca3ecf202644ad2fd515a57e1117c71bfc86429b20/pyobjc_framework_QuickLookThumbnailing-11.0-py3-none-any.whl", hash = "sha256:e0f7f62b9a1df55e5f717518baf3260dc2cb8a9722cc5e9c6fffc643f69bda27", size = 4229, upload-time = "2025-01-14T18:58:17.404Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-replaykit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/aa/43/c751c517dbb8ee599a31e59832c01080473c7964b6996ca29906f46c0967/pyobjc_framework_replaykit-11.0.tar.gz", hash = "sha256:e5693589423eb9ad99d63a7395169f97b484a58108321877b0fc27c748344593", size = 25589, upload-time = "2025-01-14T19:05:10.791Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fe/56/89a8544426a46bf176c9462511c08d4c94ae7e0403abb2d73632af68ee8e/pyobjc_framework_ReplayKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:262fb834400e8379f4c795e65137763348992f3010284602d876050b8adb9ea4", size = 9904, upload-time = "2025-01-14T18:58:19.332Z" },
- { url = "https://files.pythonhosted.org/packages/47/af/9abfa41060efc96000cc9ae77f302bb8210f3be0f793ba5d11f98a03e468/pyobjc_framework_ReplayKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:da9db123ee52761a670c6e41e5f9d9a47a2ca5582a9c4a7c8662a8bb56a0f593", size = 9903, upload-time = "2025-01-14T18:58:20.222Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-safariservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/40/ec/c9a97b1aa713145cc8c522c4146af06b293cfe1a959a03ee91007949533b/pyobjc_framework_safariservices-11.0.tar.gz", hash = "sha256:dba416bd0ed5f4481bc400bf56ce57e982c19feaae94bc4eb75d8bda9af15b7e", size = 34367, upload-time = "2025-01-14T19:05:12.914Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/40/39/d69f8e7dbf6f366cb5fdaa8aa7ceef1dadb93a5e4d9fc63217477bba5e32/pyobjc_framework_SafariServices-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55c02a533073e0a2aaf6db544f087fd861bace6b62035c3bb2e6b20f0b921b2b", size = 7262, upload-time = "2025-01-14T18:58:29.754Z" },
- { url = "https://files.pythonhosted.org/packages/36/76/a625330bdf7a5d9962299562b6e19f6cbd1ea1b14887958e42a4372d3344/pyobjc_framework_SafariServices-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31ba086a39ee06d8622a504e3ea3a1f6dc8fab1d4c4c7930d5af6e989f38ec56", size = 7262, upload-time = "2025-01-14T18:58:30.725Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-safetykit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/4e/30/89bfdbdca93e57b19891ddeff1742b20a2019cdeb2e44902027dce2642e1/pyobjc_framework_safetykit-11.0.tar.gz", hash = "sha256:9ec996a6a8eecada4b9fd1138244bcffea96a37722531f0ec16566049dfd4cdb", size = 20745, upload-time = "2025-01-14T19:05:13.925Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/37/c5/68b79c0f128eb735397aa68a40e5ac48b88c12967f69358f25f753a3fc1c/pyobjc_framework_SafetyKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:83a1f313c9c63ba107a7c543a8300ae225fa5ff17d963b1c499859da45ceaf55", size = 8395, upload-time = "2025-01-14T18:58:35.46Z" },
- { url = "https://files.pythonhosted.org/packages/99/02/2853a00e75cca8db8b5053ff2648ff2a26f5c02f07af1c70630a36b58d04/pyobjc_framework_SafetyKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c6dd23fcaca9c41d6aadf2ca0a6d07c4032a0c4ea8873ee06da6efd1e868f97e", size = 8418, upload-time = "2025-01-14T18:58:36.369Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-scenekit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/26/3f/a2761585399e752bce8275c9d56990d4b83e57b13d06dd98335891176a89/pyobjc_framework_scenekit-11.0.tar.gz", hash = "sha256:c0f37019f8de2a583f66e6d14dfd4ae23c8d8703e93f61c1c91728a21f62cd26", size = 213647, upload-time = "2025-01-14T19:05:15.129Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/aa/4c/5ec624ae043fbbe15be2a989e3fc6cb08d992e0a5061450b84b33f96429c/pyobjc_framework_SceneKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86d23456e4c7a7bb7bb49be2b98647678ac7a39955e6bb242e0ac125d8b770e8", size = 33108, upload-time = "2025-01-14T18:58:43.577Z" },
- { url = "https://files.pythonhosted.org/packages/b8/7f/fef1cf3eaf1366a6f3f93c5a6b164acfdfdc2d15b3243b70763ac217ce03/pyobjc_framework_SceneKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d0a0d557167adddf27a42fb109a1dce29a22ff09aca34558fccd1c22f08ae2b4", size = 33130, upload-time = "2025-01-14T18:58:44.549Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-screencapturekit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coremedia" },
+ { 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/77/90/71f10db2f52ea324f82eaccc959442c43d21778cc5b1294c29e1942e635c/pyobjc_framework_screencapturekit-11.0.tar.gz", hash = "sha256:ca2c960e28216e56f33e4ca9b9b1eda12d9c17b719bae727181e8b96f0314c4b", size = 53046, upload-time = "2025-01-14T19:05:16.834Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/af/aa/d6d0818564570065411874cbe3de86dee105dc9906161c0584009a1a63bc/pyobjc_framework_ScreenCaptureKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:38468e833ec1498778bd33ce30578afed2e13ac14c73e8e6290ff06a2e0c50d8", size = 11110, upload-time = "2025-01-14T18:58:51.475Z" },
- { url = "https://files.pythonhosted.org/packages/27/61/557e725aef9ad76a1a7c48b361f8c5636a606cbaf9ba520ff8f69d3cf791/pyobjc_framework_ScreenCaptureKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d8a83dcc0950699242677cfefda545b9c0a0567111f8f3d3df1cf6ed75ea480", size = 11121, upload-time = "2025-01-14T18:58:53.055Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-screensaver"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f6/b6/71c20259a1bfffcb5103be62564006b1bbc21f80180658101e2370683bcb/pyobjc_framework_screensaver-11.0.tar.gz", hash = "sha256:2e4c643624cc0cffeafc535c43faf5f8de8be030307fa8a5bea257845e8af474", size = 23774, upload-time = "2025-01-14T19:05:19.325Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/ab/f17cd36458e6cf6d64c412128641edcfc220b8147283f6b34ef56c7db111/pyobjc_framework_ScreenSaver-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:436357c822d87220df64912da04b421e82a5e1e6464d48f2dbccc69529d19cd3", size = 8445, upload-time = "2025-01-14T18:58:59.299Z" },
- { url = "https://files.pythonhosted.org/packages/52/57/300b641e929741a5d38cf80c74496918be1d2fe5e210d3fceb3e768747b2/pyobjc_framework_ScreenSaver-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:03b12e89bc164cb01527ca795f3f590f286d15de6ee0e4ff1d36705740d6d72f", size = 8372, upload-time = "2025-01-14T18:59:00.358Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-screentime"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/42/a7/ee60ee5b0471a4367eaa1c8a243418874fd48fac5dbdfdd318a653d94aaa/pyobjc_framework_screentime-11.0.tar.gz", hash = "sha256:6dd74dc64be1865346fcff63b8849253697f7ac68d83ee2708019cf3852c1cd7", size = 14398, upload-time = "2025-01-14T19:05:21.547Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/40/7a/8df61f80725e993fd0dc1a111217de6a8efec35b02a4796749de0b7e8c34/pyobjc_framework_ScreenTime-11.0-py2.py3-none-any.whl", hash = "sha256:723938c7d47e3c5c1c0f79010a01139762384bd0c03c51ee7a4736fc3f128fed", size = 3721, upload-time = "2025-01-14T18:59:04.027Z" },
- { url = "https://files.pythonhosted.org/packages/c4/62/2f86cedd4cc439625976848832c1d1571fcb69cc087dd71c9cf09e793db5/pyobjc_framework_ScreenTime-11.0-py3-none-any.whl", hash = "sha256:45db846ec9249cab90e86cbb31cf70e13800305b7c74819ab681a91854c91df2", size = 3790, upload-time = "2025-01-14T18:59:06.363Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-scriptingbridge"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/4d/f0/592af19047935e44c07ddd1eba4f05aa8eb460ee842f7d5d48501231cd69/pyobjc_framework_scriptingbridge-11.0.tar.gz", hash = "sha256:65e5edd0ea608ae7f01808b963dfa25743315f563705d75c493c2fa7032f88cc", size = 22626, upload-time = "2025-01-14T19:05:22.461Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7d/2c/2fd33c0318a8fe35f00f0089a44a2c27d4d0fd0b4b5e13628051a4d8c9d3/pyobjc_framework_ScriptingBridge-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c98d080446aa8ba4074e43eb0be1feed96781dbc0718496f172fcd20e84a9158", size = 8209, upload-time = "2025-01-14T18:59:11.107Z" },
- { url = "https://files.pythonhosted.org/packages/93/3b/b2b721248e951eef6b7e6b25cb3a1d6683702235bc73683d0239f068d2df/pyobjc_framework_ScriptingBridge-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:23a4b2e2e57b7b4d992777ea9efb15273ccd8e8105385143dab9bd5a10962317", size = 8238, upload-time = "2025-01-14T18:59:13.27Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-searchkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-coreservices" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/15/27/9676327cf7d13346d546325b411a5deaa072bd0fbe733c8aae8a9a00c0e0/pyobjc_framework_searchkit-11.0.tar.gz", hash = "sha256:36f3109e74bc5e6fab60c02be804d5ed1c511ad51ea0d597a6c6a9653573ddf5", size = 31182, upload-time = "2025-01-14T19:05:24.667Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f2/d4/64fa608b5d91859b11c26ceca83a41d2bf1d0dcbf1d9df847bab5a52ccc8/pyobjc_framework_SearchKit-11.0-py2.py3-none-any.whl", hash = "sha256:332f9d30ec3b223efaac681fbdd923ba660575e241abb4ed5e03207c97799530", size = 3633, upload-time = "2025-01-14T18:59:18.343Z" },
- { url = "https://files.pythonhosted.org/packages/93/e2/83e94c505c5436821982d724cc890f74d717f9473782f7278ce78634685d/pyobjc_framework_SearchKit-11.0-py3-none-any.whl", hash = "sha256:5f4304cb77c327b28ac0f7ec9b99313075afd742091d39368eb64f076bb7d141", size = 3699, upload-time = "2025-01-14T18:59:20.754Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-security"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c5/75/4b916bff8c650e387077a35916b7a7d331d5ff03bed7275099d96dcc6cd9/pyobjc_framework_security-11.0.tar.gz", hash = "sha256:ac078bb9cc6762d6f0f25f68325dcd7fe77acdd8c364bf4378868493f06a0758", size = 347059, upload-time = "2025-01-14T19:05:26.17Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fa/d8/092940f8c46cf09000a9d026e9854772846d5335e3e8a44d0a81aa1f359e/pyobjc_framework_Security-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:93bc23630563de2551ac49048af010ac9cb40f927cc25c898b7cc48550ccd526", size = 41499, upload-time = "2025-01-14T18:59:22.819Z" },
- { url = "https://files.pythonhosted.org/packages/0b/fc/8710bbe80b825c97ecc312aaead3b0f606a23b62b895f6e0a07df8bfeeae/pyobjc_framework_Security-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:421e03b8560ed296a7f5ee67f42f5f978f8c7959d65c8fec99cd77dc65786355", size = 41523, upload-time = "2025-01-14T18:59:25.368Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-securityfoundation"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-security" },
+ { 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/84/d6/0d817edb11d2bdb0f536059e913191e587f1984e39397bb3341209d92c21/pyobjc_framework_securityfoundation-11.0.tar.gz", hash = "sha256:5ae906ded5dd40046c013a7e0c1f59416abafb4b72bc947b6cd259749745e637", size = 13526, upload-time = "2025-01-14T19:05:27.275Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f8/41/50da30e87841c8b9ee1f17e9720dc9dbb2c2e59abac84fffe899ed5f9188/pyobjc_framework_SecurityFoundation-11.0-py2.py3-none-any.whl", hash = "sha256:8f8e43b91ae7cb45f3251c14c0c6caf5fdcdb93794176c4b118214a108ee2ef3", size = 3716, upload-time = "2025-01-14T18:59:29.79Z" },
- { url = "https://files.pythonhosted.org/packages/cb/61/e73a61de62e31b33378ee635534228f4801b1554fbd89a47e0b36965908d/pyobjc_framework_SecurityFoundation-11.0-py3-none-any.whl", hash = "sha256:1fa89969fbf7a4fd57214388a43f7ed6b6b1fd0c0ec7aa77752444eb1604143c", size = 3787, upload-time = "2025-01-14T18:59:30.764Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-securityinterface"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-security" },
+ { 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/b1/88/d7c4942650707fe5b1d3b45b42684f58f2cab7d2772ec74ca96ecef575eb/pyobjc_framework_securityinterface-11.0.tar.gz", hash = "sha256:8843a27cf30a8e4dd6e2cb7702a6d65ad4222429f0ccc6c062537af4683b1c08", size = 37118, upload-time = "2025-01-14T19:05:28.569Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/88/5f/a96da5f43da5a9d0e5d016bc672a4dca09f88d091c96d9ecff5f753ad1d5/pyobjc_framework_SecurityInterface-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2771dae043c8aa278887f96c7d206957164c7a81a562fa391bf0b9316d6755eb", size = 10706, upload-time = "2025-01-14T18:59:32.632Z" },
- { url = "https://files.pythonhosted.org/packages/50/86/fc41dcf8f5300ad2c6508568535d9c0a83b412b0a4a961616441c8acf10f/pyobjc_framework_SecurityInterface-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6453732f7608d514e8f7005d80d238422cbebc4ab4d6d6fed1e51175f9f7244f", size = 10781, upload-time = "2025-01-14T18:59:33.832Z" },
+ { 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" },
+]
+
+[[package]]
+name = "pyobjc-framework-securityui"
+version = "11.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" }
+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" },
]
[[package]]
name = "pyobjc-framework-sensitivecontentanalysis"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/00/e4/f1e0f150ae6c6ad7dde9b248f34f324f4f8b1c42260dbf62420f80d79ba9/pyobjc_framework_sensitivecontentanalysis-11.0.tar.gz", hash = "sha256:0f09034688f894c0f4409c16adaf857d78714d55472de4aa2ac40fbd7ba233d6", size = 13060, upload-time = "2025-01-14T19:05:29.655Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3d/eb/e0d60b3e233860a237fdddd44ab961c9115c33e947058d73c222dafc50af/pyobjc_framework_SensitiveContentAnalysis-11.0-py2.py3-none-any.whl", hash = "sha256:e19d2edc807f98aef31fa4db5472a509cf90523436c971d1095a000b0e357058", size = 3791, upload-time = "2025-01-14T18:59:39.563Z" },
- { url = "https://files.pythonhosted.org/packages/c4/1c/fb2138cf08cd0215ea4f78032871a1d89e7e41d9fad18b55e937f0577c03/pyobjc_framework_SensitiveContentAnalysis-11.0-py3-none-any.whl", hash = "sha256:027bd0be0785f7aea3bfd56ff7c3496e5d383211122393c599c28ea392675589", size = 3863, upload-time = "2025-01-14T18:59:40.548Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-servicemanagement"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/1b/59/8d38b5cdbcfb57ab842e080436dbd04d5a5d2080e99a2ea1e286cfad12a8/pyobjc_framework_servicemanagement-11.0.tar.gz", hash = "sha256:10b1bbcee3de5bb2b9fc3d6763eb682b7a1d9ddd4bd2c882fece62783cb17885", size = 16882, upload-time = "2025-01-14T19:05:30.537Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/5b/35/cbac7db272d0e5e71b300be1517b0a1dc7cf035944675eaed7066d41e883/pyobjc_framework_ServiceManagement-11.0-py2.py3-none-any.whl", hash = "sha256:35cfd7a369a120fa55e64b719a2dda00295b2cc6ddab16ffa8939f4326d1b37d", size = 5254, upload-time = "2025-01-14T18:59:41.438Z" },
- { url = "https://files.pythonhosted.org/packages/b3/40/26c5d63d131e3e415815bfbb4bd035ba10d45f0d87733646221966871b6b/pyobjc_framework_ServiceManagement-11.0-py3-none-any.whl", hash = "sha256:7ec19c9632f67d589ad37815d001e8e443d92e75001c370486a1070a4359e166", size = 5322, upload-time = "2025-01-14T18:59:42.585Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-sharedwithyou"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-sharedwithyoucore" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/20/84/db667061f815537717a6cac891df01a45b65e6feaa2dfa0c9d2e3803a1ef/pyobjc_framework_sharedwithyou-11.0.tar.gz", hash = "sha256:a3a03daac77ad7364ed22109ca90c6cd2dcb7611a96cbdf37d30543ef1579399", size = 33696, upload-time = "2025-01-14T19:05:31.396Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3c/ab/391ef0de3021997ec9a12d8044c0b7e884780a9bead7f847254e06d0f075/pyobjc_framework_SharedWithYou-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6dac74375d3dc18d67cae46f3f16a45cef699b1976a4012827c0f15256da55df", size = 8606, upload-time = "2025-01-14T18:59:44.537Z" },
- { url = "https://files.pythonhosted.org/packages/cf/04/6a3eb12bf9c35f3063be678f36430beb92b7e2683f4b952596396473a74d/pyobjc_framework_SharedWithYou-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6076a0893a3597e054918c136f3391671a225a37fe1b1a070046817e3a232954", size = 8629, upload-time = "2025-01-14T18:59:45.579Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-sharedwithyoucore"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/52/2a/86904cd9cc3bf5cdb9101481e17e67358f39f81ffa0f36768097287e34b3/pyobjc_framework_sharedwithyoucore-11.0.tar.gz", hash = "sha256:3932452677df5d67ea27845ab26ccaaa1d1779196bf16b62c5655f13d822c82d", size = 28877, upload-time = "2025-01-14T19:05:32.283Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/21/40/69ae712e223991cd975c1f8ba2b00a5aa4c129ac0e76838b4d936740e4c7/pyobjc_framework_SharedWithYouCore-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:46cd00a97c5fec747ef057000daa88495699ea5d5d6fe1f302bfb89b2d431645", size = 8366, upload-time = "2025-01-14T18:59:55.641Z" },
- { url = "https://files.pythonhosted.org/packages/c2/ce/500ad643f2d07e8ef065e8ddc5a08954f5d59cc199c89b700581eaf821ee/pyobjc_framework_SharedWithYouCore-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b5f180371a63da718fe6c3b58e7613c6b2adf9b483cefbf6d9467eb8ac2f0ca", size = 8380, upload-time = "2025-01-14T18:59:56.546Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-shazamkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/dd/2a/1f4ad92260860e500cb61119e8e7fe604b0788c32f5b00446b5a56705a2b/pyobjc_framework_shazamkit-11.0.tar.gz", hash = "sha256:cea736cefe90b6bb989d0a8abdc21ef4b3b431b27657abb09d6deb0b2c1bd37a", size = 25172, upload-time = "2025-01-14T19:05:34.497Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/05/81/edfcd4be626aae356dd1b991f521eaeffa1798e91ddae9e7d9ae8ed371d1/pyobjc_framework_ShazamKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ecdc2392d7e8d6e2540c7ad3073a229d08b0818c5dd044a26c93b765ce9868aa", size = 8411, upload-time = "2025-01-14T19:00:02.908Z" },
- { url = "https://files.pythonhosted.org/packages/e1/f7/f3d2ae7a604e3e3c0de93ed229895be6757edfa0cc76f2a44670f28a81c8/pyobjc_framework_ShazamKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef79d863cc7d4023aa552f55d4120653eceed862baf1edba8e08b1af10fab036", size = 8419, upload-time = "2025-01-14T19:00:05.081Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-social"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/6f/56/ed483f85105ef929241ab1a6ed3dbfd0be558bb900e36b274f997db9c869/pyobjc_framework_social-11.0.tar.gz", hash = "sha256:ccedd6eddb6744049467bce19b4ec4f0667ec60552731c02dcbfa8938a3ac798", size = 14806, upload-time = "2025-01-14T19:05:35.394Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/46/1d/2cc0f753ac8b1f5c15cfa9201d8584ff4de6dc940fc954cd9c52d1a615f9/pyobjc_framework_Social-11.0-py2.py3-none-any.whl", hash = "sha256:aa379009738afb0d6abc0347e8189f7f316109e9dfcb904f7f14e6b7c3d5bad8", size = 4362, upload-time = "2025-01-14T19:00:10.058Z" },
- { url = "https://files.pythonhosted.org/packages/a8/25/b762b1f9429f8ea0df754e7d58bafd48d73e5527b0423e67570661a7907e/pyobjc_framework_Social-11.0-py3-none-any.whl", hash = "sha256:94db183e8b3ad21272a1ba24e9cda763d603c6021fd80a96d00ce78b6b94e1c2", size = 4428, upload-time = "2025-01-14T19:00:11.242Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-soundanalysis"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/9a/14/697ca1b76228a96bb459f3cf43234798b05fdf11691202449d98d9d887af/pyobjc_framework_soundanalysis-11.0.tar.gz", hash = "sha256:f541fcd04ec5d7528dd2ae2d873a92a3092e87fb70b8df229c79defb4d807d1a", size = 16789, upload-time = "2025-01-14T19:05:36.576Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ab/d4/91afb41c514d1e236567b971a981f96c1d20f16eb0658256369c53a4bf45/pyobjc_framework_SoundAnalysis-11.0-py2.py3-none-any.whl", hash = "sha256:5969096cadb07f9ba9855cedf6f53674ddb030a324b4981091834d1b31c8c27e", size = 4111, upload-time = "2025-01-14T19:00:13.327Z" },
- { url = "https://files.pythonhosted.org/packages/af/7a/f960ad1e727f6d917e6c84b7383f3eacbb2948bc60396be3bce40cbd8128/pyobjc_framework_SoundAnalysis-11.0-py3-none-any.whl", hash = "sha256:70f70923756e118203cde4ac25083a34ead69a6034baed9c694a36f5fe2325f3", size = 4182, upload-time = "2025-01-14T19:00:15.68Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-speech"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/5f/39/e9f0a73243c38d85f8da6a1a2afda73503e2fcc31a72f5479770bceae0c1/pyobjc_framework_speech-11.0.tar.gz", hash = "sha256:92a191c3ecfe7032eea2140ab5dda826a59c7bb84b13a2edb0ebc471a76e6d7b", size = 40620, upload-time = "2025-01-14T19:05:38.391Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b0/85/e989076ff0cd40c7cfb3ed7d621703de11bfd8286f1729aca759db1f42a3/pyobjc_framework_Speech-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:353179210683e38bfbd675df6a35eec46b30ce30b7291bcb07a5cadaf11a3bd7", size = 9016, upload-time = "2025-01-14T19:00:17.661Z" },
- { url = "https://files.pythonhosted.org/packages/00/03/827acde068787c2318981e2bfef2c3cadbe8552434ccc0634b30084ef914/pyobjc_framework_Speech-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:134e08025f4638e428602f7e16bbec94b00477eec090316138d758a86e10fd5f", size = 9037, upload-time = "2025-01-14T19:00:21.186Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-spritekit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-quartz" },
+ { 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/b7/6e/642e64f5b62a7777c784931c7f018788b5620e307907d416c837fd0c4315/pyobjc_framework_spritekit-11.0.tar.gz", hash = "sha256:aa43927e325d4ac253b7c0ec4df95393b0354bd278ebe9871803419d12d1ef80", size = 129851, upload-time = "2025-01-14T19:05:39.709Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/e1/80/319f156ac6f6cab0dbc85881d81a74d4a7f17913256338683ae8d9ed56c4/pyobjc_framework_SpriteKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d0971a7a85786edc521ab897bdb0c78696278e6417bf389abdfe2151358e854", size = 18077, upload-time = "2025-01-14T19:00:26.815Z" },
- { url = "https://files.pythonhosted.org/packages/bb/09/303d76844a10745cdbac1ff76c2c8630c1ef46455014562dc79aaa72a6e3/pyobjc_framework_SpriteKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0da5f2b52636a2f04fc38a123fed9d7f8d6fd353df027c51c0bfc91e244a9d2b", size = 18145, upload-time = "2025-01-14T19:00:27.956Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-storekit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/69/ca/f4e5a1ff8c98bbbf208639b2bef7bf3b88936bccda1d8ed34aa7d052f589/pyobjc_framework_storekit-11.0.tar.gz", hash = "sha256:ef7e75b28f1fa8b0b6413e64b9d5d78b8ca358fc2477483d2783f688ff8d75e0", size = 75855, upload-time = "2025-01-14T19:05:41.605Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/ab/40/af53ad7781515866003c2c71056a053d2f033cf2aa31920a8a1fdb829d7a/pyobjc_framework_StoreKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1d51a05a5e0277c542978b1f5a6aa33331359de7c0a2cf0ad922760b36e5066a", size = 11655, upload-time = "2025-01-14T19:00:32.894Z" },
- { url = "https://files.pythonhosted.org/packages/f3/11/ba3259d3b22980e08c5e8255a48cc97180bec47d72ffbbd41ab699df39b1/pyobjc_framework_StoreKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:29269183e91043bbfee79851ae712073feba1e10845b8deeb7e6aaa20cfb3cf4", size = 11680, upload-time = "2025-01-14T19:00:35.36Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-symbols"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/dc/92/a20a3d7af3c99e0ea086e43715675160a04b86c1d069bdaeb3acdb015d92/pyobjc_framework_symbols-11.0.tar.gz", hash = "sha256:e3de7736dfb8107f515cfd23f03e874dd9468e88ab076d01d922a73fefb620fa", size = 13682, upload-time = "2025-01-14T19:05:45.727Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/66/ff/341d44f5347d48491682bece366444f3e230e33109266dcc6a17e6a7fc3d/pyobjc_framework_Symbols-11.0-py2.py3-none-any.whl", hash = "sha256:f1490823f40a8a540ac10628190695f27a717343914fe5db5fafa500f7c7bf44", size = 3263, upload-time = "2025-01-14T19:00:41.055Z" },
- { url = "https://files.pythonhosted.org/packages/94/a4/c21353872a2fc643206a44ac55b92b5b7533cdb2cb26c44a9048debc295a/pyobjc_framework_Symbols-11.0-py3-none-any.whl", hash = "sha256:0919e85fcf6f420f61d8d9a67cafa2ab4678666441ef4f001b31f5457900b314", size = 3335, upload-time = "2025-01-14T19:00:43.294Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-syncservices"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coredata" },
+ { 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/5a/22/642186906f672461bab1d7773b35ef74e432b9789ca2248186b766e9fd3b/pyobjc_framework_syncservices-11.0.tar.gz", hash = "sha256:7867c23895a8289da8d56e962c144c36ed16bd101dc07d05281c55930b142471", size = 57453, upload-time = "2025-01-14T19:05:46.559Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/15/9b/484db4eed6b1e29e0d69275bd459ab21a6b3f98e8b2ce61beeb9971303ca/pyobjc_framework_SyncServices-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:89a398df6518cff1c63b7cccf3025e388f3ef299645734112c5aa1ac5f7ca30a", size = 13989, upload-time = "2025-01-14T19:00:45.216Z" },
- { url = "https://files.pythonhosted.org/packages/8d/d8/dc86d708434b7cb59825c56549e64b118ba4b8584d2eb5a1514d1cd5d1bd/pyobjc_framework_SyncServices-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e870e82ed34c43607cc50dbae57a81dd419b75abc06670630cbbf41ae6e1402c", size = 14008, upload-time = "2025-01-14T19:00:46.188Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-systemconfiguration"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/70/70/ebebf311523f436df2407f35d7ce62482c01e530b77aceb3ca6356dcef43/pyobjc_framework_systemconfiguration-11.0.tar.gz", hash = "sha256:06487f0fdd43c6447b5fd3d7f3f59826178d32bcf74f848c5b3ea597191d471d", size = 142949, upload-time = "2025-01-14T19:05:47.466Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/28/8f/1b5f7e8e848d2c84204da08d5c63e42feff86b26cd508da7a4f95960b842/pyobjc_framework_SystemConfiguration-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:89d3c54abedcedbc2ce52c31ff4878251ca54a8535407ed6bd6584ce099c148b", size = 21836, upload-time = "2025-01-14T19:00:51.698Z" },
- { url = "https://files.pythonhosted.org/packages/6d/49/8660b3d0a46ac2f88e73cec3d10e21885b107f54635680ef0c677ac5cf3e/pyobjc_framework_SystemConfiguration-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8cbcb9662dbb5a034cfc5a44adaf2a0226a2985ae299a4ef4fd75bb49f30f5a0", size = 21727, upload-time = "2025-01-14T19:00:52.685Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-systemextensions"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/62/4b/904d818debf6216b7be009d492d998c819bf2f2791bfb75870a952e32cf9/pyobjc_framework_systemextensions-11.0.tar.gz", hash = "sha256:da293c99b428fb7f18a7a1d311b17177f73a20c7ffa94de3f72d760df924255e", size = 22531, upload-time = "2025-01-14T19:05:48.463Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/15/3c/8f91b89554ef3127e037d90b3ef83c77a994bb889b7884a995756cd06b63/pyobjc_framework_SystemExtensions-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7a2ec417fa0d383cc066bc292541aa78fd2aec9cca83a98d41b7982f185d1f7", size = 8975, upload-time = "2025-01-14T19:00:57.822Z" },
- { url = "https://files.pythonhosted.org/packages/21/8c/cf2a018b5f1ecd216f8cb26a3b6fbe590d08de81a6c6b4658e001a203886/pyobjc_framework_SystemExtensions-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:62b99c6bd88bce642960fc2b9d5903fbfca680d16be9a4565a883eb4ba17ca5e", size = 8999, upload-time = "2025-01-14T19:00:58.865Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-threadnetwork"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c4/17/fc8fde4eeb6697e0a5ba1a306cd62d3a95b53f3334744cd22b87037d8a14/pyobjc_framework_threadnetwork-11.0.tar.gz", hash = "sha256:f5713579380f6fb89c877796de86cb4e98428d7a9cbfebe566fb827ba23b2d8e", size = 13820, upload-time = "2025-01-14T19:05:49.307Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/55/a9/908184da457e33a110de7d2d262efa69beaba6db243342df5654da03566b/pyobjc_framework_ThreadNetwork-11.0-py2.py3-none-any.whl", hash = "sha256:950d46a009cb992b12dbd8169a0450d8cc101fc982e03e6543078c6d7790e353", size = 3700, upload-time = "2025-01-14T19:01:03.254Z" },
- { url = "https://files.pythonhosted.org/packages/59/d4/4694fc7a627d2b6b37c51433ba7f02a39a283a445dc77349b82fe24534f1/pyobjc_framework_ThreadNetwork-11.0-py3-none-any.whl", hash = "sha256:1218649e4f488ca411af13b74f1dee1e7a178169e0f5963342ba8a7c46037ea7", size = 3770, upload-time = "2025-01-14T19:01:05.456Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-uniformtypeidentifiers"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/56/4f/fd571c1f87d5ee3d86c4d2008806e9623d2662bbc788d9001b3fff35275f/pyobjc_framework_uniformtypeidentifiers-11.0.tar.gz", hash = "sha256:6ae6927a3ed1f0197a8c472226f11f46ccd5ed398b4449613e1d10346d9ed15d", size = 20860, upload-time = "2025-01-14T19:05:50.073Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/82/f2/094888af07fb7f0443996e5d91915e74b87e8705b599b68b516a0e94a63d/pyobjc_framework_UniformTypeIdentifiers-11.0-py2.py3-none-any.whl", hash = "sha256:acffb86e8b03b66c49274236b3df3a254cacd32b9f25bd7a5bd59baaaf738624", size = 4841, upload-time = "2025-01-14T19:01:06.404Z" },
- { url = "https://files.pythonhosted.org/packages/88/9c/4cc0522cc546e6a3bf8a921e3a9f0ed078e3cf907d616760d9f3d7754919/pyobjc_framework_UniformTypeIdentifiers-11.0-py3-none-any.whl", hash = "sha256:a3097f186c7e231b19218a3ceecb3b70a8f2b2e9e642ef409dc7a195a30c869e", size = 4910, upload-time = "2025-01-14T19:01:07.393Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-usernotifications"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/78/f5/ca3e6a7d940b3aca4323e4f5409b14b5d2eb45432158430c584e3800ce4d/pyobjc_framework_usernotifications-11.0.tar.gz", hash = "sha256:7950a1c6a8297f006c26c3d286705ffc2a07061d6e844f1106290572097b872c", size = 54857, upload-time = "2025-01-14T19:05:52.42Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/1f/bf/5545d5c9d0d10a603ad406a5ce727de6a47daace9c38d4484818611599f3/pyobjc_framework_UserNotifications-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4bf78fa37f574f5b43db9b83ca02e82ab45803589f970042afdcd1cb8c01396d", size = 9483, upload-time = "2025-01-14T19:01:09.256Z" },
- { url = "https://files.pythonhosted.org/packages/7a/1e/41f4d18120b2c006f756edde1845a2df45fdbd6957e540f8ebcfae25747f/pyobjc_framework_UserNotifications-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b4c06c3862405e103e964327581c28e5390a2d4cd0cef3d8e64afda03c9f431", size = 9506, upload-time = "2025-01-14T19:01:10.218Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-usernotificationsui"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-usernotifications" },
+ { 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/e9/e8/f0d50cdc678260a628b92e55b5752155f941c2f72b96fe3f2412a28c5d79/pyobjc_framework_usernotificationsui-11.0.tar.gz", hash = "sha256:d0ec597d189b4d228b0b836474aef318652c1c287b33442a1403c49dc59fdb7f", size = 14369, upload-time = "2025-01-14T19:05:54.498Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bb/f7/64c95c6f82e92bb1cbcb8d5c3658c79c954668627eef28f11e76025a3ed1/pyobjc_framework_UserNotificationsUI-11.0-py2.py3-none-any.whl", hash = "sha256:6185d9c9513b6a823cd72dcf40d2fb33bbf0f2c9a98528e0e112580b47ac3632", size = 3856, upload-time = "2025-01-14T19:01:15.43Z" },
- { url = "https://files.pythonhosted.org/packages/eb/c3/e1d64c9e523b5192e0179b6723ee465e74d6c282104a49a67347d527a65d/pyobjc_framework_UserNotificationsUI-11.0-py3-none-any.whl", hash = "sha256:e4439e549265929ddad1feca7b062d00c2d3732470f349cb0d594705e0257919", size = 3932, upload-time = "2025-01-14T19:01:16.486Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-videosubscriberaccount"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7e/2e/6a7debd84911a9384b4e7a9cc3f308e3461a00a9d74f33b153bdd872f15f/pyobjc_framework_videosubscriberaccount-11.0.tar.gz", hash = "sha256:163b32f361f48b9d20f317461464abd4427b3242693ae011633fc443c7d5449c", size = 29100, upload-time = "2025-01-14T19:05:55.319Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/51/82/94650fe5cc68c0c32fe56fe22cd7eb2874b28f987a9e259fac12cbea7705/pyobjc_framework_VideoSubscriberAccount-11.0-py2.py3-none-any.whl", hash = "sha256:1deec8d5a0138ae51b5ca7bfb7f6fe1b0dc3cbb52db3111059708efa5f8a8d04", size = 4637, upload-time = "2025-01-14T19:01:17.365Z" },
- { url = "https://files.pythonhosted.org/packages/61/54/1765507adad1b0c9bc6be10f09b249d425212bc0d9fef1efdfd872ee9807/pyobjc_framework_VideoSubscriberAccount-11.0-py3-none-any.whl", hash = "sha256:0095eddb5fc942f9e049bc4c683cf28c77ea60c60942552c3c48bf74c8fdca9b", size = 4709, upload-time = "2025-01-14T19:01:18.349Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-videotoolbox"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coremedia" },
- { name = "pyobjc-framework-quartz" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ba/2d/c031a132b142fcd20846cc1ac3ba92abaa58ec04164fd36ca978d9374f1c/pyobjc_framework_videotoolbox-11.0.tar.gz", hash = "sha256:a54ed8f8bcbdd2bdea2a296dc02a8a7d42f81e2b6ccbf4d1f10cec5e7a09bec0", size = 81157, upload-time = "2025-01-14T19:05:56.135Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/44/ae/ff697840bdcf3530e8fba84e2a606813eda1ee90be074f12e2857460cebf/pyobjc_framework_VideoToolbox-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:12af56190e65c3b60c6ca14fe69045e5ffb5908ea1363580506eb32603b80855", size = 13446, upload-time = "2025-01-14T19:01:21.066Z" },
- { url = "https://files.pythonhosted.org/packages/1e/ef/9e7230435da47016983a3c9ea7b1d5237b43fce2d8b2b923eb638b7694f5/pyobjc_framework_VideoToolbox-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ed7f073bd8dfecca0da6359d5cd871b2f39144883930bddd41ca818447de608", size = 13451, upload-time = "2025-01-14T19:01:22.009Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-virtualization"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/65/8d/e57e1f2c5ac950dc3da6c977effde4a55b8b70424b1bdb97b5530559f5bc/pyobjc_framework_virtualization-11.0.tar.gz", hash = "sha256:03e1c1fa20950aa7c275e5f11f1257108b6d1c6a7403afb86f4e9d5fae87b73c", size = 78144, upload-time = "2025-01-14T19:05:57.086Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/c9/b2f8322d7ced14822270481be5b44f1846aa7c09b4b3cb52517dc1054f4b/pyobjc_framework_Virtualization-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:334712792136ffcf3c63a63cea01ce33d60309a82721c95e25f0cc26b95f72cc", size = 13417, upload-time = "2025-01-14T19:01:28.821Z" },
- { url = "https://files.pythonhosted.org/packages/1e/96/d64425811a4ef2c8b38914ea1a91bbd2aa6136bb79989e4821acd6d28e67/pyobjc_framework_Virtualization-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b848b1ab365906b11a507c8146e477c27d2bf56159d49d21fda15b93c2811ec", size = 13430, upload-time = "2025-01-14T19:01:29.733Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-vision"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
- { name = "pyobjc-framework-coreml" },
- { name = "pyobjc-framework-quartz" },
+ { 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'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ef/53/dc2e0562a177af9306efceb84bc21f5cf7470acaa8f28f64e62bf828b7e1/pyobjc_framework_vision-11.0.tar.gz", hash = "sha256:45342e5253c306dbcd056a68bff04ffbfa00e9ac300a02aabf2e81053b771e39", size = 133175, upload-time = "2025-01-14T19:05:58.013Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/7f/84/d23a745d46858409a1dca3e7f5cb3089c148ebb8d42e7a6289e1972ad650/pyobjc_framework_Vision-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ca7cc48332d804a02b5b17f31bed52dd4b7c323f9e4ff4b4e7ecd35d39cc0759", size = 21754, upload-time = "2025-01-14T19:01:35.504Z" },
- { url = "https://files.pythonhosted.org/packages/3a/80/6db9fc2a3f8b991860156f4700f979ad8aa1e9617b0efa720ee3b52e3602/pyobjc_framework_Vision-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1b07aa867dda47d2a4883cd969e248039988b49190ba097cbe9747156b5d1f30", size = 17099, upload-time = "2025-01-14T19:01:37.457Z" },
+ { 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" },
]
[[package]]
name = "pyobjc-framework-webkit"
-version = "11.0"
+version = "11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pyobjc-core" },
- { name = "pyobjc-framework-cocoa" },
+ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/79/4f/02a6270acf225c2a34339677e796002c77506238475059ae6e855358a40c/pyobjc_framework_webkit-11.0.tar.gz", hash = "sha256:fa6bedf9873786b3376a74ce2ea9dcd311f2a80f61e33dcbd931cc956aa29644", size = 767210, upload-time = "2025-01-14T19:05:59.3Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/47/63/6f04faa75c4c39c54007b256a8e13838c1de213d487f561937d342ec2eac/pyobjc_framework_WebKit-11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:163abaa5a665b59626ef20cdc3dcc5e2e3fcd9830d5fc328507e13f663acd0ed", size = 44940, upload-time = "2025-01-14T19:01:44.396Z" },
- { url = "https://files.pythonhosted.org/packages/3e/61/934f03510e7f49454fbf6eeff8ad2eca5d8bfbe71aa4b8a034f8132af2fa/pyobjc_framework_WebKit-11.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e4911519e94822011d99fdb9addf4a176f45a79808dab18dc303293f4590f7c", size = 44901, upload-time = "2025-01-14T19:01:45.476Z" },
+ { 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" },
]
[[package]]
@@ -4266,9 +4164,9 @@ name = "pyopencl"
version = "2025.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "numpy" },
- { name = "platformdirs" },
- { name = "pytools" },
+ { 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'" },
]
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" }
wheels = [
@@ -4343,42 +4241,30 @@ wheels = [
[[package]]
name = "pytest"
-version = "8.3.5"
+version = "8.4.1"
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/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
+ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
]
[[package]]
name = "pytest-asyncio"
-version = "0.26.0"
+version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" },
-]
-
-[[package]]
-name = "pytest-cov"
-version = "6.1.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "coverage", extra = ["toml"] },
- { name = "pytest" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" },
+ { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" },
]
[[package]]
@@ -4395,14 +4281,14 @@ wheels = [
[[package]]
name = "pytest-mock"
-version = "3.14.0"
+version = "3.14.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814, upload-time = "2024-03-21T22:14:04.964Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863, upload-time = "2024-03-21T22:14:02.694Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" },
]
[[package]]
@@ -4431,15 +4317,15 @@ wheels = [
[[package]]
name = "pytest-subtests"
-version = "0.14.1"
+version = "0.14.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/c0/4c/ba9eab21a2250c2d46c06c0e3cd316850fde9a90da0ac8d0202f074c6817/pytest_subtests-0.14.1.tar.gz", hash = "sha256:350c00adc36c3aff676a66135c81aed9e2182e15f6c3ec8721366918bbbf7580", size = 17632, upload-time = "2024-12-10T00:21:04.856Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a9/b7/7ca948d35642ae72500efda6ba6fa61dcb6683feb596d19c4747c63c0789/pytest_subtests-0.14.1-py3-none-any.whl", hash = "sha256:e92a780d98b43118c28a16044ad9b841727bd7cb6a417073b38fd2d7ccdf052d", size = 8833, upload-time = "2024-12-10T00:20:58.873Z" },
+ { 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" },
]
[[package]]
@@ -4456,16 +4342,12 @@ wheels = [
[[package]]
name = "pytest-xdist"
-version = "3.6.1"
-source = { registry = "https://pypi.org/simple" }
+version = "3.7.1.dev3+g909e97b"
+source = { git = "https://github.com/sshane/pytest-xdist?rev=909e97b49d12401c10608f9d777bfc9dab8a4413#909e97b49d12401c10608f9d777bfc9dab8a4413" }
dependencies = [
{ name = "execnet" },
{ name = "pytest" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" },
-]
[[package]]
name = "python-dateutil"
@@ -4484,7 +4366,7 @@ name = "python-xlib"
version = "0.33"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "six" },
+ { name = "six", marker = "sys_platform != 'darwin'" },
]
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 = [
@@ -4502,9 +4384,9 @@ name = "pytools"
version = "2024.1.10"
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/ee/0f/56e109c0307f831b5d598ad73976aaaa84b4d0e98da29a642e797eaa940c/pytools-2024.1.10.tar.gz", hash = "sha256:9af6f4b045212c49be32bb31fe19606c478ee4b09631886d05a32459f4ce0a12", size = 81741, upload-time = "2024-07-17T18:47:38.287Z" }
wheels = [
@@ -4602,40 +4484,50 @@ wheels = [
[[package]]
name = "pyzmq"
-version = "26.4.0"
+version = "27.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi", marker = "implementation_name == 'pypy'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293, upload-time = "2025-04-04T12:05:44.049Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/06/50a4e9648b3e8b992bef8eb632e457307553a89d294103213cfd47b3da69/pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf", size = 280478, upload-time = "2025-06-13T14:09:07.087Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/32/6d/234e3b0aa82fd0290b1896e9992f56bdddf1f97266110be54d0177a9d2d9/pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54", size = 1339723, upload-time = "2025-04-04T12:03:24.358Z" },
- { url = "https://files.pythonhosted.org/packages/4f/11/6d561efe29ad83f7149a7cd48e498e539ed09019c6cd7ecc73f4cc725028/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030", size = 672645, upload-time = "2025-04-04T12:03:25.693Z" },
- { url = "https://files.pythonhosted.org/packages/19/fd/81bfe3e23f418644660bad1a90f0d22f0b3eebe33dd65a79385530bceb3d/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01", size = 910133, upload-time = "2025-04-04T12:03:27.625Z" },
- { url = "https://files.pythonhosted.org/packages/97/68/321b9c775595ea3df832a9516252b653fe32818db66fdc8fa31c9b9fce37/pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e", size = 867428, upload-time = "2025-04-04T12:03:29.004Z" },
- { url = "https://files.pythonhosted.org/packages/4e/6e/159cbf2055ef36aa2aa297e01b24523176e5b48ead283c23a94179fb2ba2/pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88", size = 862409, upload-time = "2025-04-04T12:03:31.032Z" },
- { url = "https://files.pythonhosted.org/packages/05/1c/45fb8db7be5a7d0cadea1070a9cbded5199a2d578de2208197e592f219bd/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6", size = 1205007, upload-time = "2025-04-04T12:03:32.687Z" },
- { url = "https://files.pythonhosted.org/packages/f8/fa/658c7f583af6498b463f2fa600f34e298e1b330886f82f1feba0dc2dd6c3/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df", size = 1514599, upload-time = "2025-04-04T12:03:34.084Z" },
- { url = "https://files.pythonhosted.org/packages/4d/d7/44d641522353ce0a2bbd150379cb5ec32f7120944e6bfba4846586945658/pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef", size = 1414546, upload-time = "2025-04-04T12:03:35.478Z" },
- { url = "https://files.pythonhosted.org/packages/72/76/c8ed7263218b3d1e9bce07b9058502024188bd52cc0b0a267a9513b431fc/pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca", size = 579247, upload-time = "2025-04-04T12:03:36.846Z" },
- { url = "https://files.pythonhosted.org/packages/c3/d0/2d9abfa2571a0b1a67c0ada79a8aa1ba1cce57992d80f771abcdf99bb32c/pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896", size = 644727, upload-time = "2025-04-04T12:03:38.578Z" },
- { url = "https://files.pythonhosted.org/packages/0d/d1/c8ad82393be6ccedfc3c9f3adb07f8f3976e3c4802640fe3f71441941e70/pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3", size = 559942, upload-time = "2025-04-04T12:03:40.143Z" },
- { url = "https://files.pythonhosted.org/packages/10/44/a778555ebfdf6c7fc00816aad12d185d10a74d975800341b1bc36bad1187/pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b", size = 1341586, upload-time = "2025-04-04T12:03:41.954Z" },
- { url = "https://files.pythonhosted.org/packages/9c/4f/f3a58dc69ac757e5103be3bd41fb78721a5e17da7cc617ddb56d973a365c/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905", size = 665880, upload-time = "2025-04-04T12:03:43.45Z" },
- { url = "https://files.pythonhosted.org/packages/fe/45/50230bcfb3ae5cb98bee683b6edeba1919f2565d7cc1851d3c38e2260795/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b", size = 902216, upload-time = "2025-04-04T12:03:45.572Z" },
- { url = "https://files.pythonhosted.org/packages/41/59/56bbdc5689be5e13727491ad2ba5efd7cd564365750514f9bc8f212eef82/pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63", size = 859814, upload-time = "2025-04-04T12:03:47.188Z" },
- { url = "https://files.pythonhosted.org/packages/81/b1/57db58cfc8af592ce94f40649bd1804369c05b2190e4cbc0a2dad572baeb/pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5", size = 855889, upload-time = "2025-04-04T12:03:49.223Z" },
- { url = "https://files.pythonhosted.org/packages/e8/92/47542e629cbac8f221c230a6d0f38dd3d9cff9f6f589ed45fdf572ffd726/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b", size = 1197153, upload-time = "2025-04-04T12:03:50.591Z" },
- { url = "https://files.pythonhosted.org/packages/07/e5/b10a979d1d565d54410afc87499b16c96b4a181af46e7645ab4831b1088c/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84", size = 1507352, upload-time = "2025-04-04T12:03:52.473Z" },
- { url = "https://files.pythonhosted.org/packages/ab/58/5a23db84507ab9c01c04b1232a7a763be66e992aa2e66498521bbbc72a71/pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f", size = 1406834, upload-time = "2025-04-04T12:03:54Z" },
- { url = "https://files.pythonhosted.org/packages/22/74/aaa837b331580c13b79ac39396601fb361454ee184ca85e8861914769b99/pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44", size = 577992, upload-time = "2025-04-04T12:03:55.815Z" },
- { url = "https://files.pythonhosted.org/packages/30/0f/55f8c02c182856743b82dde46b2dc3e314edda7f1098c12a8227eeda0833/pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be", size = 640466, upload-time = "2025-04-04T12:03:57.231Z" },
- { url = "https://files.pythonhosted.org/packages/e4/29/073779afc3ef6f830b8de95026ef20b2d1ec22d0324d767748d806e57379/pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0", size = 556342, upload-time = "2025-04-04T12:03:59.218Z" },
- { url = "https://files.pythonhosted.org/packages/04/52/a70fcd5592715702248306d8e1729c10742c2eac44529984413b05c68658/pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb", size = 834405, upload-time = "2025-04-04T12:05:13.3Z" },
- { url = "https://files.pythonhosted.org/packages/25/f9/1a03f1accff16b3af1a6fa22cbf7ced074776abbf688b2e9cb4629700c62/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1", size = 569578, upload-time = "2025-04-04T12:05:15.36Z" },
- { url = "https://files.pythonhosted.org/packages/76/0c/3a633acd762aa6655fcb71fa841907eae0ab1e8582ff494b137266de341d/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494", size = 798248, upload-time = "2025-04-04T12:05:17.376Z" },
- { url = "https://files.pythonhosted.org/packages/cd/cc/6c99c84aa60ac1cc56747bed6be8ce6305b9b861d7475772e7a25ce019d3/pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9", size = 756757, upload-time = "2025-04-04T12:05:19.19Z" },
- { url = "https://files.pythonhosted.org/packages/13/9c/d8073bd898eb896e94c679abe82e47506e2b750eb261cf6010ced869797c/pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0", size = 555371, upload-time = "2025-04-04T12:05:20.702Z" },
+ { url = "https://files.pythonhosted.org/packages/44/df/84c630654106d9bd9339cdb564aa941ed41b023a0264251d6743766bb50e/pyzmq-27.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:21457825249b2a53834fa969c69713f8b5a79583689387a5e7aed880963ac564", size = 1332718, upload-time = "2025-06-13T14:07:16.555Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/8e/f6a5461a07654d9840d256476434ae0ff08340bba562a455f231969772cb/pyzmq-27.0.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1958947983fef513e6e98eff9cb487b60bf14f588dc0e6bf35fa13751d2c8251", size = 908248, upload-time = "2025-06-13T14:07:18.033Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/93/82863e8d695a9a3ae424b63662733ae204a295a2627d52af2f62c2cd8af9/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0dc628b5493f9a8cd9844b8bee9732ef587ab00002157c9329e4fc0ef4d3afa", size = 668647, upload-time = "2025-06-13T14:07:19.378Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/85/15278769b348121eacdbfcbd8c4d40f1102f32fa6af5be1ffc032ed684be/pyzmq-27.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7bbe9e1ed2c8d3da736a15694d87c12493e54cc9dc9790796f0321794bbc91f", size = 856600, upload-time = "2025-06-13T14:07:20.906Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/af/1c469b3d479bd095edb28e27f12eee10b8f00b356acbefa6aeb14dd295d1/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc1091f59143b471d19eb64f54bae4f54bcf2a466ffb66fe45d94d8d734eb495", size = 1657748, upload-time = "2025-06-13T14:07:22.549Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/f4/17f965d0ee6380b1d6326da842a50e4b8b9699745161207945f3745e8cb5/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7011ade88c8e535cf140f8d1a59428676fbbce7c6e54fefce58bf117aefb6667", size = 2034311, upload-time = "2025-06-13T14:07:23.966Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/6e/7c391d81fa3149fd759de45d298003de6cfab343fb03e92c099821c448db/pyzmq-27.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c386339d7e3f064213aede5d03d054b237937fbca6dd2197ac8cf3b25a6b14e", size = 1893630, upload-time = "2025-06-13T14:07:25.899Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/e0/eaffe7a86f60e556399e224229e7769b717f72fec0706b70ab2c03aa04cb/pyzmq-27.0.0-cp311-cp311-win32.whl", hash = "sha256:0546a720c1f407b2172cb04b6b094a78773491497e3644863cf5c96c42df8cff", size = 567706, upload-time = "2025-06-13T14:07:27.595Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/05/89354a8cffdcce6e547d48adaaf7be17007fc75572123ff4ca90a4ca04fc/pyzmq-27.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f39d50bd6c9091c67315ceb878a4f531957b121d2a05ebd077eb35ddc5efed", size = 630322, upload-time = "2025-06-13T14:07:28.938Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/07/4ab976d5e1e63976719389cc4f3bfd248a7f5f2bb2ebe727542363c61b5f/pyzmq-27.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c5817641eebb391a2268c27fecd4162448e03538387093cdbd8bf3510c316b38", size = 558435, upload-time = "2025-06-13T14:07:30.256Z" },
+ { url = "https://files.pythonhosted.org/packages/93/a7/9ad68f55b8834ede477842214feba6a4c786d936c022a67625497aacf61d/pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52", size = 1305438, upload-time = "2025-06-13T14:07:31.676Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/ee/26aa0f98665a22bc90ebe12dced1de5f3eaca05363b717f6fb229b3421b3/pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3", size = 895095, upload-time = "2025-06-13T14:07:33.104Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/85/c57e7ab216ecd8aa4cc7e3b83b06cc4e9cf45c87b0afc095f10cd5ce87c1/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152", size = 651826, upload-time = "2025-06-13T14:07:34.831Z" },
+ { url = "https://files.pythonhosted.org/packages/69/9a/9ea7e230feda9400fb0ae0d61d7d6ddda635e718d941c44eeab22a179d34/pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22", size = 839750, upload-time = "2025-06-13T14:07:36.553Z" },
+ { url = "https://files.pythonhosted.org/packages/08/66/4cebfbe71f3dfbd417011daca267539f62ed0fbc68105357b68bbb1a25b7/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371", size = 1641357, upload-time = "2025-06-13T14:07:38.21Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/f6/b0f62578c08d2471c791287149cb8c2aaea414ae98c6e995c7dbe008adfb/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d", size = 2020281, upload-time = "2025-06-13T14:07:39.599Z" },
+ { url = "https://files.pythonhosted.org/packages/37/b9/4f670b15c7498495da9159edc374ec09c88a86d9cd5a47d892f69df23450/pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be", size = 1877110, upload-time = "2025-06-13T14:07:41.027Z" },
+ { url = "https://files.pythonhosted.org/packages/66/31/9dee25c226295b740609f0d46db2fe972b23b6f5cf786360980524a3ba92/pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4", size = 559297, upload-time = "2025-06-13T14:07:42.533Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/12/52da5509800f7ff2d287b2f2b4e636e7ea0f001181cba6964ff6c1537778/pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371", size = 619203, upload-time = "2025-06-13T14:07:43.843Z" },
+ { url = "https://files.pythonhosted.org/packages/93/6d/7f2e53b19d1edb1eb4f09ec7c3a1f945ca0aac272099eab757d15699202b/pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e", size = 551927, upload-time = "2025-06-13T14:07:45.51Z" },
+ { url = "https://files.pythonhosted.org/packages/98/a6/92394373b8dbc1edc9d53c951e8d3989d518185174ee54492ec27711779d/pyzmq-27.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd1dc59763effd1576f8368047c9c31468fce0af89d76b5067641137506792ae", size = 835948, upload-time = "2025-06-13T14:08:43.516Z" },
+ { url = "https://files.pythonhosted.org/packages/56/f3/4dc38d75d9995bfc18773df3e41f2a2ca9b740b06f1a15dbf404077e7588/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:60e8cc82d968174650c1860d7b716366caab9973787a1c060cf8043130f7d0f7", size = 799874, upload-time = "2025-06-13T14:08:45.017Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/ba/64af397e0f421453dc68e31d5e0784d554bf39013a2de0872056e96e58af/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174", size = 567400, upload-time = "2025-06-13T14:08:46.855Z" },
+ { url = "https://files.pythonhosted.org/packages/63/87/ec956cbe98809270b59a22891d5758edae147a258e658bf3024a8254c855/pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e", size = 747031, upload-time = "2025-06-13T14:08:48.419Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8a/4a3764a68abc02e2fbb0668d225b6fda5cd39586dd099cee8b2ed6ab0452/pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46", size = 544726, upload-time = "2025-06-13T14:08:49.903Z" },
+]
+
+[[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]]
@@ -4664,7 +4556,7 @@ wheels = [
[[package]]
name = "requests"
-version = "2.32.3"
+version = "2.32.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -4672,14 +4564,14 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
]
[[package]]
name = "rerun-sdk"
-version = "0.23.2"
+version = "0.23.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@@ -4689,23 +4581,23 @@ dependencies = [
{ name = "typing-extensions" },
]
wheels = [
- { url = "https://files.pythonhosted.org/packages/24/98/ee9acc4ac36e977bcacd3d65704127026a02228f4f5efa9cfd4243dd2036/rerun_sdk-0.23.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fb9f0279a8b87f64bc29ed2c4c3cbb602e36b9b373f691ff657968ed6009c5c3", size = 66816731, upload-time = "2025-05-06T15:54:46.177Z" },
- { url = "https://files.pythonhosted.org/packages/3a/84/6164958c9b4dcd9b9dab16e1adbf4cea4339f52ce212c084b57bc0fd1a05/rerun_sdk-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:02d99f48659abeca86fcbd56498d30ab0c4b34b8a0732b02324c3fe79830cf6f", size = 62697629, upload-time = "2025-05-06T15:54:52.083Z" },
- { url = "https://files.pythonhosted.org/packages/c0/4d/1b9dc66a5827f25f9ce5fe9b719f97f42403d1a2276e765082d224590dbe/rerun_sdk-0.23.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:fdc844eddb6dcc924e48b3cd8dd88ed8b70751301d386175dd2fd7c9594580b3", size = 68935354, upload-time = "2025-05-06T15:54:56.27Z" },
- { url = "https://files.pythonhosted.org/packages/e7/7b/16b9bcd67b3dea1842d556100ce2e0b5c6bd9ad7943aeab2a76bae4b2be5/rerun_sdk-0.23.2-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:dac599ef5d5563f4ebc90c20f09b77c458bac4df845e178112257f79f3eef579", size = 72209426, upload-time = "2025-05-06T15:55:00.641Z" },
- { url = "https://files.pythonhosted.org/packages/67/54/ecb761555f6fbd4e32c384ba5a8424c761925be54259708093205c916c64/rerun_sdk-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:ce3f9adfb04df79f30ca1e6deb997b021af87878672f79dfa3543a4f6a83491f", size = 58180134, upload-time = "2025-05-06T15:55:06.284Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7f/d77608b6c73e43bbc67a86510e43573b8c03abac80b7341d15f677818e9c/rerun_sdk-0.23.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3d18fdbf6f7d4fb2bb271dbbeecf1c9c2c8cdbf348b9e59a2bcd36f9c2f44df1", size = 59487147, upload-time = "2025-06-26T13:58:06.987Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/39/64ea3c404239bd709e76bc36e321dbb1343dbf84ec918480a1b87ce63a85/rerun_sdk-0.23.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e2fbded870b50058052a6e03fe890d268f77dc19d5dec171b48b524c61199256", size = 55131456, upload-time = "2025-06-26T13:58:21.616Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/d2/9809c34f486cd402ad34287efaf1f546052e7876d66821aa152a23e9cd84/rerun_sdk-0.23.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2aec8c65d2f1fee0df4b145389fab23fe91c7f07a3ed0b6d2003c177c3437ca4", size = 61577323, upload-time = "2025-06-26T13:58:38.33Z" },
+ { url = "https://files.pythonhosted.org/packages/13/5c/5c194619bbdc9545832f50bf94634fc9f548cf8f08eada76446dffce8d8d/rerun_sdk-0.23.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:229b5f12a4f6499e2575ace3a9bbc3d164245a65224789d7e3ebc5656b01538d", size = 64877348, upload-time = "2025-06-26T13:58:57.528Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/e6/98923b167df0388da2acea1a4a8cc5fdd4557c0ada25fd79a704d614ce09/rerun_sdk-0.23.4-cp39-abi3-win_amd64.whl", hash = "sha256:1c3a2480892a09165183d38788ea238a4689aaf61f3c388539aa74e1514df23c", size = 50872538, upload-time = "2025-06-26T13:59:12.107Z" },
]
[[package]]
name = "ruamel-yaml"
-version = "0.18.10"
+version = "0.18.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447, upload-time = "2025-01-06T14:08:51.334Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/39/87/6da0df742a4684263261c253f00edd5829e6aca970fff69e75028cccc547/ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7", size = 145511, upload-time = "2025-06-09T08:51:09.828Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729, upload-time = "2025-01-06T14:08:47.471Z" },
+ { url = "https://files.pythonhosted.org/packages/af/6d/6fe4805235e193aad4aaf979160dd1f3c487c57d48b810c816e6e842171b/ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2", size = 118570, upload-time = "2025-06-09T08:51:06.348Z" },
]
[[package]]
@@ -4736,36 +4628,36 @@ wheels = [
[[package]]
name = "rubicon-objc"
-version = "0.5.0"
+version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d3/13/586c9baa985eae0f718029506b40ca41295d51a546567414b2bcf8ccacef/rubicon_objc-0.5.0.tar.gz", hash = "sha256:18f075649780d95df53d483642068c767d7d2cfbbf075ddef124a44b40b6d92e", size = 173652, upload-time = "2025-01-07T00:25:10.491Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/83/e57741dcf862a2581d53eccf8b11749c97f73d9754bbc538fb6c7b527da3/rubicon_objc-0.5.1.tar.gz", hash = "sha256:90bee9fc1de4515e17615e15648989b88bb8d4d2ffc8c7c52748272cd7f30a66", size = 174639, upload-time = "2025-06-03T06:33:50.822Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6d/30/5b2407b8762ed882e5732e19c485b9ea2f07d35462615a3212638bab66c2/rubicon_objc-0.5.0-py3-none-any.whl", hash = "sha256:a9c2a605120d6e5be327d3f42a71b60963125987e116f51846757b5e110854fa", size = 62711, upload-time = "2025-01-07T00:25:08.959Z" },
+ { url = "https://files.pythonhosted.org/packages/58/0a/e451c3dbda38dd6abab1fd16c3b35623fc0635dffcbbf97f1acc55a58508/rubicon_objc-0.5.1-py3-none-any.whl", hash = "sha256:17092756241b8370231cfaad45ad6e8ce99534987f2acbc944d65df5bdf8f6cd", size = 63323, upload-time = "2025-06-03T06:33:48.863Z" },
]
[[package]]
name = "ruff"
-version = "0.11.10"
+version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/97/38/796a101608a90494440856ccfb52b1edae90de0b817e76bfade66b12d320/ruff-0.12.1.tar.gz", hash = "sha256:806bbc17f1104fd57451a98a58df35388ee3ab422e029e8f5cf30aa4af2c138c", size = 4413426, upload-time = "2025-06-26T20:34:14.784Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" },
- { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" },
- { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" },
- { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" },
- { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" },
- { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" },
- { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" },
- { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" },
- { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" },
- { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" },
- { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" },
- { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" },
- { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" },
- { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" },
- { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" },
- { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" },
- { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" },
+ { url = "https://files.pythonhosted.org/packages/06/bf/3dba52c1d12ab5e78d75bd78ad52fb85a6a1f29cc447c2423037b82bed0d/ruff-0.12.1-py3-none-linux_armv6l.whl", hash = "sha256:6013a46d865111e2edb71ad692fbb8262e6c172587a57c0669332a449384a36b", size = 10305649, upload-time = "2025-06-26T20:33:39.242Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/65/dab1ba90269bc8c81ce1d499a6517e28fe6f87b2119ec449257d0983cceb/ruff-0.12.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b3f75a19e03a4b0757d1412edb7f27cffb0c700365e9d6b60bc1b68d35bc89e0", size = 11120201, upload-time = "2025-06-26T20:33:42.207Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/3e/2d819ffda01defe857fa2dd4cba4d19109713df4034cc36f06bbf582d62a/ruff-0.12.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9a256522893cb7e92bb1e1153283927f842dea2e48619c803243dccc8437b8be", size = 10466769, upload-time = "2025-06-26T20:33:44.102Z" },
+ { url = "https://files.pythonhosted.org/packages/63/37/bde4cf84dbd7821c8de56ec4ccc2816bce8125684f7b9e22fe4ad92364de/ruff-0.12.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:069052605fe74c765a5b4272eb89880e0ff7a31e6c0dbf8767203c1fbd31c7ff", size = 10660902, upload-time = "2025-06-26T20:33:45.98Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/3a/390782a9ed1358c95e78ccc745eed1a9d657a537e5c4c4812fce06c8d1a0/ruff-0.12.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a684f125a4fec2d5a6501a466be3841113ba6847827be4573fddf8308b83477d", size = 10167002, upload-time = "2025-06-26T20:33:47.81Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/05/f2d4c965009634830e97ffe733201ec59e4addc5b1c0efa035645baa9e5f/ruff-0.12.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdecdef753bf1e95797593007569d8e1697a54fca843d78f6862f7dc279e23bd", size = 11751522, upload-time = "2025-06-26T20:33:49.857Z" },
+ { url = "https://files.pythonhosted.org/packages/35/4e/4bfc519b5fcd462233f82fc20ef8b1e5ecce476c283b355af92c0935d5d9/ruff-0.12.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:70d52a058c0e7b88b602f575d23596e89bd7d8196437a4148381a3f73fcd5010", size = 12520264, upload-time = "2025-06-26T20:33:52.199Z" },
+ { url = "https://files.pythonhosted.org/packages/85/b2/7756a6925da236b3a31f234b4167397c3e5f91edb861028a631546bad719/ruff-0.12.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84d0a69d1e8d716dfeab22d8d5e7c786b73f2106429a933cee51d7b09f861d4e", size = 12133882, upload-time = "2025-06-26T20:33:54.231Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/00/40da9c66d4a4d51291e619be6757fa65c91b92456ff4f01101593f3a1170/ruff-0.12.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cc32e863adcf9e71690248607ccdf25252eeeab5193768e6873b901fd441fed", size = 11608941, upload-time = "2025-06-26T20:33:56.202Z" },
+ { url = "https://files.pythonhosted.org/packages/91/e7/f898391cc026a77fbe68dfea5940f8213622474cb848eb30215538a2dadf/ruff-0.12.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd49a4619f90d5afc65cf42e07b6ae98bb454fd5029d03b306bd9e2273d44cc", size = 11602887, upload-time = "2025-06-26T20:33:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/02/0891872fc6aab8678084f4cf8826f85c5d2d24aa9114092139a38123f94b/ruff-0.12.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed5af6aaaea20710e77698e2055b9ff9b3494891e1b24d26c07055459bb717e9", size = 10521742, upload-time = "2025-06-26T20:34:00.465Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/98/d6534322c74a7d47b0f33b036b2498ccac99d8d8c40edadb552c038cecf1/ruff-0.12.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:801d626de15e6bf988fbe7ce59b303a914ff9c616d5866f8c79eb5012720ae13", size = 10149909, upload-time = "2025-06-26T20:34:02.603Z" },
+ { url = "https://files.pythonhosted.org/packages/34/5c/9b7ba8c19a31e2b6bd5e31aa1e65b533208a30512f118805371dbbbdf6a9/ruff-0.12.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2be9d32a147f98a1972c1e4df9a6956d612ca5f5578536814372113d09a27a6c", size = 11136005, upload-time = "2025-06-26T20:34:04.723Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/34/9bbefa4d0ff2c000e4e533f591499f6b834346025e11da97f4ded21cb23e/ruff-0.12.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:49b7ce354eed2a322fbaea80168c902de9504e6e174fd501e9447cad0232f9e6", size = 11648579, upload-time = "2025-06-26T20:34:06.766Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/1c/20cdb593783f8f411839ce749ec9ae9e4298c2b2079b40295c3e6e2089e1/ruff-0.12.1-py3-none-win32.whl", hash = "sha256:d973fa626d4c8267848755bd0414211a456e99e125dcab147f24daa9e991a245", size = 10519495, upload-time = "2025-06-26T20:34:08.718Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/56/7158bd8d3cf16394928f47c637d39a7d532268cd45220bdb6cd622985760/ruff-0.12.1-py3-none-win_amd64.whl", hash = "sha256:9e1123b1c033f77bd2590e4c1fe7e8ea72ef990a85d2484351d408224d603013", size = 11547485, upload-time = "2025-06-26T20:34:11.008Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d0/6902c0d017259439d6fd2fd9393cea1cfe30169940118b007d5e0ea7e954/ruff-0.12.1-py3-none-win_arm64.whl", hash = "sha256:78ad09a022c64c13cc6077707f036bab0fac8cd7088772dcd1e5be21c5002efc", size = 10691209, upload-time = "2025-06-26T20:34:12.928Z" },
]
[[package]]
@@ -4779,15 +4671,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
-version = "2.29.1"
+version = "2.32.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518, upload-time = "2025-05-19T14:27:38.512Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload-time = "2025-06-27T08:10:02.89Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553, upload-time = "2025-05-19T14:27:36.882Z" },
+ { url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload-time = "2025-06-27T08:10:01.424Z" },
]
[[package]]
@@ -4824,11 +4716,11 @@ wheels = [
[[package]]
name = "setuptools"
-version = "80.8.0"
+version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8d/d2/ec1acaaff45caed5c2dedb33b67055ba9d4e96b091094df90762e60135fe/setuptools-80.8.0.tar.gz", hash = "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257", size = 1319720, upload-time = "2025-05-20T14:02:53.503Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0", size = 1201470, upload-time = "2025-05-20T14:02:51.348Z" },
+ { 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" },
]
[[package]]
@@ -4836,7 +4728,7 @@ name = "shapely"
version = "2.1.1"
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/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" }
wheels = [
@@ -4947,35 +4839,6 @@ 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 = "tomli"
-version = "2.2.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
- { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
- { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
- { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
- { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
- { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
- { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
- { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
- { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
- { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
- { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
- { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
- { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
- { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
- { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
- { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
- { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
- { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
- { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
- { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
- { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
-]
-
[[package]]
name = "tqdm"
version = "4.67.1"
@@ -4990,14 +4853,14 @@ wheels = [
[[package]]
name = "types-requests"
-version = "2.32.0.20250515"
+version = "2.32.4.20250611"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "urllib3" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/06/c1/cdc4f9b8cfd9130fbe6276db574f114541f4231fcc6fb29648289e6e3390/types_requests-2.32.0.20250515.tar.gz", hash = "sha256:09c8b63c11318cb2460813871aaa48b671002e59fda67ca909e9883777787581", size = 23012, upload-time = "2025-05-15T03:04:31.817Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fe/0f/68a997c73a129287785f418c1ebb6004f81e46b53b3caba88c0e03fcd04a/types_requests-2.32.0.20250515-py3-none-any.whl", hash = "sha256:f8eba93b3a892beee32643ff836993f15a785816acca21ea0ffa006f05ef0fb2", size = 20635, upload-time = "2025-05-15T03:04:30.5Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" },
]
[[package]]
@@ -5011,20 +4874,20 @@ wheels = [
[[package]]
name = "typing-extensions"
-version = "4.13.2"
+version = "4.14.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
+ { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
]
[[package]]
name = "urllib3"
-version = "2.4.0"
+version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
+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" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
+ { 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" },
]
[[package]]
@@ -5094,7 +4957,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 = [
@@ -5103,50 +4966,50 @@ wheels = [
[[package]]
name = "yarl"
-version = "1.20.0"
+version = "1.20.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "multidict" },
{ name = "propcache" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/62/51/c0edba5219027f6eab262e139f73e2417b0f4efffa23bf562f6e18f76ca5/yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307", size = 185258, upload-time = "2025-04-17T00:45:14.661Z" }
+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/60/82/a59d8e21b20ffc836775fa7daedac51d16bb8f3010c4fcb495c4496aa922/yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3", size = 145178, upload-time = "2025-04-17T00:42:04.511Z" },
- { url = "https://files.pythonhosted.org/packages/ba/81/315a3f6f95947cfbf37c92d6fbce42a1a6207b6c38e8c2b452499ec7d449/yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a", size = 96859, upload-time = "2025-04-17T00:42:06.43Z" },
- { url = "https://files.pythonhosted.org/packages/ad/17/9b64e575583158551b72272a1023cdbd65af54fe13421d856b2850a6ddb7/yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2", size = 94647, upload-time = "2025-04-17T00:42:07.976Z" },
- { url = "https://files.pythonhosted.org/packages/2c/29/8f291e7922a58a21349683f6120a85701aeefaa02e9f7c8a2dc24fe3f431/yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e", size = 355788, upload-time = "2025-04-17T00:42:09.902Z" },
- { url = "https://files.pythonhosted.org/packages/26/6d/b4892c80b805c42c228c6d11e03cafabf81662d371b0853e7f0f513837d5/yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9", size = 344613, upload-time = "2025-04-17T00:42:11.768Z" },
- { url = "https://files.pythonhosted.org/packages/d7/0e/517aa28d3f848589bae9593717b063a544b86ba0a807d943c70f48fcf3bb/yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a", size = 370953, upload-time = "2025-04-17T00:42:13.983Z" },
- { url = "https://files.pythonhosted.org/packages/5f/9b/5bd09d2f1ad6e6f7c2beae9e50db78edd2cca4d194d227b958955573e240/yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2", size = 369204, upload-time = "2025-04-17T00:42:16.386Z" },
- { url = "https://files.pythonhosted.org/packages/9c/85/d793a703cf4bd0d4cd04e4b13cc3d44149470f790230430331a0c1f52df5/yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2", size = 358108, upload-time = "2025-04-17T00:42:18.622Z" },
- { url = "https://files.pythonhosted.org/packages/6f/54/b6c71e13549c1f6048fbc14ce8d930ac5fb8bafe4f1a252e621a24f3f1f9/yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8", size = 346610, upload-time = "2025-04-17T00:42:20.9Z" },
- { url = "https://files.pythonhosted.org/packages/a0/1a/d6087d58bdd0d8a2a37bbcdffac9d9721af6ebe50d85304d9f9b57dfd862/yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902", size = 365378, upload-time = "2025-04-17T00:42:22.926Z" },
- { url = "https://files.pythonhosted.org/packages/02/84/e25ddff4cbc001dbc4af76f8d41a3e23818212dd1f0a52044cbc60568872/yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791", size = 356919, upload-time = "2025-04-17T00:42:25.145Z" },
- { url = "https://files.pythonhosted.org/packages/04/76/898ae362353bf8f64636495d222c8014c8e5267df39b1a9fe1e1572fb7d0/yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f", size = 364248, upload-time = "2025-04-17T00:42:27.475Z" },
- { url = "https://files.pythonhosted.org/packages/1b/b0/9d9198d83a622f1c40fdbf7bd13b224a6979f2e1fc2cf50bfb1d8773c495/yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da", size = 378418, upload-time = "2025-04-17T00:42:29.333Z" },
- { url = "https://files.pythonhosted.org/packages/c7/ce/1f50c1cc594cf5d3f5bf4a9b616fca68680deaec8ad349d928445ac52eb8/yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4", size = 383850, upload-time = "2025-04-17T00:42:31.668Z" },
- { url = "https://files.pythonhosted.org/packages/89/1e/a59253a87b35bfec1a25bb5801fb69943330b67cfd266278eb07e0609012/yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5", size = 381218, upload-time = "2025-04-17T00:42:33.523Z" },
- { url = "https://files.pythonhosted.org/packages/85/b0/26f87df2b3044b0ef1a7cf66d321102bdca091db64c5ae853fcb2171c031/yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6", size = 86606, upload-time = "2025-04-17T00:42:35.873Z" },
- { url = "https://files.pythonhosted.org/packages/33/46/ca335c2e1f90446a77640a45eeb1cd8f6934f2c6e4df7db0f0f36ef9f025/yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb", size = 93374, upload-time = "2025-04-17T00:42:37.586Z" },
- { url = "https://files.pythonhosted.org/packages/c3/e8/3efdcb83073df978bb5b1a9cc0360ce596680e6c3fac01f2a994ccbb8939/yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f", size = 147089, upload-time = "2025-04-17T00:42:39.602Z" },
- { url = "https://files.pythonhosted.org/packages/60/c3/9e776e98ea350f76f94dd80b408eaa54e5092643dbf65fd9babcffb60509/yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e", size = 97706, upload-time = "2025-04-17T00:42:41.469Z" },
- { url = "https://files.pythonhosted.org/packages/0c/5b/45cdfb64a3b855ce074ae607b9fc40bc82e7613b94e7612b030255c93a09/yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e", size = 95719, upload-time = "2025-04-17T00:42:43.666Z" },
- { url = "https://files.pythonhosted.org/packages/2d/4e/929633b249611eeed04e2f861a14ed001acca3ef9ec2a984a757b1515889/yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33", size = 343972, upload-time = "2025-04-17T00:42:45.391Z" },
- { url = "https://files.pythonhosted.org/packages/49/fd/047535d326c913f1a90407a3baf7ff535b10098611eaef2c527e32e81ca1/yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58", size = 339639, upload-time = "2025-04-17T00:42:47.552Z" },
- { url = "https://files.pythonhosted.org/packages/48/2f/11566f1176a78f4bafb0937c0072410b1b0d3640b297944a6a7a556e1d0b/yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f", size = 353745, upload-time = "2025-04-17T00:42:49.406Z" },
- { url = "https://files.pythonhosted.org/packages/26/17/07dfcf034d6ae8837b33988be66045dd52f878dfb1c4e8f80a7343f677be/yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae", size = 354178, upload-time = "2025-04-17T00:42:51.588Z" },
- { url = "https://files.pythonhosted.org/packages/15/45/212604d3142d84b4065d5f8cab6582ed3d78e4cc250568ef2a36fe1cf0a5/yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018", size = 349219, upload-time = "2025-04-17T00:42:53.674Z" },
- { url = "https://files.pythonhosted.org/packages/e6/e0/a10b30f294111c5f1c682461e9459935c17d467a760c21e1f7db400ff499/yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672", size = 337266, upload-time = "2025-04-17T00:42:55.49Z" },
- { url = "https://files.pythonhosted.org/packages/33/a6/6efa1d85a675d25a46a167f9f3e80104cde317dfdf7f53f112ae6b16a60a/yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8", size = 360873, upload-time = "2025-04-17T00:42:57.895Z" },
- { url = "https://files.pythonhosted.org/packages/77/67/c8ab718cb98dfa2ae9ba0f97bf3cbb7d45d37f13fe1fbad25ac92940954e/yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7", size = 360524, upload-time = "2025-04-17T00:43:00.094Z" },
- { url = "https://files.pythonhosted.org/packages/bd/e8/c3f18660cea1bc73d9f8a2b3ef423def8dadbbae6c4afabdb920b73e0ead/yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594", size = 365370, upload-time = "2025-04-17T00:43:02.242Z" },
- { url = "https://files.pythonhosted.org/packages/c9/99/33f3b97b065e62ff2d52817155a89cfa030a1a9b43fee7843ef560ad9603/yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6", size = 373297, upload-time = "2025-04-17T00:43:04.189Z" },
- { url = "https://files.pythonhosted.org/packages/3d/89/7519e79e264a5f08653d2446b26d4724b01198a93a74d2e259291d538ab1/yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1", size = 378771, upload-time = "2025-04-17T00:43:06.609Z" },
- { url = "https://files.pythonhosted.org/packages/3a/58/6c460bbb884abd2917c3eef6f663a4a873f8dc6f498561fc0ad92231c113/yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b", size = 375000, upload-time = "2025-04-17T00:43:09.01Z" },
- { url = "https://files.pythonhosted.org/packages/3b/2a/dd7ed1aa23fea996834278d7ff178f215b24324ee527df53d45e34d21d28/yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64", size = 86355, upload-time = "2025-04-17T00:43:11.311Z" },
- { url = "https://files.pythonhosted.org/packages/ca/c6/333fe0338305c0ac1c16d5aa7cc4841208d3252bbe62172e0051006b5445/yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c", size = 92904, upload-time = "2025-04-17T00:43:13.087Z" },
- { url = "https://files.pythonhosted.org/packages/ea/1f/70c57b3d7278e94ed22d85e09685d3f0a38ebdd8c5c73b65ba4c0d0fe002/yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124", size = 46124, upload-time = "2025-04-17T00:45:12.199Z" },
+ { 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" },
]
[[package]]