X11 implementation

main
isark 2 years ago
parent 54d0065b9d
commit 5c63de0a7b

1
.gitignore vendored

@ -1,2 +1,3 @@
/target
/Cargo.lock
.vscode

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "Underlayer"
name = "underlayer"
path = "src/lib.rs"
[[example]]
@ -17,8 +17,14 @@ path = "src/examples/main.rs"
crossbeam = "0.8.2"
log = "0.4.19"
raw-window-handle = "0.5.2"
x11rb = "0.12.0"
[target.'cfg(windows)'.dependencies]
windows = {version = "0.48.0", features = ["Win32_UI_Accessibility", "Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_Graphics_Gdi", "Win32_System_Com", "Win32_System_Ole"]}
[target.'cfg(unix)'.dependencies]
[dev-dependencies]
ctor = "0.2.3"
simple_logger = "4.2.0"

@ -1,4 +1,4 @@
use Underlayer::{UnderlayEvent, register};
use underlayer::{register, UnderlayEvent};
fn init() {
simple_logger::init().expect("Could not initialize simple_logger");
@ -6,9 +6,8 @@ fn init() {
fn main() {
init();
let rx: std::sync::mpsc::Receiver<UnderlayEvent> = register("Untitled - Notepad");
let rx: std::sync::mpsc::Receiver<UnderlayEvent> = register("Untitled 1 - Mousepad");
let mut value = rx.recv();
while let Ok(event) = value {
log::info!("Got event: {event:?}");
@ -16,4 +15,4 @@ fn main() {
}
log::error!("{value:?}");
}
}

@ -16,11 +16,16 @@ pub struct Bounds {
#[derive(Debug, Clone, Copy)]
pub enum UnderlayEvent {
Attach { bounds: Bounds, has_access: bool },
Attach {
bounds: Bounds,
has_access: bool,
is_fullscreen: Option<bool>,
},
Detach,
MoveResize(Bounds),
Focus,
Blur,
X11FullscreenEvent{ is_fullscreen: bool }
}
pub fn register<T: Into<String>>(window_title: T) -> Receiver<UnderlayEvent> {

@ -1,6 +1,359 @@
use x11rb::{
connect,
connection::Connection,
protocol::{
xproto::{Atom, AtomEnum, ChangeWindowAttributesAux, ConnectionExt, EventMask, Window},
Event,
},
rust_connection::RustConnection,
};
use crate::{Bounds, UnderlayEvent};
pub fn init(window_title: String, tx: std::sync::mpsc::Sender<crate::UnderlayEvent>) -> Result<(), String> {
const WM_NAME_BUFSIZE: u32 = 512;
struct Target {
title: String,
wid: Window,
is_focused: bool,
is_destroyed: bool,
is_fullscreen: bool,
}
struct AtomCollection {
net_active_window: Atom,
net_wm_name: Atom,
utf8_string: Atom,
net_wm_state: Atom,
net_wm_state_fullscreen: Atom,
// net_wm_state_skip_taskbar: Atom,
// net_wm_state_skip_pager: Atom,
}
impl AtomCollection {
fn new(conn: &RustConnection) -> Option<Self> {
Some(Self {
net_active_window: Self::_get(conn, "_NET_ACTIVE_WINDOW")?,
net_wm_name: Self::_get(conn, "_NET_WM_NAME")?,
utf8_string: Self::_get(conn, "UTF8_STRING")?,
net_wm_state: Self::_get(conn, "_NET_WM_STATE")?,
net_wm_state_fullscreen: Self::_get(conn, "_NET_WM_STATE_FULLSCREEN")?,
// net_wm_state_skip_taskbar: Self::_get(conn, "_NET_WM_STATE_SKIP_TASKBAR")?,
// net_wm_state_skip_pager: Self::_get(conn, "_NET_WM_STATE_SKIP_PAGER")?,
})
}
fn _get<T: Into<String>>(conn: &RustConnection, name: T) -> Option<Atom> {
Some(
conn.intern_atom(false, name.into().as_bytes())
.ok()?
.reply()
.ok()?
.atom,
)
}
}
struct Runtime {
conn: RustConnection,
root: Window,
target: Target,
atoms: AtomCollection,
underlay_tx: std::sync::mpsc::Sender<UnderlayEvent>,
active: Window,
}
impl Runtime {
fn get_active_window(&self) -> Option<Window> {
let cookie = self.conn
.get_property(
false,
self.root,
self.atoms.net_active_window,
AtomEnum::WINDOW,
0,
1,
)
.ok()?;
let reply = cookie.reply().ok()?;
Err(format!("Failed init underlayer for window: {}", window_title))
let mut value32_iter = reply.value32()?;
value32_iter.next()
}
fn get_title(&self, wid: Window) -> Option<String> {
if wid == x11rb::NONE {
return None;
}
let cookie = self
.conn
.get_property(
false,
wid,
self.atoms.net_wm_name,
self.atoms.utf8_string,
0,
WM_NAME_BUFSIZE,
)
.ok()?;
let reply = cookie.reply().ok()?;
let data = reply.value8()?.collect();
String::from_utf8(data).ok()
}
fn get_content_bounds(&self, wid: Window) -> Option<Bounds> {
let geometry_cookie = self.conn.get_geometry(wid).ok()?;
let geometry_reply = geometry_cookie.reply().ok()?;
let translation_cookie = self.conn.translate_coordinates(wid, self.root, 0, 0).ok()?;
let translation_reply = translation_cookie.reply().ok()?;
Some(Bounds {
x: translation_reply.dst_x as i32,
y: translation_reply.dst_y as i32,
width: geometry_reply.width as i32,
height: geometry_reply.height as i32,
})
}
fn is_fullscreen_window(&self, wid: Window) -> Option<bool> {
let cookie = self
.conn
.get_property(false, wid, self.atoms.net_wm_state, AtomEnum::ATOM, 0, 0)
.ok()?;
let reply = cookie.reply().ok()?;
let len = reply.value32()?.count();
let cookie = self
.conn
.get_property(
false,
wid,
self.atoms.net_wm_state,
AtomEnum::ATOM,
0,
len as u32,
)
.ok()?;
let reply = cookie.reply().ok()?;
let mut is_fullscreen = false;
for state in reply.value32()? {
is_fullscreen = state == self.atoms.net_wm_state_fullscreen;
}
Some(is_fullscreen)
}
fn handle_moveresize_xevent(&self) {
if let Some(bounds) = self.get_content_bounds(self.target.wid) {
self.underlay_tx
.send(UnderlayEvent::MoveResize(bounds))
.ok();
}
}
fn handle_fullscreen_xevent(&mut self) {
if let Some(is_fullscreen) = self.is_fullscreen_window(self.target.wid) {
if self.target.is_fullscreen != is_fullscreen {
self.target.is_fullscreen = is_fullscreen;
self.underlay_tx
.send(UnderlayEvent::X11FullscreenEvent { is_fullscreen })
.ok();
}
}
}
fn check_and_handle_window(&mut self, wid: Window) {
if self.target.wid != x11rb::NONE {
if self.target.wid == wid {
if !self.target.is_focused {
self.underlay_tx.send(UnderlayEvent::Focus).ok();
}
return;
}
if self.target.is_focused {
self.target.is_focused = false;
self.underlay_tx.send(UnderlayEvent::Blur).ok();
}
if self.target.is_destroyed {
self.target.wid = x11rb::NONE;
self.target.is_destroyed = false;
self.underlay_tx.send(UnderlayEvent::Detach).ok();
}
}
log::trace!("got title: {}", self.get_title(wid).unwrap_or("ERROR".into()));
match self.get_title(wid) {
Some(title) => {
if title != self.target.title {return}
}
_ => return,
}
log::trace!("Found target!");
if self.target.wid != x11rb::NONE {
self.conn
.change_window_attributes(
self.target.wid,
&ChangeWindowAttributesAux::new().event_mask(EventMask::NO_EVENT),
)
.ok();
}
self.target.wid = wid;
self.conn
.change_window_attributes(
wid,
&ChangeWindowAttributesAux::new()
.event_mask(EventMask::PROPERTY_CHANGE | EventMask::STRUCTURE_NOTIFY),
)
.ok();
if let Some(is_fullscreen) = self.is_fullscreen_window(self.target.wid) {
if let Some(bounds) = self.get_content_bounds(self.target.wid) {
if is_fullscreen != self.target.is_fullscreen {
self.target.is_fullscreen = is_fullscreen;
}
self.underlay_tx
.send(UnderlayEvent::Attach {
bounds,
has_access: true,
is_fullscreen: Some(is_fullscreen),
})
.ok();
self.target.is_focused = true;
self.underlay_tx.send(UnderlayEvent::Focus).ok();
//Early return to not unset target wid
return;
}
}
self.target.wid = x11rb::NONE;
}
fn handle_event(&mut self, event: Event) {
match event {
Event::DestroyNotify(event) => {
log::trace!("Got DestroyNotify: {event:?}");
if event.window == self.target.wid {
self.target.is_destroyed = true;
self.check_and_handle_window(x11rb::NONE);
}
}
Event::ConfigureNotify(event) => {
log::trace!("Got ConfigureNotify: {event:?}");
if event.window == self.target.wid {
self.handle_moveresize_xevent();
}
}
Event::PropertyNotify(event) => {
log::trace!("Got PropertyNotify: {event:?}");
if event.window == self.root && event.atom == self.atoms.net_active_window {
let old = self.active;
self.active = self.get_active_window().unwrap_or(x11rb::NONE);
if old != self.target.wid {
self.conn
.change_window_attributes(
old,
&ChangeWindowAttributesAux::new().event_mask(EventMask::NO_EVENT),
)
.ok();
}
if self.active != x11rb::NONE && self.active != self.target.wid {
self.conn
.change_window_attributes(
self.active,
&ChangeWindowAttributesAux::new()
.event_mask(EventMask::PROPERTY_CHANGE),
)
.ok();
}
self.check_and_handle_window(self.active);
} else if event.window == self.target.wid && event.atom == self.atoms.net_wm_state {
self.handle_fullscreen_xevent();
} else if event.window == self.active && event.atom == self.atoms.net_wm_name {
self.check_and_handle_window(self.active);
}
}
_ => (),
}
}
}
pub fn init(
window_title: String,
tx: std::sync::mpsc::Sender<crate::UnderlayEvent>,
) -> Result<(), String> {
std::thread::spawn(move || {
let (conn, screen_num) = match connect(None) {
Ok(conn) => conn,
Err(_) => {
log::error!("Could not connect");
return;
}
};
let root = conn.setup().roots[screen_num].root;
let atoms = match AtomCollection::new(&conn) {
Some(atoms) => atoms,
None => {
log::error!("Could not initialize atoms");
return;
}
};
conn.change_window_attributes(
root,
&ChangeWindowAttributesAux::new().event_mask(EventMask::PROPERTY_CHANGE),
)
.ok();
let mut runtime = Runtime {
conn,
root,
target: Target {
title: window_title,
wid: x11rb::NONE,
is_focused: false,
is_destroyed: false,
is_fullscreen: false,
},
atoms,
underlay_tx: tx,
active: x11rb::NONE,
};
runtime.active = runtime.get_active_window().unwrap_or(x11rb::NONE);
if runtime.active != x11rb::NONE {
runtime.conn.change_window_attributes(runtime.active, &ChangeWindowAttributesAux::new().event_mask(EventMask::PROPERTY_CHANGE)).ok();
runtime.check_and_handle_window(runtime.active);
}
runtime.conn.flush().ok();
log::trace!("Initialized, listning for events");
while let Ok(event) = runtime.conn.wait_for_event() {
log::trace!("Got event");
runtime.handle_event(event);
runtime.conn.flush().ok();
}
});
Ok(())
}

@ -373,7 +373,7 @@ impl Runtime {
if let Some(bounds) = self.get_content_bounds() {
self.underlay_tx
.send(UnderlayEvent::Attach { bounds, has_access })
.send(UnderlayEvent::Attach { bounds, has_access, is_fullscreen: None })
.ok();
self.target.is_focused = true;
@ -522,4 +522,4 @@ fn register_message<T: Into<String>>(message: T) -> u32 {
let message_wide = windows::core::HSTRING::from(message.into());
let result = unsafe { RegisterWindowMessageW(PCWSTR(message_wide.as_wide().as_ptr())) };
result
}
}
Loading…
Cancel
Save