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",
]
[[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]]
name = "io-kit-sys"
version = "0.4.1"
@@ -2623,6 +2569,60 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "orbclient"
version = "0.3.55"
+5 -5
View File
@@ -1,10 +1,10 @@
[workspace]
resolver = "2"
members = [
"crates/insta360-link-protocol",
"crates/insta360-link-cli",
"crates/insta360-link-gui",
"crates/insta360-link-server",
"crates/open-360-link-protocol",
"crates/open-360-link-cli",
"crates/open-360-link-gui",
"crates/open-360-link-server",
]
[workspace.package]
@@ -12,7 +12,7 @@ version = "0.0.0"
edition = "2021"
license = "MIT OR Apache-2.0"
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]
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.
@@ -12,10 +12,10 @@ Built by reverse-engineering the proprietary USB & UVC Extension Unit (`XU1` and
| 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. |
| **`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. |
| **`insta360-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-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-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. |
| **`open-360-link-server`** | Asynchronous HTTP REST & WebSocket daemon (`axum` + `tokio`) for remote control, Stream Deck / OBS integration, and real-time state synchronization. |
| **`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/`:
- `target/release/insta360-link-ctl`
- `target/release/insta360-link-server`
- `target/release/insta360-link-gui`
- `target/release/open-360-link-ctl`
- `target/release/open-360-link-server`
- `target/release/open-360-link-gui`
---
@@ -71,12 +71,12 @@ The compiled binaries will be placed in `target/release/`:
### 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
yay -S insta360-link-controller-bin
yay -S open-360-link-bin
```
### macOS
@@ -87,74 +87,73 @@ On macOS, `nusb` uses `IOKit` to send UVC class-specific control requests direct
## 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
# 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
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)
cargo run -p insta360-link-cli -- ptz --pan 15.0 --tilt -10.0 --zoom 150
# Move gimbal to absolute pan/tilt angles and set zoom (100 = 1.0x, 200 = 2.0x)
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
cargo run -p insta360-link-cli -- home
cargo run -p open-360-link-cli -- ptz reset
# Save and recall gimbal preset slots (0..5)
cargo run -p insta360-link-cli -- preset save 0
cargo run -p insta360-link-cli -- preset recall 0
# 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
# Switch AI Video Mode (normal, auto-tracking, whiteboard, overhead, deskview, auto-framing, smart-whiteboard)
cargo run -p open-360-link-cli -- mode set auto-tracking
cargo run -p open-360-link-cli -- tracking speed fast
# 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)
cargo run -p insta360-link-cli -- audio --mode voice-focus --denoise on
# Configure microphone beamforming mode (voice-focus, noise-reduction, music-mode, original, sound-wall)
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
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:
```bash
# 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
curl http://127.0.0.1:8360/api/state
curl http://127.0.0.1:9360/api/state
# 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" \
-d '{"pan_deg": 20.0, "tilt_deg": -5.0, "zoom": 180}'
# 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" \
-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
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]
name = "insta360-link-cli"
name = "open-360-link-cli"
version.workspace = true
edition.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]]
name = "insta360-link-ctl"
name = "open-360-link-ctl"
path = "src/main.rs"
[dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" }
open-360-link-protocol = { path = "../open-360-link-protocol" }
clap.workspace = true
serde.workspace = true
serde_json.workspace = true
@@ -1,5 +1,5 @@
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
use insta360_link_protocol::{
use open_360_link_protocol::{
list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceInfo,
DeviceStatus, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture,
GestureFunction, HostPtzInfo, ImageOperation, Insta360CameraController, MockCameraTransport,
@@ -11,9 +11,9 @@ use std::process::ExitCode;
#[derive(Parser, Debug)]
#[command(
name = "insta360-link-ctl",
name = "open-360-link-ctl",
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 {
/// Run against an in-memory simulated camera model instead of physical USB
@@ -1,16 +1,16 @@
[package]
name = "insta360-link-gui"
name = "open-360-link-gui"
version.workspace = true
edition.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]]
name = "insta360-link-gui"
name = "open-360-link-gui"
path = "src/main.rs"
[dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" }
open-360-link-protocol = { path = "../open-360-link-protocol" }
eframe.workspace = true
egui.workspace = true
image = { version = "0.25", default-features = false, features = ["png"] }
@@ -7,7 +7,7 @@ mod video_engine;
use std::time::{Duration, Instant};
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,
MockCameraTransport, NusbTransport, PowerLineFrequency, VideoMode, VideoModeAuxData,
};
@@ -181,41 +181,41 @@ impl Insta360App {
if let Ok(ext) = self.controller.get_extend_function_status() {
for (func, en) in ext {
match func {
insta360_link_protocol::ExtendFunction::AiZoom => {
open_360_link_protocol::ExtendFunction::AiZoom => {
self.state.ai_zoom_enabled = en
}
insta360_link_protocol::ExtendFunction::AllGestures => {
open_360_link_protocol::ExtendFunction::AllGestures => {
self.state.enable_all_gestures = en
}
insta360_link_protocol::ExtendFunction::AutoTracking => {
open_360_link_protocol::ExtendFunction::AutoTracking => {
self.state.enable_auto_tracking = en
}
insta360_link_protocol::ExtendFunction::SingleTapTracking => {
open_360_link_protocol::ExtendFunction::SingleTapTracking => {
self.state.enable_single_tap_tracking = en
}
insta360_link_protocol::ExtendFunction::Hdr => self.state.hdr_enabled = en,
insta360_link_protocol::ExtendFunction::HorizontalMirror => {
open_360_link_protocol::ExtendFunction::Hdr => self.state.hdr_enabled = en,
open_360_link_protocol::ExtendFunction::HorizontalMirror => {
self.state.horizontal_mirror = en
}
insta360_link_protocol::ExtendFunction::VerticalMirror => {
open_360_link_protocol::ExtendFunction::VerticalMirror => {
self.state.vertical_mirror = en
}
insta360_link_protocol::ExtendFunction::VerticalMode => {
open_360_link_protocol::ExtendFunction::VerticalMode => {
self.state.vertical_resolution_mode = en
}
insta360_link_protocol::ExtendFunction::ForcedVertical => {
open_360_link_protocol::ExtendFunction::ForcedVertical => {
self.state.forced_vertical_lock = en
}
insta360_link_protocol::ExtendFunction::LowResolution => {
open_360_link_protocol::ExtendFunction::LowResolution => {
self.state.low_resolution_mode = en
}
insta360_link_protocol::ExtendFunction::SmartAdjustment => {
open_360_link_protocol::ExtendFunction::SmartAdjustment => {
self.state.smart_adjustment = en
}
insta360_link_protocol::ExtendFunction::StartupParams => {
open_360_link_protocol::ExtendFunction::StartupParams => {
self.state.startup_params_enabled = en
}
insta360_link_protocol::ExtendFunction::UltimatePrivacyMode => {
open_360_link_protocol::ExtendFunction::UltimatePrivacyMode => {
self.state.ultimate_privacy = en
}
_ => {}
@@ -231,11 +231,11 @@ impl Insta360App {
if let Ok(gestures) = self.controller.get_gesture_status() {
for (g, en) in gestures {
match g {
insta360_link_protocol::Gesture::Ok => self.state.use_gesture_ok = en,
insta360_link_protocol::Gesture::Palm => self.state.use_gesture_palm = en,
insta360_link_protocol::Gesture::LShape => self.state.use_gesture_l = en,
insta360_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::Ok => self.state.use_gesture_ok = en,
open_360_link_protocol::Gesture::Palm => self.state.use_gesture_palm = en,
open_360_link_protocol::Gesture::LShape => self.state.use_gesture_l = en,
open_360_link_protocol::Gesture::VShape => self.state.use_gesture_v = en,
open_360_link_protocol::Gesture::Rock => self.state.use_gesture_rock = en,
}
}
}
@@ -325,15 +325,15 @@ impl Insta360App {
.controller
.set_all_gestures_enabled(self.state.enable_all_gestures);
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::Palm,
open_360_link_protocol::Gesture::Palm,
self.state.use_gesture_palm,
);
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::VShape,
open_360_link_protocol::Gesture::VShape,
self.state.use_gesture_v,
);
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::LShape,
open_360_link_protocol::Gesture::LShape,
self.state.use_gesture_l,
);
let _ = self
@@ -412,14 +412,14 @@ impl Insta360App {
)
.show(ctx, |ui| {
ui.horizontal_centered(|ui| {
// Left: Green Insta360 camera icon + "Insta360 Link Controller" title
// Left: Green camera icon + "Open 360 Link" title
ui.label(
RichText::new("📷")
.color(theme.brand_green)
.size(17.0_f32),
);
ui.label(
RichText::new("Insta360 Link Controller")
RichText::new("Open 360 Link")
.strong()
.size(14.5_f32)
.color(theme.text01),
@@ -1257,7 +1257,7 @@ impl Insta360App {
.changed()
{
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::Palm,
open_360_link_protocol::Gesture::Palm,
self.state.use_gesture_palm,
);
}
@@ -1267,7 +1267,7 @@ impl Insta360App {
.changed()
{
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::VShape,
open_360_link_protocol::Gesture::VShape,
self.state.use_gesture_v,
);
}
@@ -1277,7 +1277,7 @@ impl Insta360App {
.changed()
{
let _ = self.controller.set_gesture_enabled(
insta360_link_protocol::Gesture::LShape,
open_360_link_protocol::Gesture::LShape,
self.state.use_gesture_l,
);
}
@@ -1455,13 +1455,13 @@ impl Insta360App {
ui.label("Tracking Speed:");
egui::ComboBox::from_id_salt("tb_track_speed")
.selected_text(match self.state.track_speed {
insta360_link_protocol::TrackSpeed::Fast => {
open_360_link_protocol::TrackSpeed::Fast => {
"Quick"
}
insta360_link_protocol::TrackSpeed::Normal => {
open_360_link_protocol::TrackSpeed::Normal => {
"Ordinary"
}
insta360_link_protocol::TrackSpeed::Slow => {
open_360_link_protocol::TrackSpeed::Slow => {
"Slow"
}
})
@@ -1469,37 +1469,37 @@ impl Insta360App {
if ui
.selectable_value(
&mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Fast,
open_360_link_protocol::TrackSpeed::Fast,
"Quick",
)
.clicked()
{
let _ = self.controller.set_track_speed(
insta360_link_protocol::TrackSpeed::Fast,
open_360_link_protocol::TrackSpeed::Fast,
);
}
if ui
.selectable_value(
&mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Normal,
open_360_link_protocol::TrackSpeed::Normal,
"Ordinary",
)
.clicked()
{
let _ = self.controller.set_track_speed(
insta360_link_protocol::TrackSpeed::Normal,
open_360_link_protocol::TrackSpeed::Normal,
);
}
if ui
.selectable_value(
&mut self.state.track_speed,
insta360_link_protocol::TrackSpeed::Slow,
open_360_link_protocol::TrackSpeed::Slow,
"Slow",
)
.clicked()
{
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 _ = 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 _ = 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 _ = 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_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();
println!(
@@ -2569,16 +2569,16 @@ fn main() -> eframe::Result<()> {
// Matching webcam.qml: width: 1440, height: 714, minimumWidth: 1120, minimumHeight: 536
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_app_id("insta360-link-controller")
.with_app_id("open-360-link")
.with_icon(build_app_icon())
.with_inner_size([1440.0_f32, 714.0_f32])
.with_min_inner_size([1120.0_f32, 536.0_f32])
.with_title("Insta360 Link Controller"),
.with_title("Open 360 Link"),
..Default::default()
};
eframe::run_native(
"Insta360 Link Controller",
"Open 360 Link",
native_options,
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,
ScrollArea, Sense, Stroke, TextEdit, Ui, Vec2,
};
use insta360_link_protocol::{
use open_360_link_protocol::{
AudioCaptureMode, CameraModel, CompositionStyle, ExposureCurve, FilterMode, HostPtzInfo,
ImageOperation, Insta360CameraController, PowerLineFrequency, TrackSpeed, UvcControlTransport,
Gesture,
@@ -1,7 +1,7 @@
#![allow(dead_code)]
use egui::{ColorImage, Pos2, TextureHandle};
pub use insta360_link_protocol::*;
pub use open_360_link_protocol::*;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Instant;
@@ -285,7 +285,7 @@ pub struct CameraAppState {
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)]
pub struct PersistedAppSettings {
pub format_preset: VideoFormatPreset,
@@ -325,12 +325,10 @@ pub struct PersistedAppSettings {
}
impl PersistedAppSettings {
pub fn config_path() -> PathBuf {
fn base_config_dir() -> PathBuf {
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
if !xdg.trim().is_empty() {
return PathBuf::from(xdg)
.join("insta360-link-controller")
.join("settings.json");
return PathBuf::from(xdg);
}
}
let home = std::env::var("HOME")
@@ -338,14 +336,29 @@ impl PersistedAppSettings {
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."));
home.join(".config")
.join("insta360-link-controller")
}
pub fn config_path() -> PathBuf {
Self::base_config_dir()
.join("open-360-link")
.join("settings.json")
}
pub fn load() -> Option<Self> {
let path = Self::config_path();
let data = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&data).ok()
if let Ok(data) = std::fs::read_to_string(&path) {
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) {
File renamed without changes.
@@ -1,6 +1,6 @@
#![allow(dead_code)]
use insta360_link_protocol::{
use open_360_link_protocol::{
ct_selectors, pu_selectors, xu1_selectors, xu2_selectors, CameraModel, DiscoveredCamera,
MockCameraTransport, NusbTransport, TransportError, UvcControlPacketHeader,
UvcControlTransport, UvcRequest, UvcUnitIds,
@@ -3,7 +3,7 @@ use crate::state::{
SHUTTER_DENOM_LIST,
};
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::path::{Path, PathBuf};
use std::process::{Child, ChildStdin, Command, Stdio};
@@ -1,9 +1,9 @@
[package]
name = "insta360-link-protocol"
name = "open-360-link-protocol"
version.workspace = true
edition.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]
thiserror.workspace = true
@@ -1,4 +1,4 @@
use insta360_link_protocol::*;
use open_360_link_protocol::*;
/// Builds a synthetic USB Configuration Descriptor containing:
/// - Standard Interface Descriptor for UVC VideoControl (`bInterfaceNumber = 2`, `CC_VIDEO = 0x0e`, `SC_VIDEOCONTROL = 0x01`)
@@ -1,16 +1,16 @@
[package]
name = "insta360-link-server"
name = "open-360-link-server"
version.workspace = true
edition.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]]
name = "insta360-link-server"
name = "open-360-link-server"
path = "src/main.rs"
[dependencies]
insta360-link-protocol = { path = "../insta360-link-protocol" }
open-360-link-protocol = { path = "../open-360-link-protocol" }
clap.workspace = true
tokio.workspace = true
axum.workspace = true
@@ -7,7 +7,7 @@ use axum::{
routing::{get, post},
Json, Router,
};
use insta360_link_protocol::{
use open_360_link_protocol::{
list_connected_cameras, AudioCaptureMode, CameraModel, CompositionStyle, DeviceCapabilities,
DeviceInfo, DiscoveredCamera, ExtendFunction, FilterMode, ForbiddenAreaInfo, Gesture,
GestureFunction, HostPtzInfo, Insta360CameraController, MockCameraTransport, NusbTransport,
@@ -1,16 +1,16 @@
use clap::{Parser, ValueEnum};
use insta360_link_protocol::{
use open_360_link_protocol::{
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::sync::{Arc, Mutex};
#[derive(Parser, Debug)]
#[command(
name = "insta360-link-server",
name = "open-360-link-server",
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 {
/// Bind address for HTTP and WebSocket server
@@ -47,7 +47,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
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();
@@ -1,11 +1,11 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use insta360_link_protocol::{
use open_360_link_protocol::{
AudioCaptureMode, CameraModel, CompositionStyle, ExtendFunction, FilterMode, Gesture,
GestureFunction, TrackSpeed, VideoMode,
};
use insta360_link_server::{
use open_360_link_server::{
build_router, new_mock_state, AudioRequest, CameraStateSnapshot, ImageRequest, ModeRequest,
PresetRequest, PtzRequest, SystemRequest, TrackingRequest,
};
+15 -15
View File
@@ -2,20 +2,20 @@
rust = "stable"
[tasks.default]
description = "Build and launch the Insta360 Link Controller desktop GUI"
description = "Build and launch the Open 360 Link desktop GUI"
depends = ["gui"]
[tasks.gui]
description = "Build and launch the Insta360 Link Controller desktop GUI"
run = "cargo run --release -p insta360-link-gui"
description = "Build and launch the Open 360 Link desktop GUI"
run = "cargo run --release -p open-360-link-gui"
[tasks.cli]
description = "Run the insta360-link-ctl CLI (e.g. mise run cli -- info)"
run = "cargo run --release -p insta360-link-cli --"
description = "Run the open-360-link-ctl CLI (e.g. mise run cli -- info)"
run = "cargo run --release -p open-360-link-cli --"
[tasks.server]
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]
description = "Build all workspace crates in release mode"
@@ -27,7 +27,7 @@ run = "cargo test --workspace"
[tasks.self-test]
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]
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" ;;
esac
STAGE_NAME="insta360-link-controller-v${VERSION}-${ASSET_SUFFIX}"
STAGE_NAME="open-360-link-v${VERSION}-${ASSET_SUFFIX}"
STAGE_DIR="dist/${STAGE_NAME}"
rm -rf "$STAGE_DIR"
mkdir -p "$STAGE_DIR"
if [ "$EXT" = "zip" ]; then
cp "target/${TARGET}/release/insta360-link-gui.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-ctl.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-server.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-gui.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-ctl.exe" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-server.exe" "$STAGE_DIR/"
cp README.md "$STAGE_DIR/"
python3 -c '
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"
echo "Packaged dist/${STAGE_NAME}.zip"
else
cp "target/${TARGET}/release/insta360-link-gui" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-ctl" "$STAGE_DIR/"
cp "target/${TARGET}/release/insta360-link-server" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-gui" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-ctl" "$STAGE_DIR/"
cp "target/${TARGET}/release/open-360-link-server" "$STAGE_DIR/"
cp README.md "$STAGE_DIR/"
cp -r assets "$STAGE_DIR/"
tar -C dist -czf "dist/${STAGE_NAME}.tar.gz" "$STAGE_NAME"
@@ -107,7 +107,7 @@ set -euo pipefail
VER="${1:-0.1.0}"
VER="${VER#v}"
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 \
-f version="$VER"
echo "Triggered release workflow on master for v${VER}"