easel3d_xcb.rs 9.51 KB
//
// XCB implementation for drawing some stuff...
//
// Georg Hopp <georg@steffers.org>
//
// 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 <http://www.gnu.org/licenses/>.
//
extern crate xcb;

use std::fmt::Debug;
use std::sync::Arc;
use std::ptr;
use std::thread;
use std::sync::mpsc;
use std::ops::{Add, Sub, Div};

use easel3d::easel::Easel;
use easel3d::easel::canvas::{Canvas, Vertex};
use easel3d::easel::drawable::Drawable;

#[derive(Clone)]
pub struct XcbEasel (Arc<xcb::Connection>, i32);

pub struct XcbCanvas<'a, T> { conn   :Arc<xcb::Connection>
                            , width  :u16
                            , height :u16
                            , window :u32
                            , pixmap :u32
                            , gc     :u32
                            , zbuf   :Vec<T>
                            , shm    :Box<&'a mut [u32]> }

impl XcbEasel {
    pub fn new() -> Result<XcbEasel, xcb::ConnError> {
        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<xcb::Screen> {
        let XcbEasel(_, num) = self;
        self.setup().roots().nth(*num as usize)
    }

    pub fn canvas<'a, T>( &self
                        , width  :u16
                        , height :u16) -> Option<XcbCanvas<'a, T>>
    where T: Clone + From<i32> {
        let Self(conn, _) = self;
        let size = width as usize * height as usize;
        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.black_pixel())] );

        xcb::create_gc( &conn, gc, screen.root()
                      , &[ (xcb::GC_FOREGROUND, screen.white_pixel())
                      , (xcb::GC_GRAPHICS_EXPOSURES, 0) ] );

        let zbuf :Vec<T> = vec!(0.into(); size);
        let (shmid, shm) = getshm(size);
        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
                      , zbuf:   zbuf
                      , shm:    Box::new(shm) } )
    }
}

impl<'a, T> XcbCanvas<'a, T> {
    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, T> Canvas<T> for XcbCanvas<'a, T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
       + Debug + Copy + From<i32> + PartialOrd {
    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<i32>) {
        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) {
        self.zbuf = vec!(0.into(); self.zbuf.len());
        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<T>, color: u32) {
        for c in d.plot() {
            let (x, y, zr) = c.as_tuple();
            let idx :usize = (y*(self.width as i32)+x) as usize;
            if self.zbuf[idx] < zr {
                self.zbuf[idx] = zr;
                self.shm[idx] = color;
            }
        }
    }

    fn put_text(&self, ofs :Vertex<T>, s :&str) {
        let (xofs, yofs, _) = ofs.as_tuple();
        xcb::xproto::image_text_8( &self.conn, self.pixmap, self.gc
                                 , xofs as i16, yofs as i16, s );
        self.conn.flush();
    }

    fn set_pixel(&mut self, c :Vertex<T>, color :u32) {
        let (x, y, zr) = c.as_tuple();
        let idx :usize = (y * (self.width as i32) + x) as usize;

        if self.zbuf[idx] < zr {
            self.zbuf[idx] = zr;
            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();
    }
}