pub mod clipboard;
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 {
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
use x11rb::protocol::xtest;
if keysyms.is_empty() {
return true;
}
let (conn, _screen_num) = match x11rb::connect(None) {
Ok(c) => c,
Err(_) => return false,
};
let setup = conn.setup();
let min_keycode = setup.min_keycode;
let max_keycode = setup.max_keycode;
let count = max_keycode - min_keycode + 1;
let mapping = match conn.get_keyboard_mapping(min_keycode, count) {
Ok(cookie) => match cookie.reply() {
Ok(m) => m,
Err(_) => return false,
},
Err(_) => return false,
};
let syms_per_code = mapping.keysyms_per_keycode as usize;
let keysym_to_keycode = |sym: u32| -> Option<u8> {
for i in 0..count as usize {
for col in 0..syms_per_code {
if mapping.keysyms[i * syms_per_code + col] == sym {
return Some((i as u8) + min_keycode);
}
}
}
None
};
let keycodes: Vec<u8> = match keysyms.iter().map(|s| keysym_to_keycode(*s)).collect() {
Some(v) => v,
None => return false,
};
const KEY_PRESS: u8 = 2;
const KEY_RELEASE: u8 = 3;
const DELAY: std::time::Duration = std::time::Duration::from_millis(12);
let send = |type_: u8, kc: u8| -> bool {
if xtest::fake_input(&conn, type_, kc, 0, 0u32, 0, 0, 0).is_err() {
return false;
}
if conn.flush().is_err() {
return false;
}
std::thread::sleep(DELAY);
true
};
for &kc in &keycodes {
if !send(KEY_PRESS, kc) {
return false;
}
}
for &kc in keycodes.iter().rev() {
if !send(KEY_RELEASE, kc) {
return false;
}
}
true
}
pub fn x11_send_key_repeat(keysym: u32, count: usize) -> bool {
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
use x11rb::protocol::xtest;
if count == 0 {
return true;
}
let (conn, _screen_num) = match x11rb::connect(None) {
Ok(c) => c,
Err(_) => return false,
};
let setup = conn.setup();
let min_keycode = setup.min_keycode;
let max_keycode = setup.max_keycode;
let km_count = max_keycode - min_keycode + 1;
let mapping = match conn.get_keyboard_mapping(min_keycode, km_count) {
Ok(cookie) => match cookie.reply() {
Ok(m) => m,
Err(_) => return false,
},
Err(_) => return false,
};
let syms_per_code = mapping.keysyms_per_keycode as usize;
let keycode = (|| -> Option<u8> {
for i in 0..km_count as usize {
for col in 0..syms_per_code {
if mapping.keysyms[i * syms_per_code + col] == keysym {
return Some((i as u8) + min_keycode);
}
}
}
None
})();
let kc = match keycode {
Some(v) => v,
None => return false,
};
const KEY_PRESS: u8 = 2;
const KEY_RELEASE: u8 = 3;
for _ in 0..count {
if xtest::fake_input(&conn, KEY_PRESS, kc, 0, 0u32, 0, 0, 0).is_err() {
return false;
}
if conn.flush().is_err() {
return false;
}
std::thread::yield_now();
if xtest::fake_input(&conn, KEY_RELEASE, kc, 0, 0u32, 0, 0, 0).is_err() {
return false;
}
if conn.flush().is_err() {
return false;
}
std::thread::yield_now();
}
true
}
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)> {
use x11rb::protocol::xproto::*;
let (conn, _screen_num) = x11rb::connect(None).ok()?;
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(),
))
}