navigation: Add support for event creation and simplify the API

And also allow implement serialization with serde (behind a feature) to
allow sending navigation event through the network (for example from a
browser with a WebRTC data channel).
This commit is contained in:
Thibault Saunier 2021-12-23 14:25:20 +00:00
parent 56dfe0fe59
commit fc452036d2
4 changed files with 278 additions and 194 deletions

View file

@ -24,9 +24,11 @@ gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
once_cell = "1.0" once_cell = "1.0"
futures-channel = "0.3" futures-channel = "0.3"
fragile = "1" fragile = "1"
serde = { version = "1.0", optional = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
itertools = "0.10" itertools = "0.10"
serde_json = "1.0"
gir-format-check = "0.1" gir-format-check = "0.1"
[features] [features]
@ -38,6 +40,7 @@ v1_16 = ["gst/v1_16", "gst-base/v1_16", "ffi/v1_16", "v1_14"]
v1_18 = ["gst/v1_18", "gst-base/v1_18", "ffi/v1_18", "v1_16"] v1_18 = ["gst/v1_18", "gst-base/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-base/v1_20", "ffi/v1_20", "v1_18"] v1_20 = ["gst/v1_20", "gst-base/v1_20", "ffi/v1_20", "v1_18"]
dox = ["v1_20", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox"] dox = ["v1_20", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox"]
ser_de = ["serde"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["dox"] features = ["dox"]

View file

@ -81,6 +81,7 @@ impl ToValue for ColorBalanceType {
} }
} }
#[cfg_attr(feature = "ser_de", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
#[non_exhaustive] #[non_exhaustive]
#[doc(alias = "GstNavigationCommand")] #[doc(alias = "GstNavigationCommand")]

View file

@ -58,12 +58,9 @@ mod video_overlay;
pub use crate::video_overlay::is_video_overlay_prepare_window_handle_message; pub use crate::video_overlay::is_video_overlay_prepare_window_handle_message;
pub mod video_event; pub mod video_event;
#[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
pub use crate::video_event::MouseScrollEvent;
pub use crate::video_event::{ pub use crate::video_event::{
CommandEvent, DownstreamForceKeyUnitEvent, ForceKeyUnitEvent, KeyEvent, MouseButtonEvent, DownstreamForceKeyUnitEvent, ForceKeyUnitEvent, NavigationEvent, StillFrameEvent,
MouseMoveEvent, NavigationEvent, StillFrameEvent, UpstreamForceKeyUnitEvent, UpstreamForceKeyUnitEvent,
}; };
pub mod video_message; pub mod video_message;

View file

