From 0d88a392fa72c685b3cdeae130c6c1c58279de23 Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Mon, 21 Jun 2021 21:29:40 +0200 Subject: [PATCH 1/3] add an output_map which handles... the positioning of outputs, tracks the surfaces on the outputs and sends enter/leave for the surfaces Additionally the output_map will handle re-location of windows if a output mode is changed, an output is added or removed. The shell has been updated to use the primary output for the initial placement. The fullscreen/maximize requests will now handle the shell part correctly. For real fullscreen output the rendering has to be changed. The output name is considered unique and an output can be retrieved from the map by location, output or name. This can be used as the base for HiDPI handling. --- anvil/src/drawing.rs | 10 +- anvil/src/input_handler.rs | 82 +++---- anvil/src/main.rs | 2 + anvil/src/output_map.rs | 403 +++++++++++++++++++++++++++++++++++ anvil/src/shell.rs | 249 +++++++++++++++++++--- anvil/src/state.rs | 4 +- anvil/src/udev.rs | 194 ++++++++--------- anvil/src/winit.rs | 53 ++--- src/wayland/output/mod.rs | 38 +++- src/wayland/shell/xdg/mod.rs | 24 +++ 10 files changed, 832 insertions(+), 227 deletions(-) create mode 100644 anvil/src/output_map.rs diff --git a/anvil/src/drawing.rs b/anvil/src/drawing.rs index 6b0c693..d708765 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..6c2032b 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,16 @@ 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 (max_x, max_y) = self.output_map.borrow().size(); + let clamped_x = pos_x.max(0.0).min(max_x as f64); + let clamped_y = pos_y.max(0.0).min(max_y as f64); - // 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); - - (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) } } 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..9605a3e --- /dev/null +++ b/anvil/src/output_map.rs @@ -0,0 +1,403 @@ +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) -> u32 { + // This is a simplification, we only arrange the outputs on the y axis side-by-side + // so that the max height is simply the max of all output heights. + self.outputs + .iter() + .map(|output| output.geometry.height as u32) + .max() + .unwrap_or_default() + } + + pub fn size(&self) -> (u32, u32) { + (self.width(), self.height()) + } + + 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 e0c6c99..a401e9e 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,13 @@ 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 { +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 +311,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 @@ -478,16 +500,78 @@ 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; + }; + + // Use the specified preferred output or else the current output the window + // is shown or the primary output + let output = output + .or_else(|| { + let xdg_window_map = xdg_window_map.borrow(); + let mut window_output: Option = None; + xdg_window_map + .find(wl_surface) + .and_then(|kind| xdg_window_map.location(&kind)) + .and_then(|position| { + xdg_output_map + .borrow() + .find_by_position(position, |output, _| { + output.with_output(wl_surface, |_, output| { + window_output = Some(output.to_owned()); + }) + }) + .ok() + }); + window_output + }) + .or_else(|| { + let mut primary_output: Option = None; + xdg_output_map + .borrow() + .with_primary(|output, _| { + output.with_output(wl_surface, |_, output| { + primary_output = Some(output.to_owned()); + }) + }) + .ok(); + primary_output + }); + + let fullscreen_output = if let Some(output) = output { + output + } else { + // If we have no output ignore the request + return; + }; + + let geometry = xdg_output_map + .borrow() + .find(&fullscreen_output, |_, geometry| geometry) + .ok(); + + if let Some(geometry) = 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 = Some(fullscreen_output); + }); + if ret.is_ok() { + surface.send_configure(); + } } } XdgRequest::UnFullscreen { surface } => { @@ -501,13 +585,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 } => { @@ -526,24 +633,105 @@ 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; + }; + + // Use the specified preferred output or else the current output the window + // is shown or the primary output + let output = output + .or_else(|| { + let shell_window_map = shell_window_map.borrow(); + let mut window_output: Option = None; + shell_window_map + .find(wl_surface) + .and_then(|kind| shell_window_map.location(&kind)) + .and_then(|position| { + shell_output_map + .borrow() + .find_by_position(position, |output, _| { + output.with_output(wl_surface, |_, output| { + window_output = Some(output.to_owned()); + }) + }) + .ok() + }); + window_output + }) + .or_else(|| { + let mut primary_output: Option = None; + shell_output_map + .borrow() + .with_primary(|output, _| { + output.with_output(wl_surface, |_, output| { + primary_output = Some(output.to_owned()); + }) + }) + .ok(); + primary_output + }); + + let fullscreen_output = if let Some(output) = output { + output + } else { + // If we have no output ignore the request + return; + }; + + let geometry = shell_output_map + .borrow() + .find(&fullscreen_output, |_, geometry| geometry) + .ok(); + + let location = geometry.map(|g| (g.x, g.y)).unwrap_or_default(); + + shell_window_map + .borrow_mut() + .insert(SurfaceKind::Wl(surface), location); + } ShellRequest::Move { surface, seat, @@ -654,6 +842,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..cb04bf7 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(); @@ -630,6 +614,7 @@ impl AnvilState { crtc, &mut *self.window_map.borrow_mut(), &mut 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..540b57b 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: &'static 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..b58d3ab 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -53,11 +53,17 @@ use std::{ sync::{Arc, Mutex}, }; -use wayland_server::protocol::wl_output::{Subpixel, Transform}; use wayland_server::{ protocol::wl_output::{Mode as WMode, WlOutput}, Display, Filter, Global, Main, }; +use wayland_server::{ + protocol::{ + wl_output::{Subpixel, Transform}, + wl_surface, + }, + Resource, +}; use slog::{info, o, trace, warn}; @@ -314,4 +320,34 @@ impl Output { .iter() .any(|o| o.as_ref().equals(output.as_ref())) } + + /// This function allows to run a `Fn` for the matching + /// client outputs for a specific `Resource`. + pub fn with_output(&self, resource: &R, mut f: F) + where + R: AsRef>, + II: wayland_server::Interface, + F: FnMut(&R, &WlOutput), + { + let tmp = resource.as_ref(); + self.inner + .lock() + .unwrap() + .instances + .iter() + .filter(|output| output.as_ref().same_client_as(tmp)) + .for_each(|output| f(resource, output)) + } + + /// Sends `wl_surface.enter` for the provided surface + /// with the matching client output + pub fn enter(&self, surface: &wl_surface::WlSurface) { + self.with_output(surface, |surface, 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) { + self.with_output(surface, |surface, output| surface.leave(output)) + } } diff --git a/src/wayland/shell/xdg/mod.rs b/src/wayland/shell/xdg/mod.rs index 79b99a2..11d6ff7 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)] From d7e10acc78f9c34ade884aadc01e87b93f6b1b8f Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Mon, 28 Jun 2021 12:07:33 +0200 Subject: [PATCH 2/3] workaround for fullscreen request without client output --- anvil/src/shell.rs | 94 +++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index a401e9e..bc535f1 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -513,50 +513,46 @@ pub fn init_shell(display: Rc>, log: ::sl // Use the specified preferred output or else the current output the window // is shown or the primary output let output = output + .map(|output| { + xdg_output_map + .borrow() + .find(&output, |_, geometry| (Some(output.clone()), geometry)) + .ok() + }) + .flatten() .or_else(|| { let xdg_window_map = xdg_window_map.borrow(); - let mut window_output: Option = None; + xdg_window_map .find(wl_surface) .and_then(|kind| xdg_window_map.location(&kind)) .and_then(|position| { xdg_output_map .borrow() - .find_by_position(position, |output, _| { + .find_by_position(position, |output, geometry| { + let mut window_output: Option = None; output.with_output(wl_surface, |_, output| { window_output = Some(output.to_owned()); - }) + }); + (window_output, geometry) }) .ok() - }); - window_output + }) }) .or_else(|| { - let mut primary_output: Option = None; xdg_output_map .borrow() - .with_primary(|output, _| { + .with_primary(|output, geometry| { + let mut primary_output: Option = None; output.with_output(wl_surface, |_, output| { primary_output = Some(output.to_owned()); - }) + }); + (primary_output, geometry) }) - .ok(); - primary_output + .ok() }); - let fullscreen_output = if let Some(output) = output { - output - } else { - // If we have no output ignore the request - return; - }; - - let geometry = xdg_output_map - .borrow() - .find(&fullscreen_output, |_, geometry| geometry) - .ok(); - - if let Some(geometry) = geometry { + if let Some((output, geometry)) = output { 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) { @@ -567,7 +563,7 @@ pub fn init_shell(display: Rc>, log: ::sl let ret = surface.with_pending_state(|state| { state.states.set(xdg_toplevel::State::Fullscreen); state.size = Some((geometry.width, geometry.height)); - state.fullscreen_output = Some(fullscreen_output); + state.fullscreen_output = output; }); if ret.is_ok() { surface.send_configure(); @@ -683,54 +679,50 @@ pub fn init_shell(display: Rc>, log: ::sl // Use the specified preferred output or else the current output the window // is shown or the primary output let output = output + .map(|output| { + shell_output_map + .borrow() + .find(&output, |_, geometry| (Some(output.clone()), geometry)) + .ok() + }) + .flatten() .or_else(|| { let shell_window_map = shell_window_map.borrow(); - let mut window_output: Option = None; + shell_window_map .find(wl_surface) .and_then(|kind| shell_window_map.location(&kind)) .and_then(|position| { shell_output_map .borrow() - .find_by_position(position, |output, _| { + .find_by_position(position, |output, geometry| { + let mut window_output: Option = None; output.with_output(wl_surface, |_, output| { window_output = Some(output.to_owned()); - }) + }); + (window_output, geometry) }) .ok() - }); - window_output + }) }) .or_else(|| { - let mut primary_output: Option = None; shell_output_map .borrow() - .with_primary(|output, _| { + .with_primary(|output, geometry| { + let mut primary_output: Option = None; output.with_output(wl_surface, |_, output| { primary_output = Some(output.to_owned()); - }) + }); + (primary_output, geometry) }) - .ok(); - primary_output + .ok() }); - let fullscreen_output = if let Some(output) = output { - output - } else { - // If we have no output ignore the request - return; - }; - - let geometry = shell_output_map - .borrow() - .find(&fullscreen_output, |_, geometry| geometry) - .ok(); - - let location = geometry.map(|g| (g.x, g.y)).unwrap_or_default(); - - shell_window_map - .borrow_mut() - .insert(SurfaceKind::Wl(surface), location); + if let Some((_, geometry)) = output { + shell_window_map + .borrow_mut() + .insert(SurfaceKind::Wl(surface), (geometry.x, geometry.y)); + } } ShellRequest::Move { surface, From df208fa6c8191bb578eaddd5ca80c9828d303eed Mon Sep 17 00:00:00 2001 From: Christian Meissl Date: Wed, 30 Jun 2021 19:58:13 +0200 Subject: [PATCH 3/3] clamp cursor with current output height move fullscreen output geometry calculation to a helper function rename with_outputs to with_client_outputs and take client instead of generic resource --- anvil/src/input_handler.rs | 13 +++- anvil/src/output_map.rs | 10 +-- anvil/src/shell.rs | 128 +++++++++++++------------------------ anvil/src/udev.rs | 2 +- anvil/src/winit.rs | 2 +- src/wayland/output/mod.rs | 39 +++++------ 6 files changed, 78 insertions(+), 116 deletions(-) diff --git a/anvil/src/input_handler.rs b/anvil/src/input_handler.rs index 6c2032b..2a2b803 100644 --- a/anvil/src/input_handler.rs +++ b/anvil/src/input_handler.rs @@ -265,11 +265,18 @@ impl AnvilState { } let (pos_x, pos_y) = pos; - let (max_x, max_y) = self.output_map.borrow().size(); + 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 clamped_y = pos_y.max(0.0).min(max_y as f64); + let max_y = output_map.height(clamped_x as i32); - (clamped_x, clamped_y) + if let Some(max_y) = max_y { + let clamped_y = pos_y.max(0.0).min(max_y as f64); + + (clamped_x, clamped_y) + } else { + (clamped_x, pos_y) + } } } diff --git a/anvil/src/output_map.rs b/anvil/src/output_map.rs index 9605a3e..00027da 100644 --- a/anvil/src/output_map.rs +++ b/anvil/src/output_map.rs @@ -212,18 +212,12 @@ impl OutputMap { .fold(0u32, |acc, output| acc + output.geometry.width as u32) } - pub fn height(&self) -> u32 { + pub fn height(&self, x: i32) -> Option { // This is a simplification, we only arrange the outputs on the y axis side-by-side - // so that the max height is simply the max of all output heights. self.outputs .iter() + .find(|output| x >= output.geometry.x && x < (output.geometry.x + output.geometry.width)) .map(|output| output.geometry.height as u32) - .max() - .unwrap_or_default() - } - - pub fn size(&self) -> (u32, u32) { - (self.width(), self.height()) } pub fn is_empty(&self) -> bool { diff --git a/anvil/src/shell.rs b/anvil/src/shell.rs index bc535f1..33117f5 100644 --- a/anvil/src/shell.rs +++ b/anvil/src/shell.rs @@ -297,6 +297,36 @@ pub struct ShellHandles { pub output_map: Rc>, } +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( @@ -510,49 +540,14 @@ pub fn init_shell(display: Rc>, log: ::sl return; }; - // Use the specified preferred output or else the current output the window - // is shown or the primary output - let output = output - .map(|output| { - xdg_output_map - .borrow() - .find(&output, |_, geometry| (Some(output.clone()), geometry)) - .ok() - }) - .flatten() - .or_else(|| { - let xdg_window_map = xdg_window_map.borrow(); + let output_geometry = fullscreen_output_geometry( + wl_surface, + output.as_ref(), + &xdg_window_map.borrow(), + &xdg_output_map.borrow(), + ); - xdg_window_map - .find(wl_surface) - .and_then(|kind| xdg_window_map.location(&kind)) - .and_then(|position| { - xdg_output_map - .borrow() - .find_by_position(position, |output, geometry| { - let mut window_output: Option = None; - output.with_output(wl_surface, |_, output| { - window_output = Some(output.to_owned()); - }); - (window_output, geometry) - }) - .ok() - }) - }) - .or_else(|| { - xdg_output_map - .borrow() - .with_primary(|output, geometry| { - let mut primary_output: Option = None; - output.with_output(wl_surface, |_, output| { - primary_output = Some(output.to_owned()); - }); - (primary_output, geometry) - }) - .ok() - }); - - if let Some((output, geometry)) = output { + 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) { @@ -676,49 +671,14 @@ pub fn init_shell(display: Rc>, log: ::sl return; }; - // Use the specified preferred output or else the current output the window - // is shown or the primary output - let output = output - .map(|output| { - shell_output_map - .borrow() - .find(&output, |_, geometry| (Some(output.clone()), geometry)) - .ok() - }) - .flatten() - .or_else(|| { - let shell_window_map = shell_window_map.borrow(); + let output_geometry = fullscreen_output_geometry( + wl_surface, + output.as_ref(), + &shell_window_map.borrow(), + &shell_output_map.borrow(), + ); - shell_window_map - .find(wl_surface) - .and_then(|kind| shell_window_map.location(&kind)) - .and_then(|position| { - shell_output_map - .borrow() - .find_by_position(position, |output, geometry| { - let mut window_output: Option = None; - output.with_output(wl_surface, |_, output| { - window_output = Some(output.to_owned()); - }); - (window_output, geometry) - }) - .ok() - }) - }) - .or_else(|| { - shell_output_map - .borrow() - .with_primary(|output, geometry| { - let mut primary_output: Option = None; - output.with_output(wl_surface, |_, output| { - primary_output = Some(output.to_owned()); - }); - (primary_output, geometry) - }) - .ok() - }); - - if let Some((_, geometry)) = output { + if let Some(geometry) = output_geometry { shell_window_map .borrow_mut() .insert(SurfaceKind::Wl(surface), (geometry.x, geometry.y)); diff --git a/anvil/src/udev.rs b/anvil/src/udev.rs index cb04bf7..3f4f84f 100644 --- a/anvil/src/udev.rs +++ b/anvil/src/udev.rs @@ -613,7 +613,7 @@ 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, diff --git a/anvil/src/winit.rs b/anvil/src/winit.rs index 540b57b..f3dd1da 100644 --- a/anvil/src/winit.rs +++ b/anvil/src/winit.rs @@ -22,7 +22,7 @@ use slog::Logger; use crate::drawing::*; use crate::state::{AnvilState, Backend}; -pub const OUTPUT_NAME: &'static str = "winit"; +pub const OUTPUT_NAME: &str = "winit"; pub struct WinitData; diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index b58d3ab..676b5bc 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -53,16 +53,13 @@ use std::{ sync::{Arc, Mutex}, }; -use wayland_server::{ - protocol::wl_output::{Mode as WMode, WlOutput}, - Display, Filter, Global, Main, +use wayland_server::protocol::{ + wl_output::{Subpixel, Transform}, + wl_surface, }; use wayland_server::{ - protocol::{ - wl_output::{Subpixel, Transform}, - wl_surface, - }, - Resource, + protocol::wl_output::{Mode as WMode, WlOutput}, + Client, Display, Filter, Global, Main, }; use slog::{info, o, trace, warn}; @@ -321,33 +318,37 @@ impl Output { .any(|o| o.as_ref().equals(output.as_ref())) } - /// This function allows to run a `Fn` for the matching - /// client outputs for a specific `Resource`. - pub fn with_output(&self, resource: &R, mut f: F) + /// 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 - R: AsRef>, - II: wayland_server::Interface, - F: FnMut(&R, &WlOutput), + F: FnMut(&WlOutput), { - let tmp = resource.as_ref(); self.inner .lock() .unwrap() .instances .iter() - .filter(|output| output.as_ref().same_client_as(tmp)) - .for_each(|output| f(resource, output)) + .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) { - self.with_output(surface, |surface, output| surface.enter(output)) + 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) { - self.with_output(surface, |surface, output| surface.leave(output)) + if let Some(client) = surface.as_ref().client() { + self.with_client_outputs(client, |output| surface.leave(output)) + } } }