Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07ad9294d4
|
||
|
|
7af3c29c58
|
||
|
|
394958724a
|
||
|
|
19151d5fd3
|
||
|
|
e67d52c89a
|
No files matched your search
@@ -111,7 +111,7 @@ jobs:
|
||||
run: |
|
||||
dnf install -y git rpmdevtools meson ninja-build python3-devel \
|
||||
systemd-rpm-macros vulkan-loader-devel redhat-rpm-config nodejs npm \
|
||||
gcc gettext
|
||||
gcc gettext mingw64-gcc
|
||||
|
||||
- name: Setup rpmbuild environment
|
||||
run: |
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y build-essential debhelper dpkg-dev gettext git \
|
||||
libvulkan-dev meson ninja-build nodejs npm \
|
||||
libvulkan-dev meson mingw-w64 ninja-build nodejs npm \
|
||||
pkgconf python3
|
||||
|
||||
- name: Checkout
|
||||
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
run: |
|
||||
dnf install -y git rpmdevtools meson ninja-build python3-devel \
|
||||
systemd-rpm-macros vulkan-loader-devel redhat-rpm-config nodejs npm \
|
||||
gcc gettext
|
||||
gcc gettext mingw64-gcc
|
||||
|
||||
- name: Setup rpmbuild environment
|
||||
run: |
|
||||
@@ -257,7 +257,7 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y build-essential debhelper dpkg-dev gettext git \
|
||||
libvulkan-dev meson ninja-build nodejs npm \
|
||||
libvulkan-dev meson mingw-w64 ninja-build nodejs npm \
|
||||
pkgconf python3
|
||||
|
||||
- name: Checkout PR
|
||||
|
||||
@@ -41,5 +41,8 @@ Thumbs.db
|
||||
# Compiled gettext catalogs
|
||||
portprotonqt/locales/*/LC_MESSAGES/*.mo
|
||||
|
||||
# Local MinGW toolchain and built DLL (CI builds these)
|
||||
.toolchain/
|
||||
data/NPClient64.dll
|
||||
# AI
|
||||
.codex
|
||||
+2
-2
@@ -6,8 +6,8 @@ arch=('any')
|
||||
url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3')
|
||||
makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'python-opencv' 'python-numpy' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3')
|
||||
makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext' 'mingw-w64-gcc')
|
||||
conflicts=("portprotonqt-steam-compat")
|
||||
provides=('portprotonqt-steam-compat')
|
||||
replaces=('portprotonqt-steam-compat')
|
||||
|
||||
@@ -6,7 +6,7 @@ arch=('any')
|
||||
url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'python-opencv' 'python-numpy' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast' 'sdl3')
|
||||
makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext')
|
||||
conflicts=("portprotonqt-steam-compat")
|
||||
provides=('portprotonqt-steam-compat')
|
||||
|
||||
@@ -23,6 +23,7 @@ BuildRequires: gettext
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: vulkan-loader-devel
|
||||
BuildRequires: gcc
|
||||
BuildRequires: mingw64-gcc
|
||||
|
||||
Obsoletes: python3-%{pypi_name}-git < %{version}-%{release}
|
||||
Provides: python3-%{pypi_name}-git = %{version}-%{release}
|
||||
@@ -42,6 +43,8 @@ Requires: python3-pillow
|
||||
Requires: python3-pillow-qt
|
||||
Requires: python3-rapidfuzz
|
||||
Requires: python3-libarchive-c
|
||||
Requires: python3-opencv
|
||||
Requires: python3-numpy
|
||||
Requires: perl-Image-ExifTool
|
||||
Requires: SDL3
|
||||
Requires: qt6-qtsvg
|
||||
@@ -118,6 +121,7 @@ install -Dpm 0644 ./completions/_portprotonqt -t %{buildroot}%{zsh_completions_d
|
||||
%{_datadir}/portproton/scripts/
|
||||
%{_datadir}/portproton/conf/
|
||||
%{_datadir}/portproton/img/
|
||||
%{_datadir}/%{pypi_name}/
|
||||
%{bash_completions_dir}/portprotonqt
|
||||
%{fish_completions_dir}/portprotonqt.fish
|
||||
%{zsh_completions_dir}/_portprotonqt
|
||||
|
||||
@@ -20,6 +20,7 @@ BuildRequires: gettext
|
||||
BuildRequires: systemd-rpm-macros
|
||||
BuildRequires: vulkan-loader-devel
|
||||
BuildRequires: gcc
|
||||
BuildRequires: mingw64-gcc
|
||||
|
||||
Obsoletes: python3-%{pypi_name} < %{version}-%{release}
|
||||
Provides: python3-%{pypi_name} = %{version}-%{release}
|
||||
@@ -39,6 +40,8 @@ Requires: python3-pillow
|
||||
Requires: python3-pillow-qt
|
||||
Requires: python3-rapidfuzz
|
||||
Requires: python3-libarchive-c
|
||||
Requires: python3-opencv
|
||||
Requires: python3-numpy
|
||||
Requires: perl-Image-ExifTool
|
||||
Requires: SDL3
|
||||
Requires: qt6-qtsvg
|
||||
@@ -116,6 +119,7 @@ install -Dpm 0644 ./completions/_portprotonqt -t %{buildroot}%{zsh_completions_d
|
||||
%{_datadir}/portproton/scripts/
|
||||
%{_datadir}/portproton/conf/
|
||||
%{_datadir}/portproton/img/
|
||||
%{_datadir}/%{pypi_name}/
|
||||
%{bash_completions_dir}/portprotonqt
|
||||
%{fish_completions_dir}/portprotonqt.fish
|
||||
%{zsh_completions_dir}/_portprotonqt
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
NPClient64.dll for PortProtonQt
|
||||
Copyright (C) 2026 PortProtonQt contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
---
|
||||
|
||||
The NPClient API implemented in this DLL is based on publicly
|
||||
available documentation and open-source implementations, including:
|
||||
|
||||
- linuxtrack-wine by John Tsiombikas (GPLv3)
|
||||
https://github.com/jtsiomb/linuxtrack-wine
|
||||
|
||||
- opentrack by Stanislaw Halik and contributors (ISC)
|
||||
https://github.com/opentrack/opentrack
|
||||
@@ -0,0 +1,20 @@
|
||||
# Makefile for NPClient64.dll
|
||||
# Build with: make NPClient64.dll
|
||||
# Requires mingw-w64 toolchain (x86_64-w64-mingw32-gcc)
|
||||
|
||||
CC = x86_64-w64-mingw32-gcc
|
||||
CFLAGS = -O2 -Wall -shared
|
||||
LDFLAGS = -Wl,--subsystem,windows
|
||||
|
||||
TARGET = NPClient64.dll
|
||||
SRC = npclient64.c
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRC)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* NPClient64.dll - TrackIR-compatible head tracking client for Wine.
|
||||
*
|
||||
* Implements the NaturalPoint TrackIR NPClient API, reading head tracking
|
||||
* data from the Wine shared memory file "facetracknoir-wine-shm" written
|
||||
* by PortProtonQt.
|
||||
*
|
||||
* The NPClient API was reverse-engineered from publicly available
|
||||
* documentation and open-source implementations. This file is released
|
||||
* under the GPL-3.0 license as part of PortProtonQt.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define WINE_SHM_NAME "facetracknoir-wine-shm"
|
||||
#define WINE_MTX_NAME "facetracknoir-wine-mtx"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WineSHM {
|
||||
double data[6];
|
||||
int32_t gameid;
|
||||
int32_t gameid2;
|
||||
unsigned char table[8];
|
||||
BOOL stop;
|
||||
};
|
||||
|
||||
struct NPData {
|
||||
int16_t status;
|
||||
int16_t frame;
|
||||
uint32_t checksum;
|
||||
float roll, pitch, yaw;
|
||||
float tx, ty, tz;
|
||||
float padding[9];
|
||||
};
|
||||
|
||||
struct NPSignature {
|
||||
char dllsig[200];
|
||||
char appsig[200];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static HANDLE hShm = NULL;
|
||||
static struct WineSHM *shm = NULL;
|
||||
static HANDLE hMtx = NULL;
|
||||
static int16_t frame_counter = 0;
|
||||
static int initialized = 0;
|
||||
static int transmitting = 0;
|
||||
|
||||
static struct NPSignature sigdata = {
|
||||
"precise head tracking\n put your head into the game\n now go look around\n\n "
|
||||
"Copyright EyeControl Technologies",
|
||||
|
||||
"hardware camera\n software processing data\n track user movement\n\n "
|
||||
"Copyright EyeControl Technologies"
|
||||
};
|
||||
|
||||
static BOOL impl_create_mapping(void)
|
||||
{
|
||||
if (shm != NULL)
|
||||
return TRUE;
|
||||
|
||||
hShm = CreateFileMappingA(INVALID_HANDLE_VALUE,
|
||||
NULL,
|
||||
PAGE_READWRITE,
|
||||
0,
|
||||
sizeof(struct WineSHM),
|
||||
(LPCSTR)WINE_SHM_NAME);
|
||||
if (hShm == NULL)
|
||||
return FALSE;
|
||||
|
||||
shm = (struct WineSHM *)MapViewOfFile(hShm, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct WineSHM));
|
||||
hMtx = CreateMutexA(NULL, FALSE, WINE_MTX_NAME);
|
||||
|
||||
return shm != NULL;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_GetData(struct NPData *data)
|
||||
{
|
||||
if (data == NULL)
|
||||
return 1;
|
||||
|
||||
memset(data, 0, sizeof(struct NPData));
|
||||
|
||||
if (!initialized || !transmitting)
|
||||
return 0;
|
||||
|
||||
if (impl_create_mapping() == FALSE || shm == NULL)
|
||||
return 1;
|
||||
|
||||
if (hMtx && WaitForSingleObject(hMtx, 16) == WAIT_OBJECT_0) {
|
||||
double x = shm->data[0];
|
||||
double y = shm->data[1];
|
||||
double z = shm->data[2];
|
||||
double yaw = shm->data[3];
|
||||
double pitch = shm->data[4];
|
||||
double roll = shm->data[5];
|
||||
ReleaseMutex(hMtx);
|
||||
|
||||
data->yaw = (float)(-(yaw * 180.0 / 3.14159265358979323846) / 180.0 * 16384.0);
|
||||
data->pitch = (float)(-(pitch * 180.0 / 3.14159265358979323846) / 180.0 * 16384.0);
|
||||
data->roll = (float)(-(roll * 180.0 / 3.14159265358979323846) / 180.0 * 16384.0);
|
||||
|
||||
data->tx = (float)(-x * 64.0f / 10.0f);
|
||||
data->ty = (float)(y * 64.0f / 10.0f);
|
||||
data->tz = (float)(z * 64.0f / 10.0f);
|
||||
}
|
||||
|
||||
data->status = 0;
|
||||
data->frame = frame_counter++;
|
||||
data->checksum = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_GetSignature(struct NPSignature *sig)
|
||||
{
|
||||
if (sig == NULL)
|
||||
return 1;
|
||||
memcpy(sig, &sigdata, sizeof(sigdata));
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_QueryVersion(short *ver)
|
||||
{
|
||||
if (ver == NULL)
|
||||
return 1;
|
||||
*ver = 0x0400;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_ReCenter(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_RegisterWindowHandle(void *handle)
|
||||
{
|
||||
(void)handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_UnregisterWindowHandle(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_RegisterProgramProfileID(short id)
|
||||
{
|
||||
(void)id;
|
||||
initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_RequestData(short data)
|
||||
{
|
||||
(void)data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_StopCursor(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_StartCursor(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_StartDataTransmission(void)
|
||||
{
|
||||
transmitting = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int __stdcall NP_StopDataTransmission(void)
|
||||
{
|
||||
transmitting = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
||||
{
|
||||
(void)hinstDLL;
|
||||
(void)lpvReserved;
|
||||
|
||||
switch (fdwReason) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (shm) {
|
||||
UnmapViewOfFile(shm);
|
||||
shm = NULL;
|
||||
}
|
||||
if (hShm) {
|
||||
CloseHandle(hShm);
|
||||
hShm = NULL;
|
||||
}
|
||||
if (hMtx) {
|
||||
CloseHandle(hMtx);
|
||||
hMtx = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
Binary file not shown.
Vendored
+3
@@ -7,6 +7,7 @@ Build-Depends:
|
||||
gettext,
|
||||
libvulkan-dev,
|
||||
meson,
|
||||
mingw-w64,
|
||||
ninja-build,
|
||||
pkgconf,
|
||||
python3,
|
||||
@@ -34,6 +35,8 @@ Depends:
|
||||
python3-dbus-fast,
|
||||
python3-evdev,
|
||||
python3-libarchive-c,
|
||||
python3-numpy,
|
||||
python3-opencv,
|
||||
python3-orjson,
|
||||
python3-pefile,
|
||||
python3-pil,
|
||||
|
||||
Vendored
+5
@@ -12,6 +12,11 @@ Copyright:
|
||||
2026 PortProtonQt contributors
|
||||
License: GPL-3.0
|
||||
|
||||
Files: data/face_detection_yunet_2023mar.onnx
|
||||
Copyright:
|
||||
2023 OpenCV team
|
||||
License: Apache-2.0
|
||||
|
||||
License: GPL-3.0
|
||||
This package is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License version 3.
|
||||
|
||||
Vendored
+1
@@ -11,4 +11,5 @@ usr/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
|
||||
usr/share/mime/packages/ru.linux_gaming.PortProtonQt.xml
|
||||
usr/share/polkit-1/rules.d/ru.linux_gaming.PortProtonQt.rules
|
||||
usr/share/portproton/
|
||||
usr/share/portprotonqt/
|
||||
usr/share/zsh/site-functions/_portprotonqt
|
||||
+21
@@ -102,6 +102,27 @@ install_data(
|
||||
install_dir: datadir / 'metainfo',
|
||||
)
|
||||
|
||||
# Install YuNet face detection model for head tracking
|
||||
install_data(
|
||||
'data/face_detection_yunet_2023mar.onnx',
|
||||
install_dir: datadir / meson.project_name(),
|
||||
)
|
||||
|
||||
# Build NPClient64.dll for TrackIR-compatible games (optional, requires mingw-w64)
|
||||
mingw_cc = find_program('x86_64-w64-mingw32-gcc', required: false)
|
||||
if mingw_cc.found()
|
||||
npclient64_dll = custom_target('npclient64_dll',
|
||||
input: 'build-aux/npclient/npclient64.c',
|
||||
output: 'NPClient64.dll',
|
||||
command: [mingw_cc, '-O2', '-Wall', '-shared', '-Wl,--subsystem,windows',
|
||||
'@INPUT@', '-o', '@OUTPUT@'],
|
||||
install: true,
|
||||
install_dir: datadir / meson.project_name(),
|
||||
)
|
||||
else
|
||||
warning('mingw-w64 not found, skipping NPClient64.dll build. Install mingw-w64 to enable TrackIR game support.')
|
||||
endif
|
||||
|
||||
# Install PortProton scripts
|
||||
install_subdir(
|
||||
'build-aux/share/portproton/scripts',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import cast, TYPE_CHECKING
|
||||
from typing import cast
|
||||
|
||||
from PySide6.QtCore import Qt, QObject, QEvent, QPoint, QProcess, QTimer, QUrl
|
||||
from PySide6.QtGui import QColor, QContextMenuEvent, QDesktopServices, QGuiApplication, QIcon
|
||||
@@ -26,9 +26,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from portprotonqt.main_window import MainWindow
|
||||
|
||||
from portprotonqt.config import (
|
||||
get_portproton_location,
|
||||
get_portproton_scripts_path,
|
||||
@@ -42,6 +39,7 @@ from portprotonqt.dialogs.dialog_utils import create_dialog_hints_widget, update
|
||||
from portprotonqt.dialogs.settings_mangohud import MANGOHUD_ENV_KEYS, MangoHudSettingsMixin
|
||||
from portprotonqt.dialogs.settings_gamescope import GAMESCOPE_ENV_KEYS, GamescopeSettingsMixin
|
||||
from portprotonqt.dialogs.settings_vkbasalt import VKBASALT_ENV_KEYS, VkBasaltSettingsMixin
|
||||
from portprotonqt.dialogs.settings_headtracking import HeadTrackingSettingsMixin, HT_ALL_KEYS
|
||||
from portprotonqt.localization import _, format_setting_name_for_display
|
||||
from portprotonqt.logger import get_logger
|
||||
from portprotonqt.preloader import Preloader
|
||||
@@ -132,6 +130,7 @@ class ExeSettingsDialog(
|
||||
MangoHudSettingsMixin,
|
||||
GamescopeSettingsMixin,
|
||||
VkBasaltSettingsMixin,
|
||||
HeadTrackingSettingsMixin,
|
||||
):
|
||||
"""Dialog for configuring executable-specific settings."""
|
||||
|
||||
@@ -179,11 +178,13 @@ class ExeSettingsDialog(
|
||||
self.init_mangohud_state()
|
||||
self.init_gamescope_state()
|
||||
self.init_vkbasalt_state()
|
||||
self.init_headtracking_state()
|
||||
self.blocked_keys = set()
|
||||
self.numa_nodes = {}
|
||||
self.locale_options = []
|
||||
self.logical_core_options = []
|
||||
self._gamepad_tooltip_map = {}
|
||||
self._closing = False
|
||||
|
||||
self.setWindowTitle(_("Exe Settings"))
|
||||
self.setModal(True)
|
||||
@@ -203,7 +204,7 @@ class ExeSettingsDialog(
|
||||
parent_obj = self.parent()
|
||||
while parent_obj:
|
||||
if hasattr(parent_obj, 'input_manager'):
|
||||
self.input_manager = cast("MainWindow", parent_obj).input_manager
|
||||
self.input_manager = parent_obj.input_manager # type: ignore[union-attr]
|
||||
self.main_window = parent_obj
|
||||
parent_obj = parent_obj.parent()
|
||||
|
||||
@@ -212,6 +213,7 @@ class ExeSettingsDialog(
|
||||
if self.input_manager:
|
||||
self.input_manager.enable_settings_mode(self)
|
||||
self.input_manager.gamepad_hotplug.connect(self._update_vkbasalt_toggle_key_visibility)
|
||||
self.input_manager.gamepad_hotplug.connect(self._check_gamepad_and_block_headtracking)
|
||||
|
||||
self.hints_widget, self.hints_labels = create_dialog_hints_widget(
|
||||
self.theme, self.main_window, self.input_manager, context='settings'
|
||||
@@ -290,6 +292,8 @@ class ExeSettingsDialog(
|
||||
self.vkbasalt_tab_layout = QVBoxLayout(self.vkbasalt_tab)
|
||||
self.gamescope_tab = QWidget()
|
||||
self.gamescope_tab_layout = QVBoxLayout(self.gamescope_tab)
|
||||
self.headtracking_tab = QWidget()
|
||||
self.headtracking_tab_layout = QVBoxLayout(self.headtracking_tab)
|
||||
|
||||
self.tab_widget.addTab(self.main_tab, _("Main"))
|
||||
self.tab_widget.addTab(self.advanced_tab, _("Advanced"))
|
||||
@@ -297,7 +301,9 @@ class ExeSettingsDialog(
|
||||
self.tab_widget.addTab(self.vkbasalt_tab, "vkBasalt")
|
||||
if self.gamescope_available:
|
||||
self.tab_widget.addTab(self.gamescope_tab, "Gamescope")
|
||||
self.tab_widget.addTab(self.headtracking_tab, _("Head Tracking"))
|
||||
self.tab_widget.currentChanged.connect(self.on_table_selection_changed)
|
||||
self.tab_widget.currentChanged.connect(lambda: self._on_headtracking_tab_changed())
|
||||
|
||||
self.settings_table = QTableWidget()
|
||||
self.settings_table.setAlternatingRowColors(True)
|
||||
@@ -421,6 +427,7 @@ class ExeSettingsDialog(
|
||||
self.setup_vkbasalt_tab()
|
||||
if self.gamescope_available:
|
||||
self.setup_gamescope_tab()
|
||||
self.setup_headtracking_tab()
|
||||
|
||||
self.main_layout.addWidget(self.tab_widget)
|
||||
|
||||
@@ -473,6 +480,8 @@ class ExeSettingsDialog(
|
||||
|
||||
def on_show_ppdb_finished(self, exit_code, exit_status):
|
||||
"""Handle --show-ppdb output."""
|
||||
if self._closing:
|
||||
return
|
||||
process = cast(QProcess, self.sender())
|
||||
self.current_settings = {}
|
||||
self.blocked_keys = set()
|
||||
@@ -504,6 +513,7 @@ class ExeSettingsDialog(
|
||||
or key in GAMESCOPE_ENV_KEYS
|
||||
or key in VKBASALT_ENV_KEYS
|
||||
or key in TOGGLE_BOOL_KEYS
|
||||
or key in HT_ALL_KEYS
|
||||
):
|
||||
if val.startswith('"') and val.endswith('"') and len(val) >= 2:
|
||||
val = val[1:-1]
|
||||
@@ -564,6 +574,8 @@ class ExeSettingsDialog(
|
||||
self.populate_vkbasalt()
|
||||
if self.gamescope_available:
|
||||
self.populate_gamescope()
|
||||
self.populate_headtracking()
|
||||
self._on_headtracking_tab_changed()
|
||||
self.populate_favorites(select_tab=True)
|
||||
|
||||
self.settings_container.setCurrentIndex(1)
|
||||
@@ -1228,9 +1240,11 @@ class ExeSettingsDialog(
|
||||
gamescope_changes = []
|
||||
if self.gamescope_available:
|
||||
gamescope_changes = self._collect_gamescope_changes()
|
||||
headtracking_changes = self._collect_headtracking_changes()
|
||||
changes.extend(mangohud_changes)
|
||||
changes.extend(self._collect_vkbasalt_changes())
|
||||
changes.extend(gamescope_changes)
|
||||
changes.extend(headtracking_changes)
|
||||
|
||||
# Check if PW_GAMESCOPE toggle changes are already in the list
|
||||
has_gamescope_toggle = any(change.startswith("PW_GAMESCOPE=") for change in gamescope_changes)
|
||||
@@ -1267,14 +1281,17 @@ class ExeSettingsDialog(
|
||||
QMessageBox.warning(self, _("Error"), _("Failed to apply changes. Check logs."))
|
||||
logger.error(f"Failed to apply changes: {error_output}")
|
||||
else:
|
||||
self.load_current_settings()
|
||||
self._closing = True
|
||||
self.cleanup_headtracking()
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._closing = True
|
||||
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||
self.keyboard.hide()
|
||||
if self.input_manager:
|
||||
self.input_manager.disable_settings_mode()
|
||||
self.cleanup_headtracking()
|
||||
super().closeEvent(event)
|
||||
|
||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
||||
@@ -1474,17 +1491,21 @@ class ExeSettingsDialog(
|
||||
self.show_gamepad_tooltip(show=False)
|
||||
|
||||
def reject(self):
|
||||
self._closing = True
|
||||
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||
self.keyboard.hide()
|
||||
self.gamepad_tooltip.setVisible(False)
|
||||
if self.input_manager:
|
||||
self.input_manager.disable_settings_mode()
|
||||
self.cleanup_headtracking()
|
||||
super().reject()
|
||||
|
||||
def accept(self):
|
||||
self._closing = True
|
||||
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||
self.keyboard.hide()
|
||||
self.gamepad_tooltip.setVisible(False)
|
||||
if self.input_manager:
|
||||
self.input_manager.disable_settings_mode()
|
||||
self.cleanup_headtracking()
|
||||
super().accept()
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,209 @@
|
||||
"""Global hotkey support via evdev without grab (Linux).
|
||||
|
||||
Reads keyboard events at the kernel level without grabbing devices,
|
||||
so all keys (including the hotkey) pass through to games normally.
|
||||
Works on Wayland, X11, and in games.
|
||||
"""
|
||||
|
||||
import select
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from evdev import InputDevice, ecodes, list_devices
|
||||
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Keys that can be used as global hotkeys
|
||||
SUPPORTED_HOTKEYS = {
|
||||
"scrolllock": ecodes.KEY_SCROLLLOCK,
|
||||
"insert": ecodes.KEY_INSERT,
|
||||
}
|
||||
|
||||
DEFAULT_HOTKEY = "insert"
|
||||
|
||||
|
||||
class GlobalHotkeyEvdev(QObject):
|
||||
"""Global hotkey using evdev without grab. Works on Wayland and X11."""
|
||||
|
||||
activated = Signal(str)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._callbacks: dict[str, Callable[[], None]] = {}
|
||||
self._devices: list[InputDevice] = []
|
||||
self._thread: threading.Thread | None = None
|
||||
self._running = False
|
||||
self._pressed: set[int] = set()
|
||||
self._hotkey_codes: set[int] = {SUPPORTED_HOTKEYS[DEFAULT_HOTKEY]}
|
||||
|
||||
def _find_keyboard_devices(self) -> list[InputDevice]:
|
||||
"""Find all keyboard input devices."""
|
||||
devices: list[InputDevice] = []
|
||||
for path in list_devices():
|
||||
try:
|
||||
dev = InputDevice(path)
|
||||
caps = dev.capabilities()
|
||||
if ecodes.EV_KEY not in caps:
|
||||
continue
|
||||
keys = caps[ecodes.EV_KEY]
|
||||
# Must have at least one hotkey and basic letter keys
|
||||
if ecodes.KEY_A in keys and any(h in keys for h in self._hotkey_codes):
|
||||
devices.append(dev)
|
||||
logger.debug("Keyboard device: %s (%s)", path, dev.name)
|
||||
except (OSError, PermissionError):
|
||||
continue
|
||||
logger.info("Found %d keyboard device(s)", len(devices))
|
||||
return devices
|
||||
|
||||
def _scan_devices(self) -> None:
|
||||
"""Close old devices and open new ones."""
|
||||
for dev in self._devices:
|
||||
try:
|
||||
dev.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._devices = self._find_keyboard_devices()
|
||||
|
||||
def register(
|
||||
self,
|
||||
shortcut_id: str,
|
||||
_description: str,
|
||||
callback: Callable[[], None],
|
||||
hotkey: str | None = None,
|
||||
) -> bool:
|
||||
"""Register a global hotkey and start monitoring."""
|
||||
self._callbacks[shortcut_id] = callback
|
||||
|
||||
if hotkey and hotkey.lower() in SUPPORTED_HOTKEYS:
|
||||
self._hotkey_codes.add(SUPPORTED_HOTKEYS[hotkey.lower()])
|
||||
|
||||
# Already running — just updated hotkey codes
|
||||
if self._running and self._thread is not None and self._thread.is_alive():
|
||||
logger.info("Global hotkey '%s' (Insert) active", shortcut_id)
|
||||
return True
|
||||
|
||||
self._devices = self._find_keyboard_devices()
|
||||
if not self._devices:
|
||||
logger.warning("No keyboard devices found for hotkey")
|
||||
return False
|
||||
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._run, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
logger.info(
|
||||
"Global hotkey '%s' (Insert) registered on %d device(s)",
|
||||
shortcut_id,
|
||||
len(self._devices),
|
||||
)
|
||||
return True
|
||||
|
||||
def _run(self) -> None:
|
||||
"""Main loop: wait for events with select() and dispatch."""
|
||||
scan_interval = 30.0 # Rescan devices every 30 seconds
|
||||
min_scan_interval = 5.0 # Minimum time between rescans
|
||||
last_scan = time.time()
|
||||
|
||||
while self._running:
|
||||
try:
|
||||
now = time.time()
|
||||
if now - last_scan > scan_interval:
|
||||
self._scan_devices()
|
||||
last_scan = now
|
||||
|
||||
if not self._devices:
|
||||
time.sleep(1.0)
|
||||
if now - last_scan > min_scan_interval:
|
||||
self._scan_devices()
|
||||
last_scan = time.time()
|
||||
continue
|
||||
|
||||
# Build readable file descriptors
|
||||
readable: list[int] = []
|
||||
fd_map: dict[int, InputDevice] = {}
|
||||
for dev in list(self._devices):
|
||||
try:
|
||||
fd = dev.fd
|
||||
if fd is not None and fd >= 0:
|
||||
readable.append(fd)
|
||||
fd_map[fd] = dev
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not readable:
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
|
||||
# Wait for events with timeout
|
||||
ready, _, _ = select.select(readable, [], [], 0.5)
|
||||
for fd in ready:
|
||||
dev = fd_map.get(fd)
|
||||
if dev is None:
|
||||
continue
|
||||
try:
|
||||
for event in dev.read():
|
||||
self._handle_event(event)
|
||||
except (OSError, PermissionError):
|
||||
# Device disconnected or permission lost
|
||||
logger.debug("Device %s unreadable, removing", dev.path)
|
||||
try:
|
||||
dev.close()
|
||||
except Exception:
|
||||
pass
|
||||
if dev in self._devices:
|
||||
self._devices.remove(dev)
|
||||
except Exception as e:
|
||||
logger.debug("Read error on %s: %s", dev.path, e)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Hotkey monitor error: %s", e)
|
||||
time.sleep(1.0)
|
||||
|
||||
def _handle_event(self, event) -> None:
|
||||
"""Process a single input event."""
|
||||
if event.type != ecodes.EV_KEY:
|
||||
return
|
||||
|
||||
code = event.code
|
||||
value = event.value
|
||||
|
||||
# value: 0 = release, 1 = press, 2 = repeat
|
||||
if value == 1 and code in self._hotkey_codes:
|
||||
# Find hotkey name from code and call all registered callbacks
|
||||
hotkey_name = None
|
||||
for name, key_code in SUPPORTED_HOTKEYS.items():
|
||||
if key_code == code:
|
||||
hotkey_name = name
|
||||
break
|
||||
if hotkey_name is None:
|
||||
return
|
||||
logger.info("Hotkey %s pressed", hotkey_name)
|
||||
for shortcut_id, cb in self._callbacks.items():
|
||||
try:
|
||||
cb()
|
||||
except Exception as e:
|
||||
logger.error("Hotkey callback error for '%s': %s", shortcut_id, e)
|
||||
for shortcut_id in self._callbacks:
|
||||
self.activated.emit(shortcut_id)
|
||||
|
||||
def unregister(self, shortcut_id: str) -> None:
|
||||
"""Unregister a global hotkey."""
|
||||
self._callbacks.pop(shortcut_id, None)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop and cleanup."""
|
||||
self._running = False
|
||||
if self._thread is not None:
|
||||
self._thread.join(timeout=2.0)
|
||||
self._thread = None
|
||||
for dev in self._devices:
|
||||
try:
|
||||
dev.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._devices = []
|
||||
logger.info("Global hotkey stopped")
|
||||
@@ -0,0 +1,14 @@
|
||||
"""Head tracking module for PortProtonQt."""
|
||||
|
||||
from portprotonqt.head_tracking.tracker_base import BaseTracker, PoseData
|
||||
from portprotonqt.head_tracking.tracker_camera import CameraTracker
|
||||
from portprotonqt.head_tracking.tracker_manager import TrackerManager
|
||||
from portprotonqt.head_tracking.tracking_worker import TrackingWorker
|
||||
|
||||
__all__ = [
|
||||
"BaseTracker",
|
||||
"CameraTracker",
|
||||
"PoseData",
|
||||
"TrackerManager",
|
||||
"TrackingWorker",
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
"""Output methods for head tracking data."""
|
||||
|
||||
from portprotonqt.head_tracking.outputs.flightgear_output import FlightGearOutput
|
||||
from portprotonqt.head_tracking.outputs.joystick_output import JoystickOutput
|
||||
from portprotonqt.head_tracking.outputs.mouse_output import MouseOutput
|
||||
from portprotonqt.head_tracking.outputs.npclient_output import NpclientOutput
|
||||
from portprotonqt.head_tracking.outputs.shm_output import ShmOutput
|
||||
from portprotonqt.head_tracking.outputs.udp_output import UdpOutput
|
||||
|
||||
__all__ = ["FlightGearOutput", "JoystickOutput", "MouseOutput", "NpclientOutput", "ShmOutput", "UdpOutput"]
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Base class for head tracking outputs."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
|
||||
|
||||
class BaseOutput(ABC):
|
||||
"""Abstract base for head tracking output methods."""
|
||||
|
||||
@abstractmethod
|
||||
def open(self) -> bool:
|
||||
"""Initialize output. Returns True on success."""
|
||||
|
||||
@abstractmethod
|
||||
def close(self) -> None:
|
||||
"""Release output resources."""
|
||||
|
||||
@abstractmethod
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Write pose data to output."""
|
||||
@@ -0,0 +1,75 @@
|
||||
"""FlightGear UDP output for head tracking.
|
||||
|
||||
Sends pose data in FlightGear native protocol format.
|
||||
Compatible with opentrack FlightGear protocol.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# FlightGear datagram format:
|
||||
# x, y, z in meters (pose * 1e-2, with sign flips)
|
||||
# h, p, r in degrees (with sign flips)
|
||||
# status int32 = 1
|
||||
_FG_FORMAT = "<6dI"
|
||||
_DEFAULT_HOST = "127.0.0.1"
|
||||
_DEFAULT_PORT = 5542
|
||||
|
||||
|
||||
class FlightGearOutput(BaseOutput):
|
||||
"""Send head tracking data to FlightGear via UDP."""
|
||||
|
||||
def __init__(self, host: str = _DEFAULT_HOST, port: int = _DEFAULT_PORT) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
self._sock: socket.socket | None = None
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Open UDP socket."""
|
||||
try:
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
logger.info("FlightGear output opened: %s:%d", self.host, self.port)
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.error("Failed to open FlightGear UDP socket: %s", exc)
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close UDP socket."""
|
||||
if self._sock:
|
||||
try:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._sock = None
|
||||
logger.info("FlightGear output closed")
|
||||
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Send pose data in FlightGear format."""
|
||||
if self._sock is None:
|
||||
return
|
||||
|
||||
# opentrack conversion:
|
||||
# x = -headpose[TX] * 1e-2, y = headpose[TY] * 1e-2, z = headpose[TZ] * 1e-2
|
||||
# h = -headpose[Yaw], p = headpose[Pitch], r = -headpose[Roll]
|
||||
data = (
|
||||
-pose.x * 1e-2,
|
||||
pose.y * 1e-2,
|
||||
pose.z * 1e-2,
|
||||
-pose.yaw,
|
||||
pose.pitch,
|
||||
-pose.roll,
|
||||
1, # status
|
||||
)
|
||||
packet = struct.pack(_FG_FORMAT, *data)
|
||||
|
||||
try:
|
||||
self._sock.sendto(packet, (self.host, self.port))
|
||||
except Exception as exc:
|
||||
logger.warning("FlightGear UDP send failed: %s", exc)
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Joystick emulation output for head tracking."""
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
import evdev
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Linux input axis range for absolute axes
|
||||
_AXIS_MIN = -32768
|
||||
_AXIS_MAX = 32767
|
||||
|
||||
|
||||
def _degrees_to_axis(degrees: float, max_degrees: float = 45.0) -> int:
|
||||
"""Map degrees to joystick axis value."""
|
||||
clamped = max(-max_degrees, min(max_degrees, degrees))
|
||||
return int((clamped / max_degrees) * _AXIS_MAX)
|
||||
|
||||
|
||||
class JoystickOutput(BaseOutput):
|
||||
"""Emulates joystick axes from head tracking yaw/pitch/roll."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sens_yaw: float = 1.0,
|
||||
sens_pitch: float = 1.0,
|
||||
sens_roll: float = 1.0,
|
||||
max_angle: float = 45.0,
|
||||
) -> None:
|
||||
self.sens_yaw = sens_yaw
|
||||
self.sens_pitch = sens_pitch
|
||||
self.sens_roll = sens_roll
|
||||
self.max_angle = max_angle
|
||||
self._ui = None
|
||||
self._running = False
|
||||
self._thread: threading.Thread | None = None
|
||||
self._lock = threading.Lock()
|
||||
self._pose = PoseData()
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Start joystick emulation thread."""
|
||||
try:
|
||||
import evdev.uinput
|
||||
except ImportError:
|
||||
logger.error("evdev not installed. Install: uv add evdev")
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
if self._running:
|
||||
return True
|
||||
|
||||
try:
|
||||
caps = {
|
||||
evdev.ecodes.EV_ABS: [
|
||||
(evdev.ecodes.ABS_X, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
(evdev.ecodes.ABS_Y, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
(evdev.ecodes.ABS_Z, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
(evdev.ecodes.ABS_RX, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
(evdev.ecodes.ABS_RY, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
(evdev.ecodes.ABS_RZ, evdev.AbsInfo(0, _AXIS_MIN, _AXIS_MAX, 0, 0, 0)),
|
||||
],
|
||||
evdev.ecodes.EV_KEY: [evdev.ecodes.BTN_TRIGGER],
|
||||
}
|
||||
self._ui = evdev.uinput.UInput(
|
||||
caps, name="PortProtonQt Head Tracking", version=0x3
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to create uinput joystick: %s", exc)
|
||||
return False
|
||||
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._run, daemon=True)
|
||||
self._thread.start()
|
||||
logger.info("Joystick output started")
|
||||
return True
|
||||
|
||||
def close(self) -> None:
|
||||
"""Stop joystick emulation."""
|
||||
with self._lock:
|
||||
self._running = False
|
||||
if self._ui:
|
||||
try:
|
||||
self._ui.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._ui = None
|
||||
|
||||
if self._thread:
|
||||
self._thread.join(timeout=1.0)
|
||||
self._thread = None
|
||||
logger.info("Joystick output stopped")
|
||||
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Update current head pose."""
|
||||
with self._lock:
|
||||
self._pose = pose
|
||||
|
||||
def _run(self) -> None:
|
||||
"""Main emulation loop."""
|
||||
last_values: dict[int, int] = {}
|
||||
|
||||
while True:
|
||||
with self._lock:
|
||||
if not self._running:
|
||||
break
|
||||
pose = self._pose
|
||||
ui = self._ui
|
||||
|
||||
if ui is None:
|
||||
time.sleep(0.016)
|
||||
continue
|
||||
|
||||
values = {
|
||||
evdev.ecodes.ABS_X: _degrees_to_axis(pose.yaw * self.sens_yaw, self.max_angle),
|
||||
evdev.ecodes.ABS_Y: _degrees_to_axis(pose.pitch * self.sens_pitch, self.max_angle),
|
||||
evdev.ecodes.ABS_Z: _degrees_to_axis(pose.roll * self.sens_roll, self.max_angle),
|
||||
evdev.ecodes.ABS_RX: _degrees_to_axis(pose.x * self.sens_yaw * 10, self.max_angle),
|
||||
evdev.ecodes.ABS_RY: _degrees_to_axis(pose.y * self.sens_pitch * 10, self.max_angle),
|
||||
evdev.ecodes.ABS_RZ: _degrees_to_axis(pose.z * self.sens_roll * 10, self.max_angle),
|
||||
}
|
||||
|
||||
try:
|
||||
for axis, val in values.items():
|
||||
if last_values.get(axis) != val:
|
||||
ui.write(evdev.ecodes.EV_ABS, axis, val)
|
||||
last_values[axis] = val
|
||||
ui.syn()
|
||||
except Exception as exc:
|
||||
logger.warning("Joystick output write failed: %s", exc)
|
||||
|
||||
time.sleep(0.016)
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Mouse emulation output for head tracking."""
|
||||
|
||||
import math
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
|
||||
import evdev
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MouseOutput(BaseOutput):
|
||||
"""Emulates mouse movement from head tracking yaw/pitch."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sens_yaw: float = 10.0,
|
||||
sens_pitch: float = 10.0,
|
||||
deadzone: float = 0.05,
|
||||
) -> None:
|
||||
self.sens_yaw = sens_yaw
|
||||
self.sens_pitch = sens_pitch
|
||||
self.deadzone = deadzone
|
||||
self._ui = None
|
||||
self._running = False
|
||||
self._thread: threading.Thread | None = None
|
||||
self._lock = threading.Lock()
|
||||
self._pose = PoseData()
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Start mouse emulation thread."""
|
||||
try:
|
||||
import evdev.uinput
|
||||
except ImportError:
|
||||
logger.error("evdev not installed. Install: uv add evdev")
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
if self._running:
|
||||
return True
|
||||
|
||||
try:
|
||||
caps: dict[int, Sequence[int]] = {
|
||||
evdev.ecodes.EV_REL: [evdev.ecodes.REL_X, evdev.ecodes.REL_Y],
|
||||
evdev.ecodes.EV_KEY: [evdev.ecodes.BTN_LEFT],
|
||||
}
|
||||
self._ui = evdev.uinput.UInput(
|
||||
caps, name="PortProtonQt Head Tracking"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("Failed to create uinput mouse: %s", exc)
|
||||
return False
|
||||
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._run, daemon=True)
|
||||
self._thread.start()
|
||||
logger.info("Mouse output started")
|
||||
return True
|
||||
|
||||
def close(self) -> None:
|
||||
"""Stop mouse emulation."""
|
||||
with self._lock:
|
||||
self._running = False
|
||||
if self._ui:
|
||||
try:
|
||||
self._ui.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._ui = None
|
||||
|
||||
if self._thread:
|
||||
self._thread.join(timeout=1.0)
|
||||
self._thread = None
|
||||
logger.info("Mouse output stopped")
|
||||
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Update current head pose."""
|
||||
with self._lock:
|
||||
self._pose = pose
|
||||
|
||||
def _run(self) -> None:
|
||||
"""Main emulation loop."""
|
||||
accum_x = 0.0
|
||||
accum_y = 0.0
|
||||
|
||||
while True:
|
||||
with self._lock:
|
||||
if not self._running:
|
||||
break
|
||||
pose = self._pose
|
||||
ui = self._ui
|
||||
|
||||
if ui is None:
|
||||
time.sleep(0.016)
|
||||
continue
|
||||
|
||||
yaw = pose.yaw if abs(pose.yaw) > self.deadzone else 0.0
|
||||
pitch = pose.pitch if abs(pose.pitch) > self.deadzone else 0.0
|
||||
|
||||
# Reset accumulation when head is centered to prevent drift
|
||||
if yaw == 0.0:
|
||||
accum_x = 0.0
|
||||
if pitch == 0.0:
|
||||
accum_y = 0.0
|
||||
|
||||
# Accumulate fractional movement to avoid jitter at low speeds
|
||||
accum_x += -math.radians(yaw) * self.sens_yaw
|
||||
accum_y += math.radians(pitch) * self.sens_pitch
|
||||
|
||||
dx = int(accum_x)
|
||||
dy = int(accum_y)
|
||||
|
||||
if dx != 0 or dy != 0:
|
||||
try:
|
||||
ui.write(evdev.ecodes.EV_REL, evdev.ecodes.REL_X, dx)
|
||||
ui.write(evdev.ecodes.EV_REL, evdev.ecodes.REL_Y, dy)
|
||||
ui.syn()
|
||||
accum_x -= dx
|
||||
accum_y -= dy
|
||||
except Exception as exc:
|
||||
logger.warning("Mouse output write failed: %s", exc)
|
||||
|
||||
time.sleep(0.016)
|
||||
@@ -0,0 +1,124 @@
|
||||
"""NPClient64 DLL output for native TrackIR SDK games.
|
||||
|
||||
This output writes head tracking data to the Wine SHM file and ensures
|
||||
that a compatible NPClient64.dll is present in the game's Wine prefix.
|
||||
Games using the TrackIR SDK (e.g., DCS World) load this DLL to receive
|
||||
tracking data.
|
||||
|
||||
The actual DLL implementation is provided as a separate MinGW-built
|
||||
binary placed next to the PortProtonQt data files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.outputs.shm_output import ShmOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_DLL_NAME = "NPClient64.dll"
|
||||
_DLL_SOURCE_CANDIDATES = [
|
||||
# Development / user data
|
||||
"data/NPClient64.dll",
|
||||
# Build output
|
||||
"builddir/build-aux/npclient/NPClient64.dll",
|
||||
# Installed system paths
|
||||
"/usr/share/portprotonqt/NPClient64.dll",
|
||||
"/usr/local/share/portprotonqt/NPClient64.dll",
|
||||
]
|
||||
|
||||
|
||||
class NpclientOutput(BaseOutput):
|
||||
"""Output via NPClient64.dll for TrackIR-native games."""
|
||||
|
||||
def __init__(self, prefix_path: str | None = None) -> None:
|
||||
self.prefix_path = prefix_path
|
||||
self._shm = ShmOutput()
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Open SHM and deploy DLL if prefix is known."""
|
||||
if not self._shm.open():
|
||||
return False
|
||||
if self.prefix_path:
|
||||
self._deploy_dll(self.prefix_path)
|
||||
return True
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close SHM output."""
|
||||
self._shm.close()
|
||||
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Write pose to Wine SHM (DLL reads from there)."""
|
||||
self._shm.write(pose)
|
||||
|
||||
def _deploy_dll(self, prefix_path: str) -> None:
|
||||
"""Copy NPClient64.dll into Wine prefix and set registry keys."""
|
||||
source = self._find_dll_source()
|
||||
if not source:
|
||||
logger.warning("NPClient64.dll not found; game may not detect tracker")
|
||||
return
|
||||
|
||||
target = os.path.join(prefix_path, "drive_c", "windows", "system32", _DLL_NAME)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(target), exist_ok=True)
|
||||
shutil.copy2(source, target)
|
||||
logger.info("Deployed %s to %s", _DLL_NAME, target)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to deploy %s to %s: %s", _DLL_NAME, target, exc)
|
||||
return
|
||||
|
||||
self._setup_registry(prefix_path, os.path.dirname(target))
|
||||
|
||||
def _setup_registry(self, prefix_path: str, dll_dir: str) -> None:
|
||||
"""Set Wine registry keys so games find the DLL."""
|
||||
wine_path = self._find_wine(prefix_path)
|
||||
if not wine_path:
|
||||
logger.warning("Wine not found; cannot set NPClient registry keys")
|
||||
return
|
||||
|
||||
env = os.environ.copy()
|
||||
env["WINEPREFIX"] = prefix_path
|
||||
|
||||
# Use forward slashes and add trailing slash as expected by games
|
||||
dll_dir_fwd = dll_dir.replace("\\", "/")
|
||||
if not dll_dir_fwd.endswith("/"):
|
||||
dll_dir_fwd += "/"
|
||||
|
||||
keys = [
|
||||
(r"HKCU\Software\NaturalPoint\NATURALPOINT\NPClient Location", "Path", dll_dir_fwd),
|
||||
(r"HKCU\Software\Freetrack\FreeTrackClient", "Path", dll_dir_fwd),
|
||||
]
|
||||
|
||||
for key, value_name, value in keys:
|
||||
try:
|
||||
subprocess.run(
|
||||
[wine_path, "reg", "add", key, "/v", value_name, "/d", value, "/f"],
|
||||
env=env,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
timeout=30,
|
||||
)
|
||||
logger.info("Set registry key %s\\%s", key, value_name)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to set registry key %s: %s", key, exc)
|
||||
|
||||
@staticmethod
|
||||
def _find_wine(prefix_path: str) -> str | None:
|
||||
"""Locate wine binary for the given prefix."""
|
||||
candidates = ["wine", "/usr/bin/wine", "/usr/local/bin/wine"]
|
||||
for candidate in candidates:
|
||||
if shutil.which(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _find_dll_source() -> str | None:
|
||||
"""Locate bundled NPClient64.dll."""
|
||||
for candidate in _DLL_SOURCE_CANDIDATES:
|
||||
if os.path.exists(candidate):
|
||||
return candidate
|
||||
return None
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Wine SHM output for head tracking."""
|
||||
|
||||
import math
|
||||
import mmap
|
||||
import os
|
||||
import struct
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Wine SHM protocol constants (TrackIR/FreeTrack compatible)
|
||||
WINE_SHM_NAME = "/facetracknoir-wine-shm"
|
||||
_SHM_FORMAT = "6d2i8b?"
|
||||
_SHM_SIZE = struct.calcsize(_SHM_FORMAT)
|
||||
|
||||
|
||||
class ShmOutput(BaseOutput):
|
||||
"""Writes head tracking data to Wine shared memory file."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._shm: mmap.mmap | None = None
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Open shared memory file compatible with Wine."""
|
||||
if self._shm is not None:
|
||||
return True
|
||||
|
||||
wine_shm_path = "/dev/shm/facetracknoir-wine-shm"
|
||||
try:
|
||||
fd = os.open(wine_shm_path, os.O_CREAT | os.O_RDWR, 0o666)
|
||||
os.ftruncate(fd, _SHM_SIZE)
|
||||
self._shm = mmap.mmap(fd, _SHM_SIZE)
|
||||
os.close(fd)
|
||||
self._write_pose(PoseData(), gameid=0, stop=False)
|
||||
logger.info("Wine SHM opened: %s", wine_shm_path)
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.error("Failed to open Wine SHM: %s", exc)
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close shared memory."""
|
||||
if self._shm:
|
||||
try:
|
||||
self._write_pose(PoseData(), gameid=0, stop=True)
|
||||
except Exception:
|
||||
pass
|
||||
self._shm.close()
|
||||
self._shm = None
|
||||
|
||||
wine_shm_path = "/dev/shm/facetracknoir-wine-shm"
|
||||
try:
|
||||
if os.path.exists(wine_shm_path):
|
||||
os.unlink(wine_shm_path)
|
||||
except Exception:
|
||||
pass
|
||||
logger.info("Wine SHM closed")
|
||||
|
||||
def write(self, pose: PoseData, gameid: int = 0) -> None:
|
||||
"""Write pose data to shared memory."""
|
||||
self._write_pose(pose, gameid, stop=False)
|
||||
|
||||
def _write_pose(
|
||||
self,
|
||||
pose: PoseData,
|
||||
gameid: int,
|
||||
stop: bool,
|
||||
) -> None:
|
||||
"""Internal write."""
|
||||
if self._shm is None:
|
||||
return
|
||||
|
||||
data = (
|
||||
pose.x * 10.0,
|
||||
pose.y * 10.0,
|
||||
pose.z * 10.0,
|
||||
math.radians(pose.yaw),
|
||||
math.radians(pose.pitch),
|
||||
math.radians(pose.roll),
|
||||
gameid,
|
||||
0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
stop,
|
||||
)
|
||||
self._shm.seek(0)
|
||||
self._shm.write(struct.pack(_SHM_FORMAT, *data))
|
||||
@@ -0,0 +1,71 @@
|
||||
"""UDP output for head tracking.
|
||||
|
||||
Sends 6-DOF pose data as 6 doubles over UDP.
|
||||
Compatible with opentrack UDP protocol.
|
||||
"""
|
||||
|
||||
import math
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# opentrack UDP format: 6 doubles (little-endian)
|
||||
# x, y, z in mm (pose * 10)
|
||||
# yaw, pitch, roll in radians
|
||||
_UDP_FORMAT = "<6d"
|
||||
_DEFAULT_HOST = "127.0.0.1"
|
||||
_DEFAULT_PORT = 4242
|
||||
|
||||
|
||||
class UdpOutput(BaseOutput):
|
||||
"""Send head tracking data over UDP."""
|
||||
|
||||
def __init__(self, host: str = _DEFAULT_HOST, port: int = _DEFAULT_PORT) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
self._sock: socket.socket | None = None
|
||||
|
||||
def open(self) -> bool:
|
||||
"""Open UDP socket."""
|
||||
try:
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
logger.info("UDP output opened: %s:%d", self.host, self.port)
|
||||
return True
|
||||
except Exception as exc:
|
||||
logger.error("Failed to open UDP socket: %s", exc)
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close UDP socket."""
|
||||
if self._sock:
|
||||
try:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self._sock = None
|
||||
logger.info("UDP output closed")
|
||||
|
||||
def write(self, pose: PoseData) -> None:
|
||||
"""Send pose data over UDP."""
|
||||
if self._sock is None:
|
||||
return
|
||||
|
||||
data = (
|
||||
pose.x * 10.0,
|
||||
pose.y * 10.0,
|
||||
pose.z * 10.0,
|
||||
math.radians(pose.yaw),
|
||||
math.radians(pose.pitch),
|
||||
math.radians(pose.roll),
|
||||
)
|
||||
packet = struct.pack(_UDP_FORMAT, *data)
|
||||
|
||||
try:
|
||||
self._sock.sendto(packet, (self.host, self.port))
|
||||
except Exception as exc:
|
||||
logger.warning("UDP send failed: %s", exc)
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Base tracker interface for head tracking."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import numpy as np
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoseData:
|
||||
"""6-DOF head pose data."""
|
||||
|
||||
x: float = 0.0 # Translation X (cm)
|
||||
y: float = 0.0 # Translation Y (cm)
|
||||
z: float = 0.0 # Translation Z (cm)
|
||||
yaw: float = 0.0 # Rotation Yaw (degrees)
|
||||
pitch: float = 0.0 # Rotation Pitch (degrees)
|
||||
roll: float = 0.0 # Rotation Roll (degrees)
|
||||
|
||||
def apply_offset(self, offset: "PoseData") -> "PoseData":
|
||||
"""Subtract offset from this pose."""
|
||||
return PoseData(
|
||||
x=self.x - offset.x,
|
||||
y=self.y - offset.y,
|
||||
z=self.z - offset.z,
|
||||
yaw=self.yaw - offset.yaw,
|
||||
pitch=self.pitch - offset.pitch,
|
||||
roll=self.roll - offset.roll,
|
||||
)
|
||||
|
||||
def apply_sensitivity(self, sens: "PoseData") -> "PoseData":
|
||||
"""Apply per-axis sensitivity multipliers."""
|
||||
return PoseData(
|
||||
x=self.x * sens.x,
|
||||
y=self.y * sens.y,
|
||||
z=self.z * sens.z,
|
||||
yaw=self.yaw * sens.yaw,
|
||||
pitch=self.pitch * sens.pitch,
|
||||
roll=self.roll * sens.roll,
|
||||
)
|
||||
|
||||
|
||||
class BaseTracker(ABC):
|
||||
"""Abstract base class for head trackers."""
|
||||
|
||||
@abstractmethod
|
||||
def start(self) -> bool:
|
||||
"""Start tracking. Returns True on success."""
|
||||
|
||||
@abstractmethod
|
||||
def stop(self) -> None:
|
||||
"""Stop tracking and release resources."""
|
||||
|
||||
@abstractmethod
|
||||
def get_pose(self) -> PoseData | None:
|
||||
"""Get current head pose. Returns None if tracking lost."""
|
||||
|
||||
def get_frame(self) -> tuple["np.ndarray | None", PoseData | None]:
|
||||
"""Get current camera frame with landmarks drawn and pose.
|
||||
|
||||
Default implementation returns no frame; subclasses may override.
|
||||
"""
|
||||
return None, self.get_pose()
|
||||
|
||||
@abstractmethod
|
||||
def is_running(self) -> bool:
|
||||
"""Check if tracker is active."""
|
||||
@@ -0,0 +1,350 @@
|
||||
"""Camera-based head tracker using OpenCV YuNet DNN."""
|
||||
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from portprotonqt.head_tracking.tracker_base import BaseTracker, PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
# Module-level shared VideoCapture to avoid V4L2 device busy issues
|
||||
_shared_cap: cv2.VideoCapture | None = None
|
||||
_shared_cap_index: int = -1
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def _find_yunet_model() -> str | None:
|
||||
"""Find YuNet model file in known install locations."""
|
||||
try:
|
||||
import portprotonqt
|
||||
module_dir = os.path.dirname(portprotonqt.__file__)
|
||||
except ImportError:
|
||||
module_dir = ""
|
||||
|
||||
project_root = os.path.dirname(module_dir) if module_dir else ""
|
||||
|
||||
candidates = []
|
||||
|
||||
if project_root:
|
||||
candidates.append(
|
||||
os.path.join(project_root, "data", "face_detection_yunet_2023mar.onnx")
|
||||
)
|
||||
|
||||
xdg_data_dirs = os.environ.get("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
|
||||
for d in xdg_data_dirs.split(":"):
|
||||
candidates.append(
|
||||
os.path.join(d, "portprotonqt", "face_detection_yunet_2023mar.onnx")
|
||||
)
|
||||
|
||||
candidates.append("/usr/share/portprotonqt/face_detection_yunet_2023mar.onnx")
|
||||
candidates.append("/usr/local/share/portprotonqt/face_detection_yunet_2023mar.onnx")
|
||||
|
||||
for c in candidates:
|
||||
if os.path.exists(c):
|
||||
return c
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _create_detector(model_path: str) -> cv2.FaceDetectorYN | None:
|
||||
"""Create YuNet face detector from model file."""
|
||||
try:
|
||||
detector = cv2.FaceDetectorYN.create(
|
||||
model=model_path,
|
||||
config="",
|
||||
input_size=(320, 320),
|
||||
score_threshold=0.3,
|
||||
nms_threshold=0.3,
|
||||
top_k=5000,
|
||||
)
|
||||
return detector
|
||||
except Exception as exc:
|
||||
logger.error("Failed to create YuNet detector: %s", exc)
|
||||
return None
|
||||
|
||||
|
||||
class CameraTracker(BaseTracker):
|
||||
"""Head tracker using webcam and OpenCV YuNet DNN."""
|
||||
|
||||
def __init__(self, camera_index: int = 0) -> None:
|
||||
self.camera_index = camera_index
|
||||
self._cap: cv2.VideoCapture | None = None
|
||||
self._detector: cv2.FaceDetectorYN | None = None
|
||||
self._running = False
|
||||
self._last_pose = PoseData()
|
||||
self._last_face: tuple[int, int, int, int] | None = None
|
||||
self._last_landmarks: list[tuple[int, int]] | None = None
|
||||
self._pose_history: list[PoseData] = []
|
||||
self._history_size = 5
|
||||
self._lost_frames = 0
|
||||
self._max_lost_frames = 5
|
||||
# Calibration: neutral pose at start
|
||||
self._calibrated = False
|
||||
self._neutral_yaw = 0.0
|
||||
self._neutral_pitch = 0.0
|
||||
self._neutral_roll = 0.0
|
||||
self._neutral_x = 0.0
|
||||
self._neutral_y = 0.0
|
||||
|
||||
def _get_or_create_detector(self) -> cv2.FaceDetectorYN | None:
|
||||
"""Get or create shared YuNet detector."""
|
||||
if self._detector is not None:
|
||||
return self._detector
|
||||
|
||||
model_path = _find_yunet_model()
|
||||
if not model_path:
|
||||
logger.error("YuNet model not found")
|
||||
return None
|
||||
|
||||
self._detector = _create_detector(model_path)
|
||||
return self._detector
|
||||
|
||||
def start(self) -> bool:
|
||||
"""Start camera and initialize YuNet detector."""
|
||||
global _shared_cap, _shared_cap_index
|
||||
|
||||
detector = self._get_or_create_detector()
|
||||
if detector is None:
|
||||
return False
|
||||
|
||||
if (
|
||||
_shared_cap is not None
|
||||
and _shared_cap_index == self.camera_index
|
||||
and _shared_cap.isOpened()
|
||||
):
|
||||
self._cap = _shared_cap
|
||||
self._running = True
|
||||
logger.info("Camera tracker reused existing capture")
|
||||
return True
|
||||
|
||||
if _shared_cap is not None:
|
||||
_shared_cap.release()
|
||||
_shared_cap = None
|
||||
|
||||
for attempt in range(5):
|
||||
_shared_cap = cv2.VideoCapture(self.camera_index)
|
||||
if _shared_cap.isOpened():
|
||||
break
|
||||
if _shared_cap:
|
||||
_shared_cap.release()
|
||||
_shared_cap = None
|
||||
if attempt < 4:
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
logger.error("Cannot open camera %s after retries", self.camera_index)
|
||||
return False
|
||||
|
||||
_shared_cap_index = self.camera_index
|
||||
self._cap = _shared_cap
|
||||
self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
||||
self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
||||
|
||||
self._running = True
|
||||
self._calibrated = False
|
||||
logger.info("Camera tracker started")
|
||||
return True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop tracking and release camera capture."""
|
||||
global _shared_cap, _shared_cap_index
|
||||
self._running = False
|
||||
self._detector = None
|
||||
self._cap = None
|
||||
self._calibrated = False
|
||||
if _shared_cap is not None:
|
||||
_shared_cap.release()
|
||||
_shared_cap = None
|
||||
_shared_cap_index = -1
|
||||
logger.info("Camera tracker stopped (capture released)")
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._running and self._cap is not None and self._cap.isOpened()
|
||||
|
||||
def get_pose(self) -> PoseData | None:
|
||||
"""Capture frame and compute head pose."""
|
||||
if not self.is_running() or self._cap is None:
|
||||
return None
|
||||
|
||||
ret, frame = self._cap.read()
|
||||
if not ret or frame is None:
|
||||
return None
|
||||
|
||||
pose = self._process_frame(frame)
|
||||
if pose:
|
||||
self._last_pose = pose
|
||||
return pose
|
||||
|
||||
def get_frame(self) -> tuple[np.ndarray | None, PoseData | None]:
|
||||
"""Get current camera frame with landmarks drawn and pose."""
|
||||
if not self.is_running() or self._cap is None:
|
||||
return None, None
|
||||
|
||||
ret, frame = self._cap.read()
|
||||
if not ret or frame is None:
|
||||
return None, None
|
||||
|
||||
pose = self._process_frame(frame)
|
||||
if pose:
|
||||
self._last_pose = pose
|
||||
|
||||
if self._last_landmarks:
|
||||
for x, y in self._last_landmarks:
|
||||
cv2.circle(frame, (x, y), 2, (0, 255, 0), -1)
|
||||
|
||||
return frame, pose
|
||||
|
||||
def _detect_face(self, frame: np.ndarray) -> np.ndarray | None:
|
||||
"""Detect face using YuNet, return face data array."""
|
||||
if self._detector is None:
|
||||
return None
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
self._detector.setInputSize((w, h))
|
||||
retval, faces = self._detector.detect(frame)
|
||||
|
||||
if not retval or faces is None or len(faces) == 0:
|
||||
return None
|
||||
|
||||
# Pick face closest to previous position, or highest score
|
||||
if self._last_face is not None:
|
||||
lx, ly, lw, lh = self._last_face
|
||||
lcx = lx + lw / 2
|
||||
lcy = ly + lh / 2
|
||||
best = min(
|
||||
faces,
|
||||
key=lambda f: (f[0] + f[2] / 2 - lcx) ** 2
|
||||
+ (f[1] + f[3] / 2 - lcy) ** 2,
|
||||
)
|
||||
else:
|
||||
best = max(faces, key=lambda f: f[14])
|
||||
|
||||
return best
|
||||
|
||||
def _estimate_pose(
|
||||
self, frame_w: int, frame_h: int, face: np.ndarray
|
||||
) -> PoseData | None:
|
||||
"""Estimate head pose from YuNet face landmarks.
|
||||
|
||||
Uses geometric relationships between facial landmarks
|
||||
rather than solvePnP for stability.
|
||||
"""
|
||||
# YuNet: [x, y, w, h, re_x, re_y, le_x, le_y, nt_x, nt_y,
|
||||
# rcm_x, rcm_y, lcm_x, lcm_y, score]
|
||||
re_x, re_y = face[4], face[5] # right eye
|
||||
le_x, le_y = face[6], face[7] # left eye
|
||||
nt_x = face[8] # nose tip x
|
||||
|
||||
# Face center
|
||||
face_cx = face[0] + face[2] / 2
|
||||
face_cy = face[1] + face[3] / 2
|
||||
|
||||
# Eye center
|
||||
eye_cx = (re_x + le_x) / 2
|
||||
|
||||
# Inter-ocular distance (distance between eyes)
|
||||
iod = math.sqrt((le_x - re_x) ** 2 + (le_y - re_y) ** 2)
|
||||
if iod < 1:
|
||||
return None
|
||||
|
||||
# Roll from eye line angle
|
||||
roll = math.degrees(math.atan2(le_y - re_y, le_x - re_x))
|
||||
|
||||
# Yaw from nose position relative to eye center
|
||||
# When looking right, nose is to the right of eye center
|
||||
nose_offset_x = nt_x - eye_cx
|
||||
yaw = math.degrees(math.asin(max(-1.0, min(1.0, nose_offset_x / (iod * 0.8)))))
|
||||
|
||||
# Pitch from vertical shift of face center relative to image center
|
||||
# When looking up, face center moves down on image (nose goes up)
|
||||
face_center_y = face_cy
|
||||
image_center_y = frame_h / 2
|
||||
pitch = (face_center_y - image_center_y) / (frame_h / 2) * 30.0
|
||||
|
||||
# X, Y translation from face center offset
|
||||
dx = face_cx - frame_w / 2
|
||||
dy = face_cy - frame_h / 2
|
||||
# Z from face size (inverse of inter-ocular distance)
|
||||
z = 500.0 / max(iod, 1.0)
|
||||
# Scale X/Y by Z
|
||||
fov_half = 30.0
|
||||
fx_len = frame_w / (2 * math.tan(math.radians(fov_half)))
|
||||
x = dx * z / fx_len
|
||||
y = dy * z / fx_len
|
||||
|
||||
return PoseData(x=x, y=y, z=z, yaw=yaw, pitch=pitch, roll=roll)
|
||||
|
||||
def _process_frame(self, frame: np.ndarray) -> PoseData | None:
|
||||
"""Process frame and return smoothed pose."""
|
||||
if self._detector is None:
|
||||
return None
|
||||
|
||||
face = self._detect_face(frame)
|
||||
if face is None:
|
||||
self._lost_frames += 1
|
||||
if self._lost_frames >= self._max_lost_frames:
|
||||
self._last_face = None
|
||||
self._last_landmarks = None
|
||||
return None
|
||||
|
||||
self._lost_frames = 0
|
||||
self._last_face = (int(face[0]), int(face[1]), int(face[2]), int(face[3]))
|
||||
self._last_landmarks = [
|
||||
(int(face[i]), int(face[i + 1])) for i in range(4, 14, 2)
|
||||
]
|
||||
|
||||
pose = self._estimate_pose(frame.shape[1], frame.shape[0], face)
|
||||
if pose is None:
|
||||
return None
|
||||
|
||||
# Auto-calibrate on first valid frame
|
||||
if not self._calibrated:
|
||||
self._neutral_yaw = pose.yaw
|
||||
self._neutral_pitch = pose.pitch
|
||||
self._neutral_roll = pose.roll
|
||||
self._neutral_x = pose.x
|
||||
self._neutral_y = pose.y
|
||||
self._calibrated = True
|
||||
logger.info(
|
||||
"Head tracking calibrated: yaw=%.1f, pitch=%.1f, roll=%.1f",
|
||||
self._neutral_yaw,
|
||||
self._neutral_pitch,
|
||||
self._neutral_roll,
|
||||
)
|
||||
|
||||
# Subtract neutral pose
|
||||
pose = PoseData(
|
||||
x=pose.x - self._neutral_x,
|
||||
y=pose.y - self._neutral_y,
|
||||
z=pose.z,
|
||||
yaw=pose.yaw - self._neutral_yaw,
|
||||
pitch=pose.pitch - self._neutral_pitch,
|
||||
roll=pose.roll - self._neutral_roll,
|
||||
)
|
||||
|
||||
self._pose_history.append(pose)
|
||||
if len(self._pose_history) > self._history_size:
|
||||
self._pose_history.pop(0)
|
||||
|
||||
return self._smooth_poses(self._pose_history)
|
||||
|
||||
@staticmethod
|
||||
def _smooth_poses(history: list[PoseData]) -> PoseData:
|
||||
"""Compute moving average of pose history."""
|
||||
n = len(history)
|
||||
if n == 0:
|
||||
return PoseData()
|
||||
if n == 1:
|
||||
return history[0]
|
||||
|
||||
return PoseData(
|
||||
x=sum(p.x for p in history) / n,
|
||||
y=sum(p.y for p in history) / n,
|
||||
z=sum(p.z for p in history) / n,
|
||||
yaw=sum(p.yaw for p in history) / n,
|
||||
pitch=sum(p.pitch for p in history) / n,
|
||||
roll=sum(p.roll for p in history) / n,
|
||||
)
|
||||
@@ -0,0 +1,190 @@
|
||||
"""Manager for head tracking lifecycle."""
|
||||
|
||||
import threading
|
||||
|
||||
import numpy as np
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.outputs.flightgear_output import FlightGearOutput
|
||||
from portprotonqt.head_tracking.outputs.joystick_output import JoystickOutput
|
||||
from portprotonqt.head_tracking.outputs.mouse_output import MouseOutput
|
||||
from portprotonqt.head_tracking.outputs.npclient_output import NpclientOutput
|
||||
from portprotonqt.head_tracking.outputs.shm_output import ShmOutput
|
||||
from portprotonqt.head_tracking.outputs.udp_output import UdpOutput
|
||||
from portprotonqt.head_tracking.tracker_base import BaseTracker, PoseData
|
||||
from portprotonqt.head_tracking.tracking_worker import TrackingWorker
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class TrackerManager:
|
||||
"""Manages tracker and output lifecycle."""
|
||||
|
||||
def __init__(self, prefix_path: str | None = None) -> None:
|
||||
self.tracker: BaseTracker | None = None
|
||||
self.output: BaseOutput | None = None
|
||||
self.worker: TrackingWorker | None = None
|
||||
self._lock = threading.Lock()
|
||||
self.calibration_offset: PoseData | None = None
|
||||
self.sensitivity: PoseData = PoseData(1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
|
||||
self.output_mode: str = "shm"
|
||||
self.mouse_sens_yaw: float = 10.0
|
||||
self.mouse_sens_pitch: float = 10.0
|
||||
self.mouse_deadzone: float = 0.05
|
||||
self.udp_host: str = "127.0.0.1"
|
||||
self.udp_port: int = 4242
|
||||
self.prefix_path = prefix_path
|
||||
|
||||
def _create_output(self) -> BaseOutput | None:
|
||||
"""Create output based on current mode."""
|
||||
if self.output_mode == "mouse":
|
||||
return MouseOutput(
|
||||
sens_yaw=self.mouse_sens_yaw,
|
||||
sens_pitch=self.mouse_sens_pitch,
|
||||
deadzone=self.mouse_deadzone,
|
||||
)
|
||||
if self.output_mode == "joystick":
|
||||
return JoystickOutput()
|
||||
if self.output_mode == "npclient":
|
||||
return NpclientOutput(prefix_path=self.prefix_path)
|
||||
if self.output_mode == "udp":
|
||||
return UdpOutput(host=self.udp_host, port=self.udp_port)
|
||||
if self.output_mode == "flightgear":
|
||||
return FlightGearOutput(host=self.udp_host, port=self.udp_port)
|
||||
return ShmOutput()
|
||||
|
||||
def start(self, tracker: BaseTracker) -> bool:
|
||||
"""Start tracking with given tracker."""
|
||||
with self._lock:
|
||||
if self.is_running():
|
||||
logger.warning("Tracker already running")
|
||||
return False
|
||||
|
||||
if not tracker.start():
|
||||
return False
|
||||
|
||||
output = self._create_output()
|
||||
if output is None or not output.open():
|
||||
tracker.stop()
|
||||
return False
|
||||
|
||||
self.tracker = tracker
|
||||
self.output = output
|
||||
self.worker = TrackingWorker(
|
||||
tracker,
|
||||
output,
|
||||
calibration_offset=self.calibration_offset,
|
||||
sensitivity=self.sensitivity,
|
||||
)
|
||||
self.worker.start()
|
||||
logger.info("Tracker manager started (mode=%s)", self.output_mode)
|
||||
return True
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop tracking and cleanup."""
|
||||
with self._lock:
|
||||
if self.tracker:
|
||||
self.tracker.stop()
|
||||
|
||||
if self.worker:
|
||||
self.worker.stop()
|
||||
self.worker = None
|
||||
|
||||
self.tracker = None
|
||||
if self.output:
|
||||
self.output.close()
|
||||
self.output = None
|
||||
|
||||
logger.info("Tracker manager stopped")
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""Check if tracking is active."""
|
||||
return self.worker is not None and self.worker.is_alive()
|
||||
|
||||
def get_preview_frame(self) -> tuple[np.ndarray | None, PoseData | None]:
|
||||
"""Get preview frame and calibrated pose for UI."""
|
||||
if self.tracker is None:
|
||||
return None, None
|
||||
|
||||
frame, pose = self.tracker.get_frame()
|
||||
|
||||
if pose:
|
||||
pose = self._apply_calibration_and_sensitivity(pose)
|
||||
|
||||
return frame, pose
|
||||
|
||||
def _apply_calibration_and_sensitivity(self, pose: PoseData) -> PoseData:
|
||||
"""Apply calibration offset and sensitivity to pose."""
|
||||
if self.calibration_offset:
|
||||
pose = pose.apply_offset(self.calibration_offset)
|
||||
return pose.apply_sensitivity(self.sensitivity)
|
||||
|
||||
def calibrate(self) -> None:
|
||||
"""Set current pose as zero offset."""
|
||||
if self.tracker is None:
|
||||
return
|
||||
raw = self.tracker.get_pose()
|
||||
if raw:
|
||||
self.calibration_offset = raw
|
||||
if self.worker is not None:
|
||||
self.worker.calibration_offset = raw
|
||||
logger.info("Head tracking calibrated: %s", raw)
|
||||
|
||||
def set_sensitivity(self, sens: PoseData) -> None:
|
||||
"""Set per-axis sensitivity multipliers."""
|
||||
self.sensitivity = sens
|
||||
if self.worker is not None:
|
||||
self.worker.sensitivity = sens
|
||||
|
||||
def set_output_mode(
|
||||
self,
|
||||
mode: str,
|
||||
mouse_sens_yaw: float = 10.0,
|
||||
mouse_sens_pitch: float = 10.0,
|
||||
mouse_deadzone: float = 0.05,
|
||||
udp_host: str = "127.0.0.1",
|
||||
udp_port: int = 4242,
|
||||
) -> None:
|
||||
"""Set output mode and parameters; restart output if running."""
|
||||
with self._lock:
|
||||
changed = (
|
||||
self.output_mode != mode
|
||||
or self.mouse_sens_yaw != mouse_sens_yaw
|
||||
or self.mouse_sens_pitch != mouse_sens_pitch
|
||||
or self.mouse_deadzone != mouse_deadzone
|
||||
or self.udp_host != udp_host
|
||||
or self.udp_port != udp_port
|
||||
)
|
||||
self.output_mode = mode
|
||||
self.mouse_sens_yaw = mouse_sens_yaw
|
||||
self.mouse_sens_pitch = mouse_sens_pitch
|
||||
self.mouse_deadzone = mouse_deadzone
|
||||
self.udp_host = udp_host
|
||||
self.udp_port = udp_port
|
||||
|
||||
if changed and self.is_running() and self.tracker is not None:
|
||||
# Restart output with new settings
|
||||
if self.worker:
|
||||
self.worker.stop()
|
||||
self.worker = None
|
||||
if self.output:
|
||||
self.output.close()
|
||||
self.output = None
|
||||
|
||||
output = self._create_output()
|
||||
if output is None or not output.open():
|
||||
logger.error("Failed to restart output in mode %s", mode)
|
||||
self.tracker.stop()
|
||||
self.tracker = None
|
||||
return
|
||||
|
||||
self.output = output
|
||||
self.worker = TrackingWorker(
|
||||
self.tracker,
|
||||
output,
|
||||
calibration_offset=self.calibration_offset,
|
||||
sensitivity=self.sensitivity,
|
||||
)
|
||||
self.worker.start()
|
||||
logger.info("Tracker output restarted (mode=%s)", mode)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Background worker for head tracking outputs."""
|
||||
|
||||
import threading
|
||||
|
||||
from portprotonqt.head_tracking.outputs.base_output import BaseOutput
|
||||
from portprotonqt.head_tracking.tracker_base import BaseTracker, PoseData
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class TrackingWorker(threading.Thread):
|
||||
"""Background worker that reads tracker and writes to active output."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tracker: BaseTracker,
|
||||
output: BaseOutput,
|
||||
update_interval: float = 0.016, # ~60 FPS
|
||||
calibration_offset: PoseData | None = None,
|
||||
sensitivity: PoseData | None = None,
|
||||
) -> None:
|
||||
super().__init__(daemon=True)
|
||||
self.tracker = tracker
|
||||
self.output = output
|
||||
self.update_interval = update_interval
|
||||
self.calibration_offset = calibration_offset
|
||||
self.sensitivity = sensitivity or PoseData(1.0, 1.0, 1.0, 1.0, 1.0, 1.0)
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Request worker to stop."""
|
||||
self._stop_event.set()
|
||||
self.join(timeout=2.0)
|
||||
|
||||
def _apply_calibration_and_sensitivity(self, pose: PoseData) -> PoseData:
|
||||
"""Apply calibration offset and sensitivity."""
|
||||
if self.calibration_offset:
|
||||
pose = pose.apply_offset(self.calibration_offset)
|
||||
return pose.apply_sensitivity(self.sensitivity)
|
||||
|
||||
def run(self) -> None:
|
||||
"""Main worker loop."""
|
||||
logger.info("Tracking worker started")
|
||||
while not self._stop_event.is_set():
|
||||
pose = self.tracker.get_pose()
|
||||
if pose:
|
||||
pose = self._apply_calibration_and_sensitivity(pose)
|
||||
self.output.write(pose)
|
||||
self._stop_event.wait(self.update_interval)
|
||||
logger.info("Tracking worker stopped")
|
||||
@@ -59,6 +59,8 @@ class MainWindowProtocol(Protocol):
|
||||
current_add_game_dialog: AddGameDialog | None
|
||||
game_library_manager: Any # GameLibraryManager - using Any to avoid circular import
|
||||
auto_size_slider: QSlider | None
|
||||
def toggleHeadtracking(self) -> None:
|
||||
...
|
||||
|
||||
PAD_BUTTON_SOUTH = 1000
|
||||
PAD_BUTTON_EAST = 1001
|
||||
@@ -3705,6 +3707,9 @@ class InputManager(QObject):
|
||||
if key == pygame.K_F11 and not self._is_gamescope_session:
|
||||
self.toggle_fullscreen.emit(not self._is_fullscreen)
|
||||
return True
|
||||
if key == pygame.K_SCROLLLOCK:
|
||||
self._parent.toggleHeadtracking()
|
||||
return True
|
||||
return False
|
||||
|
||||
def _handle_pygame_file_explorer_key(self, key: int) -> bool:
|
||||
|
||||
+171
-4
@@ -9,7 +9,11 @@ from queue import Empty, Queue
|
||||
from threading import Thread
|
||||
from portprotonqt.logger import get_logger
|
||||
from portprotonqt.icon_extractor import generate_thumbnail
|
||||
from portprotonqt.dialogs import FileExplorer, ExeSettingsDialog
|
||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, WinetricksDialog, ExeSettingsDialog
|
||||
from portprotonqt.head_tracking.tracker_base import PoseData
|
||||
from portprotonqt.head_tracking.tracker_camera import CameraTracker
|
||||
from portprotonqt.head_tracking.tracker_manager import TrackerManager
|
||||
from portprotonqt.global_hotkey import GlobalHotkeyEvdev
|
||||
from portprotonqt.game_card import GameCard
|
||||
from portprotonqt.animations import DetailPageAnimations
|
||||
from portprotonqt.custom_widgets import ClickableLabel, AutoSizeButton, NavLabel
|
||||
@@ -60,9 +64,9 @@ from portprotonqt.tabs.wine_tab import MainWindowWineTabMixin
|
||||
from portprotonqt.tabs.workers import MainWindowWorkersMixin
|
||||
|
||||
from PySide6.QtWidgets import (QLineEdit, QMainWindow, QWidget, QVBoxLayout, QLabel, QHBoxLayout, QStackedWidget, QComboBox,
|
||||
QMessageBox, QApplication, QPushButton, QCheckBox)
|
||||
from PySide6.QtCore import Qt, QEvent, QUrl, Signal, QTimer, Slot, QProcess, QFileSystemWatcher, QObject
|
||||
from PySide6.QtGui import QColor, QDesktopServices, QHideEvent, QShowEvent, QGuiApplication
|
||||
QDialog, QFormLayout, QMessageBox, QApplication, QPushButton, QProgressBar, QCheckBox, QSizePolicy, QGridLayout, QScrollArea, QScroller, QSlider, QFrame)
|
||||
from PySide6.QtCore import Qt, QAbstractAnimation, QEvent, QUrl, Signal, QTimer, Slot, QProcess, QProcessEnvironment, QFileSystemWatcher, QStandardPaths, QObject
|
||||
from PySide6.QtGui import QIcon, QColor, QDesktopServices, QHideEvent, QShowEvent, QKeySequence, QShortcut, QGuiApplication
|
||||
from typing import cast
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@@ -248,6 +252,27 @@ class MainWindow(
|
||||
self.portproton_location = get_portproton_location()
|
||||
self.start_sh = get_portproton_start_command()
|
||||
self.launch_exe = launch_exe # Store launch_exe path
|
||||
self.headtracking_manager = None
|
||||
self._headtracking_settings: dict[str, str] | None = None
|
||||
self._headtracking_exe_path: str | None = None
|
||||
|
||||
# Hotkey for toggling head tracking (Insert or Scroll Lock)
|
||||
self._headtracking_shortcut = QShortcut(QKeySequence("Insert"), self)
|
||||
self._headtracking_shortcut.activated.connect(self._toggle_headtracking)
|
||||
|
||||
# Global hotkey via evdev without grab (works in games on Wayland/X11)
|
||||
self._global_hotkey = GlobalHotkeyEvdev()
|
||||
registered = False
|
||||
for key in ("insert", "scrolllock"):
|
||||
if self._global_hotkey.register("toggle_headtracking", "Toggle Head Tracking",
|
||||
self.toggleHeadtracking,
|
||||
hotkey=key,
|
||||
):
|
||||
registered = True
|
||||
if registered:
|
||||
logger.info("Global hotkey registered via evdev (Insert, Scroll Lock)")
|
||||
else:
|
||||
logger.warning("Failed to register global hotkey via evdev")
|
||||
|
||||
self.game_library_manager = GameLibraryManager(self, self.theme, None)
|
||||
|
||||
@@ -1893,6 +1918,11 @@ class MainWindow(
|
||||
self.game_library_manager.update_game_grid(focus_first_card=False)
|
||||
self._refresh_current_detail_time()
|
||||
|
||||
# Stop head tracking
|
||||
self._stop_headtracking()
|
||||
self._headtracking_settings = None
|
||||
self._headtracking_exe_path = None
|
||||
|
||||
def _has_running_game_process(self) -> bool:
|
||||
return any(proc.poll() is None for proc in self.game_processes)
|
||||
|
||||
@@ -1953,8 +1983,132 @@ class MainWindow(
|
||||
self.checkProcessTimer.deleteLater()
|
||||
self.checkProcessTimer = None
|
||||
self.resetPlayButton()
|
||||
# Stop head tracking on manual stop
|
||||
self._stop_headtracking()
|
||||
self._headtracking_settings = None
|
||||
self._headtracking_exe_path = None
|
||||
|
||||
return True
|
||||
|
||||
def _start_headtracking_if_enabled(self, exe_path: str) -> None:
|
||||
"""Read head tracking settings for this executable."""
|
||||
if not self.portproton_location or not self.start_sh:
|
||||
logger.info("Cannot read HT settings: portproton_location=%s, start_sh=%s",
|
||||
bool(self.portproton_location), bool(self.start_sh))
|
||||
return
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
self.start_sh + ["cli", "--show-ppdb", exe_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
logger.info("PPDB query for %s: rc=%s, stdout=%s", exe_path, result.returncode, result.stdout[:200])
|
||||
if result.returncode != 0:
|
||||
return
|
||||
|
||||
settings = {}
|
||||
for line in result.stdout.strip().splitlines():
|
||||
if "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
settings[key.strip()] = value.strip().strip('"\'')
|
||||
|
||||
self._headtracking_settings = settings
|
||||
self._headtracking_exe_path = exe_path
|
||||
logger.info("HT settings saved for %s: PW_HEADTRACKING=%s", exe_path, settings.get("PW_HEADTRACKING"))
|
||||
except Exception as exc:
|
||||
logger.error("Error reading head tracking settings: %s", exc)
|
||||
|
||||
def _gamepad_blocks_headtracking(self) -> bool:
|
||||
"""Check if a gamepad is connected and should block head tracking."""
|
||||
return (
|
||||
getattr(self, "input_manager", None) is not None
|
||||
and getattr(self.input_manager, "gamepad", None) is not None
|
||||
)
|
||||
|
||||
def _start_headtracking_with_settings(
|
||||
self, exe_path: str, settings: dict[str, str]
|
||||
) -> None:
|
||||
"""Start head tracking with parsed settings."""
|
||||
if self._gamepad_blocks_headtracking():
|
||||
logger.info("Head tracking blocked: gamepad is connected")
|
||||
return
|
||||
self._stop_headtracking()
|
||||
mode = settings.get("PW_HEADTRACKING_OUTPUT", "shm")
|
||||
mouse_sens_x = float(settings.get("PW_HEADTRACKING_MOUSE_SENS_X", "10"))
|
||||
mouse_sens_y = float(settings.get("PW_HEADTRACKING_MOUSE_SENS_Y", "10"))
|
||||
mouse_deadzone = float(settings.get("PW_HEADTRACKING_MOUSE_DEADZONE", "0.05"))
|
||||
|
||||
prefix_path = None
|
||||
if self.portproton_location:
|
||||
prefix_name = get_prefix_name(exe_path)
|
||||
if prefix_name:
|
||||
candidate = os.path.join(
|
||||
self.portproton_location, "data", "prefixes", prefix_name
|
||||
)
|
||||
if os.path.isdir(candidate):
|
||||
prefix_path = candidate
|
||||
|
||||
self.headtracking_manager = TrackerManager(prefix_path=prefix_path)
|
||||
self.headtracking_manager.set_output_mode(
|
||||
mode,
|
||||
mouse_sens_yaw=mouse_sens_x,
|
||||
mouse_sens_pitch=mouse_sens_y,
|
||||
mouse_deadzone=mouse_deadzone,
|
||||
)
|
||||
|
||||
camera_idx = int(settings.get("PW_HEADTRACKING_CAMERA", "0"))
|
||||
sens = PoseData(
|
||||
x=float(settings.get("PW_HEADTRACKING_SENS_X", "1.0")),
|
||||
y=float(settings.get("PW_HEADTRACKING_SENS_Y", "1.0")),
|
||||
z=float(settings.get("PW_HEADTRACKING_SENS_Z", "1.0")),
|
||||
yaw=float(settings.get("PW_HEADTRACKING_SENS_YAW", "1.0")),
|
||||
pitch=float(settings.get("PW_HEADTRACKING_SENS_PITCH", "1.0")),
|
||||
roll=float(settings.get("PW_HEADTRACKING_SENS_ROLL", "1.0")),
|
||||
)
|
||||
self.headtracking_manager.set_sensitivity(sens)
|
||||
|
||||
tracker = CameraTracker(camera_index=camera_idx)
|
||||
if self.headtracking_manager.start(tracker):
|
||||
logger.info("Head tracking started for %s (mode=%s)", exe_path, mode)
|
||||
else:
|
||||
logger.warning("Failed to start head tracking for %s", exe_path)
|
||||
self.headtracking_manager = None
|
||||
|
||||
def _stop_headtracking(self) -> None:
|
||||
"""Stop head tracking if running."""
|
||||
if self.headtracking_manager:
|
||||
self.headtracking_manager.stop()
|
||||
self.headtracking_manager = None
|
||||
logger.info("Head tracking stopped")
|
||||
|
||||
def toggleHeadtracking(self) -> None:
|
||||
"""Toggle head tracking on/off via hotkey (public for InputManager)."""
|
||||
self._toggle_headtracking()
|
||||
|
||||
def _toggle_headtracking(self) -> None:
|
||||
"""Toggle head tracking on/off via Scroll Lock hotkey."""
|
||||
logger.info("Toggle headtracking called, manager=%s, settings=%s, exe=%s",
|
||||
self.headtracking_manager, bool(self._headtracking_settings), self._headtracking_exe_path)
|
||||
if self.headtracking_manager and self.headtracking_manager.is_running():
|
||||
self._stop_headtracking()
|
||||
logger.info("Head tracking toggled off")
|
||||
return
|
||||
|
||||
if not self._headtracking_settings or not self._headtracking_exe_path:
|
||||
logger.info("Cannot toggle: no settings or exe path")
|
||||
return
|
||||
|
||||
if self._headtracking_settings.get("PW_HEADTRACKING") != "1":
|
||||
logger.info("Cannot toggle: head tracking is disabled in settings")
|
||||
return
|
||||
|
||||
self._start_headtracking_with_settings(
|
||||
self._headtracking_exe_path, self._headtracking_settings
|
||||
)
|
||||
logger.info("Head tracking toggled on")
|
||||
|
||||
def _check_missing_prefix_by_name_before_launch(self, prefix_name: str, env_vars: dict[str, str]) -> None:
|
||||
"""Check prefix presence and optionally disable default recommended libs."""
|
||||
if not prefix_name or not self.portproton_location:
|
||||
@@ -2116,6 +2270,12 @@ class MainWindow(
|
||||
self.checkProcessTimer = QTimer(self)
|
||||
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
||||
self.checkProcessTimer.start(500)
|
||||
|
||||
# Start head tracking if enabled for this exe
|
||||
self._headtracking_exe_path = file_to_check
|
||||
self._start_headtracking_if_enabled(file_to_check)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to launch game {exe_name}: {e}")
|
||||
QMessageBox.warning(self, _("Error"), _("Failed to launch game: {0}").format(str(e)))
|
||||
@@ -2162,6 +2322,13 @@ class MainWindow(
|
||||
|
||||
self.disc_image_manager.cleanup_iso_rw_paths()
|
||||
self._stopBackgroundWorkers()
|
||||
self._stop_headtracking()
|
||||
self._headtracking_settings = None
|
||||
self._headtracking_exe_path = None
|
||||
|
||||
# Stop global hotkey
|
||||
if hasattr(self, "_global_hotkey") and self._global_hotkey:
|
||||
self._global_hotkey.stop()
|
||||
|
||||
# Universal stop and delete timers
|
||||
timers = [
|
||||
|
||||
@@ -10,6 +10,7 @@ install_data(
|
||||
'downloader.py',
|
||||
'game_card.py',
|
||||
'game_library_manager.py',
|
||||
'global_hotkey.py',
|
||||
'howlongtobeat_api.py',
|
||||
'image_utils.py',
|
||||
'icon_extractor.py',
|
||||
@@ -71,6 +72,7 @@ install_data(
|
||||
'dialogs/settings_mangohud.py',
|
||||
'dialogs/settings_gamescope.py',
|
||||
'dialogs/settings_vkbasalt.py',
|
||||
'dialogs/settings_headtracking.py',
|
||||
'dialogs/proton_manager.py',
|
||||
'dialogs/prefix_backup.py',
|
||||
'dialogs/wine_loader.py',
|
||||
@@ -165,6 +167,25 @@ install_data(
|
||||
install_dir: pythondir / meson.project_name() / 'config',
|
||||
)
|
||||
|
||||
# Install head_tracking package
|
||||
install_data(
|
||||
['head_tracking/__init__.py',
|
||||
'head_tracking/tracker_base.py',
|
||||
'head_tracking/tracker_camera.py',
|
||||
'head_tracking/tracker_manager.py',
|
||||
'head_tracking/tracking_worker.py',
|
||||
'head_tracking/outputs/__init__.py',
|
||||
'head_tracking/outputs/base_output.py',
|
||||
'head_tracking/outputs/shm_output.py',
|
||||
'head_tracking/outputs/mouse_output.py',
|
||||
'head_tracking/outputs/joystick_output.py',
|
||||
'head_tracking/outputs/npclient_output.py',
|
||||
'head_tracking/outputs/udp_output.py',
|
||||
'head_tracking/outputs/flightgear_output.py',
|
||||
],
|
||||
install_dir: pythondir / meson.project_name() / 'head_tracking',
|
||||
)
|
||||
|
||||
# Install themes
|
||||
install_subdir(
|
||||
'themes',
|
||||
|
||||
@@ -168,6 +168,9 @@ class _BootstrapInputHost(MainWindowControlHintsMixin, QWidget):
|
||||
def handleSystemGamepadAction(self, action: str) -> bool:
|
||||
return False
|
||||
|
||||
def toggleHeadtracking(self) -> None:
|
||||
return
|
||||
|
||||
def _show_portdata_warning(parent: QWidget, theme: Any, text: str, allow_choose: bool = False) -> bool:
|
||||
message_box = QMessageBox(parent)
|
||||
message_box.setIcon(QMessageBox.Icon.Warning)
|
||||
|
||||
@@ -284,6 +284,14 @@ CONTENT_STYLE = f"""
|
||||
padding-bottom: 15px;
|
||||
}}
|
||||
"""
|
||||
WARNING_LABEL_STYLE = f"""
|
||||
QLabel {{
|
||||
font-family: '{font_family}';
|
||||
font-size: {font_size_a};
|
||||
color: {color_warning};
|
||||
background-color: none;
|
||||
}}
|
||||
"""
|
||||
PREVIEW_WIDGET_STYLE = f"""
|
||||
QWidget {{
|
||||
margin-top: 3px;
|
||||
|
||||
@@ -75,6 +75,7 @@ color_accent_blue = "#007AFF"
|
||||
color_preloader = "#007AFF"
|
||||
color_gamepad_supported = "#00ff00"
|
||||
color_white = "#ffffff"
|
||||
color_warning = "#FFD700"
|
||||
missing_exe_cover_opacity = 0.45
|
||||
|
||||
# === Widget State Colors ===
|
||||
|
||||
@@ -26,6 +26,8 @@ dependencies = [
|
||||
"babel>=2.18.0",
|
||||
"evdev>=1.9.3",
|
||||
"libarchive-c>=5.3",
|
||||
"numpy>=2.2.0",
|
||||
"opencv-python>=4.11.0",
|
||||
"orjson>=3.11.8",
|
||||
"pillow>=12.2.0",
|
||||
"psutil>=7.2.2",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version == '3.11.*'",
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
@@ -13,11 +18,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.5.20"
|
||||
version = "2026.6.17"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -145,62 +150,62 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "dbus-fast"
|
||||
version = "5.0.4"
|
||||
version = "5.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/c7/490f9ca9da84d1d47e021585cfc6888519f5c12ae48db90cdb4ba631be93/dbus_fast-5.0.4.tar.gz", hash = "sha256:ef372607f967870780c2063e5d39037fdd27e253680aef76687a8dc6cd9413f3", size = 80559, upload-time = "2026-05-23T18:31:31.027Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/db/b621610e50b1bc46ff63534d75239553c1bf33256de6096b58214fd9808a/dbus_fast-5.0.22.tar.gz", hash = "sha256:34dc67d7d21a12399828dd13e63b352750580beea54ea7c729e708f2d2905fef", size = 83224, upload-time = "2026-06-05T18:47:59.171Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/e7/35d25d8652ded1787c198d87a3771a24c71ca04d544365e77e0babd019ba/dbus_fast-5.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7339765555cc40586fa8ee25b5a9b51c6f397c420fd9d42833ee8aec42c86bc", size = 694169, upload-time = "2026-05-23T18:38:34.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/75/dfef781f2de8c5f97c4360cba5d8abbccf617628d2feae031642e003946d/dbus_fast-5.0.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8cffe3f31d9e0108c4f0cf59e104037630fddbcf410954d821259e0cd71acf8", size = 842006, upload-time = "2026-05-23T18:38:37.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f8/39d2f6a72cd572ccf857ce7567db881113785655a5c25fd95cdf50d32121/dbus_fast-5.0.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f01e98f38215e1abbb320e0f6e0875c2ed3c17891eef3176fed2c0de8ffb7954", size = 885667, upload-time = "2026-05-23T18:38:38.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/20/73f8c0b2b17fab681680065a3a3853f992d26dbe00ab966241d114989aed/dbus_fast-5.0.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe8dd98f504af78f92ea4377839d1ca22dca9b1b7242e86a7655e866173b5df8", size = 884493, upload-time = "2026-05-23T18:38:41.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/75/d46e40d7308599a3c1df7a5b60f6e557f91cd2fbe56efc0ab2acf5a63236/dbus_fast-5.0.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f819880a12e31f489fa81beedea14c87dd14fcb92817e9b88d2527a8989ac9f", size = 848095, upload-time = "2026-05-23T18:38:43.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/b5/fa9f4399c78c237dbfdbe3e71eaccd1049e027fba1da312d3612533a96c2/dbus_fast-5.0.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bf8ff7ceb5cac93c0c6869f8dc2ce5a84200337503d66bf83a3b88a9346ea76e", size = 882021, upload-time = "2026-05-23T18:38:45.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/9d/321c26379acfbfb605a68550a404c829b3498f01563e86091e2408ab1c68/dbus_fast-5.0.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe088a16d33054607021400043f3daeff566fc0cdd45e789d331d24e99f13e93", size = 891620, upload-time = "2026-05-23T18:38:47.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/17/9297c320ab6363cbb3c0f349565f78e7e1adc0e6fb356bc37dba16bcfd91/dbus_fast-5.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20c2d83950f2a3b4536f51da0ad51dfa063ffc92dec084f80e9c7661582edeb9", size = 692498, upload-time = "2026-05-23T18:38:49.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/b6/c63d13b45bff8619e3479be45429459728870d697fe037367ef78bb208fd/dbus_fast-5.0.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca56a03b2d46263c1a0b6483677dbf1fa84f6516db9e1c032b4d57ad757cfc2", size = 842099, upload-time = "2026-05-23T18:38:51.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/85/5f9162efc2c1e6f9a9ed6732d4e4778a62f851482fb847afc82726d97f6a/dbus_fast-5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:864b659724fb011356d4a5439551e17e03fa8420e5e14b1ae7cf5baf541c61e4", size = 885967, upload-time = "2026-05-23T18:38:53.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/c6/704494d8bf28e4b5126a43c21d600af13b3e075c1c53e122e4f95a832174/dbus_fast-5.0.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d00901d51ba4e5f2eb421f83fb6e4faf9466788fc7d7c4cfaf464bd97737bc52", size = 885377, upload-time = "2026-05-23T18:38:55.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/82/8dd56dad8a32050a74261ead6bf8536823f2bf35a95cd5e2d50b4e8e585e/dbus_fast-5.0.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4d4a0f108555f94ce76c2d9be58a1a0d72f3881b83ce919e418ece575e24e88", size = 848562, upload-time = "2026-05-23T18:38:57.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/aa/b12b378005538f4355a8a376783fa7477a60adb111947a565edf12101675/dbus_fast-5.0.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0a56f1b933d6f0333653b7968cb5b38ff27e42f50c60a35801f6d27a94cc1fb9", size = 882106, upload-time = "2026-05-23T18:38:59.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/12/61c28663229b2fa3a8833d74d086c8eb7bad57efa03d9b719a4a72b6f675/dbus_fast-5.0.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6e28e4d08361697afe0be0815d2e649bdf5bc96bf012db61b3e5c6aa991bd7c3", size = 892440, upload-time = "2026-05-23T18:39:02.345Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d5/8a9dae63f6e68eafed65c2fc23aa1172bd840b93cb564f52035fe5bde409/dbus_fast-5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06fdd44811c938bf8d10bb8a5d1d09831e337eeff325b17ad7ef45640c2c88c2", size = 689936, upload-time = "2026-05-23T18:39:04.404Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/2a/67d7a011dfe5054bca33ff7faee6d60daae343e5d07e3b6f4182e5cbcecf/dbus_fast-5.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51094e6a80a7bfa6e67c59826a5604c7824e166958e6a029d0020f98287c00e", size = 803359, upload-time = "2026-05-23T18:39:06.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/15/3ed2ab01ac5fd025378e6528ca4b3067333fcaa56f2f7338111fc879c702/dbus_fast-5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a3d8f0aa43d34f19588ccdfd12e332258ab6c234b419166fa938bbae8afd52", size = 855303, upload-time = "2026-05-23T18:39:08.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/ca/88b86f5f065311eee071f30559538acd39b5dfb3b1298c4d7e599accbe8d/dbus_fast-5.0.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ff5f30056c3a68944729b88686551b56b79ec50fe27657e4e172afef4856458", size = 836412, upload-time = "2026-05-23T18:39:09.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/74/13de0346943bed77e322479a6823293712d3525c953bb130476069a4f6da/dbus_fast-5.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1582355810e2224736a008e277bab8780bd3e6a7bbf865dd9d78fe6cd4aab961", size = 810524, upload-time = "2026-05-23T18:39:12.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/58/515cafb4ecae652ac8d289f9e7e73fff26e3f4052751f014ccff0c761742/dbus_fast-5.0.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:176ea1a9c736f65262d923f9f8303824f1d1029d80bbfbdefe37a80e8a50b242", size = 833967, upload-time = "2026-05-23T18:39:13.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/13/5f369f6f388a19b662785c8e156795dd8f4df08cb49a3e2220464b69d7ee/dbus_fast-5.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:17ff032d12dc4f3dbc3b29cd3756ce9094fd4cfc8f8984efa214072f680f70b1", size = 863709, upload-time = "2026-05-23T18:39:15.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/be/0e88b7c1263583fda6213ef3370e259d19735b2f7f6e8746b77b08fb0080/dbus_fast-5.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:00fe9b762c9974304e50f73abfd75210ae769207b78d8f3a97783d7281108200", size = 685726, upload-time = "2026-05-23T18:39:18.176Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/e5/609bbd7e8e59753236a06838b5d561498695c457f08883348a7b034399c0/dbus_fast-5.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527edcecb06a6a1676c8e7eb0c167a234701715913c66431377d38da7acaa5b3", size = 800170, upload-time = "2026-05-23T18:39:19.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/a1/9542cd0fbf86be24aa4c0865667cf8bf742df90c93b5063c3364d10f8dc3/dbus_fast-5.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:556ffba0fd56e9e872ed031dab579dab99c537c702e7c3d28f460c39e686ad8a", size = 850999, upload-time = "2026-05-23T18:39:21.808Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/8e/aaf3ff0e66df530807af0381e7a9ec92a8ebd9783d54f00c1cbfaab35dc5/dbus_fast-5.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d7bb1465c2d06ddb6c38f9c7344c5a70beb09adfee8a00344322c7b2a90a5f4", size = 830440, upload-time = "2026-05-23T18:39:23.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/ca/1cbf6e7928134fa642857222c4a684f4ffd5cc0d16fca51416a659cdc9be/dbus_fast-5.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ca2a8e94610705fae090aa1c318b810da9f9df47b990da9b6e06a8005a52fb91", size = 807074, upload-time = "2026-05-23T18:39:25.667Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/f2/a5f8e20fdef07a3afdf6e50fb98df5dc266c6ff43346dd66d71cde300dd8/dbus_fast-5.0.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f4e47a01c1e196d0aac0654850911eade720e9e571f2e68ee363dfeb69699e4e", size = 830159, upload-time = "2026-05-23T18:39:27.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d7/a2307c0b32c297728d325c05b9978caf7e9a1787d259d5172cfb90946b73/dbus_fast-5.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c3587f50a3089e5cc1a09b9a72cad964b88565f3d6dcfab3681981bca81a3fac", size = 859531, upload-time = "2026-05-23T18:39:29.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/33/1dabccf932cee9408f189306bd6efdfab8cc063fddebcb218fd36d991ce1/dbus_fast-5.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:aa519fd8536ea8d100347f15af8a9d1a2d209fa5d353dadb64dfa13ecf707b3c", size = 692866, upload-time = "2026-05-23T18:39:31.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/46/a56b19ef369e55fe723c8b33fcdd18b730d77442585bd325972d2c07780f/dbus_fast-5.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6139737eda772a9a5de13368862a15e486d61570be7d3a5e6bd64c7a23131d36", size = 814255, upload-time = "2026-05-23T18:39:33.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/f2/e0397cdfa0aefb5b4c44658bd6ba8ce532b997e81bff3d9714bbd36ee858/dbus_fast-5.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ce788791929923dbcb3eee889c61b8898eed41c77e334ac73bacd24d6e5c68a", size = 856800, upload-time = "2026-05-23T18:39:35.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/49/d11afde6602cefb2fdb43dd15a1f6b6f36a0e86cd046c99b4bafb071eb30/dbus_fast-5.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8e36da263ce03faf48c5f40b9cc33949a8bbb67db1ef09b7cdb89a8b807aaaf7", size = 836628, upload-time = "2026-05-23T18:39:37.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/d8/0d711a54e4523cad9a4b08753c7c49a32580fe90a2a0949109fa8cdccc1b/dbus_fast-5.0.4-cp314-cp314-manylinux_2_41_x86_64.whl", hash = "sha256:2eeb8dc52aa377074133fd8cd15733e8de27c4fef62f9c66be90fe0f4822aaa3", size = 854900, upload-time = "2026-05-23T18:31:27.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/38/59d700c6540136740b102f66c94147dc707e917d8136e32fc7fcef649032/dbus_fast-5.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d3d3bddcec3df4e213dce8de6e456e8bf4053a5e6dc9544fa13d2803c7ade3e8", size = 820912, upload-time = "2026-05-23T18:39:38.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/58/961d5d6d7c71347bfc6d6e6fdf4501b8773eab46bad02dc0f57d1005ab66/dbus_fast-5.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e2c53a20bc6756d2d70b5a189b2adc76bb5d9856a1abd9564f160cd03f5d63bb", size = 837066, upload-time = "2026-05-23T18:39:40.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/e6/49122a2b58ceca179627b2cae1c6f536336303f00cff2d6ad37c08ce0094/dbus_fast-5.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2b0f0d98fcabc838589eb05b29322d20378bd3c98999ace909ed63bed28a92e8", size = 864390, upload-time = "2026-05-23T18:39:43.091Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/9b/55aed4a5e2905c3767dfa92b052272fa3459e2a7c46b1a97ca968163c2e9/dbus_fast-5.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2142c9be443711c8e185de57ad19781f6ff231fa2f6ebbe9312734efd7a72762", size = 1356783, upload-time = "2026-05-23T18:39:45.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/8d/df4488525e22a8e843e12656f0718d760528f56709401af979c27a6c151c/dbus_fast-5.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a80a3114dc11fc463ef5b5562de6fa7fb5615acd488efa31a157dd83d510a247", size = 1545147, upload-time = "2026-05-23T18:39:46.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/75/c18803c88e85164ce60c28f59a7fdb7819901b5e5971bbc505bcd438d02d/dbus_fast-5.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340d36759a18d8b67deeb6bbd60647e0294f92950778a02e753832840b93f899", size = 1620877, upload-time = "2026-05-23T18:39:48.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/5c/a8154e4ba68c822c96233302551c7280198fd56ad6709baacc804a51f7e8/dbus_fast-5.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dbbd41b9a641fc47e92d923331750a28c3a5001947bc1271d2392247c8fba327", size = 829639, upload-time = "2026-05-23T18:39:50.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/6b/13ef6c4fddcd7beeb15e8425e8d90dd5f06d973e959397153d53e12ec385/dbus_fast-5.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f00ef02cfdc2423b50756e32e90af8f1c3d63ad41f3af8a3d0f7cea9315e0005", size = 1560002, upload-time = "2026-05-23T18:39:52.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/54/010f9a9673ca7bf67a393486278a617b9930fead5c04563bf710bb6dc21d/dbus_fast-5.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2ba075dc3c12af888ecae09226dbc2225c75b0e105666e1f0630f95d2ca6ab84", size = 831734, upload-time = "2026-05-23T18:39:54.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/ad/14b5ed792988db43b257b9c262e39b43d8b1fdb361b96da005a5cd3e5b48/dbus_fast-5.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:210ba9eef1f63d256111207f98c5e2264b946ff8db1bf3c82ab064c7d1fb2dae", size = 1639471, upload-time = "2026-05-23T18:39:57.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/5c/fa3b060e06e0322ddd5c21a4f63e6c3e59a427a92db1553b930816d40e97/dbus_fast-5.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:027b358d55164f722d7432d6734506751c89b5cee8d516e7282eff3eaa763f05", size = 694683, upload-time = "2026-06-05T18:55:38.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/69/9235af850e2ee0cec23b4e863612492964a9c40b65f3002e4a9f32e1ffa2/dbus_fast-5.0.22-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8244561e503cffcd5e6b1ff6544a44aa0b65b6856f2f64d04345ebf82f8ac48", size = 840236, upload-time = "2026-06-05T18:55:41.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d1/08ed17331dd23a8b91b80db9acca48301c4879352024dc7882e2c5893fa2/dbus_fast-5.0.22-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32869678240f233ece5a1137960c0337699dbb39acc02c155929f52543f03fb", size = 884309, upload-time = "2026-06-05T18:55:42.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/ed/02458af54460e55cf26fdf0a778aa9b3217ac8d37e6280aa68e19f176650/dbus_fast-5.0.22-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f74700b1b4fd58acb76f8790e2cd475084d0e59149e0cc035abd239435a339b7", size = 885179, upload-time = "2026-06-05T18:55:43.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/79/4958c807e59106558a4415212e6be019775f5be9775ffc79c6bc7248b388/dbus_fast-5.0.22-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:246780dcda59d26dd2fb1d9a15991bc8483e9c241cee8713920a39a363ad4d39", size = 847693, upload-time = "2026-06-05T18:55:45.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/cc/a5322fbc172ee099f98186224cf7e3bf48fc8bcb08270951da8006b556ba/dbus_fast-5.0.22-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:58e4d41f9fea22cfd6dacdf03d53b932bbe15a825af5abad3ca79b91493a15c3", size = 882773, upload-time = "2026-06-05T18:55:46.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/e7/e068899c1d9a034ed9d5fc8519b8e2247aa8c28a6a67229162db64163b59/dbus_fast-5.0.22-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d8ca39f3f2ab34f88bb343db87c7cf8b07eefa046b23d8c38b3387f150ac609e", size = 891573, upload-time = "2026-06-05T18:55:48.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/1d/f6020228675338b7184ce5491e4535fb64c7badc0197151b9776f345a1a1/dbus_fast-5.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0926e4cf49989b4ec8233e8fd462eb35a640fcfe81bb75d91675dd47489022b", size = 693158, upload-time = "2026-06-05T18:55:49.874Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/84/dfb014de75a3a854dccaae1cce8f840e4312e3efc781768eedd60d25d9ef/dbus_fast-5.0.22-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:846f9a6602b4383f989201f7851459fb225a8912cd24b38e63894748545c3040", size = 838836, upload-time = "2026-06-05T18:55:51.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/c2/be41bcc678e97092d44ba22d09ce687f76c955b3367a7e6863377b1cfea5/dbus_fast-5.0.22-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886b43446b6fdc3986befbbb88db1365b14e49dd0a7edf84c2c67ac66c7160a4", size = 883163, upload-time = "2026-06-05T18:55:53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/cf/336c08f88fdd813a39fc1603a10a15aec67115e08a895e7c9840df54d4d7/dbus_fast-5.0.22-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3a699fca957acc845ddb12b47f741dba23ce147fdb93583e0c7e7bad3e9b2355", size = 886852, upload-time = "2026-06-05T18:55:54.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/f1/7c1aa53f25252a2317f21ad6e8eaa125246d2585ea4611f3f10a6feaeeb1/dbus_fast-5.0.22-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a5b05fd4973862042e5bee2c5e8c5a15297e0b33a975bf25b44becf7bcb3618", size = 846497, upload-time = "2026-06-05T18:55:56.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/32/a981ef2305f1bf41e538e02fa0cd69614f9043fd00ca965bf3044416c79b/dbus_fast-5.0.22-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6c4dae5292a7924ec062815c34b49043d8386cd22e165f9fb4012de00997cdf1", size = 882275, upload-time = "2026-06-05T18:55:57.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a0/78800a172f4ca32e19a70a36e175f54831f33a497c9644f3a3fa4dce01ea/dbus_fast-5.0.22-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b7d90a52be79acbaef257f3a81d5b9b9dec40f1bad29429ac5c7802684fb9b84", size = 890566, upload-time = "2026-06-05T18:55:59.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/81/ffc155f700c45191673e7f7620a28cbbbf5f116ff74a99f765895baa6f9c/dbus_fast-5.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f72b77be63f7bb24cf42936ad10994d40f43fed691f857f7854b5882d6a5227c", size = 690171, upload-time = "2026-06-05T18:56:01.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/06/233b0bc13919474f70320bf389cb81ce02811956d6cf86c45e84679b63c3/dbus_fast-5.0.22-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f0bcad7f71d2304a68a5b0bc0d24c3fcc14710a2ffcf5f2a27521e3aece71ca", size = 799464, upload-time = "2026-06-05T18:56:02.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/e9/77bc23a6f5aebfb8f2c34489795e8517aed7eca31738438e1a4c4a4891d3/dbus_fast-5.0.22-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ffcf16034f71a801bd2108aeffb6337d104c9459e8b1a218d16a917c8a2d2e9", size = 852687, upload-time = "2026-06-05T18:56:04.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/04/56769d0936d1273d1801ef574ec426ccb3f61f4b0a7a0eeb9eb2b8ccafa5/dbus_fast-5.0.22-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98de6d2c200d8182e1fd0bdde3206fa556b8fa14ebb752a044cd8daa87b4658c", size = 833814, upload-time = "2026-06-05T18:56:05.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ca/964f0d39a3be03b12a98f39519d34ad95b74360c7e3adf4cd4907dc25fd6/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b013437b66dc22b8d9aca5e0b0d46bf1980208a143409469fe482d9684a2a717", size = 806891, upload-time = "2026-06-05T18:56:07.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/25/57fe6ab509ad9da2e190498fa9c37f868e38ac521d940a9edb9ba6b6c657/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:855f15b7f7805171da2b82de1c317d01cfbb9fb8ac61fcc1e8dec54d8c69fab7", size = 830867, upload-time = "2026-06-05T18:56:09.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/2b/da036e9f4aeb776833139575fe0774544aa6cd13ba997eea7fbc4ab99852/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7d1c42963235cfc015a2d2b8c5fe42b65387493b4ad4ce0ec122601c805e6742", size = 860651, upload-time = "2026-06-05T18:56:10.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/1d/ebd02ae707328286c24582ac9189e4ed9c344399bc3b6a4b74e9687088eb/dbus_fast-5.0.22-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26e26b409e1e6edf5e2a4df8d192625fb38876b074fb5c7d0a5b15c5792e549d", size = 685921, upload-time = "2026-06-05T18:56:12.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/5a/fa81a6685c763ea488ad93228cb6e036adc9af6a560f4c31643691f4cfd8/dbus_fast-5.0.22-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de10ff3b3cb2acb1c09fe17158a470519000d37bb5ee5fd69c4075e81ce8dcf5", size = 798472, upload-time = "2026-06-05T18:56:14.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/34/6b272e6df60be1aa4d575aa30220175a52c002a649c951d9950bfa3a72d6/dbus_fast-5.0.22-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:979761985fe343c701f2b7575285d6e370123f7231d4656209ef7824bb686bbb", size = 850312, upload-time = "2026-06-05T18:56:16.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/5c/9045c3595ddcd4069e6b5d051df06bf11a2b022592f721c9acea1e0e4d22/dbus_fast-5.0.22-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fb73f1d8374253b7c17d69e902cf2ded1bfb089cb6ae67c10b4e0bdfe1b8fe08", size = 828366, upload-time = "2026-06-05T18:56:17.786Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/78/ca6881442b8fa29edbe6d99bec4b535b0b2e2f423075d015ff5b719c4e2c/dbus_fast-5.0.22-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b67a02037eb58bcf9e445df60ea0d9d7346fd334abde3aa62e03c75823b53979", size = 806036, upload-time = "2026-06-05T18:56:19.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c6/458728eb1caa26171e6a8ae1d0d99bd29aaeac67ad7824bbd95d7f854a41/dbus_fast-5.0.22-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:83940ea00d7ee2f0c5bcb5d19d7d05e7949e52d467616a0b735d72e7285402ec", size = 828353, upload-time = "2026-06-05T18:56:21.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/1d/830b1569264780210d44898e5b0d95cffe2830b952c2ee21ea481274cd81/dbus_fast-5.0.22-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:279d212e9fb262d595af2e4b5b9e951bc00c73a5c8eeb50f158caa13705b9c84", size = 857743, upload-time = "2026-06-05T18:56:22.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b6/171f8e92775254e2f42afe9e7501a57ace47f32de2969cb694a375dc9dce/dbus_fast-5.0.22-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:511655d915692f55b8c5f5a535acf80cca9c6d1a35384db7fcfdabaae05dd837", size = 692986, upload-time = "2026-06-05T18:56:24.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/8c/4eefaabdf538882528164060ae83d9a34f1172b019c32c3254436834e9b1/dbus_fast-5.0.22-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:703e0f8f9af52e8e053394ee2b578042be0c3d8ea2b1488f9db8cb14393cc13f", size = 810835, upload-time = "2026-06-05T18:56:26.356Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/cf/fd327dbb40ee67a9331fb587bf78aff2ab1500b35979978a5cacb10d7f8c/dbus_fast-5.0.22-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb1d7e8e65561d0fd438004fd9e0f981c8a862912fed58dd4e29db1936c39d73", size = 855498, upload-time = "2026-06-05T18:56:28.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/33/1709ebc16a4d353ddc4fcd29252e2b9d93bded6422a45fd6df170e0911c1/dbus_fast-5.0.22-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:959fab6420897ab99410e67d6f9f9a7f6f4cedb6014700768f5e2d71dbff5dc6", size = 833510, upload-time = "2026-06-05T18:56:29.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/fd/89d7c34152900d986b9c78e39cc62aa73eefc22b57b3a8c946d945a85540/dbus_fast-5.0.22-cp314-cp314-manylinux_2_41_x86_64.whl", hash = "sha256:eb31c5ff339a7071b914617a69d5b7c6ba7d411da4b01a5f9b5b2fe51e9d1301", size = 853669, upload-time = "2026-06-05T18:47:56.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/a1/031cc4a89d947f1fe110f663f93dcce9230213b7accaf719790d813def04/dbus_fast-5.0.22-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:856f0543c593f3480e93e67bcd1aa4ddc1d94a6076cfd3ad4e0f5e2b01b33dc3", size = 818486, upload-time = "2026-06-05T18:56:31.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/e2/de8b764fdb947314fb8c2e079b556510194fd100983776845e234a107cc9/dbus_fast-5.0.22-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:96d231d128c1f46f263790335897195dde9dac2f38571782db8ae1d8647bd548", size = 833582, upload-time = "2026-06-05T18:56:33.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/1f/e5f0dd28d07c4b3f7bafd3357bfa424c8dace355a3dad921fec05db4634b/dbus_fast-5.0.22-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:595bd3ccfd8318cbafff79f33a15709fee3728724fd61d5fa220080d73b574cb", size = 862291, upload-time = "2026-06-05T18:56:35.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/dd/61086156a1c2d8ffd04d61a232debbaff8ca9fc1cf598476999e1f06164d/dbus_fast-5.0.22-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69a077c296eaab8c30160e861b5514c33d99d67d41d17dbf02e89aae44543b11", size = 1353993, upload-time = "2026-06-05T18:56:36.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/69/5b54654f598ef98e8f94fd5a40929668b1f8fcd76e7fb50de0db73d329da/dbus_fast-5.0.22-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04bac97d0cb754a4d13037d0132517f1df28192d6e0568a0bf6df06623062285", size = 1534804, upload-time = "2026-06-05T18:56:38.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/b7/c00d01699dc87ffc35f143226d3b296372840e2e2bc15101d35df7c74949/dbus_fast-5.0.22-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3eb57d592d84b0bb90e0c077db7ecb61562f49cc9b86a3ef08cbe17243e9cc4f", size = 1613316, upload-time = "2026-06-05T18:56:40.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/94/ea0db4c1aa6409cb16551b50aa8573e72f64407ca5281b042919ef81ca1c/dbus_fast-5.0.22-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:de4d235d1282ebb3ab65b6cddab84e914c045d92ceb381ddcbdbaf66bf1fb132", size = 822053, upload-time = "2026-06-05T18:56:42.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/e4/a3bb52185b8a8c76bd8aaba3ff4fa8395eea19fbc142122b43dc377b275c/dbus_fast-5.0.22-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:048f34299fbe82d7b87c56f47e8bd83f62339a4517685abc6671d603a55d2c89", size = 1549996, upload-time = "2026-06-05T18:56:44.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/2b/6e405ba92e87d78a689a387809d975f97f8c8748b98efccfacd2b4e1d9f5/dbus_fast-5.0.22-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:92df9fb6d8adeb17b534621c2ee730295bbe1d0c2584d5c82b1db478e3f04e8f", size = 823004, upload-time = "2026-06-05T18:56:46.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/8f/77135ab8d690030cdb0ebeca879640b5945c4cbf5344ecbc507b4628da24/dbus_fast-5.0.22-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7be4271e38251f1ad726962dec60da887c8ed352d157352e4fc27f56aece5c5d", size = 1629160, upload-time = "2026-06-05T18:56:47.688Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
version = "0.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -214,7 +219,7 @@ name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
@@ -223,11 +228,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.29.0"
|
||||
version = "3.29.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/dc/be6cbe99670cd6e4ad387123647cb08e0c32975e223f82551e914c5568a6/filelock-3.29.4.tar.gz", hash = "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", size = 63028, upload-time = "2026-06-13T16:12:00.744Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/37/a065dc3bd6e49423a6532c642ca7378d3f467b1ef44c2800c937af7f9739/filelock-3.29.4-py3-none-any.whl", hash = "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767", size = 42757, upload-time = "2026-06-13T16:11:59.582Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -241,11 +246,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.16"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -275,6 +280,227 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opencv-python"
|
||||
version = "4.13.0.92"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.11.9"
|
||||
@@ -474,11 +700,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.9.6"
|
||||
version = "4.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -499,6 +725,10 @@ dependencies = [
|
||||
{ name = "dbus-fast" },
|
||||
{ name = "evdev" },
|
||||
{ name = "libarchive-c" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
{ name = "opencv-python" },
|
||||
{ name = "orjson" },
|
||||
{ name = "pefile" },
|
||||
{ name = "pillow" },
|
||||
@@ -527,6 +757,8 @@ requires-dist = [
|
||||
{ name = "dbus-fast", specifier = ">=5.0.4" },
|
||||
{ name = "evdev", specifier = ">=1.9.3" },
|
||||
{ name = "libarchive-c", specifier = ">=5.3" },
|
||||
{ name = "numpy", specifier = ">=2.2.0" },
|
||||
{ name = "opencv-python", specifier = ">=4.11.0" },
|
||||
{ name = "orjson", specifier = ">=3.11.8" },
|
||||
{ name = "pefile", specifier = ">=2024.8.26" },
|
||||
{ name = "pillow", specifier = ">=12.2.0" },
|
||||
@@ -640,15 +872,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.409"
|
||||
version = "1.1.411"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodeenv" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/ab/265f7dc69d28113ebba19092e57b075f41543b2ed048429c5f56e2b88eac/pyright-1.1.411.tar.gz", hash = "sha256:d885a0551f2e763b089a02702174e7f4ba77548cddabc972ab86d1f7f1b0f998", size = 4112861, upload-time = "2026-06-25T02:14:06.37Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/49/385be530a6a5b78d1cbcd5c2e38debc8959a2fc6bdb716f4e581002979fc/pyright-1.1.411-py3-none-any.whl", hash = "sha256:dc7c72a8e2700c55baa127554040e067041ea53ccfd50bf96308cc4291c7d5d9", size = 6181526, upload-time = "2026-06-25T02:14:04.691Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -702,7 +934,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.3"
|
||||
version = "9.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -713,9 +945,9 @@ dependencies = [
|
||||
{ name = "pygments" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -732,15 +964,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-discovery"
|
||||
version = "1.3.1"
|
||||
version = "1.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/60/e88788207d81e46362cfbef0d4aaf4c0f49efc3c12d4c3fa3f542c34ebec/python_discovery-1.3.1.tar.gz", hash = "sha256:62f6db28064c9613e7ca76cb3f00c38c839a07c31c00dfe7ed0986493d2150a6", size = 68011, upload-time = "2026-05-12T20:53:36.336Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/6f/a05a317a66fee0aad270011461f1a63a453ed12471249f172f7d2e2bc7b4/python_discovery-1.3.1-py3-none-any.whl", hash = "sha256:ed188687ebb3b82c01a17cd5ac62fc94d9f6487a7f1a0f9dfe89753fec91039c", size = 33185, upload-time = "2026-05-12T20:53:34.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -992,14 +1224,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.67.3"
|
||||
version = "4.68.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1031,7 +1263,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "21.3.3"
|
||||
version = "21.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
@@ -1040,9 +1272,9 @@ dependencies = [
|
||||
{ name = "python-discovery" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/ba/1f6e8c957e4932be060dcdc482d339c12e0216351478add3645cdaa53c05/virtualenv-21.3.3.tar.gz", hash = "sha256:f5bda277e553b1c2b3c1a8debfc30496e1288cc93ce6b7b71b3280047e317328", size = 7613784, upload-time = "2026-05-13T18:01:30.19Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/34/a9dbe051de88a63eb7408ea66630bac38e72f7f6077d4be58737106860d9/virtualenv-21.3.3-py3-none-any.whl", hash = "sha256:7d5987d8369e098e41406efb780a3d4ca79280097293899e351a6407ee153ab3", size = 7594554, upload-time = "2026-05-13T18:01:27.815Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user