feat(panel): seats

This commit is contained in:
Nova
2022-08-31 17:22:13 -04:00
parent a69ea4b34d
commit dd6c9fee7c
6 changed files with 502 additions and 71 deletions

View File

@@ -7,8 +7,10 @@ use crate::core::registry::Registry;
use crate::wayland::panel_item::{register_panel_item_ui_flex, PanelItem};
use anyhow::{anyhow, ensure, Result};
use lazy_static::lazy_static;
use libstardustxr::flex::flexbuffer_from_vector_arguments;
use nanoid::nanoid;
use parking_lot::Mutex;
use std::ops::Deref;
use std::sync::{Arc, Weak};
lazy_static! {
@@ -141,10 +143,24 @@ impl Drop for Item {
}
}
pub trait ItemSpecialization {
fn serialize_start_data(&self, vec: &mut flexbuffers::VectorBuilder);
}
pub enum ItemType {
Environment(EnvironmentItem),
Panel(PanelItem),
}
impl Deref for ItemType {
type Target = dyn ItemSpecialization;
fn deref(&self) -> &Self::Target {
match self {
ItemType::Environment(item) => item,
ItemType::Panel(item) => item,
}
}
}
pub struct EnvironmentItem {
path: String,
@@ -169,6 +185,11 @@ impl EnvironmentItem {
Ok(flexbuffers::singleton(path?.as_str()))
}
}
impl ItemSpecialization for EnvironmentItem {
fn serialize_start_data(&self, vec: &mut flexbuffers::VectorBuilder) {
vec.push(self.path.as_str());
}
}
pub struct ItemUI {
node: Weak<Node>,
@@ -208,7 +229,16 @@ impl ItemUI {
let (alias_node, _) =
item.make_alias(&node.get_client(), &(node.get_path().to_string() + "/item"));
self.aliases.add(Arc::downgrade(&alias_node));
self.send_state("create", item.uid.as_str());
let _ = node.send_remote_signal(
"create",
&flexbuffer_from_vector_arguments(|vec| {
vec.push(item.uid.as_str());
let mut start_data_vec = vec.start_vector();
item.specialization
.serialize_start_data(&mut start_data_vec);
}),
);
}
fn handle_destroy_item(&self, item: &Item) {
self.send_state("destroy", item.uid.as_str());

View File

@@ -1,35 +1,61 @@
use super::{surface::CoreSurface, WaylandState};
use std::sync::Arc;
use crate::nodes::{core::Node, item::ItemType};
use super::{panel_item::PanelItem, surface::CoreSurface, WaylandState};
use send_wrapper::SendWrapper;
use smithay::{
backend::renderer::utils::{
import_surface_tree, on_commit_buffer_handler, RendererSurfaceStateUserData,
},
delegate_compositor,
wayland::compositor::{self, CompositorHandler},
reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle},
wayland::compositor::{self, CompositorHandler, CompositorState},
};
impl CompositorHandler for WaylandState {
fn compositor_state(&mut self) -> &mut smithay::wayland::compositor::CompositorState {
fn compositor_state(&mut self) -> &mut CompositorState {
&mut self.compositor_state
}
fn commit(
&mut self,
_dh: &smithay::reexports::wayland_server::DisplayHandle,
surface: &smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
) {
fn commit(&mut self, dh: &DisplayHandle, surface: &WlSurface) {
on_commit_buffer_handler(surface);
import_surface_tree(&mut self.renderer, surface, &self.log).unwrap();
compositor::with_states(surface, |data| {
if let Some(surface_states) = data.data_map.get::<RendererSurfaceStateUserData>() {
if let Some(core_surface) = data.data_map.get::<CoreSurface>() {
*core_surface.wl_tex.lock() = surface_states
.borrow()
.texture(&self.renderer)
.cloned()
.map(SendWrapper::new);
}
let mapped = data
.data_map
.get::<RendererSurfaceStateUserData>()
.map(|surface_states| surface_states.borrow().wl_buffer().is_some())
.unwrap_or_default();
if !mapped {
return;
}
data.data_map.insert_if_missing_threadsafe(CoreSurface::new);
data.data_map.insert_if_missing_threadsafe(|| {
PanelItem::create(dh, &data.data_map, surface.clone())
});
let surface_states = data.data_map.get::<RendererSurfaceStateUserData>().unwrap();
let core_surface = data.data_map.get::<CoreSurface>().unwrap();
*core_surface.wl_tex.lock() = surface_states
.borrow()
.texture(&self.renderer)
.cloned()
.map(SendWrapper::new);
if let ItemType::Panel(panel_item) = &data
.data_map
.get::<Arc<Node>>()
.unwrap()
.item
.get()
.unwrap()
.specialization
{
panel_item.resize(&data.data_map);
}
});
}

View File

@@ -1,5 +1,6 @@
pub mod compositor;
pub mod panel_item;
pub mod seat;
pub mod shaders;
pub mod surface;
pub mod xdg_decoration;
@@ -23,7 +24,6 @@ use smithay::{
buffer::BufferHandler,
compositor::{with_states, CompositorState},
output::{Output, OutputManagerState, Scale::Integer},
seat::SeatState,
shell::xdg::{decoration::XdgDecorationState, XdgShellState},
shm::{ShmHandler, ShmState},
},
@@ -32,6 +32,8 @@ use stereokit as sk;
use stereokit::StereoKit;
use surface::CoreSurface;
use self::seat::SeatDelegate;
struct EGLRawHandles {
display: *const c_void,
config: *const c_void,
@@ -80,7 +82,7 @@ pub struct WaylandState {
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
pub output: Output,
pub seat_state: SeatState<WaylandState>,
pub seat_state: SeatDelegate,
// pub data_device_state: DataDeviceState,
}
@@ -123,7 +125,6 @@ impl WaylandState {
);
let _global = output.create_global::<Self>(&display_handle);
output.change_current_state(None, None, Some(Integer(2)), None);
let seat_state = SeatState::new();
// let data_device_state = DataDeviceState::new(&dh, log.clone());
println!("Init Wayland compositor");
@@ -139,7 +140,7 @@ impl WaylandState {
shm_state,
output_manager_state,
output,
seat_state,
seat_state: SeatDelegate,
// data_device_state,
})
}
@@ -162,17 +163,9 @@ impl WaylandState {
self.xdg_shell_state.toplevel_surfaces(|surfs| {
for surf in surfs.iter() {
with_states(surf.wl_surface(), |data| {
let core_surface = data.data_map.get::<CoreSurface>().unwrap();
core_surface.update_tex(sk);
});
send_frames_surface_tree(surf.wl_surface(), time_ms);
}
});
self.xdg_shell_state.popup_surfaces(|surfs| {
for surf in surfs.iter() {
with_states(surf.wl_surface(), |data| {
let core_surface = data.data_map.get::<CoreSurface>().unwrap();
core_surface.update_tex(sk);
if let Some(core_surface) = data.data_map.get::<CoreSurface>() {
core_surface.update_tex(sk);
}
});
send_frames_surface_tree(surf.wl_surface(), time_ms);
}

View File

@@ -5,18 +5,35 @@ use crate::{
},
nodes::{
core::Node,
item::{register_item_ui_flex, Item, ItemType, TypeInfo},
item::{register_item_ui_flex, Item, ItemSpecialization, ItemType, TypeInfo},
spatial::Spatial,
},
};
use anyhow::{anyhow, Result};
use glam::Mat4;
use lazy_static::lazy_static;
use libstardustxr::{flex::flexbuffer_from_vector_arguments, flex_to_vec2};
use nanoid::nanoid;
use smithay::{reexports::wayland_server::protocol::wl_surface::WlSurface, wayland::compositor};
use std::sync::Arc;
use parking_lot::Mutex;
use smithay::{
backend::renderer::utils::RendererSurfaceStateUserData,
reexports::wayland_server::{
protocol::{
wl_keyboard::KeyState,
wl_pointer::{Axis, ButtonState},
wl_surface::WlSurface,
},
DisplayHandle, Resource,
},
utils::{user_data::UserDataMap, Logical, Size},
wayland::compositor,
};
use std::{
convert::TryInto,
sync::{Arc, Weak},
};
use super::surface::CoreSurface;
use super::{seat::SeatData, surface::CoreSurface, WaylandState};
lazy_static! {
static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo {
@@ -48,24 +65,68 @@ lazy_static! {
}
pub struct PanelItem {
toplevel_surface: WlSurface,
node: Weak<Node>,
pub toplevel_surface: WlSurface,
seat_data: SeatData,
size: Mutex<Size<i32, Logical>>,
}
impl PanelItem {
pub fn create(toplevel_surface: WlSurface) -> Arc<Node> {
pub fn create(
dh: &DisplayHandle,
data: &UserDataMap,
toplevel_surface: WlSurface,
) -> Arc<Node> {
let node = Node::create(&INTERNAL_CLIENT, "/item/panel/item", &nanoid!(), true)
.add_to_scenegraph();
Spatial::add_to(&node, None, Mat4::IDENTITY).unwrap();
let specialization = ItemType::Panel(PanelItem { toplevel_surface });
let seat_data = SeatData::new(toplevel_surface.client_id().unwrap());
dh.create_global::<WaylandState, _, _>(7, seat_data.clone());
let size = data
.get::<RendererSurfaceStateUserData>()
.unwrap()
.borrow()
.surface_size()
.map(Mutex::new)
.unwrap();
let specialization = ItemType::Panel(PanelItem {
node: Arc::downgrade(&node),
toplevel_surface,
seat_data,
size,
});
let item =
ITEM_TYPE_INFO_PANEL
.items
.add(Item::new(&node, &ITEM_TYPE_INFO_PANEL, specialization));
let _ = node.item.set(item);
node.add_local_signal("applySurfaceMaterial", PanelItem::apply_surface_material);
node.add_local_signal("pointerDeactivate", PanelItem::pointer_deactivate);
node.add_local_signal("pointerScroll", PanelItem::pointer_scroll);
node.add_local_signal("pointerButton", PanelItem::pointer_button);
node.add_local_signal("pointerMotion", PanelItem::pointer_motion);
node.add_local_signal("keyboardSetActive", PanelItem::keyboard_set_active);
node.add_local_signal("keyboardSetKeyState", PanelItem::keyboard_set_key_state);
node.add_local_signal("keyboardSetModifiers", PanelItem::keyboard_set_modifiers);
node
}
pub fn resize(&self, data: &UserDataMap) {
if let Some(surface_states) = data.get::<RendererSurfaceStateUserData>() {
if let Some(size) = surface_states.borrow().buffer_size() {
let _ = self.node.upgrade().unwrap().send_remote_signal(
"resize",
&flexbuffer_from_vector_arguments(|vec| {
vec.push(size.w as u64);
vec.push(size.h as u64);
}),
);
}
}
}
fn apply_surface_material(node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
let material_idx = flex_vec.idx(1).get_u64()?;
@@ -97,6 +158,165 @@ impl PanelItem {
Ok(())
}
fn pointer_deactivate(node: &Node, _calling_client: Arc<Client>, _data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if *panel_item.seat_data.pointer_active.lock() {
if let Some(pointer) = panel_item.seat_data.pointer() {
pointer.leave(0, &panel_item.toplevel_surface);
*panel_item.seat_data.pointer_active.lock() = false;
}
}
}
Ok(())
}
fn pointer_motion(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
let x = flex_vec.index(0)?.get_f64()?;
let y = flex_vec.index(1)?.get_f64()?;
let mut pointer_active = panel_item.seat_data.pointer_active.lock();
if *pointer_active {
pointer.motion(0, x, y);
} else {
pointer.enter(0, &panel_item.toplevel_surface, x, y);
*pointer_active = true;
}
pointer.frame();
}
}
Ok(())
}
fn pointer_button(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() {
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
let button = flex_vec.index(0)?.get_u64()? as u32;
let active = flex_vec.index(1)?.get_bool()?;
pointer.button(
0,
0,
button,
if active {
ButtonState::Pressed
} else {
ButtonState::Released
},
);
pointer.frame();
}
}
}
Ok(())
}
fn pointer_scroll(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(pointer) = panel_item.seat_data.pointer() {
if *panel_item.seat_data.pointer_active.lock() {
let flex = flexbuffers::Reader::get_root(data)?;
if flex.flexbuffer_type().is_null() {
pointer.axis_stop(0, Axis::HorizontalScroll);
pointer.axis_stop(0, Axis::VerticalScroll);
} else {
let flex_vec = flex.get_vector()?;
let axis_continuous_vec = flex_to_vec2!(flex_vec.idx(0))
.ok_or_else(|| anyhow!("No continuous axis vector!"))?;
pointer.axis(0, Axis::HorizontalScroll, axis_continuous_vec.x as f64);
pointer.axis(0, Axis::VerticalScroll, axis_continuous_vec.y as f64);
if let Some(axis_discrete_vec) = flex_to_vec2!(flex_vec.idx(0)) {
pointer
.axis_discrete(Axis::HorizontalScroll, axis_discrete_vec.x as i32);
pointer.axis_discrete(Axis::VerticalScroll, axis_discrete_vec.y as i32);
}
}
pointer.frame();
}
}
}
Ok(())
}
fn keyboard_set_active(node: &Node, _calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
let mut keyboard_active = panel_item.seat_data.keyboard_active.lock();
let active = flexbuffers::Reader::get_root(data)?.get_bool()?;
if *keyboard_active != active {
if active {
keyboard.enter(0, &panel_item.toplevel_surface, vec![]);
} else {
keyboard.leave(0, &panel_item.toplevel_surface);
}
*keyboard_active = active;
}
}
}
Ok(())
}
fn keyboard_set_key_state(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
let active = *panel_item.seat_data.keyboard_active.lock();
if active {
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
let key = flex_vec.index(0)?.get_u64()? as u32;
let state: KeyState = (flex_vec.index(1)?.as_u64() as u32)
.try_into()
.map_err(|_| anyhow!("Invalid key state"))?;
keyboard.key(0, 0, key, state);
}
}
}
Ok(())
}
fn keyboard_set_modifiers(
node: &Node,
_calling_client: Arc<Client>,
data: &[u8],
) -> Result<()> {
if let ItemType::Panel(panel_item) = &node.item.get().unwrap().specialization {
if let Some(keyboard) = panel_item.seat_data.keyboard() {
let active = *panel_item.seat_data.keyboard_active.lock();
if active {
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
keyboard.modifiers(
0,
flex_vec.index(0)?.get_u64()? as u32,
flex_vec.index(1)?.get_u64()? as u32,
flex_vec.index(2)?.get_u64()? as u32,
flex_vec.index(3)?.get_u64()? as u32,
);
}
}
}
Ok(())
}
}
impl ItemSpecialization for PanelItem {
fn serialize_start_data(&self, vec: &mut flexbuffers::VectorBuilder) {
let mut size_vec = vec.start_vector();
let size = *self.size.lock();
size_vec.push(size.w as u32);
size_vec.push(size.h as u32);
}
}
pub fn register_panel_item_ui_flex(

172
src/wayland/seat.rs Normal file
View File

@@ -0,0 +1,172 @@
use super::WaylandState;
use nanoid::nanoid;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use smithay::reexports::wayland_server::{
backend::ClientId,
delegate_dispatch, delegate_global_dispatch,
protocol::{
wl_keyboard::{self, WlKeyboard},
wl_pointer::{self, WlPointer},
wl_seat::{self, Capability, WlSeat, EVT_NAME_SINCE},
wl_touch::{self, WlTouch},
},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use std::ops::Deref;
use std::sync::Arc;
pub struct SeatDelegate;
#[derive(Clone)]
pub struct SeatData(Arc<SeatDataInner>);
impl SeatData {
pub fn new(client: ClientId) -> Self {
SeatData(Arc::new(SeatDataInner {
client,
pointer: OnceCell::new(),
pointer_active: Mutex::new(false),
keyboard: OnceCell::new(),
keyboard_active: Mutex::new(false),
touch: OnceCell::new(),
}))
}
}
impl Deref for SeatData {
type Target = SeatDataInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct SeatDataInner {
client: ClientId,
pointer: OnceCell<WlPointer>,
pub pointer_active: Mutex<bool>,
keyboard: OnceCell<WlKeyboard>,
pub keyboard_active: Mutex<bool>,
touch: OnceCell<WlTouch>,
}
impl SeatDataInner {
pub fn pointer(&self) -> Option<&WlPointer> {
self.pointer.get()
}
pub fn keyboard(&self) -> Option<&WlKeyboard> {
self.keyboard.get()
}
pub fn touch(&self) -> Option<&WlTouch> {
self.touch.get()
}
}
impl GlobalDispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
fn bind(
_state: &mut WaylandState,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlSeat>,
data: &SeatData,
data_init: &mut DataInit<'_, WaylandState>,
) {
let resource = data_init.init(resource, data.clone());
if resource.version() >= EVT_NAME_SINCE {
resource.name(nanoid!());
}
resource.capabilities(Capability::Pointer | Capability::Keyboard);
}
fn can_view(client: Client, data: &SeatData) -> bool {
client.id() == data.0.client
}
}
delegate_global_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
impl Dispatch<WlSeat, SeatData, WaylandState> for SeatDelegate {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlSeat,
request: <WlSeat as Resource>::Request,
data: &SeatData,
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_seat::Request::GetPointer { id } => {
let _ = data.0.pointer.set(data_init.init(id, data.clone()));
}
wl_seat::Request::GetKeyboard { id } => {
let _ = data.0.keyboard.set(data_init.init(id, data.clone()));
}
wl_seat::Request::GetTouch { id } => {
let _ = data.0.touch.set(data_init.init(id, data.clone()));
}
wl_seat::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlSeat: SeatData] => SeatDelegate);
impl Dispatch<WlPointer, SeatData, WaylandState> for SeatDelegate {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlPointer,
request: <WlPointer as Resource>::Request,
_data: &SeatData,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_pointer::Request::SetCursor {
serial: _,
surface: _,
hotspot_x: _,
hotspot_y: _,
} => todo!(),
wl_pointer::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlPointer: SeatData] => SeatDelegate);
impl Dispatch<WlKeyboard, SeatData, WaylandState> for SeatDelegate {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
_data: &SeatData,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_keyboard::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlKeyboard: SeatData] => SeatDelegate);
impl Dispatch<WlTouch, SeatData, WaylandState> for SeatDelegate {
fn request(
_state: &mut WaylandState,
_client: &Client,
_resource: &WlTouch,
request: <WlTouch as Resource>::Request,
_data: &SeatData,
_dh: &DisplayHandle,
_data_init: &mut DataInit<'_, WaylandState>,
) {
match request {
wl_touch::Request::Release => (),
_ => unreachable!(),
}
}
}
delegate_dispatch!(WaylandState: [WlTouch: SeatData] => SeatDelegate);

