use super::*;
use cairo::prelude::SurfaceExt;
use cairo::XCBSurface;
use log::*;
use pango::prelude::*;
use std::sync::Arc;
pub struct X11Application {
connection: Arc<xcb::Connection>,
screen_num: i32,
windows: std::cell::RefCell<std::collections::HashMap<u32, Arc<Box<X11Window>>>>,
event_listeners: std::cell::RefCell<Vec<Box<dyn Fn(&X11Application, Event<u32>) -> ()>>>,
should_quit: std::cell::Cell<bool>,
}
pub struct X11Window {
id: u32,
root: *const X11Application,
cairo_surface: cairo::Surface,
}
impl X11Application {
fn get_atom(&self, name: &str) -> xcb::Atom {
let cookie = xcb::intern_atom(&self.connection, true, name);
let reply = cookie.get_reply().unwrap();
reply.atom()
}
}
impl Application for X11Application {
type Window = X11Window;
type WindowIdentifier = u32;
fn new() -> Self {
let (connection, screen_num) = xcb::Connection::connect(None).unwrap();
let app = X11Application {
connection: Arc::new(connection),
screen_num,
windows: std::cell::RefCell::new(std::collections::HashMap::new()),
event_listeners: std::cell::RefCell::new(vec![]),
should_quit: std::cell::Cell::new(false),
};
app.add_event_listener(Box::new(move |application, ev| match ev {
Event::Expose(expose_event) => {
let window = application.get_window(expose_event.window_id);
window.resize(
expose_event.width + expose_event.position.x as u16,
expose_event.height + expose_event.position.y as u16,
);
}
_ => {}
}));
app
}
fn create_window(&self, width: u16, height: u16) -> u32 {
let setup = self.connection.get_setup();
let screen = setup.roots().nth(self.screen_num as usize).unwrap();
let window_id = self.connection.generate_id();
xcb::create_window(
&self.connection,
xcb::COPY_FROM_PARENT as u8,
window_id,
screen.root(),
0,
0,
width,
height,
0,
xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
screen.root_visual(),
&[
(xcb::CW_BACK_PIXEL, screen.white_pixel()),
(
xcb::CW_EVENT_MASK,
xcb::EVENT_MASK_EXPOSURE
| xcb::EVENT_MASK_KEY_PRESS
| xcb::EVENT_MASK_KEY_RELEASE
| xcb::EVENT_MASK_BUTTON_PRESS
| xcb::EVENT_MASK_BUTTON_RELEASE
| xcb::EVENT_MASK_POINTER_MOTION
| xcb::EVENT_MASK_BUTTON_MOTION
| xcb::EVENT_MASK_BUTTON_1_MOTION
| xcb::EVENT_MASK_BUTTON_2_MOTION
| xcb::EVENT_MASK_BUTTON_3_MOTION
| xcb::EVENT_MASK_BUTTON_4_MOTION
| xcb::EVENT_MASK_BUTTON_5_MOTION
| xcb::EVENT_MASK_ENTER_WINDOW
| xcb::EVENT_MASK_LEAVE_WINDOW,
),
],
);
info!("Create Window '{}'", window_id);
xcb::change_property(
&self.connection,
xcb::PROP_MODE_REPLACE as u8,
window_id,
self.get_atom("WM_PROTOCOLS"),
4,
32,
&[self.get_atom("WM_DELETE_WINDOW")],
);
xcb::map_window(&self.connection, window_id);
self.connection.flush();
self.windows.borrow_mut().insert(
window_id,
Arc::new(Box::new(X11Window {
id: window_id,
root: self,
cairo_surface: {
cairo::Surface::create(
unsafe {
&cairo::XCBConnection::from_raw_full(
self.connection.get_raw_conn() as *mut cairo_sys::xcb_connection_t
)
},
&cairo::XCBDrawable(window_id),
&{
let mut visual_type = None;
'out: for i in setup.roots() {
let depth_iter = i.allowed_depths();
for j in depth_iter {
let visuals = j.visuals();
for mut v in visuals {
if screen.root_visual() == v.visual_id() {
visual_type = Some(unsafe {
cairo::XCBVisualType::from_raw_full({
let visual_ptr = (&mut v.base)
as *mut xcb::ffi::xcb_visualtype_t;
visual_ptr as *mut cairo_sys::xcb_visualtype_t
})
});
break 'out;
}
}
}
}
visual_type.unwrap()
},
width as i32,
height as i32,
)
},
})),
);
self.connection.flush();
return window_id;
}
fn main_loop(&self) {
loop {
let event = self.connection.wait_for_event();
match event {
None => {
warn!("IO Error");
}
Some(event) => {
let r = event.response_type() & !0x80;
match r {
xcb::EXPOSE => {
let expose_event: &xcb::ExposeEvent =
unsafe { xcb::cast_event(&event) };
self.trigger_event(Event::Expose(Expose {
window_id: expose_event.window(),
width: expose_event.width(),
height: expose_event.height(),
position: Position {
x: expose_event.x() as i16,
y: expose_event.y() as i16,
},
}));
trace!("Event EXPOSE triggered");
}
xcb::KEY_PRESS => {
let key_press_event: &xcb::KeyPressEvent =
unsafe { xcb::cast_event(&event) };
self.trigger_event(Event::KeyPress(KeyPress {
window_id: key_press_event.event(),
cursor_position: Position {
x: key_press_event.event_x(),
y: key_press_event.event_y(),
},
detail: key_press_event.detail(),
}));
trace!(
"Event KEY_PRESS triggered on WINDOW: {}",
key_press_event.event()
);
}
xcb::KEY_RELEASE => {
self.trigger_event(Event::KeyRelease(KeyRelease {}));
trace!("Event KEY_RELEASE triggered");
}
xcb::BUTTON_PRESS => {
let button_press_event: &xcb::ButtonPressEvent =
unsafe { xcb::cast_event(&event) };
self.trigger_event(Event::ButtonPress(ButtonPress {
window_id: button_press_event.event(),
cursor_position: Position {
x: button_press_event.event_x(),
y: button_press_event.event_y(),
},
detail: button_press_event.detail(),
}));
trace!(
"Event BUTTON_PRESS triggered on WINDOW: {}",
button_press_event.event()
);
}
xcb::BUTTON_RELEASE => {
self.trigger_event(Event::ButtonRelease(ButtonRelease {}));
trace!("Event BUTTON_RELEASE triggered");
}
xcb::MOTION_NOTIFY => {
self.trigger_event(Event::MotionNotify(MotionNotify {}));
trace!("Event MOTION_NOTIFY triggered");
}
xcb::ENTER_NOTIFY => {
self.trigger_event(Event::EnterNotify(EnterNotify {}));
trace!("Event ENTER_NOTIFY triggered");
}
xcb::LEAVE_NOTIFY => {
self.trigger_event(Event::LeaveNotify(LeaveNotify {}));
trace!("Event LEAVE_NOTIFY triggered");
}
xcb::CLIENT_MESSAGE => {
let client_message: &xcb::ClientMessageEvent =
unsafe { xcb::cast_event(&event) };
if client_message.data().data32()[0]
== self.get_atom("WM_DELETE_WINDOW")
{
self.trigger_event(Event::CloseNotify(CloseNotify {
window_id: client_message.window(),
}));
trace!("Event CLOSE_NOTIFY triggered");
} else {
trace!("Unhandled Client Message");
}
}
0 => {
let error_message: &xcb::GenericError =
unsafe { xcb::cast_event(&event) };
warn!(
"XCB Error Code: {}, Major Code: {}, Minor Code: {}",
error_message.error_code(),
unsafe { (*error_message.ptr).major_code },
unsafe { (*error_message.ptr).minor_code }
);
}
_ => {
warn!("Unhandled Event");
}
}
}
}
if self.should_quit.get() {
break;
}
}
}
fn get_window(&self, id: u32) -> Arc<Box<X11Window>> {
(*self.windows.borrow().get(&id).unwrap()).clone()
}
fn windows_len(&self) -> usize {
self.windows.borrow().len()
}
fn set_should_quit(&self, should_quit: bool) {
self.should_quit.set(should_quit);
}
fn flush(&self) -> bool {
self.connection.flush()
}
fn add_event_listener(&self, handler: Box<Fn(&Self, Event<u32>) -> ()>) {
self.event_listeners.borrow_mut().push(handler)
}
fn trigger_event(&self, event: Event<u32>) {
for handler in self.event_listeners.borrow().iter() {
handler(self, event);
}
}
fn destroy_window(&self, window_id: u32) {
xcb::destroy_window(&self.connection, window_id);
self.flush();
self.windows.borrow_mut().remove(&window_id);
info!("Window {} destroyed", window_id);
}
}
impl X11Window {
fn get_root(&self) -> &X11Application {
unsafe { &(*self.root) }
}
}
impl Window for X11Window {
fn polygon(&self, points: &[Position], color: Color) {
if points.len() >= 2 {
let context = cairo::Context::new(&self.cairo_surface);
context.set_source_rgb(color.r, color.g, color.b);
context.move_to(points[0].x as f64, points[0].y as f64);
for i in 1..(points.len()) {
context.line_to(points[i].x as f64, points[i].y as f64);
}
context.close_path();
context.fill();
}
}
fn resize(&self, width: u16, height: u16) {
self.cairo_surface.set_size(width as i32, height as i32);
}
fn draw_text(
&self,
position: Position,
color: Color,
font_size: i32,
font_family: &str,
content: &str,
) {
let cr_ctx = cairo::Context::new(&self.cairo_surface);
let pc_layout = pangocairo::functions::create_layout(&cr_ctx).unwrap();
pc_layout.set_text(content);
let mut font_description = pango::FontDescription::new();
font_description.set_absolute_size((pango::SCALE * font_size) as f64);
font_description.set_weight(pango::Weight::Bold);
font_description.set_family(font_family);
pc_layout.set_font_description(Some(&font_description));
cr_ctx.set_source_rgb(color.r, color.g, color.b);
cr_ctx.move_to(position.x as f64, position.y as f64);
pangocairo::functions::show_layout(&cr_ctx, &pc_layout);
}
fn flush(&self) {
self.cairo_surface.flush();
self.get_root().flush();
}
}