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.
This commit is contained in:
Christian Meissl 2021-06-21 21:29:40 +02:00
parent 5cfb05cde8
commit 0d88a392fa
10 changed files with 832 additions and 227 deletions

View File

@ -185,7 +185,7 @@ pub fn draw_windows<R, E, F, T>(
renderer: &mut R, renderer: &mut R,
frame: &mut F, frame: &mut F,
window_map: &WindowMap, window_map: &WindowMap,
output_rect: Option<Rectangle>, output_rect: Rectangle,
log: &::slog::Logger, log: &::slog::Logger,
) -> Result<(), SwapBuffersError> ) -> Result<(), SwapBuffersError>
where where
@ -199,12 +199,10 @@ where
// redraw the frame, in a simple but inneficient way // redraw the frame, in a simple but inneficient way
window_map.with_windows_from_bottom_to_top(|toplevel_surface, mut initial_place, bounding_box| { 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 // skip windows that do not overlap with a given output
if let Some(output) = output_rect { if !output_rect.overlaps(bounding_box) {
if !output.overlaps(bounding_box) {
return; return;
} }
initial_place.0 -= output.x; initial_place.0 -= output_rect.x;
}
if let Some(wl_surface) = toplevel_surface.get_surface() { if let Some(wl_surface) = toplevel_surface.get_surface() {
// this surface is a root of a subsurface tree that needs to be drawn // 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) { if let Err(err) = draw_surface_tree(renderer, frame, &wl_surface, initial_place, log) {

View File

@ -13,6 +13,7 @@ use smithay::{
}, },
reexports::wayland_server::protocol::wl_pointer, reexports::wayland_server::protocol::wl_pointer,
wayland::{ wayland::{
output::Mode,
seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState}, seat::{keysyms as xkb, AxisFrame, Keysym, ModifiersState},
SERIAL_COUNTER as SCOUNTER, SERIAL_COUNTER as SCOUNTER,
}, },
@ -137,7 +138,12 @@ impl<Backend> AnvilState<Backend> {
#[cfg(feature = "winit")] #[cfg(feature = "winit")]
impl AnvilState<WinitData> { impl AnvilState<WinitData> {
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) { pub fn process_input_event<B>(&mut self, event: InputEvent<B>)
where
B: InputBackend<SpecialEvent = smithay::backend::winit::WinitEvent>,
{
use smithay::backend::winit::WinitEvent;
match event { match event {
InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::<B>(event) { InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::<B>(event) {
KeyAction::None | KeyAction::Forward => {} KeyAction::None | KeyAction::Forward => {}
@ -162,6 +168,16 @@ impl AnvilState<WinitData> {
InputEvent::PointerMotionAbsolute { event, .. } => self.on_pointer_move_absolute::<B>(event), InputEvent::PointerMotionAbsolute { event, .. } => self.on_pointer_move_absolute::<B>(event),
InputEvent::PointerButton { event, .. } => self.on_pointer_button::<B>(event), InputEvent::PointerButton { event, .. } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::<B>(event), InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::<B>(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) // other events are not handled in anvil (yet)
} }
@ -206,17 +222,16 @@ impl AnvilState<UdevData> {
} }
} }
KeyAction::Screen(num) => { KeyAction::Screen(num) => {
if let Some(output) = self.backend_data.output_map.get(num) { let geometry = self
let x = self
.backend_data
.output_map .output_map
.iter() .borrow()
.take(num) .find_by_index(num, |_, geometry| geometry)
.fold(0, |acc, output| acc + output.size.0) .ok();
as f64
+ (output.size.0 as f64 / 2.0); if let Some(geometry) = geometry {
let y = output.size.1 as f64 / 2.0; let x = geometry.x as f64 + geometry.width as f64 / 2.0;
self.pointer_location = (x as f64, y as f64) let y = geometry.height as f64 / 2.0;
self.pointer_location = (x, y)
} }
} }
}, },
@ -245,47 +260,16 @@ impl AnvilState<UdevData> {
} }
fn clamp_coords(&self, pos: (f64, f64)) -> (f64, f64) { 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; return pos;
} }
let (mut x, mut y) = pos; let (pos_x, pos_y) = pos;
// max_x is the sum of the width of all outputs let (max_x, max_y) = self.output_map.borrow().size();
let max_x = self let clamped_x = pos_x.max(0.0).min(max_x as f64);
.backend_data let clamped_y = pos_y.max(0.0).min(max_y as f64);
.output_map
.iter()
.fold(0u32, |acc, output| acc + output.size.0);
x = x.max(0.0).min(max_x as f64);
// max y depends on the current output (clamped_x, clamped_y)
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
} }
} }