View File

@@ -1,24 +1,27 @@
use super::WaylandState;
use smithay::{
delegate_xdg_shell,
reexports::wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::State,
reexports::{
wayland_protocols::xdg::{
decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
shell::server::xdg_toplevel::State,
},
wayland_server::{protocol::wl_seat::WlSeat, DisplayHandle},
},
wayland::{
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
},
Serial,
},
wayland::{compositor, shell::xdg::XdgShellHandler},
};
use super::{panel_item::PanelItem, surface::CoreSurface, WaylandState};
impl XdgShellHandler for WaylandState {
fn xdg_shell_state(&mut self) -> &mut smithay::wayland::shell::xdg::XdgShellState {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell_state
}
fn new_toplevel(
&mut self,
_dh: &smithay::reexports::wayland_server::DisplayHandle,
surface: smithay::wayland::shell::xdg::ToplevelSurface,
) {
fn new_toplevel(&mut self, _dh: &DisplayHandle, surface: ToplevelSurface) {
self.output
.enter(&self.display_handle, surface.wl_surface());
surface.with_pending_state(|state| {
@@ -26,35 +29,22 @@ impl XdgShellHandler for WaylandState {
state.decoration_mode = Some(Mode::ServerSide);
});
surface.send_configure();
compositor::with_states(surface.wl_surface(), |data| {
data.data_map.insert_if_missing_threadsafe(CoreSurface::new);
data.data_map
.insert_if_missing_threadsafe(|| PanelItem::create(surface.wl_surface().clone()));
});
}
fn new_popup(
&mut self,
_dh: &smithay::reexports::wayland_server::DisplayHandle,
surface: smithay::wayland::shell::xdg::PopupSurface,
_positioner: smithay::wayland::shell::xdg::PositionerState,
_dh: &DisplayHandle,
_surface: PopupSurface,
_positioner: PositionerState,
) {
self.output
.enter(&self.display_handle, surface.wl_surface());
let _ = surface.send_configure();
compositor::with_states(surface.wl_surface(), |data| {
data.data_map.insert_if_missing(CoreSurface::new);
});
}
fn grab(
&mut self,
_dh: &smithay::reexports::wayland_server::DisplayHandle,
_surface: smithay::wayland::shell::xdg::PopupSurface,
_seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
_serial: smithay::wayland::Serial,
_dh: &DisplayHandle,
_surface: PopupSurface,
_seat: WlSeat,
_serial: Serial,
) {
todo!()
}