use super::socket_monitor::SessionMonitor;
use crate::bridge::{ActiveWindow, Request, Response};
use crate::client::{Client, WindowInfo};
use anyhow::{anyhow, bail, Context, Result};
use log::debug;
use regex::Regex;
use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::sync::Arc;
use std::thread::spawn;
use std::time::Duration;
const XREMAP_SOCKET: &str = "/run/xremap/{uid}/xremap.sock";
pub struct SocketClient {
socket_path: String,
monitor: Arc<SessionMonitor>,
}
impl SocketClient {
pub fn new() -> SocketClient {
let socket_path = std::env::var("XREMAP_SOCKET").unwrap_or(XREMAP_SOCKET.to_string());
let monitor = Arc::new(SessionMonitor::new(socket_path.clone()));
let monitor_ = monitor.clone();
let _handle = spawn(move || monitor_.run());
SocketClient { socket_path, monitor }
}
fn get_active_window(&self) -> Result<ActiveWindow> {
let json = self.call_via_socket("ActiveWindow")?;
match serde_json::from_str::<Response>(&json) {
Ok(response) => match response {
Response::ActiveWindow { title, wm_class } => Ok(ActiveWindow { title, wm_class }),
Response::Error(message) => bail!(message),
response => bail!("Wrong response from client {response:?}"),
},
Err(_) => {
Ok(serde_json::from_str::<ActiveWindow>(&json)?)
}
}
}
fn command(&self, request: Request) -> Result<()> {
match self.request_typed(request)? {
Response::Ok => Ok(()),
response => bail!("Wrong response from client {response:?}"),
}
}
fn request_typed(&self, request: Request) -> Result<Response> {
let response = self.call_via_socket(request)?;
match serde_json::from_str::<Response>(&response)? {
Response::Error(message) => bail!(format!("GNOME extension failed: {message}")),
response => Ok(response),
}
}
fn call_via_socket<T: serde::Serialize>(&self, command: T) -> Result<String> {
let session = self.monitor.get_active_session().ok_or(anyhow!("no active session"))?;
let mut stream = UnixStream::connect(&session.user_socket)
.context(format!("Could not connect to socket: {:?}", session.user_socket))?;
stream.set_write_timeout(Some(Duration::from_millis(500)))?;
stream.set_read_timeout(Some(Duration::from_millis(500)))?;
stream.write_all(serde_json::to_string(&command)?.as_bytes())?;
stream.write_all(b"\n")?;
stream.flush()?;
let mut reader = BufReader::new(stream);
let mut response = String::new();
reader.read_line(&mut response)?;
Ok(response)
}
}
impl Client for SocketClient {
fn supported(&mut self) -> bool {
debug!("Using socket path pattern: {}", self.socket_path);
let regex = Regex::new(r"/(\{uid\}/.+|[^/{]+)$").unwrap();
let parent_dir = regex.replace(&self.socket_path, "");
let path = Path::new(parent_dir.as_ref());
if path.is_dir() {
true
} else {
println!("Warning: socket directory not found: {}", parent_dir);
false
}
}
fn current_window(&mut self) -> Option<String> {
if let Ok(window) = self.get_active_window() {
return Some(window.title);
}
None
}
fn current_application(&mut self) -> Option<String> {
if let Ok(window) = self.get_active_window() {
return Some(window.wm_class);
}
None
}
fn run(&mut self, command: &Vec<String>) -> anyhow::Result<bool> {
self.command(Request::Run(command.clone()))?;
Ok(true)
}
fn window_list(&mut self) -> anyhow::Result<Vec<WindowInfo>> {
match self.request_typed(Request::WindowList)? {
Response::WindowList(window_list) => Ok(window_list),
response => bail!("Wrong response from client {response:?}"),
}
}
fn close_windows_by_app_class(&mut self, app_class: &str) -> Result<()> {
self.command(Request::CloseByAppClass(app_class.into()))
}
}