// // XCB implementation for drawing some stuff... // // Georg Hopp // // Copyright © 2019 Georg Hopp // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // extern crate xcb; use std::sync::Arc; use std::ptr; //::{null, null_mut}; use std::thread; use std::sync::mpsc; use crate::easel::{Easel, Canvas, Drawable, Coordinate, Coordinates}; #[derive(Clone)] pub struct XcbEasel (Arc, i32); pub struct XcbCanvas<'a> { conn :Arc , width :u16 , height :u16 , window :u32 , pixmap :u32 , gc :u32 , shm :Box<&'a mut [u32]> } impl XcbEasel { pub fn new() -> Result { let (conn, num) = xcb::Connection::connect(None)?; Ok(XcbEasel(Arc::new(conn), num)) } pub fn setup(&self) -> xcb::Setup { let XcbEasel(conn, _) = self; conn.get_setup() } pub fn screen(&self) -> Option { let XcbEasel(_, num) = self; self.setup().roots().nth(*num as usize) } pub fn canvas<'a>(&self, width :u16, height :u16) -> Option> { let Self(conn, _) = self; let conn = conn.clone(); let screen = match self.screen() { None => return None, Some(screen) => screen, }; println!("root depth: {}", screen.root_depth()); let shmseg = conn.generate_id(); let gc = conn.generate_id(); let pixmap = conn.generate_id(); let window = conn.generate_id(); xcb::create_window( &conn, xcb::COPY_FROM_PARENT as u8, window , screen.root(), 0, 0, width, width, 0 , xcb::WINDOW_CLASS_INPUT_OUTPUT as u16 , screen.root_visual() , &[(xcb::CW_BACK_PIXEL, screen.white_pixel())] ); xcb::create_gc( &conn, gc, screen.root() , &[ (xcb::GC_FOREGROUND, screen.black_pixel()) , (xcb::GC_GRAPHICS_EXPOSURES, 0) ] ); let (shmid, shm) = getshm((width * height) as usize); xcb::shm::attach(&conn, shmseg, shmid as u32, false); unsafe { libc::shmctl(shmid, libc::IPC_RMID, ptr::null_mut()); } xcb::shm::create_pixmap( &conn, pixmap, window, width, height , screen.root_depth(), shmseg, 0 ); xcb::map_window(&conn, window); conn.flush(); Some(XcbCanvas{ conn: conn , width: width , height: height , window: window , pixmap: pixmap , gc: gc , shm: Box::new(shm) } ) } } impl<'a> XcbCanvas<'a> { pub fn set_title(&self, title :&str) { let c = xcb::change_property_checked( &self.conn , xcb::PROP_MODE_REPLACE as u8 , self.window , xcb::ATOM_WM_NAME , xcb::ATOM_STRING , 8 , title.as_bytes() ); if self.conn.has_error().is_err() || c.request_check().is_err() { println!("Error setting title"); } } } fn getshm<'a>(size :usize) -> (i32, &'a mut [u32]) { use std::slice::from_raw_parts_mut; unsafe { let id = libc::shmget( libc::IPC_PRIVATE , size * 4 , libc::IPC_CREAT | 0o744 ); let ptr = libc::shmat(id, ptr::null(), 0); (id as i32, from_raw_parts_mut(ptr as *mut u32, size)) } } impl Easel for XcbEasel {} impl<'a> Canvas for XcbCanvas<'a> { fn init_events(&self) { let mask = [( xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_STRUCTURE_NOTIFY | xcb::EVENT_MASK_PROPERTY_CHANGE )]; xcb::change_window_attributes(&self.conn, self.window, &mask); self.conn.flush(); } fn start_events(&self, tx :mpsc::Sender) { let conn = self.conn.clone(); let window = self.window; let pixmap = self.pixmap; let gc = self.gc; let width = self.width; let height = self.height; thread::spawn(move || { loop { let event = conn.wait_for_event(); match event { None => break, Some(event) => { match event.response_type() & !0x80 { xcb::PROPERTY_NOTIFY => { let prop_notify :&xcb::PropertyNotifyEvent = unsafe { xcb::cast_event(&event) }; if prop_notify.atom() == xcb::ATOM_WM_NAME { // retrieving title let cookie = xcb::get_property( &conn , false , window , xcb::ATOM_WM_NAME , xcb::ATOM_STRING , 0, 1024 ); if let Ok(reply) = cookie.get_reply() { let r = reply.value(); let r = std::str::from_utf8(r).unwrap(); println!("title changed to: {}", r); } } }, xcb::EXPOSE => { xcb::copy_area( &conn, pixmap, window, gc , 0, 0, 0, 0 , width, height ); conn.flush(); }, xcb::KEY_PRESS => { let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(&event) }; println!( "Key '{}' pressed" , key_press.detail() ); // Q (on qwerty) if key_press.detail() == 0x18 { tx.send(1).unwrap(); break; } }, _ => {}, } }, } } }); } fn width(&self) -> u16 { self.width } fn height(&self) -> u16 { self.height } fn clear(&mut self) { unsafe { let ptr = self.shm.as_mut_ptr(); ptr::write_bytes( ptr, 0 , self.width as usize * self.height as usize); } } fn draw(&mut self, d :&dyn Drawable, ofs :Coordinate, color: u32) { let Coordinates(c) = d.plot(); let Coordinate(xofs, yofs) = ofs; for Coordinate(x, y) in c { let idx :usize = ((y+yofs)*(self.width as i32)+x+xofs) as usize; self.shm[idx] = color; } } fn show(&self) { xcb::copy_area( &self.conn, self.pixmap, self.window, self.gc , 0, 0, 0, 0 , self.width, self.height ); self.conn.flush(); } }