View File

@ -26,6 +26,8 @@ mod winit;
#[cfg(feature = "xwayland")] #[cfg(feature = "xwayland")]
mod xwayland; mod xwayland;
mod output_map;
use state::AnvilState; use state::AnvilState;
static POSSIBLE_BACKENDS: &[&str] = &[ static POSSIBLE_BACKENDS: &[&str] = &[

403
anvil/src/output_map.rs Normal file
View File

@ -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<Global<wl_output::WlOutput>>,
geometry: Rectangle,
surfaces: Vec<WlSurface>,
current_mode: Mode,
}
impl Output {
fn new<N>(
name: N,
location: (i32, i32),
display: &mut Display,
physical: PhysicalProperties,
mode: Mode,
logger: slog::Logger,
) -> Self
where
N: AsRef<str>,
{
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<RefCell<Display>>,
outputs: Vec<Output>,
window_map: Rc<RefCell<crate::window_map::WindowMap>>,
logger: slog::Logger,
}
impl OutputMap {
pub fn new(
display: Rc<RefCell<Display>>,
window_map: Rc<RefCell<crate::window_map::WindowMap>>,
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<N>(&mut self, name: N, physical: PhysicalProperties, mode: Mode)
where
N: AsRef<str>,
{
// 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<N: AsRef<str>>(&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<F, T>(&self, f: F) -> Result<T, OutputNotFound>
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<F, T>(&self, output: &wl_output::WlOutput, f: F) -> Result<T, OutputNotFound>
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<N, F, T>(&self, name: N, f: F) -> Result<T, OutputNotFound>
where
N: AsRef<str>,
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<F, T>(&self, position: (i32, i32), f: F) -> Result<T, OutputNotFound>
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<F, T>(&self, index: usize, f: F) -> Result<T, OutputNotFound>
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<N: AsRef<str>>(&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::<RefCell<SurfaceData>>();
if data.is_some() {
if states.role == Some("subsurface") {
let current = states.cached_state.current::<SubsurfaceCachedState>();
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::<RefCell<SurfaceData>>();
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,
)
}
}
});
}
}

View File

@ -9,7 +9,7 @@ use smithay::{
reexports::{ reexports::{
wayland_protocols::xdg_shell::server::xdg_toplevel, wayland_protocols::xdg_shell::server::xdg_toplevel,
wayland_server::{ 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, Display,
}, },
}, },
@ -32,6 +32,7 @@ use smithay::{
}; };
use crate::{ use crate::{
output_map::OutputMap,
state::AnvilState, state::AnvilState,
window_map::{Kind as SurfaceKind, PopupKind, WindowMap}, window_map::{Kind as SurfaceKind, PopupKind, WindowMap},
}; };
@ -293,12 +294,13 @@ pub struct ShellHandles {
pub xdg_state: Arc<Mutex<XdgShellState>>, pub xdg_state: Arc<Mutex<XdgShellState>>,
pub wl_state: Arc<Mutex<WlShellState>>, pub wl_state: Arc<Mutex<WlShellState>>,
pub window_map: Rc<RefCell<WindowMap>>, pub window_map: Rc<RefCell<WindowMap>>,
pub output_map: Rc<RefCell<OutputMap>>,
} }
pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logger) -> ShellHandles { pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::slog::Logger) -> ShellHandles {
// Create the compositor // Create the compositor
compositor_init( compositor_init(
display, &mut *display.borrow_mut(),
move |surface, mut ddata| { move |surface, mut ddata| {
let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap(); let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap();
let window_map = anvil_state.window_map.as_ref(); let window_map = anvil_state.window_map.as_ref();
@ -309,19 +311,39 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
// Init a window map, to track the location of our windows // Init a window map, to track the location of our windows
let window_map = Rc::new(RefCell::new(WindowMap::new())); 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 // init the xdg_shell
let xdg_window_map = window_map.clone(); let xdg_window_map = window_map.clone();
let xdg_output_map = output_map.clone();
let (xdg_shell_state, _, _) = xdg_shell_init( let (xdg_shell_state, _, _) = xdg_shell_init(
display, &mut *display.borrow_mut(),
move |shell_event| match shell_event { move |shell_event| match shell_event {
XdgRequest::NewToplevel { surface } => { 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}; 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 mut rng = rand::thread_rng();
let x = range.sample(&mut rng); let x = x_range.sample(&mut rng);
let y = range.sample(&mut rng); let y = y_range.sample(&mut rng);
// Do not send a configure here, the initial configure // Do not send a configure here, the initial configure
// of a xdg_surface has to be sent during the commit if // of a xdg_surface has to be sent during the commit if
// the surface is not already configured // the surface is not already configured
@ -478,18 +500,80 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
} }
} }
XdgRequest::Fullscreen { surface, output, .. } => { XdgRequest::Fullscreen { surface, 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 xdg_window_map = xdg_window_map.borrow();
let mut window_output: Option<wl_output::WlOutput> = 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<wl_output::WlOutput> = 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| { 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.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some((800, 600)); state.size = Some((geometry.width, geometry.height));
// TODO: If the provided output is None, use the output where state.fullscreen_output = Some(fullscreen_output);
// the toplevel is currently shown
state.fullscreen_output = output;
}); });
if ret.is_ok() { if ret.is_ok() {
surface.send_configure(); surface.send_configure();
} }
} }
}
XdgRequest::UnFullscreen { surface } => { XdgRequest::UnFullscreen { surface } => {
let ret = surface.with_pending_state(|state| { let ret = surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen); state.states.unset(xdg_toplevel::State::Fullscreen);
@ -501,15 +585,38 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
} }
} }
XdgRequest::Maximize { surface } => { XdgRequest::Maximize { surface } => {
// 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| { 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.states.set(xdg_toplevel::State::Maximized);
state.size = Some((800, 600)); state.size = Some((geometry.width, geometry.height));
}); });
if ret.is_ok() { if ret.is_ok() {
surface.send_configure(); surface.send_configure();
} }
} }
}
XdgRequest::UnMaximize { surface } => { XdgRequest::UnMaximize { surface } => {
let ret = surface.with_pending_state(|state| { let ret = surface.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized); state.states.unset(xdg_toplevel::State::Maximized);
@ -526,24 +633,105 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
// init the wl_shell // init the wl_shell
let shell_window_map = window_map.clone(); let shell_window_map = window_map.clone();
let shell_output_map = output_map.clone();
let (wl_shell_state, _) = wl_shell_init( let (wl_shell_state, _) = wl_shell_init(
display, &mut *display.borrow_mut(),
move |req: ShellRequest| { move |req: ShellRequest| {
match req { match req {
ShellRequest::SetKind { ShellRequest::SetKind {
surface, surface,
kind: ShellSurfaceKind::Toplevel, 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}; 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 mut rng = rand::thread_rng();
let x = range.sample(&mut rng); let x = x_range.sample(&mut rng);
let y = range.sample(&mut rng); let y = y_range.sample(&mut rng);
shell_window_map shell_window_map
.borrow_mut() .borrow_mut()
.insert(SurfaceKind::Wl(surface), (x, y)); .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<wl_output::WlOutput> = 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<wl_output::WlOutput> = 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 { ShellRequest::Move {
surface, surface,
seat, seat,
@ -654,6 +842,7 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
xdg_state: xdg_shell_state, xdg_state: xdg_shell_state,
wl_state: wl_shell_state, wl_state: wl_shell_state,
window_map, window_map,
output_map,
} }
} }

View File

@ -31,6 +31,7 @@ pub struct AnvilState<BackendData> {
pub display: Rc<RefCell<Display>>, pub display: Rc<RefCell<Display>>,
pub handle: LoopHandle<'static, AnvilState<BackendData>>, pub handle: LoopHandle<'static, AnvilState<BackendData>>,
pub window_map: Rc<RefCell<crate::window_map::WindowMap>>, pub window_map: Rc<RefCell<crate::window_map::WindowMap>>,
pub output_map: Rc<RefCell<crate::output_map::OutputMap>>,
pub dnd_icon: Arc<Mutex<Option<WlSurface>>>, pub dnd_icon: Arc<Mutex<Option<WlSurface>>>,
pub log: slog::Logger, pub log: slog::Logger,
// input-related fields // input-related fields
@ -76,7 +77,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
init_shm_global(&mut (*display).borrow_mut(), vec![], log.clone()); init_shm_global(&mut (*display).borrow_mut(), vec![], log.clone());
let shell_handles = init_shell::<BackendData>(&mut display.borrow_mut(), log.clone()); let shell_handles = init_shell::<BackendData>(display.clone(), log.clone());
let socket_name = display let socket_name = display
.borrow_mut() .borrow_mut()
@ -148,6 +149,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
display, display,
handle, handle,
window_map: shell_handles.window_map, window_map: shell_handles.window_map,
output_map: shell_handles.output_map,
dnd_icon, dnd_icon,
log, log,
socket_name, socket_name,

View File

@ -45,13 +45,12 @@ use smithay::{
nix::{fcntl::OFlag, sys::stat::dev_t}, nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::{ wayland_server::{
protocol::{wl_output, wl_surface}, protocol::{wl_output, wl_surface},
Display, Global, Display,
}, },
}, },
signaling::{Linkable, SignalToken, Signaler}, signaling::{Linkable, SignalToken, Signaler},
utils::Rectangle,
wayland::{ wayland::{
output::{Mode, Output, PhysicalProperties}, output::{Mode, PhysicalProperties},
seat::CursorImageStatus, 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 struct UdevData {
pub output_map: Vec<MyOutput>, output_map: Vec<UdevOutputMap>,
pub session: AutoSession, pub session: AutoSession,
#[cfg(feature = "egl")] #[cfg(feature = "egl")]
primary_gpu: Option<PathBuf>, primary_gpu: Option<PathBuf>,
@ -233,6 +238,7 @@ pub fn run_udev(
} else { } else {
display.borrow_mut().flush_clients(&mut state); display.borrow_mut().flush_clients(&mut state);
state.window_map.borrow_mut().refresh(); state.window_map.borrow_mut().refresh();
state.output_map.borrow_mut().refresh();
} }
} }
@ -246,68 +252,6 @@ pub fn run_udev(
Ok(()) Ok(())
} }
pub struct MyOutput {
pub device_id: dev_t,
pub crtc: crtc::Handle,
pub size: (u32, u32),
_wl: Output,
global: Option<Global<wl_output::WlOutput>>,
}
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<SessionFd>; pub type RenderSurface = GbmBufferedSurface<SessionFd>;
struct BackendData { struct BackendData {
@ -321,12 +265,12 @@ struct BackendData {
dev_id: u64, dev_id: u64,
} }
pub fn scan_connectors( fn scan_connectors(
device: &mut DrmDevice<SessionFd>, device: &mut DrmDevice<SessionFd>,
gbm: &GbmDevice<SessionFd>, gbm: &GbmDevice<SessionFd>,
renderer: &mut Gles2Renderer, renderer: &mut Gles2Renderer,
display: &mut Display, backend_output_map: &mut Vec<UdevOutputMap>,
output_map: &mut Vec<MyOutput>, output_map: &mut crate::output_map::OutputMap,
signaler: &Signaler<SessionSignal>, signaler: &Signaler<SessionSignal>,
logger: &::slog::Logger, logger: &::slog::Logger,
) -> HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>> { ) -> HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>> {
@ -388,13 +332,37 @@ pub fn scan_connectors(
} }
}; };
output_map.push(MyOutput::new( let mode = connector_info.modes()[0];
display, let size = mode.size();
device.device_id(), 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, crtc,
connector_info, device_id: device.device_id(),
logger.clone(), output_name,
)); });
entry.insert(Rc::new(RefCell::new(renderer))); entry.insert(Rc::new(RefCell::new(renderer)));
break 'outer; break 'outer;
@ -483,8 +451,8 @@ impl AnvilState<UdevData> {
&mut device, &mut device,
&gbm, &gbm,
&mut *renderer.borrow_mut(), &mut *renderer.borrow_mut(),
&mut *self.display.borrow_mut(),
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&mut *self.output_map.borrow_mut(),
&self.backend_data.signaler, &self.backend_data.signaler,
&self.log, &self.log,
))); )));
@ -543,8 +511,18 @@ impl AnvilState<UdevData> {
if let Some(ref mut backend_data) = self.backend_data.backends.get_mut(&device) { if let Some(ref mut backend_data) = self.backend_data.backends.get_mut(&device) {
let logger = self.log.clone(); let logger = self.log.clone();
let loop_handle = self.handle.clone(); let loop_handle = self.handle.clone();
let mut display = self.display.borrow_mut();
let signaler = self.backend_data.signaler.clone(); 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 self.backend_data
.output_map .output_map
.retain(|output| output.device_id != device); .retain(|output| output.device_id != device);
@ -555,8 +533,8 @@ impl AnvilState<UdevData> {
&mut *source, &mut *source,
&backend_data.gbm, &backend_data.gbm,
&mut *backend_data.renderer.borrow_mut(), &mut *backend_data.renderer.borrow_mut(),
&mut *display,
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&mut *self.output_map.borrow_mut(),
&signaler, &signaler,
&logger, &logger,
); );
@ -580,10 +558,16 @@ impl AnvilState<UdevData> {
// drop surfaces // drop surfaces
backend_data.surfaces.borrow_mut().clear(); backend_data.surfaces.borrow_mut().clear();
debug!(self.log, "Surfaces dropped"); debug!(self.log, "Surfaces dropped");
// clear outputs let removed_outputs = self
self.backend_data .backend_data
.output_map .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 = self.handle.remove(backend_data.registration_token);
let _device = backend_data.event_dispatcher.into_source_inner(); let _device = backend_data.event_dispatcher.into_source_inner();
@ -630,6 +614,7 @@ impl AnvilState<UdevData> {
crtc, crtc,
&mut *self.window_map.borrow_mut(), &mut *self.window_map.borrow_mut(),
&mut self.backend_data.output_map, &mut self.backend_data.output_map,
&*self.output_map.borrow(),
&self.pointer_location, &self.pointer_location,
&device_backend.pointer_image, &device_backend.pointer_image,
&*self.dnd_icon.lock().unwrap(), &*self.dnd_icon.lock().unwrap(),
@ -676,7 +661,8 @@ fn render_surface(
device_id: dev_t, device_id: dev_t,
crtc: crtc::Handle, crtc: crtc::Handle,
window_map: &mut WindowMap, window_map: &mut WindowMap,
output_map: &mut Vec<MyOutput>, backend_output_map: &[UdevOutputMap],
output_map: &crate::output_map::OutputMap,
pointer_location: &(f64, f64), pointer_location: &(f64, f64),
pointer_image: &Gles2Texture, pointer_image: &Gles2Texture,
dnd_icon: &Option<wl_surface::WlSurface>, dnd_icon: &Option<wl_surface::WlSurface>,
@ -685,48 +671,40 @@ fn render_surface(
) -> Result<(), SwapBuffersError> { ) -> Result<(), SwapBuffersError> {
surface.frame_submitted()?; surface.frame_submitted()?;
// get output coordinates let output_geometry = backend_output_map
let (x, y) = output_map
.iter() .iter()
.take_while(|output| output.device_id != device_id || output.crtc != crtc) .find(|o| o.device_id == device_id && o.crtc == crtc)
.fold((0u32, 0u32), |pos, output| (pos.0 + output.size.0, pos.1)); .map(|o| o.output_name.as_str())
let (width, height) = output_map .and_then(|name| output_map.find_by_name(name, |_, geometry| geometry).ok());
.iter()
.find(|output| output.device_id == device_id && output.crtc == crtc) let output_geometry = if let Some(geometry) = output_geometry {
.map(|output| output.size) geometry
.unwrap_or((0, 0)); // in this case the output will be removed. } else {
// Somehow we got called with a non existing output
return Ok(());
};
let dmabuf = surface.next_buffer()?; let dmabuf = surface.next_buffer()?;
renderer.bind(dmabuf)?; renderer.bind(dmabuf)?;
// and draw to our buffer // and draw to our buffer
match renderer match renderer
.render( .render(
width, output_geometry.width as u32,
height, output_geometry.height as u32,
Transform::Flipped180, // Scanout is rotated Transform::Flipped180, // Scanout is rotated
|renderer, frame| { |renderer, frame| {
frame.clear([0.8, 0.8, 0.9, 1.0])?; frame.clear([0.8, 0.8, 0.9, 1.0])?;
// draw the surfaces // draw the surfaces
draw_windows( draw_windows(renderer, frame, window_map, output_geometry, logger)?;
renderer,
frame,
window_map,
Some(Rectangle {
x: x as i32,
y: y as i32,
width: width as i32,
height: height as i32,
}),
logger,
)?;
// get pointer coordinates // get pointer coordinates
let (ptr_x, ptr_y) = *pointer_location; let (ptr_x, ptr_y) = *pointer_location;
let ptr_x = ptr_x.trunc().abs() as i32 - x as i32; let ptr_x = ptr_x.trunc().abs() as i32 - output_geometry.x;
let ptr_y = ptr_y.trunc().abs() as i32 - y as i32; let ptr_y = ptr_y.trunc().abs() as i32 - output_geometry.y;
// set cursor // 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 // draw the dnd icon if applicable
{ {
if let Some(ref wl_surface) = dnd_icon.as_ref() { if let Some(ref wl_surface) = dnd_icon.as_ref() {

View File

@ -11,9 +11,8 @@ use smithay::{
calloop::EventLoop, calloop::EventLoop,
wayland_server::{protocol::wl_output, Display}, wayland_server::{protocol::wl_output, Display},
}, },
utils::Rectangle,
wayland::{ wayland::{
output::{Mode, Output, PhysicalProperties}, output::{Mode, PhysicalProperties},
seat::CursorImageStatus, seat::CursorImageStatus,
}, },
}; };
@ -23,6 +22,8 @@ use slog::Logger;
use crate::drawing::*; use crate::drawing::*;
use crate::state::{AnvilState, Backend}; use crate::state::{AnvilState, Backend};
pub const OUTPUT_NAME: &'static str = "winit";
pub struct WinitData; pub struct WinitData;
impl Backend for 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 mut state = AnvilState::init(display.clone(), event_loop.handle(), WinitData, log.clone());
let (output, _) = Output::new( let mode = Mode {
&mut display.borrow_mut(), width: w as i32,
"Winit".into(), height: h as i32,
refresh: 60_000,
};
state.output_map.borrow_mut().add(
OUTPUT_NAME,
PhysicalProperties { PhysicalProperties {
width: 0, width: 0,
height: 0, height: 0,
@ -82,24 +88,9 @@ pub fn run_winit(
make: "Smithay".into(), make: "Smithay".into(),
model: "Winit".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 start_time = std::time::Instant::now();
let mut cursor_visible = true; let mut cursor_visible = true;
@ -120,16 +111,13 @@ pub fn run_winit(
// drawing logic // drawing logic
{ {
let mut renderer = renderer.borrow_mut(); let mut renderer = renderer.borrow_mut();
// This is safe to do as with winit we are guaranteed to have exactly one output
let output_rect = { let output_geometry = state
let (width, height) = renderer.window_size().physical_size.into(); .output_map
Rectangle { .borrow()
x: 0, .find_by_name(OUTPUT_NAME, |_, geometry| geometry)
y: 0, .ok()
width, .unwrap();
height,
}
};
let result = renderer let result = renderer
.render(|renderer, frame| { .render(|renderer, frame| {
@ -140,7 +128,7 @@ pub fn run_winit(
renderer, renderer,
frame, frame,
&*state.window_map.borrow(), &*state.window_map.borrow(),
Some(output_rect), output_geometry,
&log, &log,
)?; )?;
@ -203,6 +191,7 @@ pub fn run_winit(
} else { } else {
display.borrow_mut().flush_clients(&mut state); display.borrow_mut().flush_clients(&mut state);
state.window_map.borrow_mut().refresh(); state.window_map.borrow_mut().refresh();
state.output_map.borrow_mut().refresh();
} }
} }

View File

@ -53,11 +53,17 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use wayland_server::protocol::wl_output::{Subpixel, Transform};
use wayland_server::{ use wayland_server::{
protocol::wl_output::{Mode as WMode, WlOutput}, protocol::wl_output::{Mode as WMode, WlOutput},
Display, Filter, Global, Main, Display, Filter, Global, Main,
}; };
use wayland_server::{
protocol::{
wl_output::{Subpixel, Transform},
wl_surface,
},
Resource,
};
use slog::{info, o, trace, warn}; use slog::{info, o, trace, warn};
@ -314,4 +320,34 @@ impl Output {
.iter() .iter()
.any(|o| o.as_ref().equals(output.as_ref())) .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<F, R, II>(&self, resource: &R, mut f: F)
where
R: AsRef<Resource<II>>,
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))
}
} }

View File

@ -1113,6 +1113,30 @@ impl ToplevelSurface {
}) })
.unwrap()) .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<ToplevelState> {
if !self.alive() {
return None;
}
Some(
compositor::with_states(&self.wl_surface, |states| {
let attributes = states
.data_map
.get::<Mutex<XdgToplevelSurfaceRoleAttributes>>()
.unwrap()
.lock()
.unwrap();
attributes.current.clone()
})
.unwrap(),
)
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]