mirror of
https://gitlab.com/gabmus/envision.git
synced 2025-04-20 19:44:50 +00:00
feat!: wivrn pairing support
This commit is contained in:
parent
3e9c4bed80
commit
4638ac1bf4
7 changed files with 1173 additions and 579 deletions
1360
Cargo.lock
generated
1360
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -30,3 +30,4 @@ ash = "0.38.0"
|
|||
sha2 = "0.10.8"
|
||||
tokio = { version = "1.39.3", features = ["process"] }
|
||||
notify-rust = "4.11.3"
|
||||
zbus = { version = "5.1.1", features = ["tokio"] }
|
||||
|
|
|
@ -41,6 +41,7 @@ pub mod termcolor;
|
|||
pub mod ui;
|
||||
pub mod util;
|
||||
pub mod vulkaninfo;
|
||||
pub mod wivrn_dbus;
|
||||
pub mod xdg;
|
||||
pub mod xr_devices;
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ use crate::{
|
|||
},
|
||||
util::file_utils::{setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd},
|
||||
vulkaninfo::VulkanInfo,
|
||||
wivrn_dbus,
|
||||
xr_devices::XRDevice,
|
||||
};
|
||||
use adw::{prelude::*, ResponseAppearance};
|
||||
|
@ -64,7 +65,7 @@ pub struct App {
|
|||
app_win: adw::ApplicationWindow,
|
||||
inhibit_id: Option<u32>,
|
||||
|
||||
main_view: Controller<MainView>,
|
||||
main_view: AsyncController<MainView>,
|
||||
debug_view: Controller<DebugView>,
|
||||
split_view: Option<adw::NavigationSplitView>,
|
||||
about_dialog: adw::AboutDialog,
|
||||
|
@ -119,6 +120,7 @@ pub enum Msg {
|
|||
HandleCommandLine(CmdLineOpts),
|
||||
StartProber,
|
||||
OnProberExit(bool),
|
||||
WivrnCheckPairMode,
|
||||
NoOp,
|
||||
}
|
||||
|
||||
|
@ -375,11 +377,11 @@ impl AsyncComponent for App {
|
|||
Msg::OnAutostartExit(_) => self.autostart_worker = None,
|
||||
Msg::ClockTicking => {
|
||||
self.main_view.sender().emit(MainViewMsg::ClockTicking);
|
||||
let should_poll_for_devices = self.xrservice_ready
|
||||
&& self
|
||||
.xrservice_worker
|
||||
.as_ref()
|
||||
.is_some_and(JobWorker::is_alive);
|
||||
let xrservice_worker_is_alive = self
|
||||
.xrservice_worker
|
||||
.as_ref()
|
||||
.is_some_and(JobWorker::is_alive);
|
||||
let should_poll_for_devices = self.xrservice_ready && xrservice_worker_is_alive;
|
||||
if should_poll_for_devices {
|
||||
if let Some(monado) = self.libmonado.as_ref() {
|
||||
self.xr_devices = XRDevice::from_libmonado(monado);
|
||||
|
@ -393,6 +395,32 @@ impl AsyncComponent for App {
|
|||
}
|
||||
}
|
||||
}
|
||||
if xrservice_worker_is_alive
|
||||
&& self.get_selected_profile().xrservice_type == XRServiceType::Wivrn
|
||||
{
|
||||
// is in pairing mode?
|
||||
sender.input(Msg::WivrnCheckPairMode);
|
||||
}
|
||||
}
|
||||
Msg::WivrnCheckPairMode => {
|
||||
if self.get_selected_profile().xrservice_type == XRServiceType::Wivrn {
|
||||
match wivrn_dbus::is_pairing_mode().await {
|
||||
Ok(state) => {
|
||||
self.main_view
|
||||
.sender()
|
||||
.emit(MainViewMsg::SetWivrnPairingMode(state));
|
||||
self.main_view
|
||||
.sender()
|
||||
.emit(MainViewMsg::SetWivrnSupportsPairing(true));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: failed to get wivrn pairing mode: {e:?}");
|
||||
self.main_view
|
||||
.sender()
|
||||
.emit(MainViewMsg::SetWivrnSupportsPairing(false));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Msg::EnableDebugViewChanged(val) => {
|
||||
self.config.debug_view_enabled = val;
|
||||
|
|
|
@ -25,6 +25,7 @@ use crate::{
|
|||
steamvr_utils::chaperone_info_exists,
|
||||
},
|
||||
vulkaninfo::VulkanInfo,
|
||||
wivrn_dbus,
|
||||
xr_devices::XRDevice,
|
||||
};
|
||||
use adw::{prelude::*, ResponseAppearance};
|
||||
|
@ -33,7 +34,6 @@ use relm4::{
|
|||
actions::{ActionGroupName, RelmAction, RelmActionGroup},
|
||||
new_action_group, new_stateless_action,
|
||||
prelude::*,
|
||||
ComponentParts, ComponentSender, SimpleComponent,
|
||||
};
|
||||
use std::{fs::read_to_string, io::Write};
|
||||
|
||||
|
@ -73,6 +73,9 @@ pub struct MainView {
|
|||
xrservice_ready: bool,
|
||||
#[tracker::do_not_track]
|
||||
vkinfo: Option<VulkanInfo>,
|
||||
wivrn_pairing_mode: bool,
|
||||
wivrn_pin: Option<String>,
|
||||
wivrn_supports_pairing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -96,6 +99,10 @@ pub enum MainViewMsg {
|
|||
ExportProfile,
|
||||
ImportProfile,
|
||||
OpenProfileEditor(Profile),
|
||||
SetWivrnSupportsPairing(bool),
|
||||
SetWivrnPairingMode(bool),
|
||||
StopWivrnPairingMode,
|
||||
StartWivrnPairingMode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -116,7 +123,7 @@ pub struct MainViewInit {
|
|||
}
|
||||
|
||||
impl MainView {
|
||||
fn create_profile_editor(&mut self, sender: ComponentSender<MainView>, prof: Profile) {
|
||||
fn create_profile_editor(&mut self, sender: AsyncComponentSender<Self>, prof: Profile) {
|
||||
self.profile_editor = Some(
|
||||
ProfileEditor::builder()
|
||||
.launch(ProfileEditorInit {
|
||||
|
@ -130,11 +137,12 @@ impl MainView {
|
|||
}
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for MainView {
|
||||
#[relm4::component(pub async)]
|
||||
impl AsyncComponent for MainView {
|
||||
type Init = MainViewInit;
|
||||
type Input = MainViewMsg;
|
||||
type Output = MainViewOutMsg;
|
||||
type CommandOutput = ();
|
||||
|
||||
menu! {
|
||||
app_menu: {
|
||||
|
@ -240,28 +248,136 @@ impl SimpleComponent for MainView {
|
|||
set_visible: model.xrservice_active,
|
||||
add_css_class: "card",
|
||||
gtk::Label {
|
||||
#[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready())"]
|
||||
set_label: if model.xrservice_ready {
|
||||
"Service ready, you can launch XR apps"
|
||||
} else {
|
||||
#[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready()) || model.changed(Self::wivrn_pairing_mode())"]
|
||||
set_label: {
|
||||
match model.selected_profile.xrservice_type {
|
||||
XRServiceType::Monado =>
|
||||
"Starting…",
|
||||
if model.xrservice_ready {
|
||||
"Service ready, you can launch XR apps"
|
||||
} else {
|
||||
"Starting…"
|
||||
}
|
||||
XRServiceType::Wivrn =>
|
||||
"Starting, please connect your client device…",
|
||||
if model.wivrn_pairing_mode {
|
||||
"Pairing mode"
|
||||
} else {
|
||||
"Starting, connect your client device…"
|
||||
}
|
||||
}
|
||||
},
|
||||
set_margin_all: 18,
|
||||
add_css_class: "heading",
|
||||
add_css_class: "success",
|
||||
add_css_class: "warning",
|
||||
#[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready())"]
|
||||
set_class_active: ("warning", !model.xrservice_ready),
|
||||
#[track = "model.changed(Self::xrservice_active()) || model.changed(Self::xrservice_ready()) || model.changed(Self::wivrn_pairing_mode())"]
|
||||
set_class_active: (
|
||||
"success",
|
||||
model.xrservice_ready
|
||||
&& (
|
||||
model.selected_profile.xrservice_type != XRServiceType::Wivrn
|
||||
|| !model.wivrn_pairing_mode
|
||||
)
|
||||
),
|
||||
set_wrap: true,
|
||||
set_justify: gtk::Justification::Center,
|
||||
},
|
||||
},
|
||||
model.devices_box.widget(),
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_hexpand: true,
|
||||
set_vexpand: false,
|
||||
set_spacing: 12,
|
||||
add_css_class: "card",
|
||||
add_css_class: "padded",
|
||||
#[track = "model.changed(Self::wivrn_supports_pairing()) || model.changed(Self::xrservice_active()) || model.changed(Self::selected_profile()) || model.changed(Self::wivrn_pairing_mode()) || model.changed(Self::wivrn_pin())"]
|
||||
set_visible: model.wivrn_supports_pairing
|
||||
&& model.xrservice_active
|
||||
&& model.selected_profile.xrservice_type == XRServiceType::Wivrn
|
||||
&& !model.wivrn_pairing_mode,
|
||||
gtk::Label {
|
||||
add_css_class: "heading",
|
||||
set_hexpand: true,
|
||||
set_xalign: 0.0,
|
||||
set_label: "Pairing mode",
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "dim-label",
|
||||
set_hexpand: true,
|
||||
set_label: concat!(
|
||||
"To connect a new device to WiVRn, you ",
|
||||
"will need to pair it first.\n\n",
|
||||
"You can do so by starting the pairing mode ",
|
||||
"with the button below."
|
||||
),
|
||||
set_xalign: 0.0,
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "suggested-action",
|
||||
set_label: "Start pairing mode",
|
||||
set_halign: gtk::Align::Start,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::StartWivrnPairingMode);
|
||||
}
|
||||
},
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_hexpand: true,
|
||||
set_vexpand: false,
|
||||
set_spacing: 12,
|
||||
add_css_class: "card",
|
||||
add_css_class: "padded",
|
||||
#[track = "model.changed(Self::wivrn_supports_pairing()) || model.changed(Self::xrservice_active()) || model.changed(Self::selected_profile()) || model.changed(Self::wivrn_pairing_mode()) || model.changed(Self::wivrn_pin())"]
|
||||
set_visible: model.wivrn_supports_pairing
|
||||
&& model.xrservice_active
|
||||
&& model.selected_profile.xrservice_type == XRServiceType::Wivrn
|
||||
&& model.wivrn_pairing_mode && model.wivrn_pin.is_some(),
|
||||
gtk::Label {
|
||||
add_css_class: "heading",
|
||||
set_hexpand: true,
|
||||
set_xalign: 0.0,
|
||||
set_label: "Pairing mode",
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "dim-label",
|
||||
set_hexpand: true,
|
||||
set_label: concat!(
|
||||
"WiVRn is in pairing mode. Pair your client ",
|
||||
"device with the following PIN:"
|
||||
),
|
||||
set_xalign: 0.0,
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Label {
|
||||
add_css_class: "title-2",
|
||||
add_css_class: "monospace",
|
||||
set_hexpand: true,
|
||||
set_selectable: true,
|
||||
#[track = "model.changed(Self::wivrn_pin())"]
|
||||
set_label: model.wivrn_pin
|
||||
.as_deref().unwrap_or(""),
|
||||
set_xalign: 0.5,
|
||||
set_justify: gtk::Justification::Center,
|
||||
set_wrap: true,
|
||||
set_wrap_mode: gtk::pango::WrapMode::Word,
|
||||
},
|
||||
gtk::Button {
|
||||
add_css_class: "destructive-action",
|
||||
set_label: "Stop pairing mode",
|
||||
set_halign: gtk::Align::Start,
|
||||
connect_clicked[sender] => move |_| {
|
||||
sender.input(Self::Input::StopWivrnPairingMode);
|
||||
}
|
||||
},
|
||||
},
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_hexpand: true,
|
||||
|
@ -486,11 +602,48 @@ impl SimpleComponent for MainView {
|
|||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
|
||||
async fn update(
|
||||
&mut self,
|
||||
message: Self::Input,
|
||||
sender: AsyncComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
) {
|
||||
self.reset();
|
||||
|
||||
match message {
|
||||
Self::Input::ClockTicking => {}
|
||||
Self::Input::SetWivrnSupportsPairing(supported) => {
|
||||
if self.wivrn_supports_pairing != supported {
|
||||
self.set_wivrn_supports_pairing(supported)
|
||||
}
|
||||
}
|
||||
Self::Input::SetWivrnPairingMode(enabled) => {
|
||||
if self.wivrn_pairing_mode != enabled {
|
||||
self.set_wivrn_pairing_mode(enabled);
|
||||
if enabled {
|
||||
match wivrn_dbus::pairing_pin().await {
|
||||
Ok(pin) => {
|
||||
self.set_wivrn_pin(Some(pin));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error: failed to get wivrn pairing pin: {e:?}");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
self.set_wivrn_pin(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Input::StopWivrnPairingMode => {
|
||||
if let Err(e) = wivrn_dbus::disable_pairing().await {
|
||||
eprintln!("Error: failed to stop wivrn pairing mode: {e:?}");
|
||||
}
|
||||
}
|
||||
Self::Input::StartWivrnPairingMode => {
|
||||
if let Err(e) = wivrn_dbus::enable_pairing().await {
|
||||
eprintln!("Error: failed to start wivrn pairing mode: {e:?}");
|
||||
}
|
||||
}
|
||||
Self::Input::StartStopClicked => {
|
||||
sender
|
||||
.output(Self::Output::DoStartStopXRService)
|
||||
|
@ -504,6 +657,7 @@ impl SimpleComponent for MainView {
|
|||
Self::Input::XRServiceActiveChanged(active, profile, show_launch_opts) => {
|
||||
if !active {
|
||||
self.set_xrservice_ready(false);
|
||||
sender.input(Self::Input::SetWivrnPairingMode(false));
|
||||
}
|
||||
self.set_xrservice_active(active);
|
||||
self.steamvr_calibration_box
|
||||
|
@ -742,11 +896,11 @@ impl SimpleComponent for MainView {
|
|||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
async fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
sender: AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let profile_not_editable_dialog = adw::AlertDialog::builder()
|
||||
.heading("This profile is not editable")
|
||||
.body(concat!(
|
||||
|
@ -866,6 +1020,9 @@ impl SimpleComponent for MainView {
|
|||
profile_delete_action,
|
||||
profile_export_action,
|
||||
vkinfo: init.vkinfo,
|
||||
wivrn_pairing_mode: false,
|
||||
wivrn_supports_pairing: false,
|
||||
wivrn_pin: None,
|
||||
tracker: 0,
|
||||
};
|
||||
let widgets = view_output!();
|
||||
|
@ -919,7 +1076,7 @@ impl SimpleComponent for MainView {
|
|||
|
||||
root.insert_action_group(ProfileActionGroup::NAME, Some(&actions.into_action_group()));
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
AsyncComponentParts { model, widgets }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
122
src/wivrn_dbus/internal.rs
Normal file
122
src/wivrn_dbus/internal.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
//! # D-Bus interface proxy for: `io.github.wivrn.Server`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `5.0.1` from D-Bus introspection data.
|
||||
//! Source: `io.github.wivrn.Server.xml`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the [Writing a client proxy] section of the zbus
|
||||
//! documentation.
|
||||
//!
|
||||
//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the
|
||||
//! following zbus API can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//!
|
||||
//! Consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
//!
|
||||
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
||||
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
||||
use zbus::proxy;
|
||||
#[proxy(
|
||||
interface = "io.github.wivrn.Server",
|
||||
default_path = "/io/github/wivrn/Server",
|
||||
default_service = "io.github.wivrn.Server"
|
||||
)]
|
||||
pub trait Server {
|
||||
/// DisablePairing method
|
||||
fn disable_pairing(&self) -> zbus::Result<()>;
|
||||
|
||||
/// Disconnect method
|
||||
fn disconnect(&self) -> zbus::Result<()>;
|
||||
|
||||
/// EnablePairing method
|
||||
fn enable_pairing(&self, TimeoutSecs: i32) -> zbus::Result<String>;
|
||||
|
||||
/// Quit method
|
||||
fn quit(&self) -> zbus::Result<()>;
|
||||
|
||||
/// RenameKey method
|
||||
fn rename_key(&self, PublicKey: &str, Name: &str) -> zbus::Result<()>;
|
||||
|
||||
/// RevokeKey method
|
||||
fn revoke_key(&self, PublicKey: &str) -> zbus::Result<()>;
|
||||
|
||||
/// AvailableRefreshRates property
|
||||
#[zbus(property)]
|
||||
fn available_refresh_rates(&self) -> zbus::Result<Vec<f64>>;
|
||||
|
||||
/// EncryptionEnabled property
|
||||
#[zbus(property)]
|
||||
fn encryption_enabled(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// EyeGaze property
|
||||
#[zbus(property)]
|
||||
fn eye_gaze(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// FaceTracking property
|
||||
#[zbus(property)]
|
||||
fn face_tracking(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// FieldOfView property
|
||||
#[zbus(property)]
|
||||
fn field_of_view(&self) -> zbus::Result<Vec<(f64, f64, f64, f64)>>;
|
||||
|
||||
/// HandTracking property
|
||||
#[zbus(property)]
|
||||
fn hand_tracking(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// HeadsetConnected property
|
||||
#[zbus(property)]
|
||||
fn headset_connected(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// JsonConfiguration property
|
||||
#[zbus(property)]
|
||||
fn json_configuration(&self) -> zbus::Result<String>;
|
||||
#[zbus(property)]
|
||||
fn set_json_configuration(&self, value: &str) -> zbus::Result<()>;
|
||||
|
||||
/// KnownKeys property
|
||||
#[zbus(property)]
|
||||
fn known_keys(&self) -> zbus::Result<Vec<(String, String)>>;
|
||||
|
||||
/// MicChannels property
|
||||
#[zbus(property)]
|
||||
fn mic_channels(&self) -> zbus::Result<u32>;
|
||||
|
||||
/// MicSampleRate property
|
||||
#[zbus(property)]
|
||||
fn mic_sample_rate(&self) -> zbus::Result<u32>;
|
||||
|
||||
/// PairingEnabled property
|
||||
#[zbus(property)]
|
||||
fn pairing_enabled(&self) -> zbus::Result<bool>;
|
||||
|
||||
/// Pin property
|
||||
#[zbus(property)]
|
||||
fn pin(&self) -> zbus::Result<String>;
|
||||
|
||||
/// PreferredRefreshRate property
|
||||
#[zbus(property)]
|
||||
fn preferred_refresh_rate(&self) -> zbus::Result<f64>;
|
||||
|
||||
/// RecommendedEyeSize property
|
||||
#[zbus(property)]
|
||||
fn recommended_eye_size(&self) -> zbus::Result<(u32, u32)>;
|
||||
|
||||
/// SpeakerChannels property
|
||||
#[zbus(property)]
|
||||
fn speaker_channels(&self) -> zbus::Result<u32>;
|
||||
|
||||
/// SpeakerSampleRate property
|
||||
#[zbus(property)]
|
||||
fn speaker_sample_rate(&self) -> zbus::Result<u32>;
|
||||
|
||||
/// SteamCommand property
|
||||
#[zbus(property)]
|
||||
fn steam_command(&self) -> zbus::Result<String>;
|
||||
|
||||
/// SupportedCodecs property
|
||||
#[zbus(property)]
|
||||
fn supported_codecs(&self) -> zbus::Result<Vec<String>>;
|
||||
}
|
37
src/wivrn_dbus/mod.rs
Normal file
37
src/wivrn_dbus/mod.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
// how to regenerate this one:
|
||||
//
|
||||
// ```bash
|
||||
// cargo install zbus_xmlgen
|
||||
// curl -sSLO https://github.com/WiVRn/WiVRn/blob/master/dbus/io.github.wivrn.Server.xml
|
||||
// zbus-xmlgen file io.github.wivrn.Server.xml
|
||||
// ```
|
||||
//
|
||||
// it should output a file called server.rs, move it accordingly
|
||||
#[rustfmt::skip]
|
||||
#[allow(non_snake_case)]
|
||||
mod internal;
|
||||
|
||||
/// timeout for dbus methods in seconds
|
||||
const TIMEOUT: i32 = 10;
|
||||
|
||||
async fn proxy<'a>() -> zbus::Result<internal::ServerProxy<'a>> {
|
||||
let connection = zbus::Connection::session().await?;
|
||||
let proxy = internal::ServerProxy::new(&connection).await?;
|
||||
Ok(proxy)
|
||||
}
|
||||
|
||||
pub async fn is_pairing_mode() -> zbus::Result<bool> {
|
||||
proxy().await?.pairing_enabled().await
|
||||
}
|
||||
|
||||
pub async fn enable_pairing() -> zbus::Result<String> {
|
||||
proxy().await?.enable_pairing(TIMEOUT).await
|
||||
}
|
||||
|
||||
pub async fn disable_pairing() -> zbus::Result<()> {
|
||||
proxy().await?.disable_pairing().await
|
||||
}
|
||||
|
||||
pub async fn pairing_pin() -> zbus::Result<String> {
|
||||
proxy().await?.pin().await
|
||||
}
|
Loading…
Add table
Reference in a new issue