@ -1,9 +1,8 @@
// Take a look at the license at the top of the repository in the LICENSE file. // Take a look at the license at the top of the repository in the LICENSE file.
use crate::{NavigationCommand, NavigationEventType}; use crate::{NavigationCommand, NavigationEventType};
use glib::translate::{from_glib, from_glib_none, from_glib_full, IntoGlib}; use glib::translate::{from_glib, from_glib_full, IntoGlib};
use glib::ToSendValue; use glib::ToSendValue;
use std::{mem, ptr}; use std::mem;
// FIXME: Copy from gstreamer/src/event.rs // FIXME: Copy from gstreamer/src/event.rs
macro_rules! event_builder_generic_impl { macro_rules! event_builder_generic_impl {
@ -351,212 +350,296 @@ impl StillFrameEvent {
} }
} }
const NAVIGATION_EVENT_NAME: &str = "application/x-gst-navigation";
#[cfg_attr(feature = "ser_de", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ser_de", serde(tag = "event"))]
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct KeyEvent { pub enum NavigationEvent {
pub key: String, KeyPress {
key: String,
},
KeyRelease {
key: String,
},
MouseMove {
x: f64,
y: f64,
},
MouseButtonPress {
button: i32,
x: f64,
y: f64,
},
MouseButtonRelease {
button: i32,
x: f64,
y: f64,
},
Command {
command: NavigationCommand,
},
#[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
MouseScroll {
x: f64,
y: f64,
delta_x: f64,
delta_y: f64,
},
} }
impl KeyEvent { impl NavigationEvent {
#[doc(alias = "gst_navigation_event_parse_key_event")] pub fn new_key_press(key: &str) -> NavigationEvent {
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { Self::KeyPress {
let mut key = ptr::null(); key: key.to_string(),
let ret = from_glib(ffi::gst_navigation_event_parse_key_event(
event.as_mut_ptr(),
&mut key,
));
if ret {
Ok(Self {
key: from_glib_none(key),
})
} else {
Err(glib::bool_error!("Invalid key event"))
} }
} }
}
}
#[derive(Clone, PartialEq, Debug)] pub fn new_key_release(key: &str) -> NavigationEvent {
pub struct MouseButtonEvent {
pub button: i32,
pub x: f64,
pub y: f64,
}
impl MouseButtonEvent {
#[doc(alias = "gst_navigation_event_parse_mouse_button_event")]
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { Self::KeyRelease {
let mut button = mem::MaybeUninit::uninit(); key: key.to_string(),
let mut x = mem::MaybeUninit::uninit();
let mut y = mem::MaybeUninit::uninit();
let ret = from_glib(ffi::gst_navigation_event_parse_mouse_button_event(
event.as_mut_ptr(),
button.as_mut_ptr(),
x.as_mut_ptr(),
y.as_mut_ptr(),
));
let button = button.assume_init();
let x = x.assume_init();
let y = y.assume_init();
if ret {
Ok(Self { button, x, y })
} else {
Err(glib::bool_error!("Invalid mouse button event"))
} }
} }
}
}
#[derive(Clone, PartialEq, Debug)] pub fn new_mouse_move(x: f64, y: f64) -> NavigationEvent {
pub struct MouseMoveEvent {
pub x: f64,
pub y: f64,
}
impl MouseMoveEvent {
#[doc(alias = "gst_navigation_event_parse_mouse_move_event")]
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { Self::MouseMove { x, y }
let mut x = mem::MaybeUninit::uninit();
let mut y = mem::MaybeUninit::uninit();
let ret = from_glib(ffi::gst_navigation_event_parse_mouse_move_event(
event.as_mut_ptr(),
x.as_mut_ptr(),
y.as_mut_ptr(),
));
let x = x.assume_init();
let y = y.assume_init();
if ret {
Ok(Self { x, y })
} else {
Err(glib::bool_error!("Invalid mouse move event"))
} }
}
}
}
#[cfg(any(feature = "v1_18", feature = "dox"))] pub fn new_mouse_button_press(button: i32, x: f64, y: f64) -> NavigationEvent {
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
#[derive(Clone, PartialEq, Debug)]
pub struct MouseScrollEvent {
pub x: f64,
pub y: f64,
pub delta_x: f64,
pub delta_y: f64,
}
#[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
impl MouseScrollEvent {
#[doc(alias = "gst_navigation_event_parse_mouse_scroll_event")]
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { Self::MouseButtonPress { button, x, y }
let mut x = mem::MaybeUninit::uninit(); }
let mut y = mem::MaybeUninit::uninit();
let mut delta_x = mem::MaybeUninit::uninit(); pub fn new_mouse_button_release(button: i32, x: f64, y: f64) -> NavigationEvent {
let mut delta_y = mem::MaybeUninit::uninit(); assert_initialized_main_thread!();
let ret = from_glib(ffi::gst_navigation_event_parse_mouse_scroll_event( Self::MouseButtonRelease { button, x, y }
event.as_mut_ptr(), }
x.as_mut_ptr(),
y.as_mut_ptr(), #[cfg(any(feature = "v1_18", feature = "dox"))]
delta_x.as_mut_ptr(), #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
delta_y.as_mut_ptr(), pub fn new_mouse_scroll(x: f64, y: f64, delta_x: f64, delta_y: f64) -> NavigationEvent {
)); assert_initialized_main_thread!();
let x = x.assume_init(); Self::MouseScroll {
let y = y.assume_init();
let delta_x = delta_x.assume_init();
let delta_y = delta_y.assume_init();
if ret {
Ok(Self {
x, x,
y, y,
delta_x, delta_x,
delta_y, delta_y,
})
} else {
Err(glib::bool_error!("Invalid mouse button event"))
} }
} }
}
}
#[derive(Clone, PartialEq, Debug)] pub fn new_command(command: NavigationCommand) -> NavigationEvent {
pub struct CommandEvent {
cmd: NavigationCommand,
}
impl CommandEvent {
#[doc(alias = "gst_navigation_event_parse_command")]
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { Self::Command { command }
let mut command = mem::MaybeUninit::uninit();
let ret = from_glib(ffi::gst_navigation_event_parse_command(
event.as_mut_ptr(),
command.as_mut_ptr(),
));
let command = command.assume_init();
if ret {
Ok(Self {
cmd: from_glib(command),
})
} else {
Err(glib::bool_error!("Invalid navigation command event"))
} }
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum NavigationEvent {
KeyPress(KeyEvent),
KeyRelease(KeyEvent),
MouseMove(MouseMoveEvent),
MouseButtonPress(MouseButtonEvent),
MouseButtonRelease(MouseButtonEvent),
Command(CommandEvent),
#[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
MouseScroll(MouseScrollEvent),
}
impl NavigationEvent {
#[doc(alias = "gst_navigation_event_get_type")] #[doc(alias = "gst_navigation_event_get_type")]
pub fn type_(event: &gst::EventRef) -> NavigationEventType { pub fn type_(event: &gst::EventRef) -> NavigationEventType {
assert_initialized_main_thread!(); assert_initialized_main_thread!();
unsafe { from_glib(ffi::gst_navigation_event_get_type(event.as_mut_ptr())) } unsafe { from_glib(ffi::gst_navigation_event_get_type(event.as_mut_ptr())) }
} }
#[doc(alias = "gst_navigation_event_parse_mouse_button_event")]
#[doc(alias = "gst_navigation_event_parse_mouse_command")]
#[doc(alias = "gst_navigation_event_parse_mouse_scroll_event")]
#[doc(alias = "gst_navigation_event_parse_mouse_move_event")]
pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> { pub fn parse(event: &gst::EventRef) -> Result<Self, glib::error::BoolError> {
skip_assert_initialized!(); skip_assert_initialized!();
let event_type: NavigationEventType = Self::type_(event); let structure = event
.structure()
match event_type { .ok_or_else(|| glib::bool_error!("Invalid mouse event"))?;
NavigationEventType::MouseMove => MouseMoveEvent::parse(event).map(Self::MouseMove), Ok(match Self::type_(event) {
NavigationEventType::KeyPress => KeyEvent::parse(event).map(Self::KeyPress), NavigationEventType::MouseMove => Self::new_mouse_move(
NavigationEventType::KeyRelease => KeyEvent::parse(event).map(Self::KeyRelease), structure
.get("pointer_x")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("pointer_y")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
),
NavigationEventType::MouseButtonPress => Self::new_mouse_button_press(
structure
.get("button")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("pointer_x")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("pointer_y")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
),
NavigationEventType::MouseButtonRelease => Self::new_mouse_button_press(
structure
.get("button")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("pointer_x")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("pointer_y")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
),
#[cfg(any(feature = "v1_18", feature = "dox"))] #[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
NavigationEventType::MouseScroll => { NavigationEventType::MouseScroll => Self::new_mouse_scroll(
MouseScrollEvent::parse(event).map(Self::MouseScroll) structure
} .get("pointer_x")
NavigationEventType::MouseButtonPress => { .map_err(|_| glib::bool_error!("Invalid mouse event"))?,
MouseButtonEvent::parse(event).map(Self::MouseButtonPress) structure
} .get("pointer_y")
NavigationEventType::MouseButtonRelease => { .map_err(|_| glib::bool_error!("Invalid mouse event"))?,
MouseButtonEvent::parse(event).map(Self::MouseButtonRelease) structure
} .get("delta_pointer_x")
NavigationEventType::Command => CommandEvent::parse(event).map(Self::Command), .map_err(|_| glib::bool_error!("Invalid mouse event"))?,
structure
.get("delta_pointer_y")
.map_err(|_| glib::bool_error!("Invalid mouse event"))?,
),
NavigationEventType::KeyPress => Self::new_key_press(
structure
.get("key")
.map_err(|_| glib::bool_error!("Invalid key press event"))?,
),
NavigationEventType::KeyRelease => Self::new_key_release(
structure
.get("key")
.map_err(|_| glib::bool_error!("Invalid key press event"))?,
),
NavigationEventType::Command => Self::new_command(
structure
.get("command-code")
.map_err(|_| glib::bool_error!("Invalid key press event"))?,
),
NavigationEventType::Invalid | NavigationEventType::__Unknown(_) => { NavigationEventType::Invalid | NavigationEventType::__Unknown(_) => {
return Err(glib::bool_error!("Invalid navigation event")) return Err(glib::bool_error!("Invalid navigation event"))
} }
})
} }
pub fn build(&self) -> gst::Event {
skip_assert_initialized!();
gst::event::Navigation::new(match self {
Self::MouseMove { x, y } => gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "mouse-move")
.field("pointer_x", x)
.field("pointer_y", y)
.build(),
Self::MouseButtonPress { button, x, y } => {
gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "mouse-button-press")
.field("button", button)
.field("pointer_x", x)
.field("pointer_y", y)
.build()
}
Self::MouseButtonRelease { button, x, y } => {
gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "mouse-button-release")
.field("button", button)
.field("pointer_x", x)
.field("pointer_y", y)
.build()
}
#[cfg(any(feature = "v1_18", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_18")))]
Self::MouseScroll {
x,
y,
delta_x,
delta_y,
} => gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "mouse-scroll")
.field("pointer_x", x)
.field("pointer_y", y)
.field("delta_pointer_x", delta_x)
.field("delta_pointer_y", delta_y)
.build(),
Self::KeyPress { key } => gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "key-press")
.field("key", key)
.build(),
Self::KeyRelease { key } => gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "key-release")
.field("key", key)
.build(),
Self::Command { command } => gst::Structure::builder(NAVIGATION_EVENT_NAME)
.field("event", "command")
.field("command-code", command)
.build(),
})
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(feature = "ser_de")]
fn serialize_navigation_events() {
use crate::NavigationEvent;
gst::init().unwrap();
let ev = NavigationEvent::new_mouse_scroll(1.0, 2.0, 3.0, 4.0).build();
let navigation_event = NavigationEvent::parse(&ev).unwrap();
match &navigation_event {
NavigationEvent::MouseScroll {
x,
y,
delta_x,
delta_y,
} => {
assert!(*x == 1.0 && *y == 2.0 && *delta_x == 3.0 && *delta_y == 4.0);
}
_ => unreachable!(),
}
let json_event = serde_json::to_string(&navigation_event).unwrap();
assert_eq!(
json_event,
r#"{"event":"MouseScroll","x":1.0,"y":2.0,"delta_x":3.0,"delta_y":4.0}"#
);
let navigation_event: NavigationEvent = serde_json::from_str(&json_event).unwrap();
match &navigation_event {
NavigationEvent::MouseScroll {
x,
y,
delta_x,
delta_y,
} => {
assert!(*x == 1.0 && *y == 2.0 && *delta_x == 3.0 && *delta_y == 4.0);
}
_ => unreachable!(),
}
let ev = NavigationEvent::new_mouse_button_press(1, 1.0, 2.0).build();
let navigation_event = NavigationEvent::parse(&ev).unwrap();
match &navigation_event {
NavigationEvent::MouseButtonPress { button, x, y } => {
assert!(*button == 1 && *x == 1.0 && *y == 2.0);
}
_ => unreachable!(),
}
let json_event = serde_json::to_string(&navigation_event).unwrap();
assert_eq!(
json_event,
r#"{"event":"MouseButtonPress","button":1,"x":1.0,"y":2.0}"#
);
let ev = NavigationEvent::new_key_release("a").build();
let navigation_event = NavigationEvent::parse(&ev).unwrap();
match &navigation_event {
NavigationEvent::KeyRelease { key } => {
assert_eq!(key, "a");
}
_ => unreachable!(),
}
let json_event = serde_json::to_string(&navigation_event).unwrap();
assert_eq!(json_event, r#"{"event":"KeyRelease","key":"a"}"#);
} }
} }