Restructure protobufs and use them everywhere

This commit is contained in:
DatCaptainHorse
2025-10-21 18:41:45 +03:00
parent 32341574dc
commit 67f9a7d0a0
37 changed files with 3455 additions and 3074 deletions

View File

@@ -3151,6 +3151,7 @@ dependencies = [
"tokio-stream",
"tracing",
"tracing-subscriber",
"unsigned-varint 0.8.0",
"vimputti",
"webrtc",
]

View File

@@ -40,3 +40,4 @@ libp2p-tcp = { version = "0.44", features = ["tokio"] }
libp2p-websocket = "0.45"
dashmap = "6.1"
anyhow = "1.0"
unsigned-varint = "0.8"

View File

@@ -1,7 +1,5 @@
use crate::proto::proto::proto_input::InputType::{
ControllerAttach, ControllerAxis, ControllerButton, ControllerDetach, ControllerRumble,
ControllerStick, ControllerTrigger,
};
use crate::proto::proto::ProtoControllerAttach;
use crate::proto::proto::proto_message::Payload;
use anyhow::Result;
use std::collections::HashMap;
use std::sync::Arc;
@@ -48,157 +46,234 @@ impl ControllerInput {
pub struct ControllerManager {
vimputti_client: Arc<vimputti::client::VimputtiClient>,
cmd_tx: mpsc::Sender<crate::proto::proto::ProtoInput>,
cmd_tx: mpsc::Sender<Payload>,
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>, // (slot, strong, weak, duration_ms)
attach_tx: mpsc::Sender<ProtoControllerAttach>,
}
impl ControllerManager {
pub fn new(
vimputti_client: Arc<vimputti::client::VimputtiClient>,
) -> Result<(Self, mpsc::Receiver<(u32, u16, u16, u16)>)> {
let (cmd_tx, cmd_rx) = mpsc::channel(100);
let (rumble_tx, rumble_rx) = mpsc::channel(100);
) -> Result<(
Self,
mpsc::Receiver<(u32, u16, u16, u16)>,
mpsc::Receiver<ProtoControllerAttach>,
)> {
let (cmd_tx, cmd_rx) = mpsc::channel(512);
let (rumble_tx, rumble_rx) = mpsc::channel(256);
let (attach_tx, attach_rx) = mpsc::channel(64);
tokio::spawn(command_loop(
cmd_rx,
vimputti_client.clone(),
rumble_tx.clone(),
attach_tx.clone(),
));
Ok((
Self {
vimputti_client,
cmd_tx,
rumble_tx,
attach_tx,
},
rumble_rx,
attach_rx,
))
}
pub async fn send_command(&self, input: crate::proto::proto::ProtoInput) -> Result<()> {
self.cmd_tx.send(input).await?;
pub async fn send_command(&self, payload: Payload) -> Result<()> {
self.cmd_tx.send(payload).await?;
Ok(())
}
}
struct ControllerSlot {
controller: ControllerInput,
session_id: String,
last_activity: std::time::Instant,
}
// Returns first free controller slot from 0-7
fn get_free_slot(controllers: &HashMap<u32, ControllerSlot>) -> Option<u32> {
for slot in 0..8 {
if !controllers.contains_key(&slot) {
return Some(slot);
}
}
None
}
async fn command_loop(
mut cmd_rx: mpsc::Receiver<crate::proto::proto::ProtoInput>,
mut cmd_rx: mpsc::Receiver<Payload>,
vimputti_client: Arc<vimputti::client::VimputtiClient>,
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>,
attach_tx: mpsc::Sender<ProtoControllerAttach>,
) {
let mut controllers: HashMap<u32, ControllerInput> = HashMap::new();
while let Some(input) = cmd_rx.recv().await {
if let Some(input_type) = input.input_type {
match input_type {
ControllerAttach(data) => {
// Check if controller already exists in the slot, if so, ignore
if controllers.contains_key(&(data.slot as u32)) {
tracing::warn!(
"Controller slot {} already occupied, ignoring attach",
data.slot
let mut controllers: HashMap<u32, ControllerSlot> = HashMap::new();
while let Some(payload) = cmd_rx.recv().await {
match payload {
Payload::ControllerAttach(data) => {
let session_id = data.session_id.clone();
// Check if this session already has a slot (reconnection)
let existing_slot = controllers
.iter()
.find(|(_, slot)| slot.session_id == session_id && !session_id.is_empty())
.map(|(slot_num, _)| *slot_num);
let slot = existing_slot.or_else(|| get_free_slot(&controllers));
if let Some(slot) = slot {
if let Ok(mut controller) =
ControllerInput::new(data.id.clone(), &vimputti_client).await
{
let rumble_tx = rumble_tx.clone();
let attach_tx = attach_tx.clone();
controller
.device_mut()
.on_rumble(move |strong, weak, duration_ms| {
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms));
})
.await
.map_err(|e| {
tracing::warn!(
"Failed to register rumble callback for slot {}: {}",
slot,
e
);
})
.ok();
// Return to attach_tx what slot was assigned
let attach_info = ProtoControllerAttach {
id: data.id.clone(),
slot: slot as i32,
session_id: session_id.clone(),
};
match attach_tx.send(attach_info).await {
Ok(_) => {
controllers.insert(
slot,
ControllerSlot {
controller,
session_id: session_id.clone(),
last_activity: std::time::Instant::now(),
},
);
tracing::info!(
"Controller {} attached to slot {} (session: {})",
data.id,
slot,
session_id
);
}
Err(e) => {
tracing::error!(
"Failed to send attach info for slot {}: {}",
slot,
e
);
}
}
} else {
tracing::error!(
"Failed to create controller of type {} for slot {}",
data.id,
slot
);
}
}
}
Payload::ControllerDetach(data) => {
if controllers.remove(&(data.slot as u32)).is_some() {
tracing::info!("Controller detached from slot {}", data.slot);
} else {
tracing::warn!("No controller found in slot {} to detach", data.slot);
}
}
Payload::ControllerButton(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
let device = controller.controller.device();
device.button(button, data.pressed);
device.sync();
}
} else {
tracing::warn!("Controller slot {} not found for button event", data.slot);
}
}
Payload::ControllerStick(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.controller.device();
if data.stick == 0 {
// Left stick
device.axis(vimputti::Axis::LeftStickX, data.x);
device.sync();
device.axis(vimputti::Axis::LeftStickY, data.y);
} else if data.stick == 1 {
// Right stick
device.axis(vimputti::Axis::RightStickX, data.x);
device.sync();
device.axis(vimputti::Axis::RightStickY, data.y);
}
device.sync();
} else {
tracing::warn!("Controller slot {} not found for stick event", data.slot);
}
}
Payload::ControllerTrigger(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.controller.device();
if data.trigger == 0 {
// Left trigger
device.axis(vimputti::Axis::LowerLeftTrigger, data.value);
} else if data.trigger == 1 {
// Right trigger
device.axis(vimputti::Axis::LowerRightTrigger, data.value);
}
device.sync();
} else {
tracing::warn!("Controller slot {} not found for trigger event", data.slot);
}
}
Payload::ControllerAxis(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.controller.device();
if data.axis == 0 {
// dpad x
device.axis(vimputti::Axis::DPadX, data.value);
} else if data.axis == 1 {
// dpad y
device.axis(vimputti::Axis::DPadY, data.value);
}
device.sync();
}
}
Payload::ClientDisconnected(data) => {
tracing::info!(
"Client disconnected, cleaning up controller slots: {:?} (client session: {})",
data.controller_slots,
data.session_id
);
// Remove all controllers for the disconnected slots
for slot in &data.controller_slots {
if controllers.remove(&(*slot as u32)).is_some() {
tracing::info!(
"Removed controller from slot {} (client session: {})",
slot,
data.session_id
);
} else {
if let Ok(mut controller) =
ControllerInput::new(data.id.clone(), &vimputti_client).await
{
let slot = data.slot as u32;
let rumble_tx = rumble_tx.clone();
controller
.device_mut()
.on_rumble(move |strong, weak, duration_ms| {
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms));
})
.await
.map_err(|e| {
tracing::warn!(
"Failed to register rumble callback for slot {}: {}",
slot,
e
);
})
.ok();
controllers.insert(data.slot as u32, controller);
tracing::info!("Controller {} attached to slot {}", data.id, data.slot);
} else {
tracing::error!(
"Failed to create controller of type {} for slot {}",
data.id,
data.slot
);
}
tracing::warn!(
"No controller found in slot {} to cleanup (client session: {})",
slot,
data.session_id
);
}
}
ControllerDetach(data) => {
if controllers.remove(&(data.slot as u32)).is_some() {
tracing::info!("Controller detached from slot {}", data.slot);
} else {
tracing::warn!("No controller found in slot {} to detach", data.slot);
}
}
ControllerButton(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
let device = controller.device();
device.button(button, data.pressed);
device.sync();
}
} else {
tracing::warn!("Controller slot {} not found for button event", data.slot);
}
}
ControllerStick(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.device();
if data.stick == 0 {
// Left stick
device.axis(vimputti::Axis::LeftStickX, data.x);
device.sync();
device.axis(vimputti::Axis::LeftStickY, data.y);
} else if data.stick == 1 {
// Right stick
device.axis(vimputti::Axis::RightStickX, data.x);
device.sync();
device.axis(vimputti::Axis::RightStickY, data.y);
}
device.sync();
} else {
tracing::warn!("Controller slot {} not found for stick event", data.slot);
}
}
ControllerTrigger(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.device();
if data.trigger == 0 {
// Left trigger
device.axis(vimputti::Axis::LowerLeftTrigger, data.value);
} else if data.trigger == 1 {
// Right trigger
device.axis(vimputti::Axis::LowerRightTrigger, data.value);
}
device.sync();
} else {
tracing::warn!("Controller slot {} not found for trigger event", data.slot);
}
}
ControllerAxis(data) => {
if let Some(controller) = controllers.get(&(data.slot as u32)) {
let device = controller.device();
if data.axis == 0 {
// dpad x
device.axis(vimputti::Axis::DPadX, data.value);
} else if data.axis == 1 {
// dpad y
device.axis(vimputti::Axis::DPadY, data.value);
}
device.sync();
}
}
// Rumble will be outgoing event..
ControllerRumble(_) => {
//no-op
}
_ => {
//no-op
}
}
_ => {
//no-op
}
}
}

