blob: 8b5ff093d2e798113833e45e539729e988f40451 [file] [log] [blame] [edit]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::ffi;
use crate::protocol;
use crate::shell;
use log::debug;
use std::any::Any;
use std::cell::RefCell;
use std::ops::DerefMut;
use std::rc::Rc;
use crate::server::Resource;
use protocol::xdg_popup::{self, XdgPopup};
use protocol::xdg_positioner::{self, XdgPositioner};
use protocol::xdg_surface::{self, XdgSurface};
use protocol::xdg_toplevel::{self, XdgToplevel};
use protocol::xdg_wm_base::{self, XdgWmBase};
struct Positioner {
size: ffi::weston_size,
anchor_rect: ffi::weston_geometry,
anchor: xdg_positioner::Anchor,
offset: ffi::weston_position,
gravity: xdg_positioner::Gravity,
constraint_adjustment: xdg_positioner::ConstraintAdjustment,
}
impl Positioner {
fn new() -> Positioner {
Positioner {
size: ffi::weston_size { width: 0, height: 0 },
anchor_rect: ffi::weston_geometry { x: 0, y: 0, width: 0, height: 0 },
anchor: xdg_positioner::Anchor::None,
offset: ffi::weston_position { x: 0, y: 0 },
gravity: xdg_positioner::Gravity::None,
constraint_adjustment: xdg_positioner::ConstraintAdjustment::None,
}
}
#[allow(dead_code)]
fn get_geometry(&self) -> ffi::weston_geometry {
use xdg_positioner::{Anchor, Gravity};
let anchor = ffi::weston_position {
x: match self.anchor {
Anchor::Left | Anchor::TopLeft | Anchor::BottomLeft => self.anchor_rect.x,
Anchor::Right | Anchor::TopRight | Anchor::BottomRight => {
self.anchor_rect.x + self.anchor_rect.width
}
Anchor::None | Anchor::Top | Anchor::Bottom => {
self.anchor_rect.x + self.anchor_rect.width / 2
}
},
y: match self.anchor {
Anchor::Top | Anchor::TopLeft | Anchor::TopRight => self.anchor_rect.y,
Anchor::Bottom | Anchor::BottomLeft | Anchor::BottomRight => {
self.anchor_rect.x + self.anchor_rect.height
}
Anchor::None | Anchor::Left | Anchor::Right => {
self.anchor_rect.y + self.anchor_rect.height / 2
}
},
};
let gravity = ffi::weston_position {
x: match self.gravity {
Gravity::Left | Gravity::TopLeft | Gravity::BottomLeft => self.size.width,
Gravity::None | Gravity::Top | Gravity::Bottom => self.size.width / 2,
Gravity::Right | Gravity::TopRight | Gravity::BottomRight => 0,
},
y: match self.gravity {
Gravity::Top | Gravity::TopLeft | Gravity::TopRight => self.size.height,
Gravity::None | Gravity::Left | Gravity::Right => self.size.height / 2,
Gravity::Bottom | Gravity::BottomLeft | Gravity::BottomRight => 0,
},
};
ffi::weston_geometry {
x: self.offset.x + anchor.x - gravity.x,
y: self.offset.y + anchor.y - gravity.y,
width: self.size.width,
height: self.size.height,
}
}
}
fn handle_positioner_request(
resource: &mut Resource<XdgPositioner>,
request: &xdg_positioner::Request,
) {
let mut p = resource.user_data::<Positioner>();
debug!("{:?}", request);
use xdg_positioner::Request::*;
match *request {
Destroy => {}
SetSize { width, height } => {
if width < 1 || height < 1 {
resource.post_error(
xdg_positioner::Error::InvalidInput as u32,
"width and height must be positives and non-zero",
);
return;
}
p.size = ffi::weston_size { width, height };
}
SetAnchorRect { x, y, width, height } => {
if width < 0 || height < 0 {
resource.post_error(
xdg_positioner::Error::InvalidInput as u32,
"width and height must be non-negative",
);
return;
}
p.anchor_rect = ffi::weston_geometry { x, y, width, height };
}
SetAnchor { anchor } => p.anchor = anchor,
SetGravity { gravity } => p.gravity = gravity,
SetConstraintAdjustment { constraint_adjustment: bits } => {
p.constraint_adjustment =
xdg_positioner::ConstraintAdjustment::from_bits_truncate(bits);
}
SetOffset { x, y } => p.offset = ffi::weston_position { x, y },
SetReactive => {}
SetParentSize { parent_width: _, parent_height: _ } => {}
SetParentConfigure { serial: _ } => {}
}
}
fn state_to_xdg_vec(state: &shell::SurfaceState) -> Vec<u8> {
IntoIterator::into_iter([
(state.maximized, xdg_toplevel::State::Maximized),
(state.fullscreen, xdg_toplevel::State::Fullscreen),
(state.activated, xdg_toplevel::State::Activated),
])
.filter_map(|(flag, token)| if flag { Some(token as u8) } else { None })
.collect()
}
struct Toplevel {
surface: *mut ffi::weston_surface,
shsurf: Rc<RefCell<shell::Surface>>,
surface_resource: Resource<XdgSurface>,
resource: Resource<XdgToplevel>,
configured: bool,
commit_listener: Option<Box<dyn Any>>,
state_change_listener: Option<Box<dyn Any>>,
}
impl Toplevel {
fn new(surface: &Surface, resource: Resource<XdgToplevel>) -> Rc<RefCell<Self>> {
let shsurf_rc = unsafe {
let view = ffi::weston_view_create(surface.surface);
shell::Surface::new(
surface.shell.borrow_mut().deref_mut(),
surface.surface,
view,
)
};
let toplevel_rc = Rc::new(RefCell::new(Toplevel {
surface: surface.surface,
shsurf: Rc::clone(&shsurf_rc),
surface_resource: surface.resource.clone(),
resource: resource.clone(),
configured: false,
commit_listener: None,
state_change_listener: None,
}));
let mut toplevel = toplevel_rc.borrow_mut();
let weak = Rc::downgrade(&toplevel_rc);
let commit_signal = unsafe { &mut (*toplevel.surface).commit_signal };
toplevel.commit_listener = Some(ffi::WlListener::new(commit_signal, move |_| {
if let Some(rc) = weak.upgrade() {
let mut toplevel = rc.borrow_mut();
if !toplevel.configured {
toplevel.schedule_configure(Rc::clone(&rc));
}
}
}));
let mut shsurf = shsurf_rc.borrow_mut();
let weak = Rc::downgrade(&toplevel_rc);
toplevel.state_change_listener = Some(ffi::WlListener::new(
&mut shsurf.state_change_signal,
move |_| {
debug!("state change signal fired");
if let Some(rc) = weak.upgrade() {
let mut toplevel = rc.borrow_mut();
toplevel.schedule_configure(Rc::clone(&rc))
}
},
));
Rc::clone(&toplevel_rc)
}
fn schedule_configure(&mut self, rc: Rc<RefCell<Toplevel>>) {
debug!("schedule configure!");
tokio::task::spawn_local(async move {
let toplevel = rc.borrow_mut();
let shsurf = toplevel.shsurf.borrow_mut();
let configure = protocol::xdg_toplevel::Event::Configure {
width: shsurf.pending_size.width,
height: shsurf.pending_size.height,
states: state_to_xdg_vec(&shsurf.pending_state),
};
debug!(
"Sending configure, surface state: {:?}",
shsurf.pending_state
);
toplevel.resource.send(configure);
let configure = protocol::xdg_surface::Event::Configure { serial: 0 };
toplevel.surface_resource.send(configure);
});
}
}
fn handle_xdg_toplevel_request(
resource: &mut Resource<XdgToplevel>,
request: &xdg_toplevel::Request,
) {
use xdg_toplevel::Request::*;
let rc = resource.user_data_rc::<RefCell<Toplevel>>();
let shsurf_rc = Rc::clone(&rc.borrow().shsurf);
let mut shsurf = shsurf_rc.borrow_mut();
match request {
Destroy => (),
SetParent { parent: _ } => (),
SetTitle { .. } => {}
SetAppId { .. } => {}
ShowWindowMenu { seat: _, serial: _, x: _, y: _ } => (),
Move { seat, serial } => {
let seat = seat.as_ref().user_data::<ffi::weston_seat>();
if let Some(pointer) = seat.get_pointer() {
if !pointer.focus.is_null()
&& pointer.button_count > 0
&& pointer.grab_serial == *serial
{
//let focus = &mut *pointer.focus;
//let focus = ffi::weston_surface_get_main_surface(focus.surface);
// if (focus == surface
shsurf.r#move(pointer, true);
}
}
if let Some(touch) = seat.get_touch() {
if !touch.focus.is_null() && touch.grab_serial == *serial {}
}
}
Resize { seat, serial, edges } => {
let seat = seat.as_ref().user_data::<ffi::weston_seat>();
if let Some(pointer) = seat.get_pointer() {
if !pointer.focus.is_null()
&& pointer.button_count > 0
&& pointer.grab_serial == *serial
{
// let focus = get main surf
if let Some(edges) = shell::ResizeEdge::from_u32(edges.to_raw()) {
shsurf.resize(pointer, edges)
} else {
todo!("post error or silently ignore?")
}
}
}
}
SetMaxSize { .. } => {}
SetMinSize { .. } => {}
SetMaximized => shsurf.set_maximized(true),
UnsetMaximized => shsurf.set_maximized(false),
SetFullscreen { output } => shsurf.set_fullscreen(
true,
output
.as_ref()
.map_or(0 as *mut _, |output| output.as_ref().user_data()),
),
UnsetFullscreen => shsurf.set_fullscreen(false, 0 as *mut _),
SetMinimized => shsurf.set_minimized(),
}
}
fn handle_xdg_toplevel_surface_request(
resource: &mut Resource<XdgSurface>,
request: &xdg_surface::Request,
) {
use xdg_surface::Request::*;
let rc = resource.user_data_rc::<RefCell<Toplevel>>();
let mut toplevel = rc.borrow_mut();
let shsurf_rc = Rc::clone(&toplevel.shsurf);
let mut shsurf = shsurf_rc.borrow_mut();
match request {
Destroy => (),
GetToplevel { .. } | GetPopup { .. } => {
resource.post_error(
xdg_wm_base::Error::Role as u32,
"surface already has role XdgToplevel",
);
return;
}
SetWindowGeometry { x, y, width, height } => {
shsurf.geometry = ffi::weston_geometry {
x: *x,
y: *y,
width: *width,
height: *height,
}
}
AckConfigure { serial: _ } => toplevel.configured = true,
}
}
fn handle_xdg_popup_request(_resource: &mut Resource<XdgPopup>, request: &xdg_popup::Request) {
use xdg_popup::Request::*;
match request {
Destroy => todo!(),
Grab { seat: _, serial: _ } => todo!(),
Reposition { positioner: _, token: _ } => todo!(),
}
}
fn handle_xdg_popup_surface_request(
resource: &mut Resource<XdgSurface>,
request: &xdg_surface::Request,
) {
use xdg_surface::Request::*;
match request {
Destroy => todo!(),
GetToplevel { .. } | GetPopup { .. } => {
resource.post_error(
xdg_wm_base::Error::Role as u32,
"surface already has role XdgPopup",
);
return;
}
SetWindowGeometry { x: _, y: _, width: _, height: _ } => todo!(),
AckConfigure { serial: _ } => todo!(),
}
}
fn handle_xdg_surface_request(resource: &mut Resource<XdgSurface>, request: &xdg_surface::Request) {
use xdg_surface::Request::*;
use xdg_wm_base::Error;
let surface_rc = resource.user_data_rc::<RefCell<Surface>>();
let surface = surface_rc.borrow_mut();
match request {
Destroy => (),
GetToplevel { id } => {
let toplevel = Toplevel::new(&surface, id.clone().into());
id.on_request(handle_xdg_toplevel_request);
id.set_user_data_rc(Rc::clone(&toplevel));
resource.on_request(handle_xdg_toplevel_surface_request);
resource.set_user_data_rc(Rc::clone(&toplevel));
}
GetPopup { id, parent, positioner: _ } => {
if parent.is_none() {
resource.post_error(
Error::InvalidPopupParent as u32,
"popup parent must be non-null",
);
return;
};
id.on_request(handle_xdg_popup_request);
resource.on_request(handle_xdg_popup_surface_request);
}
SetWindowGeometry { .. } | AckConfigure { .. } => {
resource.post_error(
xdg_surface::Error::NotConstructed as u32,
"xdg_surface must have a role",
);
}
}
}
struct Client {
shell: Rc<RefCell<crate::Shell>>,
}
impl Client {
fn new(rc: Rc<RefCell<crate::Shell>>) -> Self {
Client { shell: rc }
}
}
struct Surface {
shell: Rc<RefCell<crate::Shell>>,
surface: *mut ffi::weston_surface,
resource: Resource<XdgSurface>,
}
impl Surface {
fn new(
shell: Rc<RefCell<crate::Shell>>,
surface: *mut ffi::weston_surface,
resource: Resource<XdgSurface>,
) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self { shell, surface, resource }))
}
}
fn handle_xdg_wm_base_request(resource: &mut Resource<XdgWmBase>, request: &xdg_wm_base::Request) {
use xdg_wm_base::Request::*;
let client = resource.user_data::<Client>();
match request {
Destroy => {
// drop resource
debug!("destroy")
}
CreatePositioner { id } => {
debug!("create positioner");
id.on_request(handle_positioner_request);
let b = Box::new(Positioner::new());
id.set_user_data(Box::into_raw(b));
}
GetXdgSurface { id, surface } => {
debug!("get xdg surface");
let xdg_surface = Surface::new(
Rc::clone(&client.shell),
surface.as_ref().user_data::<ffi::weston_surface>() as *mut _,
id.clone().into(),
);
id.on_request(handle_xdg_surface_request);
id.set_user_data_rc(xdg_surface);
}
Pong { serial: _ } => {
todo!("update timer, end busy cursor, pong");
}
}
}
pub fn init(rc: &Rc<RefCell<crate::Shell>>) {
let shell = &rc.borrow();
let mut compositor = shell.compositor.borrow_mut();
let rc = rc.clone();
let global = compositor
.server
.create_global::<XdgWmBase>(1)
.on_bind(move |resource| {
/* xxx: set up ping timer */
let c = Box::new(Client::new(rc.clone()));
let p = Box::into_raw(c) as *mut _;
resource.set_user_data(p);
true
})
.on_request(handle_xdg_wm_base_request);
Box::into_raw(Box::new(global));
}