Rename project, crates, and binaries to open-360-link

This commit is contained in:
Lucas Oskorep committed 2026-10-27 11:27:30 -04:00
1 parent ef641ac31b
commit bbdcde3f0f
28 files changed
+219 -207

No files matched your search

Generated
+54 -54
View File
@@ -1829,60 +1829,6 @@ dependencies = [
"hashbrown 0.17.1", "hashbrown 0.17.1",
] ]
[[package]]
name = "insta360-link-cli"
version = "0.0.0"
dependencies = [
"clap",
"insta360-link-protocol",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "insta360-link-gui"
version = "0.0.0"
dependencies = [
"eframe",
"egui",
"image",
"insta360-link-protocol",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "insta360-link-protocol"
version = "0.0.0"
dependencies = [
"bitflags 2.13.2",
"libc",
"nusb",
"serde",
"serde_json",
"thiserror 2.0.21",
"tracing",
]
[[package]]
name = "insta360-link-server"
version = "0.0.0"
dependencies = [
"axum",
"clap",
"insta360-link-protocol",
"serde",
"serde_json",
"tokio",
"tower-http",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "io-kit-sys" name = "io-kit-sys"
version = "0.4.1" version = "0.4.1"
@@ -2623,6 +2569,60 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "open-360-link-cli"
version = "0.0.0"
dependencies = [
"clap",
"open-360-link-protocol",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "open-360-link-gui"
version = "0.0.0"
dependencies = [
"eframe",
"egui",
"image",
"open-360-link-protocol",
"serde",
"serde_json",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "open-360-link-protocol"
version = "0.0.0"
dependencies = [
"bitflags 2.13.2",
"libc",
"nusb",
"serde",
"serde_json",
"thiserror 2.0.21",
"tracing",
]
[[package]]
name = "open-360-link-server"
version = "0.0.0"
dependencies = [
"axum",
"clap",
"open-360-link-protocol",
"serde",
"serde_json",
"tokio",
"tower-http",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "orbclient" name = "orbclient"
version = "0.3.55" version = "0.3.55"
+5 -5
View File
@@ -1,10 +1,10 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"crates/insta360-link-protocol", "crates/open-360-link-protocol",
"crates/insta360-link-cli", "crates/open-360-link-cli",
"crates/insta360-link-gui", "crates/open-360-link-gui",
"crates/insta360-link-server", "crates/open-360-link-server",
] ]
[workspace.package] [workspace.package]
@@ -12,7 +12,7 @@ version = "0.0.0"
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
authors = ["Lucas Oskorep <lucas.oskorep@gmail.com>"] authors = ["Lucas Oskorep <lucas.oskorep@gmail.com>"]
repository = "https://github.com/lucasoskorep/insta360-link-controller" repository = "https://github.com/lucasoskorep/open-360-link"
[workspace.dependencies] [workspace.dependencies]
thiserror = "2.0" thiserror = "2.0"
+38 -39
View File
@@ -1,4 +1,4 @@
# Insta360 Link Open Controller # Open 360 Link
A pure-Rust, cross-platform (**Linux**, **Windows**, **macOS**) hardware controller suite for the **Insta360 Link**, **Insta360 Link 2**, **Insta360 Link 2C**, and **Insta360 Link 2 Pro (PUC3)** 4K AI webcams. A pure-Rust, cross-platform (**Linux**, **Windows**, **macOS**) hardware controller suite for the **Insta360 Link**, **Insta360 Link 2**, **Insta360 Link 2C**, and **Insta360 Link 2 Pro (PUC3)** 4K AI webcams.
@@ -12,10 +12,10 @@ Built by reverse-engineering the proprietary USB & UVC Extension Unit (`XU1` and
| Crate / Binary | Description | | Crate / Binary | Description |
| :--- | :--- | | :--- | :--- |
| **`insta360-link-protocol`** | Core Rust library implementing USB device discovery, UVC VideoControl descriptor parsing, standard UVC `CT_*`/`PU_*` controls, proprietary `XU1`/`XU2` codecs, `NusbTransport` (pure-Rust USB via `nusb`), and a stateful `MockCameraTransport` simulator. | | **`open-360-link-protocol`** | Core Rust library implementing USB device discovery, UVC VideoControl descriptor parsing, standard UVC `CT_*`/`PU_*` controls, proprietary `XU1`/`XU2` codecs, `NusbTransport` (pure-Rust USB via `nusb`), and a stateful `MockCameraTransport` simulator. |
| **`insta360-link-ctl`** (`insta360-link-cli`) | Command-line tool for scripting PTZ movement, presets, video modes (`Normal`, `AutoTracking`, `WhiteBoard`, `OverheadShot`, `DeskView`, `AutoFraming`, `SmartWhiteBoard`), exposure/ISO, white balance, audio beamforming modes, filters, and extended hardware switches. | | **`open-360-link-ctl`** (`open-360-link-cli`) | Command-line tool for scripting PTZ movement, presets, video modes (`Normal`, `AutoTracking`, `WhiteBoard`, `OverheadShot`, `DeskView`, `AutoFraming`, `SmartWhiteBoard`), exposure/ISO, white balance, audio beamforming modes, filters, and extended hardware switches. |
| **`insta360-link-server`** | Asynchronous HTTP REST & WebSocket daemon (`axum` + `tokio`) for remote control, Stream Deck / OBS integration, and real-time state synchronization. | | **`open-360-link-server`** | Asynchronous HTTP REST & WebSocket daemon (`axum` + `tokio`) for remote control, Stream Deck / OBS integration, and real-time state synchronization. |
| **`insta360-link-gui`** | Native cross-platform desktop GUI (`egui` / `eframe`) featuring a virtual gimbal PTZ joystick, preset slots, AI mode switcher, image adjustment sliders, audio & filter controls, and built-in simulated camera mode. | | **`open-360-link-gui`** | Native cross-platform desktop GUI (`egui` / `eframe`) featuring a virtual gimbal PTZ joystick, AI mode switcher, image adjustment sliders, audio & filter controls, and live camera preview/recording. |
--- ---
@@ -61,9 +61,9 @@ cargo build --release
``` ```
The compiled binaries will be placed in `target/release/`: The compiled binaries will be placed in `target/release/`:
- `target/release/insta360-link-ctl` - `target/release/open-360-link-ctl`
- `target/release/insta360-link-server` - `target/release/open-360-link-server`
- `target/release/insta360-link-gui` - `target/release/open-360-link-gui`
--- ---
@@ -71,12 +71,12 @@ The compiled binaries will be placed in `target/release/`:
### Linux (`V4L2` & `uvcvideo`) ### Linux (`V4L2` & `uvcvideo`)
On Linux, `insta360-link-protocol` communicates directly with the primary V4L2 video capture node (`/dev/video*`) using `UVCIOC_CTRL_QUERY` for proprietary Insta360 Extension Unit (`XU1`/`XU2`) commands and standard V4L2 `VIDIOC_G_CTRL`/`VIDIOC_S_CTRL` ioctls for Processing Unit / Camera Terminal controls, while reading USB descriptors from sysfs. Because `systemd-logind` automatically grants the active desktop user session access to `/dev/video*`, **no custom `udev` rules or `sudo` privileges are required**. On Linux, `open-360-link-protocol` communicates directly with the primary V4L2 video capture node (`/dev/video*`) using `UVCIOC_CTRL_QUERY` for proprietary Insta360 Extension Unit (`XU1`/`XU2`) commands and standard V4L2 `VIDIOC_G_CTRL`/`VIDIOC_S_CTRL` ioctls for Processing Unit / Camera Terminal controls, while reading USB descriptors from sysfs. Because `systemd-logind` automatically grants the active desktop user session access to `/dev/video*`, **no custom `udev` rules or `sudo` privileges are required**.
#### Arch Linux (AUR) #### Arch Linux & Arch Linux ARM (AUR)
```bash ```bash
yay -S insta360-link-controller-bin yay -S open-360-link-bin
``` ```
### macOS ### macOS
@@ -87,74 +87,73 @@ On macOS, `nusb` uses `IOKit` to send UVC class-specific control requests direct
## Usage Examples ## Usage Examples
### 1. Command-Line Interface (`insta360-link-ctl`) ### 1. Command-Line Interface (`open-360-link-ctl`)
> **Tip**: Pass `--mock` (and optionally `--mock-model link2`, `link2c`, or `link`) to test any command against the simulated camera without physical hardware attached. > **Tip**: Pass `--mock link2` (`link`, `link2`, or `link2c`) to test any command against the simulated camera without physical hardware attached.
```bash ```bash
# List connected Insta360 Link cameras and discovered UVC unit IDs # List connected Insta360 Link cameras and discovered UVC unit IDs
cargo run -p insta360-link-cli -- list cargo run -p open-360-link-cli -- list
# Show complete camera status, firmware version, PTZ angles, and active features # Show complete camera status, firmware version, PTZ angles, and active features
cargo run -p insta360-link-cli -- status cargo run -p open-360-link-cli -- info
# Move gimbal to absolute pan/tilt angles (in degrees) and set zoom (100 = 1.0x, 200 = 2.0x) # Move gimbal to absolute pan/tilt angles and set zoom (100 = 1.0x, 200 = 2.0x)
cargo run -p insta360-link-cli -- ptz --pan 15.0 --tilt -10.0 --zoom 150 cargo run -p open-360-link-cli -- ptz set-abs --pan 54000 --tilt -36000
cargo run -p open-360-link-cli -- ptz zoom 150
# Center / home the gimbal # Center / home the gimbal
cargo run -p insta360-link-cli -- home cargo run -p open-360-link-cli -- ptz reset
# Save and recall gimbal preset slots (0..5) # Switch AI Video Mode (normal, auto-tracking, whiteboard, overhead, deskview, auto-framing, smart-whiteboard)
cargo run -p insta360-link-cli -- preset save 0 cargo run -p open-360-link-cli -- mode set auto-tracking
cargo run -p insta360-link-cli -- preset recall 0 cargo run -p open-360-link-cli -- tracking speed fast
# Switch AI Video Mode (normal, tracking, whiteboard, overhead, deskview, auto-framing, smart-whiteboard)
cargo run -p insta360-link-cli -- mode tracking
cargo run -p insta360-link-cli -- tracking --speed fast --composition half-body
# Configure image settings (brightness, contrast, saturation, sharpness, white balance, HDR) # Configure image settings (brightness, contrast, saturation, sharpness, white balance, HDR)
cargo run -p insta360-link-cli -- image --brightness 60 --contrast 55 --hdr on --wb-kelvin 5600 cargo run -p open-360-link-cli -- image brightness 60
cargo run -p open-360-link-cli -- image hdr true
# Configure microphone beamforming mode (voice-focus, noise-suppression, music-balance, original, sound-wall) # Configure microphone beamforming mode (voice-focus, noise-reduction, music-mode, original, sound-wall)
cargo run -p insta360-link-cli -- audio --mode voice-focus --denoise on cargo run -p open-360-link-cli -- audio mode voice-focus
cargo run -p open-360-link-cli -- audio denoise true
``` ```
### 2. HTTP & WebSocket Server (`insta360-link-server`) ### 2. HTTP & WebSocket Server (`open-360-link-server`)
Start the daemon on `127.0.0.1:8360` (use `--mock` if no physical camera is connected): Start the daemon on `127.0.0.1:9360` (use `--mock link2` if no physical camera is connected):
```bash ```bash
cargo run -p insta360-link-server -- --bind 127.0.0.1:8360 --mock cargo run -p open-360-link-server -- --bind 127.0.0.1:9360 --mock link2
``` ```
Example REST API calls: Example REST API calls:
```bash ```bash
# List discovered cameras # List discovered cameras
curl http://127.0.0.1:8360/api/cameras curl http://127.0.0.1:9360/api/cameras
# Fetch full camera state snapshot # Fetch full camera state snapshot
curl http://127.0.0.1:8360/api/state curl http://127.0.0.1:9360/api/state
# Set PTZ pan, tilt, or zoom # Set PTZ pan, tilt, or zoom
curl -X POST http://127.0.0.1:8360/api/ptz \ curl -X POST http://127.0.0.1:9360/api/ptz \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"pan_deg": 20.0, "tilt_deg": -5.0, "zoom": 180}' -d '{"pan_deg": 20.0, "tilt_deg": -5.0, "zoom": 180}'
# Switch video mode # Switch video mode
curl -X POST http://127.0.0.1:8360/api/mode \ curl -X POST http://127.0.0.1:9360/api/mode \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"mode": "DeskView"}' -d '{"mode": "DeskView"}'
``` ```
Connect to `ws://127.0.0.1:8360/ws` over WebSockets to receive real-time JSON state broadcasts and send low-latency PTZ/control commands. Connect to `ws://127.0.0.1:9360/ws` over WebSockets to receive real-time JSON state broadcasts and send low-latency PTZ/control commands.
### 3. Desktop GUI (`insta360-link-gui`) ### 3. Desktop GUI (`open-360-link-gui`)
Launch the native cross-platform GUI (automatically connects to a physical Insta360 Link camera if found, or falls back to the interactive simulator): Launch the native cross-platform GUI (automatically connects to a physical Insta360 Link camera if found):
```bash ```bash
cargo run -p insta360-link-gui cargo run -p open-360-link-gui
``` ```
--- ---
-14
View File
@@ -1,14 +0,0 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Insta360 Link Controller
GenericName=Webcam Controller
Comment=Hardware PTZ, AI tracking, image settings, and live preview controller for Insta360 Link webcams
Exec=insta360-link-gui
TryExec=insta360-link-gui
Icon=insta360-link-controller
Terminal=false
Categories=AudioVideo;Video;
Keywords=insta360;link;webcam;camera;ptz;gimbal;uvc;video;tracking;
StartupNotify=true
StartupWMClass=insta360-link-controller
+14
View File
@@ -0,0 +1,14 @@
[Desktop Entry]
Version=1.0
Type=Application
Name=Open 360 Link
GenericName=Webcam Controller
Comment=Open-source hardware PTZ, AI tracking, image settings, and live preview controller for Insta360 Link webcams
Exec=open-360-link-gui
TryExec=open-360-link-gui
Icon=open-360-link
Terminal=false
Categories=AudioVideo;Video;
Keywords=open-360-link;insta360;link;webcam;camera;ptz;gimbal;uvc;video;tracking;
StartupNotify=true
StartupWMClass=open-360-link
File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1,16 +1,16 @@
[package] [package]
name = "insta360-link-cli" name = "open-360-link-cli"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
description = "Cross-platform CLI controller for Insta360 Link, Link 2, and Link 2C cameras" description = "Open 360 Link: Cross-platform CLI controller for Insta360 Link, Link 2, and Link 2C cameras"
[[bin]] [[bin]]
name = "insta360-link-ctl" name = "open-360-link-ctl"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" } open-360-link-protocol = { path = "../open-360-link-protocol" }
clap.workspace = true clap.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@@ -1,5 +1,5 @@
use clap::{ArgAction, Parser, Subcommand, ValueEnum}; use clap::{ArgAction, Parser, Subcommand, ValueEnum};
use insta360_link_protocol::{ use open_360_link_protocol::{
list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceInfo, list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceInfo,
DeviceStatus, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture, DeviceStatus, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture,
GestureFunction, HostPtzInfo, ImageOperation, Insta360CameraController, MockCameraTransport, GestureFunction, HostPtzInfo, ImageOperation, Insta360CameraController, MockCameraTransport,
@@ -11,9 +11,9 @@ use std::process::ExitCode;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command( #[command(
name = "insta360-link-ctl", name = "open-360-link-ctl",
version, version,
about = "Cross-platform CLI controller for Insta360 Link, Link 2, and Link 2C cameras" about = "Open 360 Link: Cross-platform CLI controller for Insta360 Link, Link 2, and Link 2C cameras"
)] )]
pub struct Cli { pub struct Cli {
/// Run against an in-memory simulated camera model instead of physical USB /// Run against an in-memory simulated camera model instead of physical USB
@@ -1,16 +1,16 @@
[package] [package]
name = "insta360-link-gui" name = "open-360-link-gui"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
description = "Cross-platform pure-Rust desktop GUI controller for Insta360 Link, Link 2, and Link 2C" description = "Open 360 Link: Cross-platform pure-Rust desktop GUI controller for Insta360 Link, Link 2, and Link 2C"
[[bin]] [[bin]]
name = "insta360-link-gui" name = "open-360-link-gui"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" } open-360-link-protocol = { path = "../open-360-link-protocol" }
eframe.workspace = true eframe.workspace = true
egui.workspace = true egui.workspace = true
image = { version = "0.25", default-features = false, features = ["png"] } image = { version = "0.25", default-features = false, features = ["png"] }
@@ -7,7 +7,7 @@ mod video_engine;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use eframe::egui::{self, Align2, Color32, FontId, Margin, Pos2, Rect, RichText, Rounding, Stroke, Vec2}; use eframe::egui::{self, Align2, Color32, FontId, Margin, Pos2, Rect, RichText, Rounding, Stroke, Vec2};
use insta360_link_protocol::{ use open_360_link_protocol::{
list_connected_cameras, CameraModel, DiscoveredCamera, Insta360CameraController, list_connected_cameras, CameraModel, DiscoveredCamera, Insta360CameraController,
MockCameraTransport, NusbTransport, PowerLineFrequency, VideoMode, VideoModeAuxData, MockCameraTransport, NusbTransport, PowerLineFrequency, VideoMode, VideoModeAuxData,
}; };
@@ -181,41 +181,41 @@ impl Insta360App {
if let Ok(ext) = self.controller.get_extend_function_status() { if let Ok(ext) = self.controller.get_extend_function_status() {
for (func, en) in ext { for (func, en) in ext {
match func { match func {
insta360_link_protocol::ExtendFunction::AiZoom => { open_360_link_protocol::ExtendFunction::AiZoom => {
self.state.ai_zoom_enabled = en self.state.ai_zoom_enabled = en
} }
insta360_link_protocol::ExtendFunction::AllGestures => { open_360_link_protocol::ExtendFunction::AllGestures => {
self.state.enable_all_gestures = en self.state.enable_all_gestures = en
} }
insta360_link_protocol::ExtendFunction::AutoTracking => { open_360_link_protocol::ExtendFunction::AutoTracking => {
self.state.enable_auto_tracking = en self.state.enable_auto_tracking = en
} }
insta360_link_protocol::ExtendFunction::SingleTapTracking => { open_360_link_protocol::ExtendFunction::SingleTapTracking => {
self.state.enable_single_tap_tracking = en self.state.enable_single_tap_tracking = en
} }
insta360_link_protocol::ExtendFunction::Hdr => self.state.hdr_enabled = en, open_360_link_protocol::ExtendFunction::Hdr => self.state.hdr_enabled = en,
insta360_link_protocol::ExtendFunction::HorizontalMirror => { open_360_link_protocol::ExtendFunction::HorizontalMirror => {
self.state.horizontal_mirror = en self.state.horizontal_mirror = en
} }
insta360_link_protocol::ExtendFunction::VerticalMirror => { open_360_link_protocol::ExtendFunction::VerticalMirror => {
self.state.vertical_mirror = en self.state.vertical_mirror = en
} }
insta360_link_protocol::ExtendFunction::VerticalMode => { open_360_link_protocol::ExtendFunction::VerticalMode => {
self.state.vertical_resolution_mode = en self.state.vertical_resolution_mode = en
} }
insta360_link_protocol::ExtendFunction::ForcedVertical => { open_360_link_protocol::ExtendFunction::ForcedVertical => {
self.state.forced_vertical_lock = en self.state.forced_vertical_lock = en
} }
insta360_link_protocol::ExtendFunction::LowResolution => { open_360_link_protocol::ExtendFunction::LowResolution => {
self.state.low_resolution_mode = en self.state.low_resolution_mode = en
} }
insta360_link_protocol::ExtendFunction::SmartAdjustment => { open_360_link_protocol::ExtendFunction::SmartAdjustment => {
self.state.smart_adjustment = en self.state.smart_adjustment = en
} }
insta360_link_protocol::ExtendFunction::StartupParams => { open_360_link_protocol::ExtendFunction::StartupParams => {
self.state.startup_params_enabled = en self.state.startup_params_enabled = en
} }
insta360_link_protocol::ExtendFunction::UltimatePrivacyMode => { open_360_link_protocol::ExtendFunction::UltimatePrivacyMode => {
self.state.ultimate_privacy = en self.state.ultimate_privacy = en
} }
_ => {} _ => {}
@@ -231,11 +231,11 @@ impl Insta360App {
if let Ok(gestures) = self.controller.get_gesture_status() { if let Ok(gestures) = self.controller.get_gesture_status() {
for (g, en) in gestures { for (g, en) in gestures {
match g { match g {
insta360_link_protocol::Gesture::Ok => self.state.use_gesture_ok = en, open_360_link_protocol::Gesture::Ok => self.state.use_gesture_ok = en,
insta360_link_protocol::Gesture::Palm => self.state.use_gesture_palm = en, open_360_link_protocol::Gesture::Palm => self.state.use_gesture_palm = en,
insta360_link_protocol::Gesture::LShape => self.state.use_gesture_l = en, open_360_link_protocol::Gesture::LShape => self.state.use_gesture_l = en,
insta360_link_protocol::Gesture::VShape => self.state.use_gesture_v = en, open_360_link_protocol::Gesture::VShape => self.state.use_gesture_v = en,
insta360_link_protocol::Gesture::Rock => self.state.use_gesture_rock = en, open_360_link_protocol::Gesture::Rock => self.state.use_gesture_rock = en,
} }
} }
} }
@@ -325,15 +325,15 @@ impl Insta360App {
.controller .controller
.set_all_gestures_enabled(self.state.enable_all_gestures); .set_all_gestures_enabled(self.state.enable_all_gestures);
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::Palm, open_360_link_protocol::Gesture::Palm,
self.state.use_gesture_palm, self.state.use_gesture_palm,
); );
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::VShape, open_360_link_protocol::Gesture::VShape,
self.state.use_gesture_v, self.state.use_gesture_v,
); );
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::LShape, open_360_link_protocol::Gesture::LShape,
self.state.use_gesture_l, self.state.use_gesture_l,
); );
let _ = self let _ = self
@@ -412,14 +412,14 @@ impl Insta360App {
) )
.show(ctx, |ui| { .show(ctx, |ui| {
ui.horizontal_centered(|ui| { ui.horizontal_centered(|ui| {
// Left: Green Insta360 camera icon + "Insta360 Link Controller" title // Left: Green camera icon + "Open 360 Link" title
ui.label( ui.label(
RichText::new("📷") RichText::new("📷")
.color(theme.brand_green) .color(theme.brand_green)
.size(17.0_f32), .size(17.0_f32),
); );
ui.label( ui.label(
RichText::new("Insta360 Link Controller") RichText::new("Open 360 Link")
.strong() .strong()
.size(14.5_f32) .size(14.5_f32)
.color(theme.text01), .color(theme.text01),
@@ -1257,7 +1257,7 @@ impl Insta360App {
.changed() .changed()
{ {
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::Palm, open_360_link_protocol::Gesture::Palm,
self.state.use_gesture_palm, self.state.use_gesture_palm,
); );
} }
@@ -1267,7 +1267,7 @@ impl Insta360App {
.changed() .changed()
{ {
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::VShape, open_360_link_protocol::Gesture::VShape,
self.state.use_gesture_v, self.state.use_gesture_v,
); );
} }
@@ -1277,7 +1277,7 @@ impl Insta360App {
.changed() .changed()
{ {
let _ = self.controller.set_gesture_enabled( let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::LShape, open_360_link_protocol::Gesture::LShape,
self.state.use_gesture_l, self.state.use_gesture_l,
); );
} }
@@ -1455,13 +1455,13 @@ impl Insta360App {
ui.label("Tracking Speed:"); ui.label("Tracking Speed:");
egui::ComboBox::from_id_salt("tb_track_speed") egui::ComboBox::from_id_salt("tb_track_speed")
.selected_text(match self.state.track_speed { .selected_text(match self.state.track_speed {
insta360_link_protocol::TrackSpeed::Fast => { open_360_link_protocol::TrackSpeed::Fast => {
"Quick" "Quick"
} }
insta360_link_protocol::TrackSpeed::Normal => { open_360_link_protocol::TrackSpeed::Normal => {
"Ordinary" "Ordinary"
} }
insta360_link_protocol::TrackSpeed::Slow => { open_360_link_protocol::TrackSpeed::Slow => {
"Slow" "Slow"
} }
}) })
@@ -1469,37 +1469,37 @@ impl Insta360App {
if ui if ui
.selectable_value( .selectable_value(
&mut self.state.track_speed, &mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Fast, open_360_link_protocol::TrackSpeed::Fast,
"Quick", "Quick",
) )
.clicked() .clicked()
{ {
let _ = self.controller.set_track_speed( let _ = self.controller.set_track_speed(
insta360_link_protocol::TrackSpeed::Fast, open_360_link_protocol::TrackSpeed::Fast,
); );
} }
if ui if ui
.selectable_value( .selectable_value(
&mut self.state.track_speed, &mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Normal, open_360_link_protocol::TrackSpeed::Normal,
"Ordinary", "Ordinary",
) )
.clicked() .clicked()
{ {
let _ = self.controller.set_track_speed( let _ = self.controller.set_track_speed(
insta360_link_protocol::TrackSpeed::Normal, open_360_link_protocol::TrackSpeed::Normal,
); );
} }
if ui if ui
.selectable_value( .selectable_value(
&mut self.state.track_speed, &mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Slow, open_360_link_protocol::TrackSpeed::Slow,
"Slow", "Slow",
) )
.clicked() .clicked()
{ {
let _ = self.controller.set_track_speed( let _ = self.controller.set_track_speed(
insta360_link_protocol::TrackSpeed::Slow, open_360_link_protocol::TrackSpeed::Slow,
); );
} }
}); });
@@ -2307,9 +2307,9 @@ impl eframe::App for Insta360App {
let z_read = self.controller.get_zoom().unwrap_or(0); let z_read = self.controller.get_zoom().unwrap_or(0);
let _ = self.controller.set_zoom(100); let _ = self.controller.set_zoom(100);
let _ = self.controller.set_track_speed(insta360_link_protocol::TrackSpeed::Fast); let _ = self.controller.set_track_speed(open_360_link_protocol::TrackSpeed::Fast);
let ts_fast = self.controller.get_track_speed().ok(); let ts_fast = self.controller.get_track_speed().ok();
let _ = self.controller.set_track_speed(insta360_link_protocol::TrackSpeed::Normal); let _ = self.controller.set_track_speed(open_360_link_protocol::TrackSpeed::Normal);
let ts_norm = self.controller.get_track_speed().ok(); let ts_norm = self.controller.get_track_speed().ok();
let _ = self.controller.set_auto_exposure(true); let _ = self.controller.set_auto_exposure(true);
@@ -2370,7 +2370,7 @@ impl eframe::App for Insta360App {
let _ = self.controller.set_all_gestures_enabled(true); let _ = self.controller.set_all_gestures_enabled(true);
let _ = self let _ = self
.controller .controller
.set_gesture_enabled(insta360_link_protocol::Gesture::Palm, true); .set_gesture_enabled(open_360_link_protocol::Gesture::Palm, true);
let gest = self.controller.get_gesture_status().ok(); let gest = self.controller.get_gesture_status().ok();
println!( println!(
@@ -2569,16 +2569,16 @@ fn main() -> eframe::Result<()> {
// Matching webcam.qml: width: 1440, height: 714, minimumWidth: 1120, minimumHeight: 536 // Matching webcam.qml: width: 1440, height: 714, minimumWidth: 1120, minimumHeight: 536
let native_options = eframe::NativeOptions { let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default() viewport: egui::ViewportBuilder::default()
.with_app_id("insta360-link-controller") .with_app_id("open-360-link")
.with_icon(build_app_icon()) .with_icon(build_app_icon())
.with_inner_size([1440.0_f32, 714.0_f32]) .with_inner_size([1440.0_f32, 714.0_f32])
.with_min_inner_size([1120.0_f32, 536.0_f32]) .with_min_inner_size([1120.0_f32, 536.0_f32])
.with_title("Insta360 Link Controller"), .with_title("Open 360 Link"),
..Default::default() ..Default::default()
}; };
eframe::run_native( eframe::run_native(
"Insta360 Link Controller", "Open 360 Link",
native_options, native_options,
Box::new(|cc| Ok(Box::new(Insta360App::new(cc)))), Box::new(|cc| Ok(Box::new(Insta360App::new(cc)))),
) )
@@ -2,7 +2,7 @@ use egui::{
Align, Align2, Color32, Context, FontId, Grid, Layout, Pos2, Rect, RichText, Rounding, Align, Align2, Color32, Context, FontId, Grid, Layout, Pos2, Rect, RichText, Rounding,
ScrollArea, Sense, Stroke, TextEdit, Ui, Vec2, ScrollArea, Sense, Stroke, TextEdit, Ui, Vec2,
}; };
use insta360_link_protocol::{ use open_360_link_protocol::{
AudioCaptureMode, CameraModel, CompositionStyle, ExposureCurve, FilterMode, HostPtzInfo, AudioCaptureMode, CameraModel, CompositionStyle, ExposureCurve, FilterMode, HostPtzInfo,
ImageOperation, Insta360CameraController, PowerLineFrequency, TrackSpeed, UvcControlTransport, ImageOperation, Insta360CameraController, PowerLineFrequency, TrackSpeed, UvcControlTransport,
Gesture, Gesture,
@@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use egui::{ColorImage, Pos2, TextureHandle}; use egui::{ColorImage, Pos2, TextureHandle};
pub use insta360_link_protocol::*; pub use open_360_link_protocol::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Instant; use std::time::Instant;
@@ -285,7 +285,7 @@ pub struct CameraAppState {
pub last_saved_settings: Option<PersistedAppSettings>, pub last_saved_settings: Option<PersistedAppSettings>,
} }
/// User settings persisted across sessions in `~/.config/insta360-link-controller/settings.json`. /// User settings persisted across sessions in `~/.config/open-360-link/settings.json`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PersistedAppSettings { pub struct PersistedAppSettings {
pub format_preset: VideoFormatPreset, pub format_preset: VideoFormatPreset,
@@ -325,12 +325,10 @@ pub struct PersistedAppSettings {
} }
impl PersistedAppSettings { impl PersistedAppSettings {
pub fn config_path() -> PathBuf { fn base_config_dir() -> PathBuf {
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
if !xdg.trim().is_empty() { if !xdg.trim().is_empty() {
return PathBuf::from(xdg) return PathBuf::from(xdg);
.join("insta360-link-controller")
.join("settings.json");
} }
} }
let home = std::env::var("HOME") let home = std::env::var("HOME")
@@ -338,14 +336,29 @@ impl PersistedAppSettings {
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from(".")); .unwrap_or_else(|_| PathBuf::from("."));
home.join(".config") home.join(".config")
.join("insta360-link-controller") }
pub fn config_path() -> PathBuf {
Self::base_config_dir()
.join("open-360-link")
.join("settings.json") .join("settings.json")
} }
pub fn load() -> Option<Self> { pub fn load() -> Option<Self> {
let path = Self::config_path(); let path = Self::config_path();
let data = std::fs::read_to_string(path).ok()?; if let Ok(data) = std::fs::read_to_string(&path) {
serde_json::from_str(&data).ok() if let Ok(parsed) = serde_json::from_str(&data) {
return Some(parsed);
}
}
// Automatic migration from legacy `~/.config/insta360-link-controller/settings.json`
let legacy = Self::base_config_dir()
.join("insta360-link-controller")
.join("settings.json");
let data = std::fs::read_to_string(legacy).ok()?;
let parsed: Self = serde_json::from_str(&data).ok()?;
parsed.save();
Some(parsed)
} }
pub fn save(&self) { pub fn save(&self) {
File renamed without changes.
@@ -1,6 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use insta360_link_protocol::{ use open_360_link_protocol::{
ct_selectors, pu_selectors, xu1_selectors, xu2_selectors, CameraModel, DiscoveredCamera, ct_selectors, pu_selectors, xu1_selectors, xu2_selectors, CameraModel, DiscoveredCamera,
MockCameraTransport, NusbTransport, TransportError, UvcControlPacketHeader, MockCameraTransport, NusbTransport, TransportError, UvcControlPacketHeader,
UvcControlTransport, UvcRequest, UvcUnitIds, UvcControlTransport, UvcRequest, UvcUnitIds,
@@ -3,7 +3,7 @@ use crate::state::{
SHUTTER_DENOM_LIST, SHUTTER_DENOM_LIST,
}; };
use egui::{Color32, ColorImage, Context, TextureHandle, TextureOptions}; use egui::{Color32, ColorImage, Context, TextureHandle, TextureOptions};
use insta360_link_protocol::{TrackSpeed, UvcRect, VideoMode}; use open_360_link_protocol::{TrackSpeed, UvcRect, VideoMode};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, Command, Stdio}; use std::process::{Child, ChildStdin, Command, Stdio};
@@ -1,9 +1,9 @@
[package] [package]
name = "insta360-link-protocol" name = "open-360-link-protocol"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
description = "Pure-Rust cross-platform UVC & Extension Unit protocol library for Insta360 Link, Link 2, and Link 2C webcams" description = "Open 360 Link: Pure-Rust cross-platform UVC & Extension Unit protocol library for Insta360 Link, Link 2, and Link 2C webcams"
[dependencies] [dependencies]
thiserror.workspace = true thiserror.workspace = true
@@ -1,4 +1,4 @@
use insta360_link_protocol::*; use open_360_link_protocol::*;
/// Builds a synthetic USB Configuration Descriptor containing: /// Builds a synthetic USB Configuration Descriptor containing:
/// - Standard Interface Descriptor for UVC VideoControl (`bInterfaceNumber = 2`, `CC_VIDEO = 0x0e`, `SC_VIDEOCONTROL = 0x01`) /// - Standard Interface Descriptor for UVC VideoControl (`bInterfaceNumber = 2`, `CC_VIDEO = 0x0e`, `SC_VIDEOCONTROL = 0x01`)
@@ -1,16 +1,16 @@
[package] [package]
name = "insta360-link-server" name = "open-360-link-server"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
description = "Cross-platform HTTP REST & WebSocket remote control server for Insta360 Link cameras" description = "Open 360 Link: Cross-platform HTTP REST & WebSocket remote control server for Insta360 Link cameras"
[[bin]] [[bin]]
name = "insta360-link-server" name = "open-360-link-server"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" } open-360-link-protocol = { path = "../open-360-link-protocol" }
clap.workspace = true clap.workspace = true
tokio.workspace = true tokio.workspace = true
axum.workspace = true axum.workspace = true
@@ -7,7 +7,7 @@ use axum::{
routing::{get, post}, routing::{get, post},
Json, Router, Json, Router,
}; };
use insta360_link_protocol::{ use open_360_link_protocol::{
list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceCapabilities, list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceCapabilities,
DeviceInfo, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture, DeviceInfo, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture,
GestureFunction, HostPtzInfo, Insta360CameraController, MockCameraTransport, NusbTransport, GestureFunction, HostPtzInfo, Insta360CameraController, MockCameraTransport, NusbTransport,
@@ -1,16 +1,16 @@
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use insta360_link_protocol::{ use open_360_link_protocol::{
CameraModel, Insta360CameraController, NusbTransport, UvcControlTransport, CameraModel, Insta360CameraController, NusbTransport, UvcControlTransport,
}; };
use insta360_link_server::{build_router, new_mock_state, AppState, ServerTransport}; use open_360_link_server::{build_router, new_mock_state, AppState, ServerTransport};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command( #[command(
name = "insta360-link-server", name = "open-360-link-server",
version, version,
about = "Cross-platform HTTP REST & WebSocket remote control server for Insta360 Link cameras" about = "Open 360 Link: Cross-platform HTTP REST & WebSocket remote control server for Insta360 Link cameras"
)] )]
struct Cli { struct Cli {
/// Bind address for HTTP and WebSocket server /// Bind address for HTTP and WebSocket server
@@ -47,7 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env() tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "insta360_link_server=info,tower_http=info".into()), .unwrap_or_else(|_| "open_360_link_server=info,tower_http=info".into()),
) )
.init(); .init();
@@ -1,11 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use insta360_link_protocol::{ use open_360_link_protocol::{
AudioCaptureMode, CameraModel, CompositionStyle, ExtendFunction, FilterMode, Gesture, AudioCaptureMode, CameraModel, CompositionStyle, ExtendFunction, FilterMode, Gesture,
GestureFunction, TrackSpeed, VideoMode, GestureFunction, TrackSpeed, VideoMode,
}; };
use insta360_link_server::{ use open_360_link_server::{
build_router, new_mock_state, AudioRequest, CameraStateSnapshot, ImageRequest, ModeRequest, build_router, new_mock_state, AudioRequest, CameraStateSnapshot, ImageRequest, ModeRequest,
PresetRequest, PtzRequest, SystemRequest, TrackingRequest, PresetRequest, PtzRequest, SystemRequest, TrackingRequest,
}; };
+15 -15
View File
@@ -2,20 +2,20 @@
rust = "stable" rust = "stable"
[tasks.default] [tasks.default]
description = "Build and launch the Insta360 Link Controller desktop GUI" description = "Build and launch the Open 360 Link desktop GUI"
depends = ["gui"] depends = ["gui"]
[tasks.gui] [tasks.gui]
description = "Build and launch the Insta360 Link Controller desktop GUI" description = "Build and launch the Open 360 Link desktop GUI"
run = "cargo run --release -p insta360-link-gui" run = "cargo run --release -p open-360-link-gui"
[tasks.cli] [tasks.cli]
description = "Run the insta360-link-ctl CLI (e.g. mise run cli -- info)" description = "Run the open-360-link-ctl CLI (e.g. mise run cli -- info)"
run = "cargo run --release -p insta360-link-cli --" run = "cargo run --release -p open-360-link-cli --"
[tasks.server] [tasks.server]
description = "Start the HTTP & WebSocket remote control server on 127.0.0.1:8360" description = "Start the HTTP & WebSocket remote control server on 127.0.0.1:8360"
run = "cargo run --release -p insta360-link-server --" run = "cargo run --release -p open-360-link-server --"
[tasks.build] [tasks.build]
description = "Build all workspace crates in release mode" description = "Build all workspace crates in release mode"
@@ -27,7 +27,7 @@ run = "cargo test --workspace"
[tasks.self-test] [tasks.self-test]
description = "Run the automated live hardware GUI verification sequence" description = "Run the automated live hardware GUI verification sequence"
run = "INSTA360_GUI_SELF_TEST=1 cargo run --release -p insta360-link-gui" run = "INSTA360_GUI_SELF_TEST=1 cargo run --release -p open-360-link-gui"
[tasks.package-release] [tasks.package-release]
description = "Build and package release binaries for a target (usage: mise run package-release [target] [version])" description = "Build and package release binaries for a target (usage: mise run package-release [target] [version])"
@@ -67,15 +67,15 @@ case "$TARGET" in
*) ASSET_SUFFIX="$TARGET"; EXT="tar.gz" ;; *) ASSET_SUFFIX="$TARGET"; EXT="tar.gz" ;;
esac esac
STAGE_NAME="insta360-link-controller-v${VERSION}-${ASSET_SUFFIX}" STAGE_NAME="open-360-link-v${VERSION}-${ASSET_SUFFIX}"
STAGE_DIR="dist/${STAGE_NAME}" STAGE_DIR="dist/${STAGE_NAME}"
rm -rf "$STAGE_DIR" rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR" mkdir -p "$STAGE_DIR"
if [ "$EXT" = "zip" ]; then if [ "$EXT" = "zip" ]; then
cp "target/${TARGET}/release/insta360-link-gui.exe" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-gui.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-ctl.exe" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-ctl.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-server.exe" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-server.exe" "$STAGE_DIR/"
cp README.md "$STAGE_DIR/" cp README.md "$STAGE_DIR/"
python3 -c ' python3 -c '
import pathlib, sys, zipfile import pathlib, sys, zipfile
@@ -88,9 +88,9 @@ with zipfile.ZipFile(out_zip, "w", zipfile.ZIP_DEFLATED) as zf:
' "$STAGE_DIR" "dist/${STAGE_NAME}.zip" ' "$STAGE_DIR" "dist/${STAGE_NAME}.zip"
echo "Packaged dist/${STAGE_NAME}.zip" echo "Packaged dist/${STAGE_NAME}.zip"
else else
cp "target/${TARGET}/release/insta360-link-gui" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-gui" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-ctl" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-ctl" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-server" "$STAGE_DIR/" cp "target/${TARGET}/release/open-360-link-server" "$STAGE_DIR/"
cp README.md "$STAGE_DIR/" cp README.md "$STAGE_DIR/"
cp -r assets "$STAGE_DIR/" cp -r assets "$STAGE_DIR/"
tar -C dist -czf "dist/${STAGE_NAME}.tar.gz" "$STAGE_NAME" tar -C dist -czf "dist/${STAGE_NAME}.tar.gz" "$STAGE_NAME"
@@ -107,7 +107,7 @@ set -euo pipefail
VER="${1:-0.1.0}" VER="${1:-0.1.0}"
VER="${VER#v}" VER="${VER#v}"
env -u GITHUB_TOKEN -u GH_TOKEN -u GITHUB_AUTH_TOKEN gh workflow run release.yml \ env -u GITHUB_TOKEN -u GH_TOKEN -u GITHUB_AUTH_TOKEN gh workflow run release.yml \
--repo lucasoskorep/insta360-link-controller \ --repo lucasoskorep/open-360-link \
--ref master \ --ref master \
-f version="$VER" -f version="$VER"
echo "Triggered release workflow on master for v${VER}" echo "Triggered release workflow on master for v${VER}"