View File

@@ -3,7 +3,6 @@ mod enc_helper;
mod gpu;
mod input;
mod latency;
mod messages;
mod nestrisink;
mod p2p;
mod proto;
@@ -257,11 +256,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
None
}
};
let (controller_manager, rumble_rx) = if let Some(vclient) = vimputti_client {
let (controller_manager, rumble_rx) = ControllerManager::new(vclient)?;
(Some(Arc::new(controller_manager)), Some(rumble_rx))
let (controller_manager, rumble_rx, attach_rx) = if let Some(vclient) = vimputti_client {
let (controller_manager, rumble_rx, attach_rx) = ControllerManager::new(vclient)?;
(
Some(Arc::new(controller_manager)),
Some(rumble_rx),
Some(attach_rx),
)
} else {
(None, None)
(None, None, None)
};
/*** PIPELINE CREATION ***/
@@ -416,6 +419,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
video_source.clone(),
controller_manager,
rumble_rx,
attach_rx,
)
.await?;
let webrtcsink = BaseWebRTCSink::with_signaller(Signallable::from(signaller.clone()));
@@ -550,7 +554,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}
// Make sure QOS is disabled to avoid latency
video_encoder.set_property("qos", false);
video_encoder.set_property("qos", true);
// Optimize latency of pipeline
video_source

View File

