use crate::error::UiResult;
use crate::key::Key;
use crate::window::event::WindowEvent;
use crate::window::ime::{IMEData, IME};
use crate::window::x11::clipboard::X11ClipBoard;
use crate::window::x11::handle::X11WindowHandle;
use crate::window::x11::ime::flag::Modifiers;
use crate::window::{WindowId, WindowKind, WindowType};
use crate::{Pos, Size, WindowAttribute};
use std::ffi::CString;
use std::ptr::null_mut;
use std::sync::{Arc, RwLock};
use std::{mem, ptr};
use x11::xlib;
use x11::xlib::{XCloseDisplay, XLookupString};
pub mod ime;
pub mod handle;
mod clipboard;
pub struct X11Window {
display: *mut xlib::Display,
handles: Vec<Arc<WindowType>>,
wm_delete_atom: xlib::Atom,
size: RwLock<Size>,
root: xlib::Window,
}
impl X11Window {
pub fn new(attr: &WindowAttribute, ime: Arc<IME>) -> Result<Self, String> {
unsafe {
if xlib::XInitThreads() == 0 {
panic!("XInitThreads failed");
}
let display = xlib::XOpenDisplay(ptr::null());
if display.is_null() {
return Err("Cannot open X display".into());
}
let screen = xlib::XDefaultScreen(display);
let root = xlib::XRootWindow(display, screen);
let mut wm_delete = xlib::XInternAtom(display, b"WM_DELETE_WINDOW\0".as_ptr() as *const i8, 0);
let mut window = X11Window::create_window(display, screen, root, attr, &mut wm_delete);
let update_atom = xlib::XInternAtom(display, b"MY_CUSTOM_MESSAGE\0".as_ptr() as *const i8, 0);
window.update_atom = update_atom;
let p = CString::new("@im=none").unwrap();
xlib::XSetLocaleModifiers(p.as_ptr());
let window = WindowType {
kind: WindowKind::X11(window),
id: WindowId::unique_id(),
type_: WindowType::ROOT,
ime,
};
Ok(Self {
display,
handles: vec![Arc::new(window)],
wm_delete_atom: wm_delete,
size: RwLock::new(attr.inner_size),
root,
})
}
}
pub fn last_window(&self) -> Arc<WindowType> {
self.handles.last().cloned().unwrap()
}
fn create_window(display: *mut xlib::Display, screen: i32, root: xlib::Window, attr: &WindowAttribute, wm_delete: &mut xlib::Atom) -> X11WindowHandle {
unsafe {
let child = xlib::XCreateSimpleWindow(
display, root,
attr.position[0], attr.position[1],
attr.inner_size.width, attr.inner_size.height,
1, xlib::XBlackPixel(display, screen),
xlib::XWhitePixel(display, screen),
);
let c_title = CString::new(attr.title.clone()).unwrap();
xlib::XStoreName(display, child, c_title.as_ptr());
let events = xlib::ExposureMask
| xlib::FocusChangeMask
| xlib::KeyPressMask
| xlib::KeyReleaseMask
| xlib::ButtonPressMask
| xlib::ButtonReleaseMask
| xlib::PointerMotionMask
| xlib::StructureNotifyMask;
xlib::XSelectInput(display, child, events as i64);
let mut attrs: xlib::XSetWindowAttributes = mem::zeroed();
attrs.background_pixel = 0;
xlib::XChangeWindowAttributes(display, child, xlib::CWBackPixmap, &mut attrs);
xlib::XSetWindowBackgroundPixmap(display, child, 0); xlib::XMapWindow(display, child);
xlib::XFlush(display);
xlib::XSetWMProtocols(display, child, wm_delete, 1);
X11WindowHandle {
display,
window: child,
update_atom: 0,
screen,
clipboard: X11ClipBoard::new(display).unwrap(),
}
}
}
pub fn create_child_window(&mut self, parent: &Arc<WindowType>, attr: &WindowAttribute) -> UiResult<Arc<WindowType>> {
let mut window = X11Window::create_window(
parent.x11().display, parent.x11().screen, self.root, attr,
&mut self.wm_delete_atom);
window.update_atom = parent.x11().update_atom;
let window = Arc::from(WindowType {
id: WindowId::unique_id(),
kind: WindowKind::X11(window),
type_: WindowType::CHILD,
ime: parent.ime.clone(),
});
self.handles.push(window.clone());
Ok(window)
}
pub fn run(&mut self) -> (WindowId, WindowEvent) {
unsafe {
let mut event: xlib::XEvent = mem::zeroed();
xlib::XNextEvent(self.display, &mut event);
let window = self.handles.iter().find(|x| x.x11().window == event.expose.window);
if window.is_none() { return (WindowId::unique_id(), WindowEvent::None); }
let window = window.unwrap();
window.ime.update_working();
if window.ime.is_working() { window.ime.update(); }
let typ = event.get_type();
match typ {
xlib::Expose => return (window.id, WindowEvent::Redraw),
xlib::FocusIn => {
window.ime.focus_in();
println!("focus in window");
}
xlib::FocusOut => {
window.ime.focus_out();
println!("focus out");
}
xlib::ConfigureNotify => {
let xcfg: xlib::XConfigureEvent = event.configure;
let new_w = xcfg.width as u32;
let new_h = xcfg.height as u32;
let mut size = self.size.write().unwrap();
if new_w == 0 || new_h == 0 {
} else if new_w != size.width || new_h != size.height {
size.width = new_w;
size.height = new_h;
println!("resize {}-{}-{}-{}", xcfg.width, xcfg.height, new_w, new_h);
return (window.id, WindowEvent::Resize(size.clone()));
}
}
xlib::ClientMessage => {
let xclient: xlib::XClientMessageEvent = event.client_message;
if xclient.message_type == window.x11().update_atom {
return match xclient.data.get_long(0) {
0 => (window.id, WindowEvent::ReqUpdate),
1 => (window.id, WindowEvent::CreateChild),
2 => (window.id, WindowEvent::ReInit),
3 => (window.id, WindowEvent::UserUpdate),
_ => (window.id, WindowEvent::None)
};
} else if xclient.data.get_long(0) as xlib::Atom == self.wm_delete_atom {
let pos = self.handles.iter().position(|x| x.x11().window == event.expose.window).unwrap();
let window = self.handles.remove(pos);
return (window.id, WindowEvent::ReqClose);
}
}
xlib::KeyPress => {
let mut keysym = 0;
let mut buffer: [i8; 32] = [0; 32];
let len = XLookupString(&mut event.key, buffer.as_mut_ptr(), 32, &mut keysym, null_mut());
let handle = window.ime.post_key(keysym as u32, event.key.keycode, Modifiers::Empty).unwrap();
println!("press-handle-{}-{}-{}", handle, window.ime.is_commited(), keysym);
return if handle {
window.ime.update();
(window.id, WindowEvent::IME(IMEData::Preedit(window.ime.chars())))
} else {
let ctrl_press = (event.key.state & xlib::ControlMask) != 0;
if ctrl_press && keysym == x11::keysym::XK_c as u64 {
return (window.id, WindowEvent::KeyPress(Key::CtrlC));
} else if ctrl_press && (keysym == x11::keysym::XK_v as u64) {
return (window.id, WindowEvent::KeyPress(Key::CtrlV));
}
(window.id, WindowEvent::KeyPress(Key::from_c_ulong(event.key.keycode, &buffer[..len as usize])))
};
}
xlib::KeyRelease => {
let mut keysym = 0;
let mut buffer: [i8; 32] = [0; 32];
let len = XLookupString(&mut event.key, buffer.as_mut_ptr(), 32, &mut keysym, null_mut());
let handle = window.ime.post_key(keysym as u32, event.key.keycode, Modifiers::Release).unwrap();
println!("release-handle-{}-{}", handle, window.ime.is_commited());
if !handle {
if window.ime.is_commited() {
return (window.id, WindowEvent::IME(IMEData::Commit(window.ime.ime_done())));
}
let ctrl_press = (event.key.state & xlib::ControlMask) != 0;
if ctrl_press && keysym == x11::keysym::XK_c as u64 {
return (window.id, WindowEvent::None);
} else if ctrl_press && (keysym == x11::keysym::XK_v as u64) {
return (window.id, WindowEvent::None);
} else if ctrl_press {
return (window.id, WindowEvent::None);
}
return (window.id, WindowEvent::KeyRelease(Key::from_c_ulong(event.key.keycode, &buffer[..len as usize])));
}
}
xlib::ButtonRelease => {
let xb: xlib::XButtonEvent = event.button;
match xb.button {
1 => return (window.id, WindowEvent::MouseRelease(Pos { x: xb.x as f32, y: xb.y as f32 })),
2 => {} 3 => {} 4 => return (window.id, WindowEvent::MouseWheel(1.0)), 5 => return (window.id, WindowEvent::MouseWheel(-1.0)), _ => {}
}
}
xlib::ButtonPress => {
let xb: xlib::XButtonEvent = event.button;
match xb.button {
1 => return (window.id, WindowEvent::MousePress(Pos { x: xb.x as f32, y: xb.y as f32 })),
_ => {}
}
}
xlib::MotionNotify => {
let xm: xlib::XMotionEvent = event.motion;
return if window.ime.is_commited() {
(window.id, WindowEvent::IME(IMEData::Commit(window.ime.ime_done())))
} else {
(window.id, WindowEvent::MouseMove(Pos { x: xm.x as f32, y: xm.y as f32 }))
};
}
xlib::SelectionRequest => window.x11().clipboard.handle_request(&event).unwrap(),
xlib::SelectionNotify => {
let res = window.x11().clipboard.get_clipboard_data(event, window.x11().window).unwrap();
println!("clipboard_res: {:?}", res);
return (window.id, WindowEvent::Clipboard(res));
}
_ => {}
}
if window.ime.is_working() { window.ime.update(); }
(window.id, WindowEvent::None)
}
}
}
impl Drop for X11Window {
fn drop(&mut self) {
unsafe {
XCloseDisplay(self.display);
let _ = Box::from_raw(self.display);
}
}
}