use std::cell::{Cell, RefCell};
use std::collections::BinaryHeap;
use std::convert::{TryFrom, TryInto};
use std::os::unix::io::RawFd;
use std::panic::Location;
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::scale::Scalable;
use anyhow::{anyhow, Context, Error};
use cairo::{XCBConnection as CairoXCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
use tracing::{error, info, warn};
use x11rb::connection::Connection;
use x11rb::errors::ReplyOrIdError;
use x11rb::properties::{WmHints, WmHintsState, WmSizeHints};
use x11rb::protocol::present::{CompleteNotifyEvent, ConnectionExt as _, IdleNotifyEvent};
use x11rb::protocol::render::{ConnectionExt as _, Pictformat};
use x11rb::protocol::xfixes::{ConnectionExt as _, Region as XRegion};
use x11rb::protocol::xproto::{
self, AtomEnum, ChangeWindowAttributesAux, ColormapAlloc, ConfigureNotifyEvent,
ConfigureWindowAux, ConnectionExt, CreateGCAux, EventMask, Gcontext, ImageFormat,
ImageOrder as X11ImageOrder, Pixmap, PropMode, Rectangle, Visualtype, WindowClass,
};
use x11rb::wrapper::ConnectionExt as _;
use x11rb::xcb_ffi::XCBConnection;
#[cfg(feature = "raw-win-handle")]
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, XcbWindowHandle};
use crate::backend::shared::Timer;
use crate::common_util::IdleCallback;
use crate::dialog::FileDialogOptions;
use crate::error::Error as ShellError;
use crate::keyboard::{KeyState, Modifiers};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent};
use crate::piet::{Piet, PietText, RenderContext};
use crate::region::Region;
use crate::scale::Scale;
use crate::text::{simulate_input, Event};
use crate::window::{
FileDialogToken, IdleToken, TextFieldToken, TimerToken, WinHandler, WindowLevel,
};
use crate::{window, KeyEvent, ScaledArea};
use super::application::Application;
use super::dialog;
use super::menu::Menu;
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct xcb_visualtype_t {
pub visual_id: u32,
pub class: u8,
pub bits_per_rgb_value: u8,
pub colormap_entries: u16,
pub red_mask: u32,
pub green_mask: u32,
pub blue_mask: u32,
pub pad0: [u8; 4],
}
impl From<Visualtype> for xcb_visualtype_t {
fn from(value: Visualtype) -> xcb_visualtype_t {
xcb_visualtype_t {
visual_id: value.visual_id,
class: value.class.into(),
bits_per_rgb_value: value.bits_per_rgb_value,
colormap_entries: value.colormap_entries,
red_mask: value.red_mask,
green_mask: value.green_mask,
blue_mask: value.blue_mask,
pad0: [0; 4],
}
}
}
fn size_hints(resizable: bool, size: Size, min_size: Size) -> WmSizeHints {
let mut size_hints = WmSizeHints::new();
if resizable {
size_hints.min_size = Some((min_size.width as i32, min_size.height as i32));
} else {
size_hints.min_size = Some((size.width as i32, size.height as i32));
size_hints.max_size = Some((size.width as i32, size.height as i32));
}
size_hints
}
pub(crate) struct WindowBuilder {
app: Application,
handler: Option<Box<dyn WinHandler>>,
title: String,
transparent: bool,
position: Option<Point>,
size: Size,
min_size: Size,
resizable: bool,
level: WindowLevel,
state: Option<window::WindowState>,
}
impl WindowBuilder {
pub fn new(app: Application) -> WindowBuilder {
WindowBuilder {
app,
handler: None,
title: String::new(),
transparent: false,
position: None,
size: Size::new(500.0, 400.0),
min_size: Size::new(0.0, 0.0),
resizable: true,
level: WindowLevel::AppWindow,
state: None,
}
}
pub fn set_handler(&mut self, handler: Box<dyn WinHandler>) {
self.handler = Some(handler);
}
pub fn set_size(&mut self, size: Size) {
self.size = if size.width == 0. || size.height == 0. {
Size::new(1., 1.)
} else {
size
};
}
pub fn set_min_size(&mut self, min_size: Size) {
self.min_size = min_size;
}
pub fn resizable(&mut self, resizable: bool) {
self.resizable = resizable;
}
pub fn show_titlebar(&mut self, _show_titlebar: bool) {
warn!("WindowBuilder::show_titlebar is currently unimplemented for X11 backend.");
}
pub fn set_transparent(&mut self, transparent: bool) {
self.transparent = transparent;
}
pub fn set_position(&mut self, position: Point) {
self.position = Some(position);
}
pub fn set_level(&mut self, level: window::WindowLevel) {
self.level = level;
}
pub fn set_window_state(&mut self, state: window::WindowState) {
self.state = Some(state);
}
pub fn set_title<S: Into<String>>(&mut self, title: S) {
self.title = title.into();
}
pub fn set_menu(&mut self, _menu: Menu) {
}
fn create_cairo_surface(
&self,
window_id: u32,
visual_type: &Visualtype,
) -> Result<XCBSurface, Error> {
let conn = self.app.connection();
let cairo_xcb_connection = unsafe {
CairoXCBConnection::from_raw_none(
conn.get_raw_xcb_connection() as *mut cairo_sys::xcb_connection_t
)
};
let cairo_drawable = XCBDrawable(window_id);
let mut xcb_visual = xcb_visualtype_t::from(*visual_type);
let cairo_visual_type = unsafe {
XCBVisualType::from_raw_none(
&mut xcb_visual as *mut xcb_visualtype_t as *mut cairo_sys::xcb_visualtype_t,
)
};
let cairo_surface = XCBSurface::create(
&cairo_xcb_connection,
&cairo_drawable,
&cairo_visual_type,
self.size.width as i32,
self.size.height as i32,
)
.map_err(|status| anyhow!("Failed to create cairo surface: {}", status))?;
Ok(cairo_surface)
}
pub fn build(self) -> Result<WindowHandle, Error> {
let conn = self.app.connection();
let screen_num = self.app.screen_num();
let id = conn.generate_id()?;
let setup = conn.setup();
let env_dpi = std::env::var("DRUID_X11_DPI")
.ok()
.map(|x| x.parse::<f64>());
let scale = match env_dpi.or_else(|| self.app.rdb.get_value("Xft.dpi", "").transpose()) {
Some(Ok(dpi)) => {
let scale = dpi / 96.;
Scale::new(scale, scale)
}
None => Scale::default(),
Some(Err(err)) => {
let default = Scale::default();
warn!(
"Unable to parse dpi: {:?}, defaulting to {:?}",
err, default
);
default
}
};
let size_px = self.size.to_px(scale);
let screen = setup
.roots
.get(screen_num)
.ok_or_else(|| anyhow!("Invalid screen num: {}", screen_num))?;
let visual_type = if self.transparent {
self.app.argb_visual_type()
} else {
None
};
let (transparent, visual_type) = match visual_type {
Some(visual) => (true, visual),
None => (false, self.app.root_visual_type()),
};
if transparent != self.transparent {
warn!("Windows with transparent backgrounds do not work");
}
let mut cw_values = xproto::CreateWindowAux::new().event_mask(
EventMask::EXPOSURE
| EventMask::STRUCTURE_NOTIFY
| EventMask::KEY_PRESS
| EventMask::KEY_RELEASE
| EventMask::BUTTON_PRESS
| EventMask::BUTTON_RELEASE
| EventMask::POINTER_MOTION
| EventMask::FOCUS_CHANGE,
);
if transparent {
let colormap = conn.generate_id()?;
conn.create_colormap(
ColormapAlloc::NONE,
colormap,
screen.root,
visual_type.visual_id,
)?;
cw_values = cw_values
.border_pixel(screen.white_pixel)
.colormap(colormap);
};
let (parent, parent_origin) = match &self.level {
WindowLevel::AppWindow => (Weak::new(), Vec2::ZERO),
WindowLevel::Tooltip(parent)
| WindowLevel::DropDown(parent)
| WindowLevel::Modal(parent) => {
let handle = parent.0.window.clone();
let origin = handle
.upgrade()
.map(|x| x.get_position())
.unwrap_or_default()
.to_vec2();
(handle, origin)
}
};
let pos = (self.position.unwrap_or_default() + parent_origin).to_px(scale);
let (width_px, height_px) = (size_px.width as u16, size_px.height as u16);
let depth = if transparent { 32 } else { screen.root_depth };
conn.create_window(
depth,
id,
screen.root,
pos.x as _,
pos.y as _,
width_px,
height_px,
0,
WindowClass::INPUT_OUTPUT,
visual_type.visual_id,
&cw_values,
)?
.check()
.context("create window")?;
if let Some(colormap) = cw_values.colormap {
conn.free_colormap(colormap)?;
}
let gc = conn.generate_id()?;
conn.create_gc(gc, id, &CreateGCAux::new())?
.check()
.context("create graphics context")?;
let cairo_surface = RefCell::new(self.create_cairo_surface(id, &visual_type)?);
let present_data = match self.initialize_present_data(id) {
Ok(p) => Some(p),
Err(e) => {
info!("Failed to initialize present extension: {}", e);
None
}
};
let handler = RefCell::new(self.handler.unwrap());
let buf_count = if present_data.is_some() { 2 } else { 1 };
let buffers = RefCell::new(Buffers::new(
conn, id, buf_count, width_px, height_px, depth,
)?);
let atoms = self.app.atoms();
let pid = nix::unistd::Pid::this().as_raw();
if let Ok(pid) = u32::try_from(pid) {
conn.change_property32(
xproto::PropMode::REPLACE,
id,
atoms._NET_WM_PID,
AtomEnum::CARDINAL,
&[pid],
)?
.check()
.context("set _NET_WM_PID")?;
}
if let Some(name) = std::env::args_os().next() {
let path: &std::path::Path = name.as_ref();
let name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
let mut wm_class = Vec::with_capacity(2 * (name.len() + 1));
wm_class.extend(name.as_bytes());
wm_class.push(0);
if let Some(&first) = wm_class.first() {
wm_class.push(first.to_ascii_uppercase());
wm_class.extend(&name.as_bytes()[1..]);
}
wm_class.push(0);
conn.change_property8(
PropMode::REPLACE,
id,
AtomEnum::WM_CLASS,
AtomEnum::STRING,
&wm_class,
)?;
} else {
}
let protocols = [atoms.WM_DELETE_WINDOW];
conn.change_property32(
PropMode::REPLACE,
id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&protocols,
)?
.check()
.context("set WM_PROTOCOLS")?;
let min_size = self.min_size.to_px(scale);
log_x11!(size_hints(self.resizable, size_px, min_size)
.set_normal_hints(conn.as_ref(), id)
.context("set wm normal hints"));
let mut hints = WmHints::new();
if let Some(state) = self.state {
hints.initial_state = Some(match state {
window::WindowState::Maximized => WmHintsState::Normal,
window::WindowState::Minimized => WmHintsState::Iconic,
window::WindowState::Restored => WmHintsState::Normal,
});
}
log_x11!(hints.set(conn.as_ref(), id).context("set wm hints"));
{
let window_type = match self.level {
WindowLevel::AppWindow => atoms._NET_WM_WINDOW_TYPE_NORMAL,
WindowLevel::Tooltip(_) => atoms._NET_WM_WINDOW_TYPE_TOOLTIP,
WindowLevel::Modal(_) => atoms._NET_WM_WINDOW_TYPE_DIALOG,
WindowLevel::DropDown(_) => atoms._NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
};
let conn = self.app.connection();
log_x11!(conn.change_property32(
xproto::PropMode::REPLACE,
id,
atoms._NET_WM_WINDOW_TYPE,
AtomEnum::ATOM,
&[window_type],
));
if matches!(
self.level,
WindowLevel::DropDown(_) | WindowLevel::Modal(_) | WindowLevel::Tooltip(_)
) {
log_x11!(conn.change_window_attributes(
id,
&ChangeWindowAttributesAux::new().override_redirect(1),
));
}
}
let window = Rc::new(Window {
id,
gc,
app: self.app.clone(),
handler,
cairo_surface,
area: Cell::new(ScaledArea::from_px(size_px, scale)),
scale: Cell::new(scale),
min_size,
invalid: RefCell::new(Region::EMPTY),
destroyed: Cell::new(false),
timer_queue: Mutex::new(BinaryHeap::new()),
idle_queue: Arc::new(Mutex::new(Vec::new())),
idle_pipe: self.app.idle_pipe(),
present_data: RefCell::new(present_data),
buffers,
active_text_field: Cell::new(None),
parent,
});
window.set_title(&self.title);
if let Some(pos) = self.position {
window.set_position(pos);
}
let handle = WindowHandle::new(id, visual_type.visual_id, Rc::downgrade(&window));
window.connect(handle.clone())?;
self.app.add_window(id, window)?;
Ok(handle)
}
fn initialize_present_data(&self, window_id: u32) -> Result<PresentData, Error> {
if self.app.present_opcode().is_some() {
let conn = self.app.connection();
let id = conn.generate_id()?;
use x11rb::protocol::present::EventMask;
conn.present_select_input(
id,
window_id,
EventMask::COMPLETE_NOTIFY | EventMask::IDLE_NOTIFY,
)?
.check()
.context("set present event mask")?;
let region_id = conn.generate_id()?;
conn.xfixes_create_region(region_id, &[])
.context("create region")?;
Ok(PresentData {
serial: 0,
region: region_id,
waiting_on: None,
needs_present: false,
last_msc: None,
last_ust: None,
})
} else {
Err(anyhow!("no present opcode"))
}
}
}
pub(crate) struct Window {
id: u32,
gc: Gcontext,
app: Application,
handler: RefCell<Box<dyn WinHandler>>,
cairo_surface: RefCell<XCBSurface>,
area: Cell<ScaledArea>,
scale: Cell<Scale>,
min_size: Size,
destroyed: Cell<bool>,
invalid: RefCell<Region>,
timer_queue: Mutex<BinaryHeap<Timer<()>>>,
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
idle_pipe: RawFd,
present_data: RefCell<Option<PresentData>>,
buffers: RefCell<Buffers>,
active_text_field: Cell<Option<TextFieldToken>>,
parent: Weak<Window>,
}
struct Buffers {
idle_pixmaps: Vec<Pixmap>,
all_pixmaps: Vec<Pixmap>,
width: u16,
height: u16,
depth: u8,
}
#[derive(Debug)]
struct PresentData {
serial: u32,
region: XRegion,
waiting_on: Option<u32>,
needs_present: bool,
last_msc: Option<u64>,
last_ust: Option<u64>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CustomCursor(xproto::Cursor);
impl Window {
#[track_caller]
fn with_handler<T, F: FnOnce(&mut dyn WinHandler) -> T>(&self, f: F) -> Option<T> {
if self.cairo_surface.try_borrow_mut().is_err()
|| self.invalid.try_borrow_mut().is_err()
|| self.present_data.try_borrow_mut().is_err()
|| self.buffers.try_borrow_mut().is_err()
{
error!("other RefCells were borrowed when calling into the handler");
return None;
}
self.with_handler_and_dont_check_the_other_borrows(f)
}
#[track_caller]
fn with_handler_and_dont_check_the_other_borrows<T, F: FnOnce(&mut dyn WinHandler) -> T>(
&self,
f: F,
) -> Option<T> {
match self.handler.try_borrow_mut() {
Ok(mut h) => Some(f(&mut **h)),
Err(_) => {
error!("failed to borrow WinHandler at {}", Location::caller());
None
}
}
}
fn connect(&self, handle: WindowHandle) -> Result<(), Error> {
let size = self.size().size_dp();
let scale = self.scale.get();
self.with_handler(|h| {
h.connect(&handle.into());
h.scale(scale);
h.size(size);
});
Ok(())
}
pub fn destroy(&self) {
if !self.destroyed() {
self.destroyed.set(true);
log_x11!(self.app.connection().destroy_window(self.id));
}
}
fn destroyed(&self) -> bool {
self.destroyed.get()
}
fn size(&self) -> ScaledArea {
self.area.get()
}
fn size_changed(&self, size: Size) -> Result<(), Error> {
let scale = self.scale.get();
let new_size = {
if size != self.area.get().size_px() {
self.area.set(ScaledArea::from_px(size, scale));
true
} else {
false
}
};
if new_size {
borrow_mut!(self.buffers)?.set_size(
self.app.connection(),
self.id,
size.width as u16,
size.height as u16,
);
borrow_mut!(self.cairo_surface)?
.set_size(size.width as i32, size.height as i32)
.map_err(|status| {
anyhow!(
"Failed to update cairo surface size to {:?}: {}",
size,
status
)
})?;
self.add_invalid_rect(size.to_dp(scale).to_rect())?;
self.with_handler(|h| h.size(size.to_dp(scale)));
self.with_handler(|h| h.scale(scale));
}
Ok(())
}
fn update_cairo_surface(&self) -> Result<(), Error> {
let mut buffers = borrow_mut!(self.buffers)?;
let pixmap = if let Some(p) = buffers.idle_pixmaps.last() {
*p
} else {
info!("ran out of idle pixmaps, creating a new one");
buffers.create_pixmap(self.app.connection(), self.id)?
};
let drawable = XCBDrawable(pixmap);
borrow_mut!(self.cairo_surface)?
.set_drawable(&drawable, buffers.width as i32, buffers.height as i32)
.map_err(|e| anyhow!("Failed to update cairo drawable: {}", e))?;
Ok(())
}
fn render(&self) -> Result<(), Error> {
self.with_handler(|h| h.prepare_paint());
if self.destroyed() {
return Ok(());
}
self.update_cairo_surface()?;
let invalid = std::mem::replace(&mut *borrow_mut!(self.invalid)?, Region::EMPTY);
{
let surface = borrow!(self.cairo_surface)?;
let cairo_ctx = cairo::Context::new(&*surface).unwrap();
let scale = self.scale.get();
for rect in invalid.rects() {
let rect = rect.to_px(scale).round();
cairo_ctx.rectangle(rect.x0, rect.y0, rect.width(), rect.height());
}
cairo_ctx.clip();
cairo_ctx.scale(scale.x(), scale.y());
let mut piet_ctx = Piet::new(&cairo_ctx);
let result = self.with_handler_and_dont_check_the_other_borrows(|handler| {
handler.paint(&mut piet_ctx, &invalid);
piet_ctx
.finish()
.map_err(|e| anyhow!("Window::render - piet finish failed: {}", e))
});
let err = match result {
None => {
piet_ctx
.finish()
.map_err(|e| anyhow!("Window::render - piet finish failed: {}", e))
}
Some(e) => {
e
}
};
cairo_ctx.reset_clip();
err?;
}
self.set_needs_present(false)?;
let mut buffers = borrow_mut!(self.buffers)?;
let pixmap = *buffers
.idle_pixmaps
.last()
.ok_or_else(|| anyhow!("after rendering, no pixmap to present"))?;
let scale = self.scale.get();
if let Some(present) = borrow_mut!(self.present_data)?.as_mut() {
present.present(self.app.connection(), pixmap, self.id, &invalid, scale)?;
buffers.idle_pixmaps.pop();
} else {
for rect in invalid.rects() {
let rect = rect.to_px(scale).round();
let (x, y) = (rect.x0 as i16, rect.y0 as i16);
let (w, h) = (rect.width() as u16, rect.height() as u16);
self.app
.connection()
.copy_area(pixmap, self.id, self.gc, x, y, x, y, w, h)?;
}
}
Ok(())
}
fn show(&self) {
if !self.destroyed() {
log_x11!(self.app.connection().map_window(self.id));
}
}
fn close(&self) {
self.destroy();
}
fn resizable(&self, resizable: bool) {
let conn = self.app.connection().as_ref();
log_x11!(size_hints(resizable, self.size().size_px(), self.min_size)
.set_normal_hints(conn, self.id)
.context("set normal hints"));
}
fn show_titlebar(&self, _show_titlebar: bool) {
warn!("Window::show_titlebar is currently unimplemented for X11 backend.");
}
fn parent_origin(&self) -> Vec2 {
self.parent
.upgrade()
.map(|x| x.get_position())
.unwrap_or_default()
.to_vec2()
}
fn get_position(&self) -> Point {
fn _get_position(window: &Window) -> Result<Point, Error> {
let conn = window.app.connection();
let scale = window.scale.get();
let geom = conn.get_geometry(window.id)?.reply()?;
let cord = conn
.translate_coordinates(window.id, geom.root, 0, 0)?
.reply()?;
Ok(Point::new(cord.dst_x as _, cord.dst_y as _).to_dp(scale))
}
let pos = _get_position(self);
log_x11!(&pos);
pos.map(|pos| pos - self.parent_origin())
.unwrap_or_default()
}
fn set_position(&self, pos: Point) {
let conn = self.app.connection();
let scale = self.scale.get();
let pos = (pos + self.parent_origin()).to_px(scale).expand();
log_x11!(conn.configure_window(
self.id,
&ConfigureWindowAux::new().x(pos.x as i32).y(pos.y as i32),
));
}
fn set_size(&self, size: Size) {
let conn = self.app.connection();
let scale = self.scale.get();
let size = size.to_px(scale).expand();
log_x11!(conn.configure_window(
self.id,
&ConfigureWindowAux::new()
.width(size.width as u32)
.height(size.height as u32),
));
}
fn bring_to_front_and_focus(&self) {
if self.destroyed() {
return;
}
let conn = self.app.connection();
log_x11!(conn.configure_window(
self.id,
&xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE),
));
log_x11!(conn.set_input_focus(
xproto::InputFocus::POINTER_ROOT,
self.id,
xproto::Time::CURRENT_TIME,
));
}
fn add_invalid_rect(&self, rect: Rect) -> Result<(), Error> {
let scale = self.scale.get();
borrow_mut!(self.invalid)?.add_rect(rect.to_px(scale).expand().to_dp(scale));
Ok(())
}
fn redraw_now(&self) -> Result<(), Error> {
if self.waiting_on_present()? {
self.set_needs_present(true)?;
} else {
self.render()?;
}
Ok(())
}
fn request_anim_frame(&self) {
if let Ok(true) = self.waiting_on_present() {
if let Err(e) = self.set_needs_present(true) {
error!(
"Window::request_anim_frame - failed to schedule present: {}",
e
);
}
} else {
let idle = IdleHandle {
queue: Arc::clone(&self.idle_queue),
pipe: self.idle_pipe,
};
idle.schedule_redraw();
}
}
fn invalidate(&self) {
let rect = self.size().size_dp().to_rect();
self.add_invalid_rect(rect)
.unwrap_or_else(|err| error!("Window::invalidate - failed to invalidate: {}", err));
self.request_anim_frame();
}
fn invalidate_rect(&self, rect: Rect) {
if let Err(err) = self.add_invalid_rect(rect) {
error!("Window::invalidate_rect - failed to enlarge rect: {}", err);
}
self.request_anim_frame();
}
fn set_title(&self, title: &str) {
if self.destroyed() {
return;
}
let atoms = self.app.atoms();
log_x11!(self.app.connection().change_property8(
xproto::PropMode::REPLACE,
self.id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
));
log_x11!(self.app.connection().change_property8(
xproto::PropMode::REPLACE,
self.id,
atoms._NET_WM_NAME,
atoms.UTF8_STRING,
title.as_bytes(),
));
}
fn set_cursor(&self, cursor: &Cursor) {
let cursors = &self.app.cursors;
#[allow(deprecated)]
let cursor = match cursor {
Cursor::Arrow => cursors.default,
Cursor::IBeam => cursors.text,
Cursor::Pointer => cursors.pointer,
Cursor::Crosshair => cursors.crosshair,
Cursor::OpenHand => {
warn!("Cursor::OpenHand not supported for x11 backend. using arrow cursor");
None
}
Cursor::NotAllowed => cursors.not_allowed,
Cursor::ResizeLeftRight => cursors.col_resize,
Cursor::ResizeUpDown => cursors.row_resize,
Cursor::Custom(custom) => Some(custom.0),
};
if cursor.is_none() {
warn!("Unable to load cursor {:?}", cursor);
return;
}
let conn = self.app.connection();
let changes = ChangeWindowAttributesAux::new().cursor(cursor);
if let Err(e) = conn.change_window_attributes(self.id, &changes) {
error!("Changing cursor window attribute failed {}", e);
};
}
fn set_menu(&self, _menu: Menu) {
}
fn get_scale(&self) -> Result<Scale, Error> {
Ok(self.scale.get())
}
pub fn handle_expose(&self, expose: &xproto::ExposeEvent) -> Result<(), Error> {
let rect = Rect::from_origin_size(
(expose.x as f64, expose.y as f64),
(expose.width as f64, expose.height as f64),
)
.to_dp(self.scale.get());
self.add_invalid_rect(rect)?;
if self.waiting_on_present()? {
self.set_needs_present(true)?;
} else if expose.count == 0 {
self.request_anim_frame();
}
Ok(())
}
pub fn handle_key_event(&self, event: KeyEvent) {
self.with_handler(|h| match event.state {
KeyState::Down => {
simulate_input(h, self.active_text_field.get(), event);
}
KeyState::Up => h.key_up(event),
});
}
pub fn handle_button_press(
&self,
button_press: &xproto::ButtonPressEvent,
) -> Result<(), Error> {
let button = mouse_button(button_press.detail);
let scale = self.scale.get();
let mouse_event = MouseEvent {
pos: Point::new(button_press.event_x as f64, button_press.event_y as f64).to_dp(scale),
buttons: mouse_buttons(button_press.state).with(button),
mods: key_mods(button_press.state),
count: 1,
focus: false,
button,
wheel_delta: Vec2::ZERO,
};
self.with_handler(|h| h.mouse_down(&mouse_event));
Ok(())
}
pub fn handle_button_release(
&self,
button_release: &xproto::ButtonReleaseEvent,
) -> Result<(), Error> {
let scale = self.scale.get();
let button = mouse_button(button_release.detail);
let mouse_event = MouseEvent {
pos: Point::new(button_release.event_x as f64, button_release.event_y as f64)
.to_dp(scale),
buttons: mouse_buttons(button_release.state).without(button),
mods: key_mods(button_release.state),
count: 0,
focus: false,
button,
wheel_delta: Vec2::ZERO,
};
self.with_handler(|h| h.mouse_up(&mouse_event));
Ok(())
}
pub fn handle_wheel(&self, event: &xproto::ButtonPressEvent) -> Result<(), Error> {
let button = event.detail;
let mods = key_mods(event.state);
let scale = self.scale.get();
let is_shift = mods.shift();
let delta = match button {
4 if is_shift => (-120.0, 0.0),
4 => (0.0, -120.0),
5 if is_shift => (120.0, 0.0),
5 => (0.0, 120.0),
6 => (-120.0, 0.0),
7 => (120.0, 0.0),
_ => return Err(anyhow!("unexpected mouse wheel button: {}", button)),
};
let mouse_event = MouseEvent {
pos: Point::new(event.event_x as f64, event.event_y as f64).to_dp(scale),
buttons: mouse_buttons(event.state),
mods: key_mods(event.state),
count: 0,
focus: false,
button: MouseButton::None,
wheel_delta: delta.into(),
};
self.with_handler(|h| h.wheel(&mouse_event));
Ok(())
}
pub fn handle_motion_notify(
&self,
motion_notify: &xproto::MotionNotifyEvent,
) -> Result<(), Error> {
let scale = self.scale.get();
let mouse_event = MouseEvent {
pos: Point::new(motion_notify.event_x as f64, motion_notify.event_y as f64)
.to_dp(scale),
buttons: mouse_buttons(motion_notify.state),
mods: key_mods(motion_notify.state),
count: 0,
focus: false,
button: MouseButton::None,
wheel_delta: Vec2::ZERO,
};
self.with_handler(|h| h.mouse_move(&mouse_event));
Ok(())
}
pub fn handle_got_focus(&self) {
self.with_handler(|h| h.got_focus());
}
pub fn handle_lost_focus(&self) {
self.with_handler(|h| h.lost_focus());
}
pub fn handle_client_message(&self, client_message: &xproto::ClientMessageEvent) {
let atoms = self.app.atoms();
if client_message.type_ == atoms.WM_PROTOCOLS && client_message.format == 32 {
let protocol = client_message.data.as_data32()[0];
if protocol == atoms.WM_DELETE_WINDOW {
self.with_handler(|h| h.request_close());
}
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn handle_destroy_notify(&self, _destroy_notify: &xproto::DestroyNotifyEvent) {
self.with_handler(|h| h.destroy());
}
pub fn handle_configure_notify(&self, event: &ConfigureNotifyEvent) -> Result<(), Error> {
self.size_changed(Size::new(event.width as f64, event.height as f64))
}
pub fn handle_complete_notify(&self, event: &CompleteNotifyEvent) -> Result<(), Error> {
if let Some(present) = borrow_mut!(self.present_data)?.as_mut() {
if present.waiting_on != Some(event.serial) {
warn!(
"Got a notify for serial {}, but waiting on {:?}",
event.serial, present.waiting_on
);
}
if let Some(last_msc) = present.last_msc {
if last_msc.wrapping_add(1) != event.msc {
tracing::debug!(
"missed a present: msc went from {} to {}",
last_msc,
event.msc
);
if let Some(last_ust) = present.last_ust {
tracing::debug!("ust went from {} to {}", last_ust, event.ust);
}
}
}
present.last_msc = if present.needs_present {
Some(event.msc)
} else {
None
};
present.last_ust = Some(event.ust);
present.waiting_on = None;
}
if self.needs_present()? {
self.render()?;
}
Ok(())
}
pub fn handle_idle_notify(&self, event: &IdleNotifyEvent) -> Result<(), Error> {
if self.destroyed() {
return Ok(());
}
let mut buffers = borrow_mut!(self.buffers)?;
if buffers.all_pixmaps.contains(&event.pixmap) {
buffers.idle_pixmaps.push(event.pixmap);
} else {
self.app.connection().free_pixmap(event.pixmap)?;
}
Ok(())
}
fn waiting_on_present(&self) -> Result<bool, Error> {
Ok(borrow!(self.present_data)?
.as_ref()
.map(|p| p.waiting_on.is_some())
.unwrap_or(false))
}
fn set_needs_present(&self, val: bool) -> Result<(), Error> {
if let Some(present) = borrow_mut!(self.present_data)?.as_mut() {
present.needs_present = val;
}
Ok(())
}
fn needs_present(&self) -> Result<bool, Error> {
Ok(borrow!(self.present_data)?
.as_ref()
.map(|p| p.needs_present)
.unwrap_or(false))
}
pub(crate) fn run_idle(&self) {
let mut queue = Vec::new();
std::mem::swap(&mut *self.idle_queue.lock().unwrap(), &mut queue);
let mut needs_redraw = false;
self.with_handler(|handler| {
for callback in queue {
match callback {
IdleKind::Callback(f) => {
f.call(handler);
}
IdleKind::Token(tok) => {
handler.idle(tok);
}
IdleKind::Redraw => {
needs_redraw = true;
}
}
}
});
if needs_redraw {
if let Err(e) = self.redraw_now() {
error!("Error redrawing: {}", e);
}
}
}
pub(crate) fn next_timeout(&self) -> Option<Instant> {
self.timer_queue
.lock()
.unwrap()
.peek()
.map(|timer| timer.deadline())
}
pub(crate) fn run_timers(&self, now: Instant) {
while let Some(deadline) = self.next_timeout() {
if deadline > now {
break;
}
let token = self.timer_queue.lock().unwrap().pop().unwrap().token();
self.with_handler(|h| h.timer(token));
}
}
}
impl Buffers {
fn new(
conn: &Rc<XCBConnection>,
window_id: u32,
buf_count: usize,
width: u16,
height: u16,
depth: u8,
) -> Result<Buffers, Error> {
let mut ret = Buffers {
width,
height,
depth,
idle_pixmaps: Vec::new(),
all_pixmaps: Vec::new(),
};
ret.create_pixmaps(conn, window_id, buf_count)?;
Ok(ret)
}
fn free_pixmaps(&mut self, conn: &Rc<XCBConnection>) {
for &p in &self.idle_pixmaps {
log_x11!(conn.free_pixmap(p));
}
self.all_pixmaps.clear();
self.idle_pixmaps.clear();
}
fn set_size(&mut self, conn: &Rc<XCBConnection>, window_id: u32, width: u16, height: u16) {
fn next_size(x: u16) -> u16 {
let accuracy = 1 << ((16 - x.leading_zeros()).max(7) - 2);
let mask = accuracy - 1;
(x + mask) & !mask
}
let width = next_size(width);
let height = next_size(height);
if (width, height) != (self.width, self.height) {
let count = self.all_pixmaps.len();
self.free_pixmaps(conn);
self.width = width;
self.height = height;
log_x11!(self.create_pixmaps(conn, window_id, count));
}
}
fn create_pixmap(&mut self, conn: &Rc<XCBConnection>, window_id: u32) -> Result<Pixmap, Error> {
let pixmap_id = conn.generate_id()?;
conn.create_pixmap(self.depth, pixmap_id, window_id, self.width, self.height)?;
self.all_pixmaps.push(pixmap_id);
self.idle_pixmaps.push(pixmap_id);
Ok(pixmap_id)
}
fn create_pixmaps(
&mut self,
conn: &Rc<XCBConnection>,
window_id: u32,
count: usize,
) -> Result<(), Error> {
if !self.all_pixmaps.is_empty() {
self.free_pixmaps(conn);
}
for _ in 0..count {
self.create_pixmap(conn, window_id)?;
}
Ok(())
}
}
impl PresentData {
fn present(
&mut self,
conn: &Rc<XCBConnection>,
pixmap: Pixmap,
window_id: u32,
region: &Region,
scale: Scale,
) -> Result<(), Error> {
let x_rects: Vec<Rectangle> = region
.rects()
.iter()
.map(|r| {
let r = r.to_px(scale).round();
Rectangle {
x: r.x0 as i16,
y: r.y0 as i16,
width: r.width() as u16,
height: r.height() as u16,
}
})
.collect();
conn.xfixes_set_region(self.region, &x_rects[..])?;
conn.present_pixmap(
window_id,
pixmap,
self.serial,
self.region,
self.region,
0,
0,
x11rb::NONE,
x11rb::NONE,
x11rb::NONE,
x11rb::protocol::present::Option::NONE.into(),
0,
1,
0,
&[],
)?;
self.waiting_on = Some(self.serial);
self.serial += 1;
Ok(())
}
}
fn mouse_button(button: u8) -> MouseButton {
match button {
1 => MouseButton::Left,
2 => MouseButton::Middle,
3 => MouseButton::Right,
4..=7 => MouseButton::None,
8 => MouseButton::X1,
9 => MouseButton::X2,
_ => {
warn!("unknown mouse button code {}", button);
MouseButton::None
}
}
}
fn mouse_buttons(mods: u16) -> MouseButtons {
let mut buttons = MouseButtons::new();
let button_masks = &[
(xproto::ButtonMask::M1, MouseButton::Left),
(xproto::ButtonMask::M2, MouseButton::Middle),
(xproto::ButtonMask::M3, MouseButton::Right),
];
for (mask, button) in button_masks {
if mods & u16::from(*mask) != 0 {
buttons.insert(*button);
}
}
buttons
}
fn key_mods(mods: u16) -> Modifiers {
let mut ret = Modifiers::default();
let mut key_masks = [
(xproto::ModMask::SHIFT, Modifiers::SHIFT),
(xproto::ModMask::CONTROL, Modifiers::CONTROL),
(xproto::ModMask::M1, Modifiers::ALT),
(xproto::ModMask::M2, Modifiers::NUM_LOCK),
(xproto::ModMask::M4, Modifiers::META),
(xproto::ModMask::LOCK, Modifiers::CAPS_LOCK),
];
for (mask, modifiers) in &mut key_masks {
if mods & u16::from(*mask) != 0 {
ret |= *modifiers;
}
}
ret
}
#[derive(Clone)]
pub struct IdleHandle {
queue: Arc<Mutex<Vec<IdleKind>>>,
pipe: RawFd,
}
pub(crate) enum IdleKind {
Callback(Box<dyn IdleCallback>),
Token(IdleToken),
Redraw,
}
impl IdleHandle {
fn wake(&self) {
loop {
match nix::unistd::write(self.pipe, &[0]) {
Err(nix::errno::Errno::EINTR) => {}
Err(nix::errno::Errno::EAGAIN) => {}
Err(e) => {
error!("Failed to write to idle pipe: {}", e);
break;
}
Ok(_) => {
break;
}
}
}
}
pub(crate) fn schedule_redraw(&self) {
self.add_idle(IdleKind::Redraw);
}
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&mut dyn WinHandler) + Send + 'static,
{
self.add_idle(IdleKind::Callback(Box::new(callback)));
}
pub fn add_idle_token(&self, token: IdleToken) {
self.add_idle(IdleKind::Token(token));
}
fn add_idle(&self, idle: IdleKind) {
self.queue.lock().unwrap().push(idle);
self.wake();
}
}
#[derive(Clone, Default)]
pub(crate) struct WindowHandle {
id: u32,
#[allow(dead_code)] visual_id: u32,
window: Weak<Window>,
}
impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for WindowHandle {}
impl WindowHandle {
fn new(id: u32, visual_id: u32, window: Weak<Window>) -> WindowHandle {
WindowHandle {
id,
visual_id,
window,
}
}
pub fn show(&self) {
if let Some(w) = self.window.upgrade() {
w.show();
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn close(&self) {
if let Some(w) = self.window.upgrade() {
w.close();
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn resizable(&self, resizable: bool) {
if let Some(w) = self.window.upgrade() {
w.resizable(resizable);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn show_titlebar(&self, show_titlebar: bool) {
if let Some(w) = self.window.upgrade() {
w.show_titlebar(show_titlebar);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn set_position(&self, position: Point) {
if let Some(w) = self.window.upgrade() {
w.set_position(position);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn get_position(&self) -> Point {
if let Some(w) = self.window.upgrade() {
w.get_position()
} else {
error!("Window {} has already been dropped", self.id);
Point::new(0.0, 0.0)
}
}
pub fn content_insets(&self) -> Insets {
warn!("WindowHandle::content_insets unimplemented for X11 backend.");
Insets::ZERO
}
pub fn set_size(&self, size: Size) {
if let Some(w) = self.window.upgrade() {
w.set_size(size);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn get_size(&self) -> Size {
if let Some(w) = self.window.upgrade() {
w.size().size_dp()
} else {
error!("Window {} has already been dropped", self.id);
Size::ZERO
}
}
pub fn set_window_state(&self, _state: window::WindowState) {
warn!("WindowHandle::set_window_state is currently unimplemented for X11 backend.");
}
pub fn get_window_state(&self) -> window::WindowState {
warn!("WindowHandle::get_window_state is currently unimplemented for X11 backend.");
window::WindowState::Restored
}
pub fn handle_titlebar(&self, _val: bool) {
warn!("WindowHandle::handle_titlebar is currently unimplemented for X11 backend.");
}
pub fn bring_to_front_and_focus(&self) {
if let Some(w) = self.window.upgrade() {
w.bring_to_front_and_focus();
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn request_anim_frame(&self) {
if let Some(w) = self.window.upgrade() {
w.request_anim_frame();
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn invalidate(&self) {
if let Some(w) = self.window.upgrade() {
w.invalidate();
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn invalidate_rect(&self, rect: Rect) {
if let Some(w) = self.window.upgrade() {
w.invalidate_rect(rect);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn set_title(&self, title: &str) {
if let Some(w) = self.window.upgrade() {
w.set_title(title);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn set_menu(&self, menu: Menu) {
if let Some(w) = self.window.upgrade() {
w.set_menu(menu);
} else {
error!("Window {} has already been dropped", self.id);
}
}
pub fn text(&self) -> PietText {
PietText::new()
}
pub fn add_text_field(&self) -> TextFieldToken {
TextFieldToken::next()
}
pub fn remove_text_field(&self, token: TextFieldToken) {
if let Some(window) = self.window.upgrade() {
if window.active_text_field.get() == Some(token) {
window.active_text_field.set(None)
}
}
}
pub fn set_focused_text_field(&self, active_field: Option<TextFieldToken>) {
if let Some(window) = self.window.upgrade() {
window.active_text_field.set(active_field);
}
}
pub fn update_text_field(&self, _token: TextFieldToken, _update: Event) {
}
pub fn request_timer(&self, deadline: Instant) -> TimerToken {
if let Some(w) = self.window.upgrade() {
let timer = Timer::new(deadline, ());
w.timer_queue.lock().unwrap().push(timer);
timer.token()
} else {
TimerToken::INVALID
}
}
pub fn set_cursor(&mut self, cursor: &Cursor) {
if let Some(w) = self.window.upgrade() {
w.set_cursor(cursor);
}
}
pub fn make_cursor(&self, desc: &CursorDesc) -> Option<Cursor> {
if let Some(w) = self.window.upgrade() {
match w.app.render_argb32_pictformat_cursor() {
None => {
warn!("Custom cursors are not supported by the X11 server");
None
}
Some(format) => {
let conn = w.app.connection();
let setup = &conn.setup();
let screen = &setup.roots[w.app.screen_num()];
match make_cursor(conn, setup.image_byte_order, screen.root, format, desc) {
Ok(cursor) => Some(cursor),
Err(err) => {
error!("Failed to create custom cursor: {:?}", err);
None
}
}
}
}
} else {
None
}
}
pub fn open_file(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
if let Some(w) = self.window.upgrade() {
if let Some(idle) = self.get_idle_handle() {
Some(dialog::open_file(w.id, idle, options))
} else {
warn!("Couldn't open file because no idle handle available");
None
}
} else {
None
}
}
pub fn save_as(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
if let Some(w) = self.window.upgrade() {
if let Some(idle) = self.get_idle_handle() {
Some(dialog::save_file(w.id, idle, options))
} else {
warn!("Couldn't save file because no idle handle available");
None
}
} else {
None
}
}
pub fn show_context_menu(&self, _menu: Menu, _pos: Point) {
warn!("WindowHandle::show_context_menu is currently unimplemented for X11 backend.");
}
pub fn get_idle_handle(&self) -> Option<IdleHandle> {
self.window.upgrade().map(|w| IdleHandle {
queue: Arc::clone(&w.idle_queue),
pipe: w.idle_pipe,
})
}
pub fn get_scale(&self) -> Result<Scale, ShellError> {
if let Some(w) = self.window.upgrade() {
Ok(w.get_scale()?)
} else {
error!("Window {} has already been dropped", self.id);
Ok(Scale::new(1.0, 1.0))
}
}
}
#[cfg(feature = "raw-win-handle")]
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = XcbWindowHandle::empty();
handle.window = self.id;
handle.visual_id = self.visual_id;
RawWindowHandle::Xcb(handle)
}
}
fn make_cursor(
conn: &XCBConnection,
byte_order: X11ImageOrder,
root_window: u32,
argb32_format: Pictformat,
desc: &CursorDesc,
) -> Result<Cursor, ReplyOrIdError> {
fn multiply_alpha(color: u8, alpha: u8) -> u8 {
let (color, alpha) = (u16::from(color), u16::from(alpha));
let temp = color * alpha + 0x80u16;
((temp + (temp >> 8)) >> 8) as u8
}
let pixels = desc
.image
.pixel_colors()
.flat_map(|row| {
row.flat_map(|color| {
let (r, g, b, a) = color.as_rgba8();
let (r, g, b) = (
multiply_alpha(r, a),
multiply_alpha(g, a),
multiply_alpha(b, a),
);
let (b0, b1, b2, b3) = match byte_order {
X11ImageOrder::LSB_FIRST => (b, g, r, a),
_ => (a, r, g, b),
};
vec![b0, b1, b2, b3]
})
})
.collect::<Vec<u8>>();
let width = desc.image.width().try_into().expect("Invalid cursor width");
let height = desc
.image
.height()
.try_into()
.expect("Invalid cursor height");
let pixmap = conn.generate_id()?;
let gc = conn.generate_id()?;
let picture = conn.generate_id()?;
conn.create_pixmap(32, pixmap, root_window, width, height)?;
conn.create_gc(gc, pixmap, &Default::default())?;
conn.put_image(
ImageFormat::Z_PIXMAP,
pixmap,
gc,
width,
height,
0,
0,
0,
32,
&pixels,
)?;
conn.render_create_picture(picture, pixmap, argb32_format, &Default::default())?;
conn.free_gc(gc)?;
conn.free_pixmap(pixmap)?;
let cursor = conn.generate_id()?;
conn.render_create_cursor(cursor, picture, desc.hot.x as u16, desc.hot.y as u16)?;
conn.render_free_picture(picture)?;
Ok(Cursor::Custom(CustomCursor(cursor)))
}