use core::iter::FromIterator;
use std::collections::HashSet;
use std::io::BufReader;
use std::io::prelude::*;
use std::process::{Command, Stdio, ChildStdout};
use std::thread;
use std::time::Duration;
use x11::xlib::Window;
use namer;
#[derive(Clone)]
pub struct SpawnOptions {
pub current_directory: Option<String>,
pub command: String,
pub unmap: bool
}
#[derive(Clone)]
pub struct Instance {
pub window: Window,
pub servername: String,
pub title: String
}
pub const STOCKED_WINDOW_ROLE: &'static str = "STOCKING";
pub const SUMMONED_WINDOW_ROLE: &'static str = "PANTY";
pub fn find_instances(visibility: bool) -> Vec<Instance> {
with_display!(display => {
let windows = fetch_all_windows(display);
let mut result = vec![];
for window in windows {
debug!("find_visible_gvim_instances: window = {}", window);
if window == 0 || is_window_visible(display, window) != visibility {
continue;
}
if let Some(class) = get_window_class(display, window) {
if class.as_str() == "Gvim" {
if let Some(servername) = get_text_property(display, window, "_PANTY_SERVERNAME") {
if let Some(title) = get_text_property(display, window, "WM_NAME") {
result.push(Instance {
window: window,
servername: servername.clone(),
title: title
});
}
}
}
}
}
result
})
}
pub fn fetch_window_id(servername: &str) -> Option<Window> {
let output: Vec<u8> = Command::new("gvim")
.arg("--servername")
.arg(servername)
.arg("--remote-expr")
.arg("v:windowid")
.output()
.unwrap()
.stdout;
String::from_utf8(output).ok().and_then(|number| number.trim().parse().ok())
}
pub fn find_instances_without_panty(visibility: bool) -> Vec<Instance> {
let servernames: Vec<String> = fetch_existing_servernames();
let join_handles: Vec<_> =
servernames.iter().map(|servername| {
let servername = servername.clone();
thread::spawn(move || {
with_display!(display => {
if let Some(window) = fetch_window_id(&servername) {
if is_window_visible(display, window) == visibility {
if let Some(title) = get_text_property(display, window, "WM_NAME") {
return Some(Instance{
window: window,
servername: servername,
title: title
})
}
}
}
None
})
})
}).collect();
{
let mut result = vec![];
for handle in join_handles {
if let Ok(found) = handle.join() {
if let Some(instance) = found {
result.push(instance);
}
}
}
result
}
}
pub fn send_files(servername: &str, files: Vec<String>, tab: bool) {
if files.is_empty() {
return
}
Command::new("gvim")
.arg("--servername")
.arg(servername)
.arg(if tab {"--remote-tab"} else {"--remote"})
.args(files.as_slice())
.spawn().unwrap();
}
pub fn remote(servername: &str, keys: &[String], expressions: &[String]) -> Option<BufReader<ChildStdout>> {
fn gen_args(name: &str, items: &[String]) -> Vec<String> {
let buffer: Vec<Vec<String>> = items.iter().map(|it| vec![name.to_string(), it.to_string()]).collect();
buffer.concat()
}
if keys.is_empty() && expressions.is_empty() {
return None
}
let child = Command::new("gvim")
.arg("--servername")
.arg(servername)
.args(gen_args("--remote-send", keys).as_slice())
.args(gen_args("--remote-expr", expressions).as_slice())
.stdout(Stdio::piped())
.spawn().unwrap();
Some(BufReader::new(child.stdout.unwrap()))
}
pub fn spawn(servername: &str, options: &SpawnOptions) -> (Window, BufReader<ChildStdout>) {
let mut command = Command::new(options.command.clone());
command.arg("--nofork")
.arg("--echo-wid")
.arg(format!("--role={}", STOCKED_WINDOW_ROLE))
.arg("-iconic")
.arg("--servername")
.arg(servername);
if let Some(current_directory) = options.current_directory.clone() {
command.current_dir(current_directory);
}
let child = command
.stdout(Stdio::piped())
.spawn()
.expect("Failed to execute process");
let mut line: String = String::new();
let mut reader = BufReader::new(child.stdout.unwrap());
for _ in 0..300 {
if let Ok(len) = reader.read_line(&mut line) {
if len > 0 {
if let Some(pos) = line.find("WID: ") {
let (_, wid) = line.split_at(pos + 5);
return (wid.trim().parse().unwrap(), reader);
}
continue;
}
}
break;
}
panic!("WID not found!")
}
pub fn spawn_secretly(servername: &str, options: &SpawnOptions) -> (Window, BufReader<ChildStdout>) {
with_display!(display => {
let (wid, reader) = spawn(servername, options);
trace!("spawning: {}", wid);
while !window_exists(display, wid) {
thread::sleep(Duration::from_millis(1));
}
set_text_property(display, wid, "_PANTY_SERVERNAME", servername);
if options.unmap {
{
let max_tries = 50;
let mut tried = 0;
while !is_window_visible(display, wid) && tried < max_tries {
tried += 1;
thread::sleep(Duration::from_millis(1));
}
if tried < max_tries {
unmap_window(display, wid);
}
}
}
trace!("spawned: {}", wid);
(wid, reader)
})
}
pub fn new_servernames(windows: usize) -> Vec<String> {
let mut result = vec![];
let mut names: HashSet<String> = fetch_existing_servernames();
loop {
let name = namer::name();
if !names.contains(&name) {
result.push(name.clone());
names.insert(name);
if result.len() >= windows {
break;
}
}
}
result
}
pub fn fetch_existing_servernames<T>() -> T
where T: FromIterator<String> {
let output: Vec<u8> = Command::new("gvim")
.arg("--serverlist")
.output()
.unwrap()
.stdout;
String::from_utf8(output).unwrap().lines().map(|it| it.to_string()).collect()
}