@@ -1,50 +0,0 @@
use crate::latency::LatencyTracker;
use serde::{Deserialize, Serialize};
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageBase {
pub payload_type: String,
pub latency: Option<LatencyTracker>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageRaw {
#[serde(flatten)]
pub base: MessageBase,
pub data: serde_json::Value,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageLog {
#[serde(flatten)]
pub base: MessageBase,
pub level: String,
pub message: String,
pub time: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageMetrics {
#[serde(flatten)]
pub base: MessageBase,
pub usage_cpu: f64,
pub usage_memory: f64,
pub uptime: u64,
pub pipeline_latency: f64,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageICE {
#[serde(flatten)]
pub base: MessageBase,
pub candidate: RTCIceCandidateInit,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MessageSDP {
#[serde(flatten)]
pub base: MessageBase,
pub sdp: RTCSessionDescription,
}

View File

@@ -1,11 +1,11 @@
use crate::input::controller::ControllerManager;
use crate::messages::{MessageBase, MessageICE, MessageRaw, MessageSDP};
use crate::p2p::p2p::NestriConnection;
use crate::p2p::p2p_protocol_stream::NestriStreamProtocol;
use crate::proto::proto::proto_input::InputType::{
KeyDown, KeyUp, MouseKeyDown, MouseKeyUp, MouseMove, MouseMoveAbs, MouseWheel,
use crate::proto::proto::proto_message::Payload;
use crate::proto::proto::{
ProtoControllerAttach, ProtoControllerRumble, ProtoIce, ProtoMessage, ProtoSdp,
ProtoServerPushStream, RtcIceCandidateInit, RtcSessionDescriptionInit,
};
use crate::proto::proto::{ProtoInput, ProtoMessageInput};
use anyhow::Result;
use glib::subclass::prelude::*;
use gstreamer::glib;
@@ -16,8 +16,6 @@ use parking_lot::RwLock as PLRwLock;
use prost::Message;
use std::sync::{Arc, LazyLock};
use tokio::sync::{Mutex, mpsc};
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
pub struct Signaller {
stream_room: PLRwLock<Option<String>>,
@@ -26,6 +24,7 @@ pub struct Signaller {
data_channel: PLRwLock<Option<Arc<gstreamer_webrtc::WebRTCDataChannel>>>,
controller_manager: PLRwLock<Option<Arc<ControllerManager>>>,
rumble_rx: Mutex<Option<mpsc::Receiver<(u32, u16, u16, u16)>>>,
attach_rx: Mutex<Option<mpsc::Receiver<ProtoControllerAttach>>>,
}
impl Default for Signaller {
fn default() -> Self {
@@ -36,6 +35,7 @@ impl Default for Signaller {
data_channel: PLRwLock::new(None),
controller_manager: PLRwLock::new(None),
rumble_rx: Mutex::new(None),
attach_rx: Mutex::new(None),
}
}
}
@@ -74,11 +74,23 @@ impl Signaller {
*self.rumble_rx.lock().await = Some(rumble_rx);
}
// Change getter to take ownership:
pub async fn take_rumble_rx(&self) -> Option<mpsc::Receiver<(u32, u16, u16, u16)>> {
self.rumble_rx.lock().await.take()
}
pub async fn set_attach_rx(
&self,
attach_rx: mpsc::Receiver<crate::proto::proto::ProtoControllerAttach>,
) {
*self.attach_rx.lock().await = Some(attach_rx);
}
pub async fn take_attach_rx(
&self,
) -> Option<mpsc::Receiver<crate::proto::proto::ProtoControllerAttach>> {
self.attach_rx.lock().await.take()
}
pub fn set_data_channel(&self, data_channel: gstreamer_webrtc::WebRTCDataChannel) {
*self.data_channel.write() = Some(Arc::new(data_channel));
}
@@ -95,68 +107,85 @@ impl Signaller {
};
{
let self_obj = self.obj().clone();
stream_protocol.register_callback("answer", move |data| {
if let Ok(message) = serde_json::from_slice::<MessageSDP>(&data) {
let sdp = gst_sdp::SDPMessage::parse_buffer(message.sdp.sdp.as_bytes())
.map_err(|e| anyhow::anyhow!("Invalid SDP in 'answer': {e:?}"))?;
let answer = WebRTCSessionDescription::new(WebRTCSDPType::Answer, sdp);
Ok(self_obj.emit_by_name::<()>(
"session-description",
&[&"unique-session-id", &answer],
))
stream_protocol.register_callback("answer", move |msg| {
if let Some(payload) = msg.payload {
match payload {
Payload::Sdp(sdp) => {
if let Some(sdp) = sdp.sdp {
let sdp = gst_sdp::SDPMessage::parse_buffer(sdp.sdp.as_bytes())
.map_err(|e| {
anyhow::anyhow!("Invalid SDP in 'answer': {e:?}")
})?;
let answer =
WebRTCSessionDescription::new(WebRTCSDPType::Answer, sdp);
return Ok(self_obj.emit_by_name::<()>(
"session-description",
&[&"unique-session-id", &answer],
));
}
}
_ => {
tracing::warn!("Unexpected payload type for answer");
return Ok(());
}
}
} else {
anyhow::bail!("Failed to decode SDP message");
anyhow::bail!("Failed to decode answer message");
}
Ok(())
});
}
{
let self_obj = self.obj().clone();
stream_protocol.register_callback("ice-candidate", move |data| {
if let Ok(message) = serde_json::from_slice::<MessageICE>(&data) {
let candidate = message.candidate;
let sdp_m_line_index = candidate.sdp_mline_index.unwrap_or(0) as u32;
let sdp_mid = candidate.sdp_mid;
Ok(self_obj.emit_by_name::<()>(
"handle-ice",
&[
&"unique-session-id",
&sdp_m_line_index,
&sdp_mid,
&candidate.candidate,
],
))
stream_protocol.register_callback("ice-candidate", move |msg| {
if let Some(payload) = msg.payload {
match payload {
Payload::Ice(ice) => {
if let Some(candidate) = ice.candidate {
let sdp_m_line_index = candidate.sdp_m_line_index.unwrap_or(0);
return Ok(self_obj.emit_by_name::<()>(
"handle-ice",
&[
&"unique-session-id",
&sdp_m_line_index,
&candidate.sdp_mid,
&candidate.candidate,
],
));
}
}
_ => {
tracing::warn!("Unexpected payload type for ice-candidate");
return Ok(());
}
}
} else {
anyhow::bail!("Failed to decode ICE message");
}
Ok(())
});
}
{
let self_obj = self.obj().clone();
stream_protocol.register_callback("push-stream-ok", move |data| {
if let Ok(answer) = serde_json::from_slice::<MessageRaw>(&data) {
// Decode room name string
if let Some(room_name) = answer.data.as_str() {
gstreamer::info!(
gstreamer::CAT_DEFAULT,
"Received OK answer for room: {}",
room_name
);
} else {
gstreamer::error!(
gstreamer::CAT_DEFAULT,
"Failed to decode room name from answer"
);
}
// Send our SDP offer
Ok(self_obj.emit_by_name::<()>(
"session-requested",
&[
&"unique-session-id",
&"consumer-identifier",
&None::<WebRTCSessionDescription>,
],
))
stream_protocol.register_callback("push-stream-ok", move |msg| {
if let Some(payload) = msg.payload {
return match payload {
Payload::ServerPushStream(_res) => {
// Send our SDP offer
Ok(self_obj.emit_by_name::<()>(
"session-requested",
&[
&"unique-session-id",
&"consumer-identifier",
&None::<WebRTCSessionDescription>,
],
))
}
_ => {
tracing::warn!("Unexpected payload type for push-stream-ok");
Ok(())
}
};
} else {
anyhow::bail!("Failed to decode answer");
}
@@ -200,12 +229,14 @@ impl Signaller {
// Spawn async task to take the receiver and set up
tokio::spawn(async move {
let rumble_rx = signaller.imp().take_rumble_rx().await;
let attach_rx = signaller.imp().take_attach_rx().await;
let controller_manager =
signaller.imp().get_controller_manager();
setup_data_channel(
controller_manager,
rumble_rx,
attach_rx,
data_channel,
&wayland_src,
);
@@ -243,19 +274,18 @@ impl SignallableImpl for Signaller {
return;
};
let push_msg = MessageRaw {
base: MessageBase {
payload_type: "push-stream-room".to_string(),
latency: None,
},
data: serde_json::Value::from(stream_room),
};
let Some(stream_protocol) = self.get_stream_protocol() else {
gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return;
};
let push_msg = crate::proto::create_message(
Payload::ServerPushStream(ProtoServerPushStream {
room_name: stream_room,
}),
"push-stream-room",
None,
);
if let Err(e) = stream_protocol.send_message(&push_msg) {
tracing::error!("Failed to send push stream room message: {:?}", e);
}
@@ -266,20 +296,22 @@ impl SignallableImpl for Signaller {
}
fn send_sdp(&self, _session_id: &str, sdp: &WebRTCSessionDescription) {
let sdp_message = MessageSDP {
base: MessageBase {
payload_type: "offer".to_string(),
latency: None,
},
sdp: RTCSessionDescription::offer(sdp.sdp().as_text().unwrap()).unwrap(),
};
let Some(stream_protocol) = self.get_stream_protocol() else {
gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return;
};
if let Err(e) = stream_protocol.send_message(&sdp_message) {
let sdp_msg = crate::proto::create_message(
Payload::Sdp(ProtoSdp {
sdp: Some(RtcSessionDescriptionInit {
sdp: sdp.sdp().as_text().unwrap(),
r#type: "offer".to_string(),
}),
}),
"offer",
None,
);
if let Err(e) = stream_protocol.send_message(&sdp_msg) {
tracing::error!("Failed to send SDP message: {:?}", e);
}
}
@@ -291,26 +323,25 @@ impl SignallableImpl for Signaller {
sdp_m_line_index: u32,
sdp_mid: Option<String>,
) {
let candidate_init = RTCIceCandidateInit {
candidate: candidate.to_string(),
sdp_mid,
sdp_mline_index: Some(sdp_m_line_index as u16),
..Default::default()
};
let ice_message = MessageICE {
base: MessageBase {
payload_type: "ice-candidate".to_string(),
latency: None,
},
candidate: candidate_init,
};
let Some(stream_protocol) = self.get_stream_protocol() else {
gstreamer::error!(gstreamer::CAT_DEFAULT, "Stream protocol not set");
return;
};
if let Err(e) = stream_protocol.send_message(&ice_message) {
let candidate_init = RtcIceCandidateInit {
candidate: candidate.to_string(),
sdp_mid,
sdp_m_line_index: Some(sdp_m_line_index),
..Default::default() //username_fragment: Some(session_id.to_string()), TODO: required?
};
let ice_msg = crate::proto::create_message(
Payload::Ice(ProtoIce {
candidate: Some(candidate_init),
}),
"ice-candidate",
None,
);
if let Err(e) = stream_protocol.send_message(&ice_msg) {
tracing::error!("Failed to send ICE candidate message: {:?}", e);
}
}
@@ -352,6 +383,7 @@ impl ObjectImpl for Signaller {
fn setup_data_channel(
controller_manager: Option<Arc<ControllerManager>>,
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>, // (slot, strong, weak, duration_ms)
attach_rx: Option<mpsc::Receiver<ProtoControllerAttach>>,
data_channel: Arc<gstreamer_webrtc::WebRTCDataChannel>,
wayland_src: &gstreamer::Element,
) {
@@ -361,11 +393,11 @@ fn setup_data_channel(
// Spawn async processor
tokio::spawn(async move {
while let Some(data) = rx.recv().await {
match ProtoMessageInput::decode(data.as_slice()) {
Ok(message_input) => {
if let Some(message_base) = message_input.message_base {
match ProtoMessage::decode(data.as_slice()) {
Ok(msg_wrapper) => {
if let Some(message_base) = msg_wrapper.message_base {
if message_base.payload_type == "input" {
if let Some(input_data) = message_input.data {
if let Some(input_data) = msg_wrapper.payload {
if let Some(event) = handle_input_message(input_data) {
// Send the event to wayland source, result bool is ignored
let _ = wayland_src.send_event(event);
@@ -373,7 +405,7 @@ fn setup_data_channel(
}
} else if message_base.payload_type == "controllerInput" {
if let Some(controller_manager) = &controller_manager {
if let Some(input_data) = message_input.data {
if let Some(input_data) = msg_wrapper.payload {
let _ = controller_manager.send_command(input_data).await;
}
}
@@ -392,25 +424,16 @@ fn setup_data_channel(
let data_channel_clone = data_channel.clone();
tokio::spawn(async move {
while let Some((slot, strong, weak, duration_ms)) = rumble_rx.recv().await {
let rumble_msg = ProtoMessageInput {
message_base: Some(crate::proto::proto::ProtoMessageBase {
payload_type: "controllerInput".to_string(),
latency: None,
let rumble_msg = crate::proto::create_message(
Payload::ControllerRumble(ProtoControllerRumble {
slot: slot as i32,
low_frequency: weak as i32,
high_frequency: strong as i32,
duration: duration_ms as i32,
}),
data: Some(ProtoInput {
input_type: Some(
crate::proto::proto::proto_input::InputType::ControllerRumble(
crate::proto::proto::ProtoControllerRumble {
r#type: "ControllerRumble".to_string(),
slot: slot as i32,
low_frequency: weak as i32,
high_frequency: strong as i32,
duration: duration_ms as i32,
},
),
),
}),
};
"controllerInput",
None,
);
let data = rumble_msg.encode_to_vec();
let bytes = glib::Bytes::from_owned(data);
@@ -422,6 +445,27 @@ fn setup_data_channel(
});
}
// Spawn attach sender
if let Some(mut attach_rx) = attach_rx {
let data_channel_clone = data_channel.clone();
tokio::spawn(async move {
while let Some(attach_msg) = attach_rx.recv().await {
let proto_msg = crate::proto::create_message(
Payload::ControllerAttach(attach_msg),
"controllerInput",
None,
);
let data = proto_msg.encode_to_vec();
let bytes = glib::Bytes::from_owned(data);
if let Err(e) = data_channel_clone.send_data_full(Some(&bytes)) {
tracing::warn!("Failed to send controller attach data: {}", e);
}
}
});
}
data_channel.connect_on_message_data(move |_data_channel, data| {
if let Some(data) = data {
let _ = tx.send(data.to_vec());
@@ -429,68 +473,64 @@ fn setup_data_channel(
});
}
fn handle_input_message(input_msg: ProtoInput) -> Option<gstreamer::Event> {
if let Some(input_type) = input_msg.input_type {
match input_type {
MouseMove(data) => {
let structure = gstreamer::Structure::builder("MouseMoveRelative")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
fn handle_input_message(payload: Payload) -> Option<gstreamer::Event> {
match payload {
Payload::MouseMove(data) => {
let structure = gstreamer::Structure::builder("MouseMoveRelative")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
MouseMoveAbs(data) => {
let structure = gstreamer::Structure::builder("MouseMoveAbsolute")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
KeyDown(data) => {
let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", true)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
KeyUp(data) => {
let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", false)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
MouseWheel(data) => {
let structure = gstreamer::Structure::builder("MouseAxis")
.field("x", data.x as f64)
.field("y", data.y as f64)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
MouseKeyDown(data) => {
let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", true)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
MouseKeyUp(data) => {
let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", false)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
_ => None,
Some(gstreamer::event::CustomUpstream::new(structure))
}
} else {
None
Payload::MouseMoveAbs(data) => {
let structure = gstreamer::Structure::builder("MouseMoveAbsolute")
.field("pointer_x", data.x as f64)
.field("pointer_y", data.y as f64)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
Payload::KeyDown(data) => {
let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", true)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
Payload::KeyUp(data) => {
let structure = gstreamer::Structure::builder("KeyboardKey")
.field("key", data.key as u32)
.field("pressed", false)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
Payload::MouseWheel(data) => {
let structure = gstreamer::Structure::builder("MouseAxis")
.field("x", data.x as f64)
.field("y", data.y as f64)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
Payload::MouseKeyDown(data) => {
let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", true)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
Payload::MouseKeyUp(data) => {
let structure = gstreamer::Structure::builder("MouseButton")
.field("button", data.key as u32)
.field("pressed", false)
.build();
Some(gstreamer::event::CustomUpstream::new(structure))
}
_ => None,
}
}

View File

@@ -19,6 +19,7 @@ impl NestriSignaller {
wayland_src: Arc<gstreamer::Element>,
controller_manager: Option<Arc<ControllerManager>>,
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>,
attach_rx: Option<mpsc::Receiver<crate::proto::proto::ProtoControllerAttach>>,
) -> Result<Self, Box<dyn std::error::Error>> {
let obj: Self = glib::Object::new();
obj.imp().set_stream_room(room);
@@ -30,6 +31,9 @@ impl NestriSignaller {
if let Some(rumble_rx) = rumble_rx {
obj.imp().set_rumble_rx(rumble_rx).await;
}
if let Some(attach_rx) = attach_rx {
obj.imp().set_attach_rx(attach_rx).await;
}
Ok(obj)
}
}

View File

@@ -3,21 +3,22 @@ use crate::p2p::p2p_safestream::SafeStream;
use anyhow::Result;
use dashmap::DashMap;
use libp2p::StreamProtocol;
use prost::Message;
use std::sync::Arc;
use tokio::sync::mpsc;
// Cloneable callback type
pub type CallbackInner = dyn Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static;
pub type CallbackInner = dyn Fn(crate::proto::proto::ProtoMessage) -> Result<()> + Send + Sync + 'static;
pub struct Callback(Arc<CallbackInner>);
impl Callback {
pub fn new<F>(f: F) -> Self
where
F: Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static,
F: Fn(crate::proto::proto::ProtoMessage) -> Result<()> + Send + Sync + 'static,
{
Callback(Arc::new(f))
}
pub fn call(&self, data: Vec<u8>) -> Result<()> {
pub fn call(&self, data: crate::proto::proto::ProtoMessage) -> Result<()> {
self.0(data)
}
}
@@ -104,26 +105,31 @@ impl NestriStreamProtocol {
}
};
match serde_json::from_slice::<crate::messages::MessageBase>(&data) {
Ok(base_message) => {
let response_type = base_message.payload_type;
match crate::proto::proto::ProtoMessage::decode(data.as_slice()) {
Ok(message) => {
if let Some(base_message) = &message.message_base {
let response_type = &base_message.payload_type;
let response_type = response_type.clone();
// With DashMap, we don't need explicit locking
// we just get the callback directly if it exists
if let Some(callback) = callbacks.get(&response_type) {
// Execute the callback
if let Err(e) = callback.call(data.clone()) {
tracing::error!(
"Callback for response type '{}' errored: {:?}",
response_type,
e
// With DashMap, we don't need explicit locking
// we just get the callback directly if it exists
if let Some(callback) = callbacks.get(&response_type) {
// Execute the callback
if let Err(e) = callback.call(message) {
tracing::error!(
"Callback for response type '{}' errored: {:?}",
response_type,
e
);
}
} else {
tracing::warn!(
"No callback registered for response type: {}",
response_type
);
}
} else {
tracing::warn!(
"No callback registered for response type: {}",
response_type
);
tracing::error!("No base message in decoded protobuf message",);
}
}
Err(e) => {
@@ -154,8 +160,9 @@ impl NestriStreamProtocol {
})
}
pub fn send_message<M: serde::Serialize>(&self, message: &M) -> Result<()> {
let json_data = serde_json::to_vec(message)?;
pub fn send_message(&self, message: &crate::proto::proto::ProtoMessage) -> Result<()> {
let mut buf = Vec::new();
message.encode(&mut buf)?;
let Some(tx) = &self.tx else {
return Err(anyhow::Error::msg(
if self.read_handle.is_none() && self.write_handle.is_none() {
@@ -165,13 +172,13 @@ impl NestriStreamProtocol {
},
));
};
tx.try_send(json_data)?;
tx.try_send(buf)?;
Ok(())
}
pub fn register_callback<F>(&self, response_type: &str, callback: F)
where
F: Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static,
F: Fn(crate::proto::proto::ProtoMessage) -> Result<()> + Send + Sync + 'static,
{
self.callbacks
.insert(response_type.to_string(), Callback::new(callback));

View File

@@ -1,11 +1,9 @@
use anyhow::Result;
use byteorder::{BigEndian, ByteOrder};
use libp2p::futures::io::{ReadHalf, WriteHalf};
use libp2p::futures::{AsyncReadExt, AsyncWriteExt};
use std::sync::Arc;
use tokio::sync::Mutex;
const MAX_SIZE: usize = 1024 * 1024; // 1MB
use unsigned_varint::{decode, encode};
pub struct SafeStream {
stream_read: Arc<Mutex<ReadHalf<libp2p::Stream>>>,
@@ -29,34 +27,52 @@ impl SafeStream {
}
async fn send_with_length_prefix(&self, data: &[u8]) -> Result<()> {
if data.len() > MAX_SIZE {
anyhow::bail!("Data exceeds maximum size");
}
let mut buffer = Vec::with_capacity(4 + data.len());
buffer.extend_from_slice(&(data.len() as u32).to_be_bytes()); // Length prefix
buffer.extend_from_slice(data); // Payload
let mut stream_write = self.stream_write.lock().await;
stream_write.write_all(&buffer).await?; // Single write
// Encode length as varint
let mut length_buf = encode::usize_buffer();
let length_bytes = encode::usize(data.len(), &mut length_buf);
// Write varint length prefix
stream_write.write_all(length_bytes).await?;
// Write payload
stream_write.write_all(data).await?;
stream_write.flush().await?;
Ok(())
}
async fn receive_with_length_prefix(&self) -> Result<Vec<u8>> {
let mut stream_read = self.stream_read.lock().await;
// Read length prefix + data in one syscall
let mut length_prefix = [0u8; 4];
stream_read.read_exact(&mut length_prefix).await?;
let length = BigEndian::read_u32(&length_prefix) as usize;
// Read varint length prefix (up to 10 bytes for u64)
let mut length_buf = Vec::new();
let mut temp_byte = [0u8; 1];
if length > MAX_SIZE {
anyhow::bail!("Received data exceeds maximum size");
loop {
stream_read.read_exact(&mut temp_byte).await?;
length_buf.push(temp_byte[0]);
// Check if this is the last byte (MSB = 0)
if temp_byte[0] & 0x80 == 0 {
break;
}
// Protect against malicious infinite varints
if length_buf.len() > 10 {
anyhow::bail!("Invalid varint encoding");
}
}
// Decode the varint
let (length, _) = decode::usize(&length_buf)
.map_err(|e| anyhow::anyhow!("Failed to decode varint: {}", e))?;
// Read payload
let mut buffer = vec![0u8; length];
stream_read.read_exact(&mut buffer).await?;
Ok(buffer)
}
}

View File

@@ -1 +1,35 @@
pub mod proto;
pub struct CreateMessageOptions {
pub sequence_id: Option<String>,
pub latency: Option<proto::ProtoLatencyTracker>,
}
pub fn create_message(
payload: proto::proto_message::Payload,
payload_type: impl Into<String>,
options: Option<CreateMessageOptions>,
) -> proto::ProtoMessage {
let opts = options.unwrap_or(CreateMessageOptions {
sequence_id: None,
latency: None,
});
let latency = opts.latency.or_else(|| {
opts.sequence_id.map(|seq_id| proto::ProtoLatencyTracker {
sequence_id: seq_id,
timestamps: vec![proto::ProtoTimestampEntry {
stage: "created".to_string(),
time: Some(prost_types::Timestamp::from(std::time::SystemTime::now())),
}],
})
});
proto::ProtoMessage {
message_base: Some(proto::ProtoMessageBase {
payload_type: payload_type.into(),
latency,
}),
payload: Some(payload),
}
}

View File

@@ -1,202 +0,0 @@
// @generated
// This file is @generated by prost-build.
/// EntityState represents the state of an entity in the mesh (e.g., a room).
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EntityState {
/// Type of entity (e.g., "room")
#[prost(string, tag="1")]
pub entity_type: ::prost::alloc::string::String,
/// Unique identifier (e.g., room name)
#[prost(string, tag="2")]
pub entity_id: ::prost::alloc::string::String,
/// Whether the entity is active
#[prost(bool, tag="3")]
pub active: bool,
/// Relay ID that owns this entity
#[prost(string, tag="4")]
pub owner_relay_id: ::prost::alloc::string::String,
}
/// MeshMessage is the top-level message for all relay-to-relay communication.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct MeshMessage {
#[prost(oneof="mesh_message::Type", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13")]
pub r#type: ::core::option::Option<mesh_message::Type>,
}
/// Nested message and enum types in `MeshMessage`.
pub mod mesh_message {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Type {
/// Level 0
#[prost(message, tag="1")]
StateUpdate(super::StateUpdate),
#[prost(message, tag="2")]
Ack(super::Ack),
#[prost(message, tag="3")]
RetransmissionRequest(super::RetransmissionRequest),
#[prost(message, tag="4")]
Retransmission(super::Retransmission),
#[prost(message, tag="5")]
Heartbeat(super::Heartbeat),
#[prost(message, tag="6")]
SuspectRelay(super::SuspectRelay),
#[prost(message, tag="7")]
Disconnect(super::Disconnect),
/// Level 1
#[prost(message, tag="8")]
ForwardSdp(super::ForwardSdp),
#[prost(message, tag="9")]
ForwardIce(super::ForwardIce),
#[prost(message, tag="10")]
ForwardIngest(super::ForwardIngest),
#[prost(message, tag="11")]
StreamRequest(super::StreamRequest),
/// Level 2
#[prost(message, tag="12")]
Handshake(super::Handshake),
#[prost(message, tag="13")]
HandshakeResponse(super::HandshakeResponse),
}
}
/// Handshake to inititiate new connection to mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Handshake {
/// UUID of the relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// base64 encoded Diffie-Hellman public key
#[prost(string, tag="2")]
pub dh_public_key: ::prost::alloc::string::String,
}
/// HandshakeResponse to respond to a mesh joiner.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct HandshakeResponse {
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub dh_public_key: ::prost::alloc::string::String,
/// relay id to signature
#[prost(map="string, string", tag="3")]
pub approvals: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
}
/// Forwarded SDP from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardSdp {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub participant_id: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub sdp: ::prost::alloc::string::String,
/// "offer" or "answer"
#[prost(string, tag="4")]
pub r#type: ::prost::alloc::string::String,
}
/// Forwarded ICE candidate from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardIce {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub participant_id: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub candidate: ::prost::alloc::string::String,
}
/// Forwarded ingest room from another relay.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ForwardIngest {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
}
/// Stream request from mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StreamRequest {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
}
/// StateUpdate propagates entity state changes across the mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StateUpdate {
/// Unique sequence number for this update
#[prost(uint64, tag="1")]
pub sequence_number: u64,
/// Key: entity_id (e.g., room name), Value: EntityState
#[prost(map="string, message", tag="2")]
pub entities: ::std::collections::HashMap<::prost::alloc::string::String, EntityState>,
}
/// Ack acknowledges receipt of a StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Ack {
/// UUID of the acknowledging relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Sequence number being acknowledged
#[prost(uint64, tag="2")]
pub sequence_number: u64,
}
/// RetransmissionRequest requests a missed StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RetransmissionRequest {
/// UUID of the requesting relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Sequence number of the missed update
#[prost(uint64, tag="2")]
pub sequence_number: u64,
}
/// Retransmission resends a StateUpdate.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Retransmission {
/// UUID of the sending relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// The retransmitted update
#[prost(message, optional, tag="2")]
pub state_update: ::core::option::Option<StateUpdate>,
}
/// Heartbeat signals relay liveness.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Heartbeat {
/// UUID of the sending relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Time of the heartbeat
#[prost(message, optional, tag="2")]
pub timestamp: ::core::option::Option<::prost_types::Timestamp>,
}
/// SuspectRelay marks a relay as potentially unresponsive.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SuspectRelay {
/// UUID of the suspected relay
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Reason for suspicion (e.g., "no heartbeat")
#[prost(string, tag="2")]
pub reason: ::prost::alloc::string::String,
}
/// Disconnect signals to remove a relay from the mesh.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Disconnect {
/// UUID of the relay to disconnect
#[prost(string, tag="1")]
pub relay_id: ::prost::alloc::string::String,
/// Reason for disconnection (e.g., "unresponsive")
#[prost(string, tag="2")]
pub reason: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -20,80 +20,59 @@ pub struct ProtoLatencyTracker {
/// MouseMove message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoMouseMove {
/// Fixed value "MouseMove"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub x: i32,
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub y: i32,
}
/// MouseMoveAbs message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoMouseMoveAbs {
/// Fixed value "MouseMoveAbs"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub x: i32,
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub y: i32,
}
/// MouseWheel message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoMouseWheel {
/// Fixed value "MouseWheel"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub x: i32,
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub y: i32,
}
/// MouseKeyDown message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyDown {
/// Fixed value "MouseKeyDown"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub key: i32,
}
/// MouseKeyUp message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoMouseKeyUp {
/// Fixed value "MouseKeyUp"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub key: i32,
}
// Keyboard messages
/// KeyDown message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoKeyDown {
/// Fixed value "KeyDown"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub key: i32,
}
/// KeyUp message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoKeyUp {
/// Fixed value "KeyUp"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub key: i32,
}
// Controller messages
@@ -102,159 +81,167 @@ pub struct ProtoKeyUp {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoControllerAttach {
/// Fixed value "ControllerAttach"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// One of the following enums: "ps", "xbox" or "switch"
#[prost(string, tag="2")]
#[prost(string, tag="1")]
pub id: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub slot: i32,
/// Session ID of the client attaching the controller
#[prost(string, tag="3")]
pub session_id: ::prost::alloc::string::String,
}
/// ControllerDetach message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerDetach {
/// Fixed value "ControllerDetach"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
}
/// ControllerButton message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerButton {
/// Fixed value "ControllerButtons"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
/// Button code (linux input event code)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub button: i32,
/// true if pressed, false if released
#[prost(bool, tag="4")]
#[prost(bool, tag="3")]
pub pressed: bool,
}
/// ControllerTriggers message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerTrigger {
/// Fixed value "ControllerTriggers"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
/// Trigger number (0 for left, 1 for right)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub trigger: i32,
/// trigger value (-32768 to 32767)
#[prost(int32, tag="4")]
#[prost(int32, tag="3")]
pub value: i32,
}
/// ControllerSticks message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerStick {
/// Fixed value "ControllerStick"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
/// Stick number (0 for left, 1 for right)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub stick: i32,
/// X axis value (-32768 to 32767)
#[prost(int32, tag="4")]
#[prost(int32, tag="3")]
pub x: i32,
/// Y axis value (-32768 to 32767)
#[prost(int32, tag="5")]
#[prost(int32, tag="4")]
pub y: i32,
}
/// ControllerAxis message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerAxis {
/// Fixed value "ControllerAxis"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
/// Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub axis: i32,
/// axis value (-1 to 1)
#[prost(int32, tag="4")]
#[prost(int32, tag="3")]
pub value: i32,
}
/// ControllerRumble message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct ProtoControllerRumble {
/// Fixed value "ControllerRumble"
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// Slot number (0-3)
#[prost(int32, tag="2")]
#[prost(int32, tag="1")]
pub slot: i32,
/// Low frequency rumble (0-65535)
#[prost(int32, tag="3")]
#[prost(int32, tag="2")]
pub low_frequency: i32,
/// High frequency rumble (0-65535)
#[prost(int32, tag="4")]
#[prost(int32, tag="3")]
pub high_frequency: i32,
/// Duration in milliseconds
#[prost(int32, tag="5")]
#[prost(int32, tag="4")]
pub duration: i32,
}
/// Union of all Input types
// WebRTC + signaling
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoInput {
#[prost(oneof="proto_input::InputType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14")]
pub input_type: ::core::option::Option<proto_input::InputType>,
pub struct RtcIceCandidateInit {
#[prost(string, tag="1")]
pub candidate: ::prost::alloc::string::String,
#[prost(uint32, optional, tag="2")]
pub sdp_m_line_index: ::core::option::Option<u32>,
#[prost(string, optional, tag="3")]
pub sdp_mid: ::core::option::Option<::prost::alloc::string::String>,
#[prost(string, optional, tag="4")]
pub username_fragment: ::core::option::Option<::prost::alloc::string::String>,
}
/// Nested message and enum types in `ProtoInput`.
pub mod proto_input {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum InputType {
#[prost(message, tag="1")]
MouseMove(super::ProtoMouseMove),
#[prost(message, tag="2")]
MouseMoveAbs(super::ProtoMouseMoveAbs),
#[prost(message, tag="3")]
MouseWheel(super::ProtoMouseWheel),
#[prost(message, tag="4")]
MouseKeyDown(super::ProtoMouseKeyDown),
#[prost(message, tag="5")]
MouseKeyUp(super::ProtoMouseKeyUp),
#[prost(message, tag="6")]
KeyDown(super::ProtoKeyDown),
#[prost(message, tag="7")]
KeyUp(super::ProtoKeyUp),
#[prost(message, tag="8")]
ControllerAttach(super::ProtoControllerAttach),
#[prost(message, tag="9")]
ControllerDetach(super::ProtoControllerDetach),
#[prost(message, tag="10")]
ControllerButton(super::ProtoControllerButton),
#[prost(message, tag="11")]
ControllerTrigger(super::ProtoControllerTrigger),
#[prost(message, tag="12")]
ControllerStick(super::ProtoControllerStick),
#[prost(message, tag="13")]
ControllerAxis(super::ProtoControllerAxis),
#[prost(message, tag="14")]
ControllerRumble(super::ProtoControllerRumble),
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RtcSessionDescriptionInit {
#[prost(string, tag="1")]
pub sdp: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub r#type: ::prost::alloc::string::String,
}
/// ProtoICE message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoIce {
#[prost(message, optional, tag="1")]
pub candidate: ::core::option::Option<RtcIceCandidateInit>,
}
/// ProtoSDP message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoSdp {
#[prost(message, optional, tag="1")]
pub sdp: ::core::option::Option<RtcSessionDescriptionInit>,
}
/// ProtoRaw message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoRaw {
#[prost(string, tag="1")]
pub data: ::prost::alloc::string::String,
}
/// ProtoClientRequestRoomStream message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoClientRequestRoomStream {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub session_id: ::prost::alloc::string::String,
}
/// ProtoClientDisconnected message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoClientDisconnected {
#[prost(string, tag="1")]
pub session_id: ::prost::alloc::string::String,
#[prost(int32, repeated, tag="2")]
pub controller_slots: ::prost::alloc::vec::Vec<i32>,
}
/// ProtoServerPushStream message
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoServerPushStream {
#[prost(string, tag="1")]
pub room_name: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -266,10 +253,59 @@ pub struct ProtoMessageBase {
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtoMessageInput {
pub struct ProtoMessage {
#[prost(message, optional, tag="1")]
pub message_base: ::core::option::Option<ProtoMessageBase>,
#[prost(message, optional, tag="2")]
pub data: ::core::option::Option<ProtoInput>,
#[prost(oneof="proto_message::Payload", tags="2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25")]
pub payload: ::core::option::Option<proto_message::Payload>,
}
/// Nested message and enum types in `ProtoMessage`.
pub mod proto_message {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Payload {
/// Input types
#[prost(message, tag="2")]
MouseMove(super::ProtoMouseMove),
#[prost(message, tag="3")]
MouseMoveAbs(super::ProtoMouseMoveAbs),
#[prost(message, tag="4")]
MouseWheel(super::ProtoMouseWheel),
#[prost(message, tag="5")]
MouseKeyDown(super::ProtoMouseKeyDown),
#[prost(message, tag="6")]
MouseKeyUp(super::ProtoMouseKeyUp),
#[prost(message, tag="7")]
KeyDown(super::ProtoKeyDown),
#[prost(message, tag="8")]
KeyUp(super::ProtoKeyUp),
#[prost(message, tag="9")]
ControllerAttach(super::ProtoControllerAttach),
#[prost(message, tag="10")]
ControllerDetach(super::ProtoControllerDetach),
#[prost(message, tag="11")]
ControllerButton(super::ProtoControllerButton),
#[prost(message, tag="12")]
ControllerTrigger(super::ProtoControllerTrigger),
#[prost(message, tag="13")]
ControllerStick(super::ProtoControllerStick),
#[prost(message, tag="14")]
ControllerAxis(super::ProtoControllerAxis),
#[prost(message, tag="15")]
ControllerRumble(super::ProtoControllerRumble),
/// Signaling types
#[prost(message, tag="20")]
Ice(super::ProtoIce),
#[prost(message, tag="21")]
Sdp(super::ProtoSdp),
#[prost(message, tag="22")]
Raw(super::ProtoRaw),
#[prost(message, tag="23")]
ClientRequestRoomStream(super::ProtoClientRequestRoomStream),
#[prost(message, tag="24")]
ClientDisconnected(super::ProtoClientDisconnected),
#[prost(message, tag="25")]
ServerPushStream(super::ProtoServerPushStream),
}
}
// @@protoc_insertion_point(module)