diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index c39dc6a..0cf2ab2 100644 --- a/anvil/src/drawing.rs +++ b/anvil/src/drawing.rs @@ -185,7 +185,7 @@ pub fn draw_windows( renderer: &mut R, frame: &mut F, window_map: &WindowMap, - output_rect: Option, + output_rect: Rectangle, log: &::slog::Logger, ) -> Result<(), SwapBuffersError> where @@ -199,12 +199,10 @@ where // redraw the frame, in a simple but inneficient way window_map.with_windows_from_bottom_to_top(|toplevel_surface, mut initial_place, bounding_box| { // skip windows that do not overlap with a given output - if let Some(output) = output_rect { - if !output.overlaps(bounding_box) { - return; - } - initial_place.0 -= output.x; + if !output_rect.overlaps(bounding_box) { + return; } + initial_place.0 -= output_rect.x; if let Some(wl_surface) = toplevel_surface.get_surface() { // this surface is a root of a subsurface tree that needs to be drawn if let Err(err) = draw_surface_tree(renderer, frame, &wl_surface, initial_place, log) { diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 06dd097..2a2b803 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -13,6 +13,7 @@ use smithay::{ }, reexports::wayland_server::protocol::wl_pointer, wayland::{ + output::Mode, seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState}, SERIAL_COUNTER as SCOUNTER, }, @@ -137,7 +138,12 @@ impl AnvilState { #[cfg(feature = "winit")] impl AnvilState { - pub fn process_input_event(&mut self, event: InputEvent) { + pub fn process_input_event(&mut self, event: InputEvent) + where + B: InputBackend, + { + use smithay::backend::winit::WinitEvent; + match event { InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::(event) { KeyAction::None | KeyAction::Forward => {} @@ -162,6 +168,16 @@ impl AnvilState { InputEvent::PointerMotionAbsolute { event, .. } => self.on_pointer_move_absolute::(event), InputEvent::PointerButton { event, .. } => self.on_pointer_button::(event), InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::(event), + InputEvent::Special(WinitEvent::Resized { size, .. }) => { + self.output_map.borrow_mut().update_mode( + crate::winit::OUTPUT_NAME, + Mode { + width: size.0 as i32, + height: size.1 as i32, + refresh: 60_000, + }, + ); + } _ => { // other events are not handled in anvil (yet) } @@ -206,17 +222,16 @@ impl AnvilState { } } KeyAction::Screen(num) => { - if let Some(output) = self.backend_data.output_map.get(num) { - let x = self - .backend_data - .output_map - .iter() - .take(num) - .fold(0, |acc, output| acc + output.size.0) - as f64 - + (output.size.0 as f64 / 2.0); - let y = output.size.1 as f64 / 2.0; - self.pointer_location = (x as f64, y as f64) + let geometry = self + .output_map + .borrow() + .find_by_index(num, |_, geometry| geometry) + .ok(); + + if let Some(geometry) = geometry { + let x = geometry.x as f64 + geometry.width as f64 / 2.0; + let y = geometry.height as f64 / 2.0; + self.pointer_location = (x, y) } } }, @@ -245,47 +260,23 @@ impl AnvilState { } fn clamp_coords(&self, pos: (f64, f64)) -> (f64, f64) { - if self.backend_data.output_map.is_empty() { + if self.output_map.borrow().is_empty() { return pos; } - let (mut x, mut y) = pos; - // max_x is the sum of the width of all outputs - let max_x = self - .backend_data - .output_map - .iter() - .fold(0u32, |acc, output| acc + output.size.0); - x = x.max(0.0).min(max_x as f64); + let (pos_x, pos_y) = pos; + let output_map = self.output_map.borrow(); + let max_x = output_map.width(); + let clamped_x = pos_x.max(0.0).min(max_x as f64); + let max_y = output_map.height(clamped_x as i32); - // max y depends on the current output - let max_y = self.current_output_size(x).1; - y = y.max(0.0).min(max_y as f64); + if let Some(max_y) = max_y { + let clamped_y = pos_y.max(0.0).min(max_y as f64); - (x, y) - } - - fn current_output_idx(&self, x: f64) -> usize { - self.backend_data - .output_map - .iter() - // map each output to their x position - .scan(0u32, |acc, output| { - let curr_x = *acc; - *acc += output.size.0; - Some(curr_x) - }) - // get an index - .enumerate() - // find the first one with a greater x - .find(|(_idx, x_pos)| *x_pos as f64 > x) - // the previous output is the one we are on - .map(|(idx, _)| idx - 1) - .unwrap_or(self.backend_data.output_map.len() - 1) - } - - fn current_output_size(&self, x: f64) -> (u32, u32) { - self.backend_data.output_map[self.current_output_idx(x)].size + (clamped_x, clamped_y) + } else { + (clamped_x, pos_y) + } } } diff --git a/anvil/src/main.rs b/anvil/src/main.rs index bddfff5..257ccab 100644 --- a/anvil/src/main.rs +++ b/anvil/src/main.rs @@ -26,6 +26,8 @@ mod winit; #[cfg(feature = "xwayland")] mod xwayland; +mod output_map; + use state::AnvilState; static POSSIBLE_BACKENDS: &[&str] = &[ diff --git a/anvil/src/output_map.rs b/anvil/src/output_map.rs new file mode 100644 index 0000000..00027da --- /dev/null +++ b/anvil/src/output_map.rs @@ -0,0 +1,397 @@ +use std::{cell::RefCell, rc::Rc}; + +use smithay::{ + reexports::{ + wayland_protocols::xdg_shell::server::xdg_toplevel, + wayland_server::{ + protocol::{wl_output, wl_surface::WlSurface}, + Display, Global, + }, + }, + utils::Rectangle, + wayland::{ + compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction}, + output::{self, Mode, PhysicalProperties}, + }, +}; + +use crate::shell::SurfaceData; + +struct Output { + name: String, + output: output::Output, + global: Option>, + geometry: Rectangle, + surfaces: Vec, + current_mode: Mode, +} + +impl Output { + fn new( + name: N, + location: (i32, i32), + display: &mut Display, + physical: PhysicalProperties, + mode: Mode, + logger: slog::Logger, + ) -> Self + where + N: AsRef, + { + let (output, global) = output::Output::new(display, name.as_ref().into(), physical, logger); + + output.change_current_state(Some(mode), None, None); + output.set_preferred(mode); + + Self { + name: name.as_ref().to_owned(), + global: Some(global), + output, + geometry: Rectangle { + x: location.0, + y: location.1, + width: mode.width, + height: mode.height, + }, + surfaces: Vec::new(), + current_mode: mode, + } + } +} + +impl Drop for Output { + fn drop(&mut self) { + self.global.take().unwrap().destroy(); + } +} + +#[derive(Debug)] +pub struct OutputNotFound; + +impl std::fmt::Display for OutputNotFound { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("The output could not be found") + } +} + +impl std::error::Error for OutputNotFound {} + +pub struct OutputMap { + display: Rc>, + outputs: Vec, + window_map: Rc>, + logger: slog::Logger, +} + +impl OutputMap { + pub fn new( + display: Rc>, + window_map: Rc>, + logger: ::slog::Logger, + ) -> Self { + Self { + display, + outputs: Vec::new(), + window_map, + logger, + } + } + + pub fn arrange(&mut self) { + // First recalculate the outputs location + let mut output_x = 0; + for output in self.outputs.iter_mut() { + output.geometry.x = output_x; + output.geometry.y = 0; + output_x += output.geometry.width; + } + + // Check if any windows are now out of outputs range + // and move them to the primary output + let primary_output_location = self + .with_primary(|_, geometry| geometry) + .ok() + .map(|o| (o.x, o.y)) + .unwrap_or_default(); + let mut window_map = self.window_map.borrow_mut(); + // TODO: This is a bit unfortunate, we save the windows in a temp vector + // cause we can not call window_map.set_location within the closure. + let mut windows_to_move = Vec::new(); + window_map.with_windows_from_bottom_to_top(|kind, _, bbox| { + let within_outputs = self.outputs.iter().any(|o| o.geometry.overlaps(bbox)); + + if !within_outputs { + windows_to_move.push((kind.to_owned(), primary_output_location)); + } + }); + for (window, location) in windows_to_move.drain(..) { + window_map.set_location(&window, location); + } + + // Update the size and location for maximized and fullscreen windows + window_map.with_windows_from_bottom_to_top(|kind, location, _| { + if let crate::window_map::Kind::Xdg(xdg) = kind { + if let Some(state) = xdg.current_state() { + if state.states.contains(xdg_toplevel::State::Maximized) + || state.states.contains(xdg_toplevel::State::Fullscreen) + { + let output_geometry = if let Some(output) = state.fullscreen_output.as_ref() { + self.find(output, |_, geometry| geometry).ok() + } else { + self.find_by_position(location, |_, geometry| geometry).ok() + }; + + if let Some(geometry) = output_geometry { + if location != (geometry.x, geometry.y) { + windows_to_move.push((kind.to_owned(), (geometry.x, geometry.y))); + } + + let res = xdg.with_pending_state(|pending_state| { + pending_state.size = Some((geometry.width, geometry.height)); + }); + + if res.is_ok() { + xdg.send_configure(); + } + } + } + } + } + }); + for (window, location) in windows_to_move.drain(..) { + window_map.set_location(&window, location); + } + } + + pub fn add(&mut self, name: N, physical: PhysicalProperties, mode: Mode) + where + N: AsRef, + { + // Append the output to the end of the existing + // outputs by placing it after the current overall + // width + let location = (self.width() as i32, 0); + + let output = Output::new( + name, + location, + &mut *self.display.borrow_mut(), + physical, + mode, + self.logger.clone(), + ); + + self.outputs.push(output); + + // We call arrange here albeit the output is only appended and + // this would not affect windows, but arrange could re-organize + // outputs from a configuration. + self.arrange(); + } + + pub fn remove>(&mut self, name: N) { + let removed_outputs = self.outputs.iter_mut().filter(|o| o.name == name.as_ref()); + + for output in removed_outputs { + for surface in output.surfaces.drain(..) { + output.output.leave(&surface); + } + } + self.outputs.retain(|o| o.name != name.as_ref()); + + // Re-arrange outputs cause one or more outputs have + // been removed + self.arrange(); + } + + pub fn width(&self) -> u32 { + // This is a simplification, we only arrange the outputs on the y axis side-by-side + // so that the total width is simply the sum of all output widths. + self.outputs + .iter() + .fold(0u32, |acc, output| acc + output.geometry.width as u32) + } + + pub fn height(&self, x: i32) -> Option { + // This is a simplification, we only arrange the outputs on the y axis side-by-side + self.outputs + .iter() + .find(|output| x >= output.geometry.x && x < (output.geometry.x + output.geometry.width)) + .map(|output| output.geometry.height as u32) + } + + pub fn is_empty(&self) -> bool { + self.outputs.is_empty() + } + + pub fn with_primary(&self, f: F) -> Result + where + F: FnOnce(&output::Output, Rectangle) -> T, + { + let output = self.outputs.get(0).ok_or(OutputNotFound)?; + + Ok(f(&output.output, output.geometry)) + } + + pub fn find(&self, output: &wl_output::WlOutput, f: F) -> Result + where + F: FnOnce(&output::Output, Rectangle) -> T, + { + let output = self + .outputs + .iter() + .find(|o| o.output.owns(output)) + .ok_or(OutputNotFound)?; + + Ok(f(&output.output, output.geometry)) + } + + pub fn find_by_name(&self, name: N, f: F) -> Result + where + N: AsRef, + F: FnOnce(&output::Output, Rectangle) -> T, + { + let output = self + .outputs + .iter() + .find(|o| o.name == name.as_ref()) + .ok_or(OutputNotFound)?; + + Ok(f(&output.output, output.geometry)) + } + + pub fn find_by_position(&self, position: (i32, i32), f: F) -> Result + where + F: FnOnce(&output::Output, Rectangle) -> T, + { + let output = self + .outputs + .iter() + .find(|o| o.geometry.contains(position)) + .ok_or(OutputNotFound)?; + + Ok(f(&output.output, output.geometry)) + } + + pub fn find_by_index(&self, index: usize, f: F) -> Result + where + F: FnOnce(&output::Output, Rectangle) -> T, + { + let output = self.outputs.get(index).ok_or(OutputNotFound)?; + + Ok(f(&output.output, output.geometry)) + } + + pub fn update_mode>(&mut self, name: N, mode: Mode) { + let output = self.outputs.iter_mut().find(|o| o.name == name.as_ref()); + + // NOTE: This will just simply shift all outputs after + // the output who's mode has changed left or right depending + // on if the mode width increased or decreased. + // We could also re-configure toplevels here. + // If a surface is now visible on an additional output because + // the output width decreased the refresh method will take + // care and will send enter for the output. + if let Some(output) = output { + output.geometry.width = mode.width; + output.geometry.height = mode.height; + + output.output.delete_mode(output.current_mode); + output.output.change_current_state(Some(mode), None, None); + output.output.set_preferred(mode); + output.current_mode = mode; + + // Re-arrange outputs cause the size of one output changed + self.arrange(); + } + } + + pub fn refresh(&mut self) { + // Clean-up dead surfaces + self.outputs + .iter_mut() + .for_each(|o| o.surfaces.retain(|s| s.as_ref().is_alive())); + + let window_map = self.window_map.clone(); + + window_map + .borrow() + .with_windows_from_bottom_to_top(|kind, location, bbox| { + for output in self.outputs.iter_mut() { + // Check if the bounding box of the toplevel intersects with + // the output, if not no surface in the tree can intersect with + // the output. + if !output.geometry.overlaps(bbox) { + if let Some(surface) = kind.get_surface() { + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |wl_surface, _, _| { + if output.surfaces.contains(wl_surface) { + output.output.leave(wl_surface); + output.surfaces.retain(|s| s != wl_surface); + } + }, + |_, _, _| true, + ) + } + continue; + } + + if let Some(surface) = kind.get_surface() { + with_surface_tree_downward( + surface, + location, + |_, states, &(mut x, mut y)| { + let data = states.data_map.get::>(); + + if data.is_some() { + if states.role == Some("subsurface") { + let current = states.cached_state.current::(); + x += current.location.0; + y += current.location.1; + } + + TraversalAction::DoChildren((x, y)) + } else { + // If the parent surface is unmapped, then the child surfaces are hidden as + // well, no need to consider them here. + TraversalAction::SkipChildren + } + }, + |wl_surface, states, &(x, y)| { + let data = states.data_map.get::>(); + + if let Some((width, height)) = data.and_then(|d| d.borrow().size()) { + let surface_rectangle = Rectangle { x, y, width, height }; + + if output.geometry.overlaps(&surface_rectangle) { + // We found a matching output, check if we already sent enter + if !output.surfaces.contains(wl_surface) { + output.output.enter(wl_surface); + output.surfaces.push(wl_surface.clone()); + } + } else { + // Surface does not match output, if we sent enter earlier + // we should now send leave + if output.surfaces.contains(wl_surface) { + output.output.leave(wl_surface); + output.surfaces.retain(|s| s != wl_surface); + } + } + } else { + // Maybe the the surface got unmapped, send leave on output + if output.surfaces.contains(wl_surface) { + output.output.leave(wl_surface); + output.surfaces.retain(|s| s != wl_surface); + } + } + }, + |_, _, _| true, + ) + } + } + }); + } +} diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index 7c9447a..dad6d0d 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -9,7 +9,7 @@ use smithay::{ reexports::{ wayland_protocols::xdg_shell::server::xdg_toplevel, wayland_server::{ - protocol::{wl_buffer, wl_pointer::ButtonState, wl_shell_surface, wl_surface}, + protocol::{wl_buffer, wl_output, wl_pointer::ButtonState, wl_shell_surface, wl_surface}, Display, }, }, @@ -32,6 +32,7 @@ use smithay::{ }; use crate::{ + output_map::OutputMap, state::AnvilState, window_map::{Kind as SurfaceKind, PopupKind, WindowMap}, }; @@ -293,12 +294,43 @@ pub struct ShellHandles { pub xdg_state: Arc>, pub wl_state: Arc>, pub window_map: Rc>, + pub output_map: Rc>, } -pub fn init_shell(display: &mut Display, log: ::slog::Logger) -> ShellHandles { +fn fullscreen_output_geometry( + wl_surface: &wl_surface::WlSurface, + wl_output: Option<&wl_output::WlOutput>, + window_map: &WindowMap, + output_map: &OutputMap, +) -> Option { + // First test if a specific output has been requested + // if the requested output is not found ignore the request + if let Some(wl_output) = wl_output { + return output_map.find(&wl_output, |_, geometry| geometry).ok(); + } + + // There is no output preference, try to find the output + // where the window is currently active + let window_location = window_map + .find(wl_surface) + .and_then(|kind| window_map.location(&kind)); + + if let Some(location) = window_location { + let window_output = output_map.find_by_position(location, |_, geometry| geometry).ok(); + + if let Some(result) = window_output { + return Some(result); + } + } + + // Fallback to primary output + output_map.with_primary(|_, geometry| geometry).ok() +} + +pub fn init_shell(display: Rc>, log: ::slog::Logger) -> ShellHandles { // Create the compositor compositor_init( - display, + &mut *display.borrow_mut(), move |surface, mut ddata| { let anvil_state = ddata.get::>().unwrap(); let window_map = anvil_state.window_map.as_ref(); @@ -309,19 +341,39 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg // Init a window map, to track the location of our windows let window_map = Rc::new(RefCell::new(WindowMap::new())); + let output_map = Rc::new(RefCell::new(OutputMap::new( + display.clone(), + window_map.clone(), + log.clone(), + ))); // init the xdg_shell let xdg_window_map = window_map.clone(); + let xdg_output_map = output_map.clone(); let (xdg_shell_state, _, _) = xdg_shell_init( - display, + &mut *display.borrow_mut(), move |shell_event| match shell_event { XdgRequest::NewToplevel { surface } => { - // place the window at a random location in the [0;800]x[0;800] square + // place the window at a random location on the primary output + // or if there is not output in a [0;800]x[0;800] square use rand::distributions::{Distribution, Uniform}; - let range = Uniform::new(0, 800); + + let output_geometry = xdg_output_map + .borrow() + .with_primary(|_, geometry| geometry) + .ok() + .unwrap_or_else(|| Rectangle { + width: 800, + height: 800, + ..Default::default() + }); + let max_x = output_geometry.x + (((output_geometry.width as f32) / 3.0) * 2.0) as i32; + let max_y = output_geometry.y + (((output_geometry.height as f32) / 3.0) * 2.0) as i32; + let x_range = Uniform::new(output_geometry.x, max_x); + let y_range = Uniform::new(output_geometry.y, max_y); let mut rng = rand::thread_rng(); - let x = range.sample(&mut rng); - let y = range.sample(&mut rng); + let x = x_range.sample(&mut rng); + let y = y_range.sample(&mut rng); // Do not send a configure here, the initial configure // of a xdg_surface has to be sent during the commit if // the surface is not already configured @@ -493,16 +545,39 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg } } XdgRequest::Fullscreen { surface, output, .. } => { - let ret = surface.with_pending_state(|state| { - // TODO: Use size of current output the window is on and set position to (0,0) - state.states.set(xdg_toplevel::State::Fullscreen); - state.size = Some((800, 600)); - // TODO: If the provided output is None, use the output where - // the toplevel is currently shown - state.fullscreen_output = output; - }); - if ret.is_ok() { - surface.send_configure(); + // NOTE: This is only one part of the solution. We can set the + // location and configure size here, but the surface should be rendered fullscreen + // independently from its buffer size + let wl_surface = if let Some(surface) = surface.get_surface() { + surface + } else { + // If there is no underlying surface just ignore the request + return; + }; + + let output_geometry = fullscreen_output_geometry( + wl_surface, + output.as_ref(), + &xdg_window_map.borrow(), + &xdg_output_map.borrow(), + ); + + if let Some(geometry) = output_geometry { + if let Some(surface) = surface.get_surface() { + let mut xdg_window_map = xdg_window_map.borrow_mut(); + if let Some(kind) = xdg_window_map.find(surface) { + xdg_window_map.set_location(&kind, (geometry.x, geometry.y)); + } + } + + let ret = surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Fullscreen); + state.size = Some((geometry.width, geometry.height)); + state.fullscreen_output = output; + }); + if ret.is_ok() { + surface.send_configure(); + } } } XdgRequest::UnFullscreen { surface } => { @@ -516,13 +591,36 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg } } XdgRequest::Maximize { surface } => { - let ret = surface.with_pending_state(|state| { - // TODO: Use size of current output the window is on and set position to (0,0) - state.states.set(xdg_toplevel::State::Maximized); - state.size = Some((800, 600)); - }); - if ret.is_ok() { - surface.send_configure(); + // NOTE: This should use layer-shell when it is implemented to + // get the correct maximum size + let output_geometry = { + let xdg_window_map = xdg_window_map.borrow(); + surface + .get_surface() + .and_then(|s| xdg_window_map.find(s)) + .and_then(|k| xdg_window_map.location(&k)) + .and_then(|position| { + xdg_output_map + .borrow() + .find_by_position(position, |_, geometry| geometry) + .ok() + }) + }; + + if let Some(geometry) = output_geometry { + if let Some(surface) = surface.get_surface() { + let mut xdg_window_map = xdg_window_map.borrow_mut(); + if let Some(kind) = xdg_window_map.find(surface) { + xdg_window_map.set_location(&kind, (geometry.x, geometry.y)); + } + } + let ret = surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Maximized); + state.size = Some((geometry.width, geometry.height)); + }); + if ret.is_ok() { + surface.send_configure(); + } } } XdgRequest::UnMaximize { surface } => { @@ -541,24 +639,66 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg // init the wl_shell let shell_window_map = window_map.clone(); + let shell_output_map = output_map.clone(); let (wl_shell_state, _) = wl_shell_init( - display, + &mut *display.borrow_mut(), move |req: ShellRequest| { match req { ShellRequest::SetKind { surface, kind: ShellSurfaceKind::Toplevel, } => { - // place the window at a random location in the [0;800]x[0;800] square + // place the window at a random location on the primary output + // or if there is not output in a [0;800]x[0;800] square use rand::distributions::{Distribution, Uniform}; - let range = Uniform::new(0, 800); + + let output_geometry = shell_output_map + .borrow() + .with_primary(|_, geometry| geometry) + .ok() + .unwrap_or_else(|| Rectangle { + width: 800, + height: 800, + ..Default::default() + }); + let max_x = output_geometry.x + (((output_geometry.width as f32) / 3.0) * 2.0) as i32; + let max_y = output_geometry.y + (((output_geometry.height as f32) / 3.0) * 2.0) as i32; + let x_range = Uniform::new(output_geometry.x, max_x); + let y_range = Uniform::new(output_geometry.y, max_y); let mut rng = rand::thread_rng(); - let x = range.sample(&mut rng); - let y = range.sample(&mut rng); + let x = x_range.sample(&mut rng); + let y = y_range.sample(&mut rng); shell_window_map .borrow_mut() .insert(SurfaceKind::Wl(surface), (x, y)); } + ShellRequest::SetKind { + surface, + kind: ShellSurfaceKind::Fullscreen { output, .. }, + } => { + // NOTE: This is only one part of the solution. We can set the + // location and configure size here, but the surface should be rendered fullscreen + // independently from its buffer size + let wl_surface = if let Some(surface) = surface.get_surface() { + surface + } else { + // If there is no underlying surface just ignore the request + return; + }; + + let output_geometry = fullscreen_output_geometry( + wl_surface, + output.as_ref(), + &shell_window_map.borrow(), + &shell_output_map.borrow(), + ); + + if let Some(geometry) = output_geometry { + shell_window_map + .borrow_mut() + .insert(SurfaceKind::Wl(surface), (geometry.x, geometry.y)); + } + } ShellRequest::Move { surface, seat, @@ -669,6 +809,7 @@ pub fn init_shell(display: &mut Display, log: ::slog::Logg xdg_state: xdg_shell_state, wl_state: wl_shell_state, window_map, + output_map, } } diff --git a/anvil/src/state.rs b/anvil/src/state.rs index cd335d0..36975be 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -31,6 +31,7 @@ pub struct AnvilState { pub display: Rc>, pub handle: LoopHandle<'static, AnvilState>, pub window_map: Rc>, + pub output_map: Rc>, pub dnd_icon: Arc>>, pub log: slog::Logger, // input-related fields @@ -76,7 +77,7 @@ impl AnvilState { init_shm_global(&mut (*display).borrow_mut(), vec![], log.clone()); - let shell_handles = init_shell::(&mut display.borrow_mut(), log.clone()); + let shell_handles = init_shell::(display.clone(), log.clone()); let socket_name = display .borrow_mut() @@ -148,6 +149,7 @@ impl AnvilState { display, handle, window_map: shell_handles.window_map, + output_map: shell_handles.output_map, dnd_icon, log, socket_name, diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index 6a85a05..3f4f84f 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -45,13 +45,12 @@ use smithay::{ nix::{fcntl::OFlag, sys::stat::dev_t}, wayland_server::{ protocol::{wl_output, wl_surface}, - Display, Global, + Display, }, }, signaling::{Linkable, SignalToken, Signaler}, - utils::Rectangle, wayland::{ - output::{Mode, Output, PhysicalProperties}, + output::{Mode, PhysicalProperties}, seat::CursorImageStatus, }, }; @@ -76,8 +75,14 @@ impl AsRawFd for SessionFd { } } +struct UdevOutputMap { + pub device_id: dev_t, + pub crtc: crtc::Handle, + pub output_name: String, +} + pub struct UdevData { - pub output_map: Vec, + output_map: Vec, pub session: AutoSession, #[cfg(feature = "egl")] primary_gpu: Option, @@ -233,6 +238,7 @@ pub fn run_udev( } else { display.borrow_mut().flush_clients(&mut state); state.window_map.borrow_mut().refresh(); + state.output_map.borrow_mut().refresh(); } } @@ -246,68 +252,6 @@ pub fn run_udev( Ok(()) } -pub struct MyOutput { - pub device_id: dev_t, - pub crtc: crtc::Handle, - pub size: (u32, u32), - _wl: Output, - global: Option>, -} - -impl MyOutput { - fn new( - display: &mut Display, - device_id: dev_t, - crtc: crtc::Handle, - conn: ConnectorInfo, - logger: ::slog::Logger, - ) -> MyOutput { - let (output, global) = Output::new( - display, - format!("{:?}", conn.interface()), - PhysicalProperties { - width: conn.size().unwrap_or((0, 0)).0 as i32, - height: conn.size().unwrap_or((0, 0)).1 as i32, - subpixel: wl_output::Subpixel::Unknown, - make: "Smithay".into(), - model: "Generic DRM".into(), - }, - logger, - ); - - let mode = conn.modes()[0]; - let (w, h) = mode.size(); - output.change_current_state( - Some(Mode { - width: w as i32, - height: h as i32, - refresh: (mode.vrefresh() * 1000) as i32, - }), - None, - None, - ); - output.set_preferred(Mode { - width: w as i32, - height: h as i32, - refresh: (mode.vrefresh() * 1000) as i32, - }); - - MyOutput { - device_id, - crtc, - size: (w as u32, h as u32), - _wl: output, - global: Some(global), - } - } -} - -impl Drop for MyOutput { - fn drop(&mut self) { - self.global.take().unwrap().destroy(); - } -} - pub type RenderSurface = GbmBufferedSurface; struct BackendData { @@ -321,12 +265,12 @@ struct BackendData { dev_id: u64, } -pub fn scan_connectors( +fn scan_connectors( device: &mut DrmDevice, gbm: &GbmDevice, renderer: &mut Gles2Renderer, - display: &mut Display, - output_map: &mut Vec, + backend_output_map: &mut Vec, + output_map: &mut crate::output_map::OutputMap, signaler: &Signaler, logger: &::slog::Logger, ) -> HashMap>> { @@ -388,13 +332,37 @@ pub fn scan_connectors( } }; - output_map.push(MyOutput::new( - display, - device.device_id(), + let mode = connector_info.modes()[0]; + let size = mode.size(); + let mode = Mode { + width: size.0 as i32, + height: size.1 as i32, + refresh: (mode.vrefresh() * 1000) as i32, + }; + + let output_name = format!( + "{:?}-{}", + connector_info.interface(), + connector_info.interface_id() + ); + + output_map.add( + &output_name, + PhysicalProperties { + width: connector_info.size().unwrap_or((0, 0)).0 as i32, + height: connector_info.size().unwrap_or((0, 0)).1 as i32, + subpixel: wl_output::Subpixel::Unknown, + make: "Smithay".into(), + model: "Generic DRM".into(), + }, + mode, + ); + + backend_output_map.push(UdevOutputMap { crtc, - connector_info, - logger.clone(), - )); + device_id: device.device_id(), + output_name, + }); entry.insert(Rc::new(RefCell::new(renderer))); break 'outer; @@ -483,8 +451,8 @@ impl AnvilState { &mut device, &gbm, &mut *renderer.borrow_mut(), - &mut *self.display.borrow_mut(), &mut self.backend_data.output_map, + &mut *self.output_map.borrow_mut(), &self.backend_data.signaler, &self.log, ))); @@ -543,8 +511,18 @@ impl AnvilState { if let Some(ref mut backend_data) = self.backend_data.backends.get_mut(&device) { let logger = self.log.clone(); let loop_handle = self.handle.clone(); - let mut display = self.display.borrow_mut(); let signaler = self.backend_data.signaler.clone(); + let removed_outputs = self + .backend_data + .output_map + .iter() + .filter(|o| o.device_id == device) + .map(|o| o.output_name.as_str()); + + for output in removed_outputs { + self.output_map.borrow_mut().remove(output); + } + self.backend_data .output_map .retain(|output| output.device_id != device); @@ -555,8 +533,8 @@ impl AnvilState { &mut *source, &backend_data.gbm, &mut *backend_data.renderer.borrow_mut(), - &mut *display, &mut self.backend_data.output_map, + &mut *self.output_map.borrow_mut(), &signaler, &logger, ); @@ -580,10 +558,16 @@ impl AnvilState { // drop surfaces backend_data.surfaces.borrow_mut().clear(); debug!(self.log, "Surfaces dropped"); - // clear outputs - self.backend_data + let removed_outputs = self + .backend_data .output_map - .retain(|output| output.device_id != device); + .iter() + .filter(|o| o.device_id == device) + .map(|o| o.output_name.as_str()); + for output_id in removed_outputs { + self.output_map.borrow_mut().remove(output_id); + } + self.backend_data.output_map.retain(|o| o.device_id != device); let _device = self.handle.remove(backend_data.registration_token); let _device = backend_data.event_dispatcher.into_source_inner(); @@ -629,7 +613,8 @@ impl AnvilState { device_backend.dev_id, crtc, &mut *self.window_map.borrow_mut(), - &mut self.backend_data.output_map, + &self.backend_data.output_map, + &*self.output_map.borrow(), &self.pointer_location, &device_backend.pointer_image, &*self.dnd_icon.lock().unwrap(), @@ -676,7 +661,8 @@ fn render_surface( device_id: dev_t, crtc: crtc::Handle, window_map: &mut WindowMap, - output_map: &mut Vec, + backend_output_map: &[UdevOutputMap], + output_map: &crate::output_map::OutputMap, pointer_location: &(f64, f64), pointer_image: &Gles2Texture, dnd_icon: &Option, @@ -685,48 +671,40 @@ fn render_surface( ) -> Result<(), SwapBuffersError> { surface.frame_submitted()?; - // get output coordinates - let (x, y) = output_map + let output_geometry = backend_output_map .iter() - .take_while(|output| output.device_id != device_id || output.crtc != crtc) - .fold((0u32, 0u32), |pos, output| (pos.0 + output.size.0, pos.1)); - let (width, height) = output_map - .iter() - .find(|output| output.device_id == device_id && output.crtc == crtc) - .map(|output| output.size) - .unwrap_or((0, 0)); // in this case the output will be removed. + .find(|o| o.device_id == device_id && o.crtc == crtc) + .map(|o| o.output_name.as_str()) + .and_then(|name| output_map.find_by_name(name, |_, geometry| geometry).ok()); + + let output_geometry = if let Some(geometry) = output_geometry { + geometry + } else { + // Somehow we got called with a non existing output + return Ok(()); + }; let dmabuf = surface.next_buffer()?; renderer.bind(dmabuf)?; // and draw to our buffer match renderer .render( - width, - height, + output_geometry.width as u32, + output_geometry.height as u32, Transform::Flipped180, // Scanout is rotated |renderer, frame| { frame.clear([0.8, 0.8, 0.9, 1.0])?; // draw the surfaces - draw_windows( - renderer, - frame, - window_map, - Some(Rectangle { - x: x as i32, - y: y as i32, - width: width as i32, - height: height as i32, - }), - logger, - )?; + draw_windows(renderer, frame, window_map, output_geometry, logger)?; // get pointer coordinates let (ptr_x, ptr_y) = *pointer_location; - let ptr_x = ptr_x.trunc().abs() as i32 - x as i32; - let ptr_y = ptr_y.trunc().abs() as i32 - y as i32; + let ptr_x = ptr_x.trunc().abs() as i32 - output_geometry.x; + let ptr_y = ptr_y.trunc().abs() as i32 - output_geometry.y; // set cursor - if ptr_x >= 0 && ptr_x < width as i32 && ptr_y >= 0 && ptr_y < height as i32 { + if ptr_x >= 0 && ptr_x < output_geometry.width && ptr_y >= 0 && ptr_y < output_geometry.height + { // draw the dnd icon if applicable { if let Some(ref wl_surface) = dnd_icon.as_ref() { diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 58eec6f..f3dd1da 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -11,9 +11,8 @@ use smithay::{ calloop::EventLoop, wayland_server::{protocol::wl_output, Display}, }, - utils::Rectangle, wayland::{ - output::{Mode, Output, PhysicalProperties}, + output::{Mode, PhysicalProperties}, seat::CursorImageStatus, }, }; @@ -23,6 +22,8 @@ use slog::Logger; use crate::drawing::*; use crate::state::{AnvilState, Backend}; +pub const OUTPUT_NAME: &str = "winit"; + pub struct WinitData; impl Backend for WinitData { @@ -72,9 +73,14 @@ pub fn run_winit( let mut state = AnvilState::init(display.clone(), event_loop.handle(), WinitData, log.clone()); - let (output, _) = Output::new( - &mut display.borrow_mut(), - "Winit".into(), + let mode = Mode { + width: w as i32, + height: h as i32, + refresh: 60_000, + }; + + state.output_map.borrow_mut().add( + OUTPUT_NAME, PhysicalProperties { width: 0, height: 0, @@ -82,24 +88,9 @@ pub fn run_winit( make: "Smithay".into(), model: "Winit".into(), }, - log.clone(), + mode, ); - output.change_current_state( - Some(Mode { - width: w as i32, - height: h as i32, - refresh: 60_000, - }), - None, - None, - ); - output.set_preferred(Mode { - width: w as i32, - height: h as i32, - refresh: 60_000, - }); - let start_time = std::time::Instant::now(); let mut cursor_visible = true; @@ -120,16 +111,13 @@ pub fn run_winit( // drawing logic { let mut renderer = renderer.borrow_mut(); - - let output_rect = { - let (width, height) = renderer.window_size().physical_size.into(); - Rectangle { - x: 0, - y: 0, - width, - height, - } - }; + // This is safe to do as with winit we are guaranteed to have exactly one output + let output_geometry = state + .output_map + .borrow() + .find_by_name(OUTPUT_NAME, |_, geometry| geometry) + .ok() + .unwrap(); let result = renderer .render(|renderer, frame| { @@ -140,7 +128,7 @@ pub fn run_winit( renderer, frame, &*state.window_map.borrow(), - Some(output_rect), + output_geometry, &log, )?; @@ -203,6 +191,7 @@ pub fn run_winit( } else { display.borrow_mut().flush_clients(&mut state); state.window_map.borrow_mut().refresh(); + state.output_map.borrow_mut().refresh(); } } diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index 2fcb7b1..676b5bc 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -53,10 +53,13 @@ use std::{ sync::{Arc, Mutex}, }; -use wayland_server::protocol::wl_output::{Subpixel, Transform}; +use wayland_server::protocol::{ + wl_output::{Subpixel, Transform}, + wl_surface, +}; use wayland_server::{ protocol::wl_output::{Mode as WMode, WlOutput}, - Display, Filter, Global, Main, + Client, Display, Filter, Global, Main, }; use slog::{info, o, trace, warn}; @@ -314,4 +317,38 @@ impl Output { .iter() .any(|o| o.as_ref().equals(output.as_ref())) } + + /// This function allows to run a [FnMut] on every + /// [WlOutput] matching the same [Client] as provided + pub fn with_client_outputs(&self, client: Client, mut f: F) + where + F: FnMut(&WlOutput), + { + self.inner + .lock() + .unwrap() + .instances + .iter() + .filter(|output| match output.as_ref().client() { + Some(output_client) => output_client.equals(&client), + None => false, + }) + .for_each(|output| f(output)) + } + + /// Sends `wl_surface.enter` for the provided surface + /// with the matching client output + pub fn enter(&self, surface: &wl_surface::WlSurface) { + if let Some(client) = surface.as_ref().client() { + self.with_client_outputs(client, |output| surface.enter(output)) + } + } + + /// Sends `wl_surface.leave` for the provided surface + /// with the matching client output + pub fn leave(&self, surface: &wl_surface::WlSurface) { + if let Some(client) = surface.as_ref().client() { + self.with_client_outputs(client, |output| surface.leave(output)) + } + } } diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index 2b82057..c63253c 100644 --- a/src/wayland/shell/xdg/mod.rs +++ b/src/wayland/shell/xdg/mod.rs @@ -1113,6 +1113,30 @@ impl ToplevelSurface { }) .unwrap()) } + + /// Gets a copy of the current state of this toplevel + /// + /// Returns `None` if the underlying surface has been + /// destroyed + pub fn current_state(&self) -> Option { + if !self.alive() { + return None; + } + + Some( + compositor::with_states(&self.wl_surface, |states| { + let attributes = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + + attributes.current.clone() + }) + .unwrap(), + ) + } } #[derive(Debug, Clone)]