feat!: wivrn pairing support

This commit is contained in:
GabMus 2024-11-27 18:47:54 +00:00
parent 3e9c4bed80
commit 4638ac1bf4
7 changed files with 1173 additions and 579 deletions

1360
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"] }

View file

@ -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;

View file

@ -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;

View file

@ -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
View 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
View 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
}