| // 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)); |
| } |