#![warn(unsafe_op_in_unsafe_fn)]
#![cfg_attr(doc, feature(doc_cfg))]
#[cfg(feature = "uinput")]
pub mod device;
pub mod consts {
include!(concat!(env!("OUT_DIR"), "/consts.rs"));
#[cfg_attr(doc, doc(cfg(feature = "update-offset")))]
#[cfg(any(doc, feature = "update-offset"))]
include!("update_offset.rs");
}
pub mod pointer {
use crate::consts::*;
use libc::{iovec, process_vm_readv};
use std::{ffi::c_void, fmt::Display, fs::File, io::Read, process::Command, ptr};
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct KWinPid(i32);
impl KWinPid {
pub unsafe fn from(i: i32) -> Self {
unsafe {
if libc::getuid() != 0 {
if libc::setuid(0) != 0 {
panic!("cannot set uid to 0, further code could not be executed.")
}
}
}
Self(i)
}
pub unsafe fn search(all_user: bool) -> Self {
unsafe {
Self::from(
String::from_utf8_lossy(
&Command::new("ps")
.arg(if all_user { "ax" } else { "x" }) .output()
.expect("cannot enumerate programs")
.stdout,
)
.lines()
.filter(|x| x.contains("/kwin_wayland "))
.next()
.expect("failed to find kwin_wayland session")
.trim()
.split_once(' ')
.expect("cannot parse `ps`'s output")
.0
.parse()
.expect("cannot parse the pid"),
)
}
}
}
#[derive(Eq, PartialEq)]
pub struct Workspace(KWinPid, *mut c_void);
impl Workspace {
pub unsafe fn new(search_all_user: bool) -> Self {
unsafe { Self::get(KWinPid::search(search_all_user), WORKSPACE_OFFSET) }
}
pub fn get(pid: KWinPid, workspace_offset: usize) -> Self {
let mut buffer = String::new();
File::open(&format!("/proc/{}/maps", pid.0))
.unwrap_or_else(|e| panic!("cannot open file (require permissions?)\n{:?}", e))
.read_to_string(&mut buffer)
.expect("read maps failed");
let buffer0 = buffer
.split_once("libkwin.so")
.expect("program does not load libkwin.so (is it really kwin_wayland?)")
.0;
let buffer1 = buffer0.rsplit_once('\n').unwrap_or(("", buffer0)).1.trim();
let Some((offset, start)) = buffer1.split_once(" r--p ") else {
panic!("get offset failed, the buffer line is `{buffer1}`")
};
assert!(start.trim().starts_with("00000000"));
let offset1 = offset.split_once('-').expect("maps format error").0;
let base =
usize::from_str_radix(offset1, 16).expect("cannot parse to base 16") as *mut c_void;
let ret = unsafe { base.byte_add(workspace_offset) };
println!("base offset: {base:?}, {ret:?}");
Self(pid, ret)
}
pub fn get_offset_with_readelf(readelf: &str, path_to_libkwin: &str) -> usize {
usize::from_str_radix(
&String::from_utf8(
Command::new(readelf)
.args(["-WCs", path_to_libkwin])
.output()
.expect("readelf execute failed")
.stdout,
)
.expect("failed to parse readelf")
.split_once(r#"KWin::Workspace::_self"#)
.expect("cannot find KWin::Workspace::_self")
.0
.rsplit_once('\n')
.expect("cannot read offset of KWin::Workspace::_self")
.1
.split_once(':')
.expect("parse `:` failed.")
.1
.trim()
.split_once(' ')
.expect("cannot parse space")
.0,
16,
)
.expect("failed to process readelf.")
}
pub fn get_mouse(&self) -> Mouse {
let mut addr: *mut c_void = ptr::null_mut();
let local = iovec {
iov_base: &mut addr as *mut _ as *mut c_void,
iov_len: 8,
};
let remote = iovec {
iov_base: self.1,
iov_len: 8,
};
match unsafe { process_vm_readv(self.0.0, &local, 1, &remote, 1, 0) } {
8 => assert!(!addr.is_null()),
-1 => {
eprintln!("failed, check errno for more details.")
}
x => eprintln!("unknown bytes readed: {x}"),
}
Mouse(self.0, unsafe { addr.byte_add(POS_OFFSET) })
}
}
#[derive(Eq, PartialEq)]
pub struct Mouse(KWinPid, *mut c_void);
impl Mouse {
pub fn loc(&self) -> (f64, f64) {
let mut xy = [0f64; 2];
let local = iovec {
iov_base: xy.as_mut_ptr() as *mut c_void,
iov_len: 16,
};
let remote = iovec {
iov_base: self.1,
iov_len: 16,
};
match unsafe { process_vm_readv(self.0.0, &local, 1, &remote, 1, 0) } {
16 => return (xy[0], xy[1]),
-1 => {
eprintln!("failed, check errno for more details.")
}
x => eprintln!("unknown bytes readed: {x}"),
}
panic!("reading failed.");
}
}
impl Display for Mouse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.loc(), f)
}
}
}
#[cfg(test)]
#[cfg(feature = "test")]
mod test {
use crate::*;
use consts::WORKSPACE_OFFSET;
use pointer::{KWinPid, Workspace};
#[test]
fn equality() {
let w1 = unsafe { Workspace::new(true) };
let pid = unsafe { KWinPid::search(true) }; let offset = Workspace::get_offset_with_readelf("readelf", "/usr/lib/libkwin.so"); let w2 = Workspace::get(pid, offset); assert!(w1 == w2);
assert!(WORKSPACE_OFFSET == offset);
}
#[test]
fn get_loc() {
let workspace = unsafe { Workspace::new(true) };
let mouse = workspace.get_mouse();
println!("{:?} {}", mouse.loc(), mouse);
}
}