parent
54d0065b9d
commit
5c63de0a7b
@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/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