parent
54d0065b9d
commit
5c63de0a7b
@ -1,2 +1,3 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
.vscode
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in new issue