1use std::env::{current_dir, temp_dir};2use std::path::Path;3use std::time::Duration;4use std::{fs, io};56use anyhow::{bail, Context as _};7use nix::libc;8use remowt_link_shared::editor::EditorEndpointsClient;9use tokio::process::Command;10use zbus::{fdo, interface, proxy, Connection};1112use remowt_link_shared::BifConfig;1314const BUS_NAME: &str = "lach.RemowtEditor";15const SERVICE_PATH: &str = "/lach/Editor";1617pub struct EditorService {18 editor: EditorEndpointsClient<BifConfig>,19}2021#[interface(name = "lach.RemowtEditor")]22impl EditorService {23 24 25 async fn edit(&self, socket_path: String) -> fdo::Result<()> {26 self.editor27 .open_editor(socket_path)28 .await29 .map_err(|e| fdo::Error::Failed(format!("requesting editor on the User: {e}")))?30 .map_err(|e| fdo::Error::Failed(format!("editor failed: {e}")))?;31 Ok(())32 }3334 async fn forward_tcp(&self, addr: String) -> fdo::Result<u16> {35 let local = self36 .editor37 .expose_tcp(addr)38 .await39 .map_err(|e| fdo::Error::Failed(format!("requesting tcp forward on the User: {e}")))?40 .map_err(|e| fdo::Error::Failed(format!("tcp forward failed: {e}")))?;41 Ok(local)42 }4344 async fn forward_udp(&self, addr: String) -> fdo::Result<u16> {45 let local = self46 .editor47 .expose_udp(addr)48 .await49 .map_err(|e| fdo::Error::Failed(format!("requesting udp forward on the User: {e}")))?50 .map_err(|e| fdo::Error::Failed(format!("udp forward failed: {e}")))?;51 Ok(local)52 }53}5455pub async fn serve(56 conn: &Connection,57 editor: EditorEndpointsClient<BifConfig>,58) -> anyhow::Result<()> {59 conn.object_server()60 .at(SERVICE_PATH, EditorService { editor })61 .await?;62 conn.request_name(BUS_NAME).await?;63 Ok(())64}6566#[proxy(interface = "lach.RemowtEditor")]67trait RemowtEditor {68 async fn edit(&self, socket_path: &str) -> fdo::Result<()>;69 async fn forward_tcp(&self, addr: &str) -> fdo::Result<u16>;70 async fn forward_udp(&self, addr: &str) -> fdo::Result<u16>;71}7273pub async fn forward(udp: bool, addr: String) -> anyhow::Result<()> {74 let conn = Connection::session()75 .await76 .context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;77 let proxy = RemowtEditorProxy::builder(&conn)78 .destination(BUS_NAME)?79 .path(SERVICE_PATH)?80 .build()81 .await?;82 let local = if udp {83 proxy.forward_udp(&addr).await?84 } else {85 proxy.forward_tcp(&addr).await?86 };87 println!("{local}");88 Ok(())89}9091pub async fn edit(path: String) -> anyhow::Result<()> {92 let path = Path::new(&path);93 let abs = if path.is_absolute() {94 path.to_path_buf()95 } else {96 current_dir()?.join(path)97 };9899 let sock = temp_dir().join(format!("remowt-nvim-{}.sock", uuid::Uuid::new_v4()));100 let sock_str = sock101 .to_str()102 .context("temp socket path is not utf-8")?103 .to_owned();104105 let mut child = Command::new("nvim");106 child107 .arg("--headless")108 .arg("--listen")109 .arg(&sock)110 .arg("--")111 .arg(&abs)112 .kill_on_drop(true);113 114 unsafe {115 child.pre_exec(|| {116 if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as libc::c_ulong) != 0 {117 return Err(io::Error::last_os_error());118 }119 Ok(())120 });121 }122 let mut child = child.spawn().context("spawning nvim")?;123124 wait_for_socket(&sock)125 .await126 .context("nvim did not start its server")?;127128 let conn = Connection::session()129 .await130 .context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;131 let proxy = RemowtEditorProxy::builder(&conn)132 .destination(BUS_NAME)?133 .path(SERVICE_PATH)?134 .build()135 .await?;136 let result = proxy.edit(&sock_str).await;137138 if tokio::time::timeout(Duration::from_secs(2), child.wait())139 .await140 .is_err()141 {142 let _ = child.kill().await;143 }144 let _ = fs::remove_file(&sock);145146 result?;147 Ok(())148}149150async fn wait_for_socket(path: &Path) -> anyhow::Result<()> {151 for _ in 0..200 {152 if tokio::fs::try_exists(path).await.unwrap_or(false) {153 return Ok(());154 }155 tokio::time::sleep(Duration::from_millis(50)).await;156 }157 bail!("timed out waiting for {}", path.display())158}