use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use swayipc::{Connection, Node, NodeType};
use crate::target::Target;
pub fn connect() -> Result<Connection, String> {
if let Some(stream) = env_socket().and_then(|path| UnixStream::connect(path).ok()) {
return Ok(Connection::from(stream));
}
match live_sockets().as_slice() {
[path] => UnixStream::connect(path)
.map(Connection::from)
.map_err(|e| format!("cannot reach sway on {} ({e})", path.display())),
[] => Err(
"cannot reach sway; wl-pick reads the window list from its IPC \
socket, and no running sway has one"
.to_string(),
),
many => Err(format!(
"several sway sockets to choose from ({}); set SWAYSOCK to the one you mean",
many.iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(", ")
)),
}
}
fn socket_pid(name: &str) -> Option<&str> {
name.strip_prefix("sway-ipc.")?
.strip_suffix(".sock")?
.rsplit('.')
.next()
.filter(|pid| !pid.is_empty() && pid.bytes().all(|b| b.is_ascii_digit()))
}
fn env_socket() -> Option<PathBuf> {
["I3SOCK", "SWAYSOCK"]
.into_iter()
.filter_map(std::env::var_os)
.map(PathBuf::from)
.find(|path| path.exists())
}
fn live_sockets() -> Vec<PathBuf> {
let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR") else {
return Vec::new();
};
let Ok(entries) = std::fs::read_dir(dir) else {
return Vec::new();
};
let mut found: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path())
.filter(|path| {
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
return false;
};
socket_pid(name)
.and_then(|pid| std::fs::read_to_string(format!("/proc/{pid}/comm")).ok())
.is_some_and(|comm| comm.trim() == "sway")
})
.collect();
found.sort();
found
}
pub fn windows(conn: &mut Connection) -> Result<Vec<Target>, swayipc::Error> {
let mut out = Vec::new();
collect(&conn.get_tree()?, &mut out);
Ok(out)
}
fn collect(node: &Node, out: &mut Vec<Target>) {
let is_con = matches!(node.node_type, NodeType::Con | NodeType::FloatingCon);
let class = node
.window_properties
.as_ref()
.and_then(|p| p.class.clone());
if is_con && (node.app_id.is_some() || class.is_some()) {
out.push(Target::window(
node.id,
node.foreign_toplevel_identifier.clone().unwrap_or_default(),
node.app_id.clone().or(class).unwrap_or_default(),
node.name.clone().unwrap_or_default(),
));
}
for child in node.nodes.iter().chain(node.floating_nodes.iter()) {
collect(child, out);
}
}
#[derive(Clone, Debug)]
pub struct Display {
pub name: String,
pub width: i32,
pub height: i32,
pub scale: i32,
pub focused: bool,
}
pub fn displays(conn: &mut Connection) -> Result<Vec<Display>, swayipc::Error> {
Ok(conn
.get_outputs()?
.into_iter()
.filter(|o| o.active)
.map(|o| Display {
name: o.name,
width: o.rect.width,
height: o.rect.height,
scale: (o.scale.unwrap_or(1.0).ceil() as i32).max(1),
focused: o.focused,
})
.collect())
}
pub fn focused(displays: &[Display]) -> Option<&Display> {
displays
.iter()
.find(|d| d.focused)
.or_else(|| displays.first())
}
#[cfg(test)]
mod tests {
use super::socket_pid;
#[test]
fn a_socket_name_gives_up_its_pid() {
assert_eq!(socket_pid("sway-ipc.1000.573773.sock"), Some("573773"));
assert_eq!(socket_pid("wayland-1"), None);
assert_eq!(socket_pid("sway-ipc.1000.573773.sock.bak"), None);
assert_eq!(socket_pid("i3-ipc.1000.5.sock"), None);
assert_eq!(socket_pid("sway-ipc.1000..sock"), None);
assert_eq!(socket_pid("sway-ipc.1000.notapid.sock"), None);
}
}