pub mod clipboard;
mod keyboard;
pub use keyboard::KeyComboError;
pub mod monitor;
pub mod overlay;
pub mod render_util;
pub mod visualizer;
pub fn x11_centre_and_raise_xid(wid: u32) -> bool {
use x11rb::connection::Connection;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xproto::*;
let (conn, screen_num) = match x11rb::connect(None) {
Ok(c) => c,
Err(_) => return false,
};
let screen = &conn.setup().roots[screen_num];
let root = screen.root;
let atom_names: &[&[u8]] = &[
b"_NET_WM_STATE",
b"_NET_WM_STATE_ABOVE",
b"_NET_ACTIVE_WINDOW",
];
let cookies: Vec<_> = atom_names
.iter()
.map(|n| conn.intern_atom(false, n))
.collect::<Vec<_>>();
let mut atoms = Vec::new();
for cookie in cookies {
let cookie = match cookie {
Ok(c) => c,
Err(_) => return false,
};
let atom = match cookie.reply() {
Ok(r) => r.atom,
Err(_) => return false,
};
atoms.push(atom);
}
let a_wm_state = atoms[0];
let a_above = atoms[1];
let a_active = atoms[2];
let pointer = match conn.query_pointer(root) {
Ok(cookie) => match cookie.reply() {
Ok(p) => p,
Err(_) => return false,
},
Err(_) => return false,
};
let px = pointer.root_x as i32;
let py = pointer.root_y as i32;
let (mon_x, mon_y, mon_w, mon_h) = {
let default = (0i32, 0i32, 1920i32, 1080i32);
let resources = match conn.randr_get_screen_resources(root) {
Ok(cookie) => match cookie.reply() {
Ok(r) => r,
Err(_) => return false,
},
Err(_) => return false,
};
let mut found = default;
let mut any_monitor = false;
for &crtc in &resources.crtcs {
let info = match conn.randr_get_crtc_info(crtc, 0) {
Ok(cookie) => match cookie.reply() {
Ok(i) => i,
Err(_) => continue,
},
Err(_) => continue,
};
if info.width == 0 || info.height == 0 {
continue;
}
let cx = info.x as i32;
let cy = info.y as i32;
let cw = info.width as i32;
let ch = info.height as i32;
if !any_monitor {
found = (cx, cy, cw, ch);
any_monitor = true;
}
if px >= cx && px < cx + cw && py >= cy && py < cy + ch {
found = (cx, cy, cw, ch);
break;
}
}
found
};
let geom = match conn.get_geometry(wid) {
Ok(cookie) => match cookie.reply() {
Ok(g) => g,
Err(_) => return false,
},
Err(_) => return false,
};
let win_w = geom.width as i32;
let win_h = geom.height as i32;
if win_w == 0 || win_h == 0 {
return false;
}
let x = mon_x + (mon_w - win_w) / 2;
let y = mon_y + (mon_h - win_h) / 2;
let _ = conn.configure_window(wid, &ConfigureWindowAux::new().x(x).y(y));
let above_event = ClientMessageEvent::new(32, wid, a_wm_state, [1u32, a_above, 0, 0, 0]);
let _ = conn.send_event(
false,
root,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
above_event,
);
let activate_event = ClientMessageEvent::new(32, wid, a_active, [1u32, 0, 0, 0, 0]);
let _ = conn.send_event(
false,
root,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
activate_event,
);
let _ = conn.flush();
true
}
pub fn x11_centre_and_raise(title: &str) -> bool {
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
let (conn, screen_num) = match x11rb::connect(None) {
Ok(c) => c,
Err(_) => return false,
};
let screen = &conn.setup().roots[screen_num];
let root = screen.root;
let atom_names: &[&[u8]] = &[b"_NET_CLIENT_LIST", b"_NET_WM_NAME", b"UTF8_STRING"];
let cookies: Vec<_> = atom_names
.iter()
.map(|n| conn.intern_atom(false, n))
.collect::<Vec<_>>();
let mut atoms = Vec::new();
for cookie in cookies {
let cookie = match cookie {
Ok(c) => c,
Err(_) => return false,
};
let atom = match cookie.reply() {
Ok(r) => r.atom,
Err(_) => return false,
};
atoms.push(atom);
}
let a_client_list = atoms[0];
let a_wm_name = atoms[1];
let a_utf8 = atoms[2];
let client_list = match conn.get_property(false, root, a_client_list, AtomEnum::WINDOW, 0, 1024)
{
Ok(cookie) => match cookie.reply() {
Ok(prop) => prop,
Err(_) => return false,
},
Err(_) => return false,
};
let wid_vec: Vec<u32> = match client_list.value32() {
Some(iter) => iter.collect(),
None => return false,
};
for &wid in &wid_vec {
if let Ok(cookie) = conn.get_property(false, wid, a_wm_name, a_utf8, 0, 256) {
if let Ok(prop) = cookie.reply() {
if String::from_utf8_lossy(&prop.value) == title {
return x11_centre_and_raise_xid(wid);
}
}
}
if let Ok(cookie) =
conn.get_property(false, wid, AtomEnum::WM_NAME, AtomEnum::STRING, 0, 256)
{
if let Ok(prop) = cookie.reply() {
if String::from_utf8_lossy(&prop.value) == title {
return x11_centre_and_raise_xid(wid);
}
}
}
}
false
}
pub fn x11_activate_window(wid: u32) -> bool {
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
let (conn, screen_num) = match x11rb::connect(None) {
Ok(c) => c,
Err(_) => return false,
};
let root = conn.setup().roots[screen_num].root;
let atom = match conn.intern_atom(false, b"_NET_ACTIVE_WINDOW") {
Ok(cookie) => match cookie.reply() {
Ok(r) => r.atom,
Err(_) => return false,
},
Err(_) => return false,
};
let event = ClientMessageEvent::new(32, wid, atom, [2u32, 0, 0, 0, 0]);
let _ = conn.send_event(
false,
root,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
event,
);
let _ = conn.flush();
true
}
pub fn x11_send_key_combo(keysyms: &[u32]) -> bool {
x11_send_key_combo_checked(keysyms).is_ok()
}
pub fn x11_send_key_combo_checked(keysyms: &[u32]) -> Result<(), KeyComboError> {
keyboard::send_key_combo(keysyms)
}
pub fn x11_send_key_repeat(keysym: u32, count: usize) -> bool {
x11_send_key_repeat_checked(keysym, count).is_ok()
}
pub fn x11_send_key_repeat_checked(keysym: u32, count: usize) -> Result<(), KeyComboError> {
keyboard::send_key_repeat(keysym, count)
}
pub fn x11_client_base(wid: u32) -> Option<u32> {
use x11rb::connection::Connection;
let (conn, _screen_num) = x11rb::connect(None).ok()?;
let mask = conn.setup().resource_id_mask;
Some(crate::x11::clipboard::client_base(wid, mask))
}
pub fn x11_get_active_window() -> Option<u32> {
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
let (conn, screen_num) = x11rb::connect(None).ok()?;
let root = conn.setup().roots[screen_num].root;
let atom = conn
.intern_atom(false, b"_NET_ACTIVE_WINDOW")
.ok()?
.reply()
.ok()?
.atom;
let prop = conn
.get_property(false, root, atom, AtomEnum::WINDOW, 0, 1)
.ok()?
.reply()
.ok()?;
let wid = prop.value32()?.next()?;
if wid == 0 {
return None;
}
Some(wid)
}
pub fn x11_get_wm_class(wid: u32) -> Option<(String, String)> {
let (conn, _screen_num) = x11rb::connect(None).ok()?;
x11_get_wm_class_from_connection(&conn, wid)
}
fn x11_get_wm_class_from_connection<C: x11rb::connection::Connection>(
conn: &C,
wid: u32,
) -> Option<(String, String)> {
use x11rb::protocol::xproto::*;
let atom_wm_class = conn
.intern_atom(false, b"WM_CLASS")
.ok()?
.reply()
.ok()?
.atom;
let prop = conn
.get_property(false, wid, atom_wm_class, AtomEnum::ANY, 0, 1024 / 4)
.ok()?
.reply()
.ok()?;
if prop.format != 8 {
return None;
}
let bytes = prop.value;
let mut parts = bytes.split(|&b| b == 0);
let instance = parts.next()?;
let class = parts.next()?;
if instance.is_empty() && class.is_empty() {
return None;
}
Some((
String::from_utf8_lossy(instance).into_owned(),
String::from_utf8_lossy(class).into_owned(),
))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X11TargetSnapshot {
pub pid: u32,
pub wm_class: (String, String),
}
pub fn x11_target_snapshot(wid: u32) -> Option<X11TargetSnapshot> {
use x11rb::protocol::xproto::*;
let (conn, _screen_num) = x11rb::connect(None).ok()?;
let attributes = conn.get_window_attributes(wid).ok()?.reply().ok()?;
if attributes.map_state != MapState::VIEWABLE {
return None;
}
let pid_atom = conn
.intern_atom(false, b"_NET_WM_PID")
.ok()?
.reply()
.ok()?
.atom;
let pid_property = conn
.get_property(false, wid, pid_atom, AtomEnum::CARDINAL, 0, 1)
.ok()?
.reply()
.ok()?;
let pid = pid_property.value32()?.next()?;
if pid == 0 {
return None;
}
let wm_class = x11_get_wm_class_from_connection(&conn, wid)?;
Some(X11TargetSnapshot { pid, wm_class })
}
#[cfg(test)]
mod keyboard_api_tests {
#[test]
fn public_bool_wrappers_remain_compatible() {
let _combo: fn(&[u32]) -> bool = super::x11_send_key_combo;
let _repeat: fn(u32, usize) -> bool = super::x11_send_key_repeat;
}
}