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,
frame: &mut F,
window_map: &WindowMap,
output_rect: Option<Rectangle>,
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) {

View File

@ -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<Backend> AnvilState<Backend> {
#[cfg(feature = "winit")]
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 {
InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::<B>(event) {
KeyAction::None | KeyAction::Forward => {}
@ -162,6 +168,16 @@ impl AnvilState<WinitData> {
InputEvent::PointerMotionAbsolute { event, .. } => self.on_pointer_move_absolute::<B>(event),
InputEvent::PointerButton { event, .. } => self.on_pointer_button::<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)
}
@ -206,17 +222,16 @@ impl AnvilState<UdevData> {
}
}
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<UdevData> {
}
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)
}
}

View File

@ -26,6 +26,8 @@ mod winit;
#[cfg(feature = "xwayland")]
mod xwayland;
mod output_map;
use state::AnvilState;
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::{
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<Mutex<XdgShellState>>,
pub wl_state: Arc<Mutex<WlShellState>>,
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
compositor_init(
display,
&mut *display.borrow_mut(),
move |surface, mut ddata| {
let anvil_state = ddata.get::<AnvilState<BackendData>>().unwrap();
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
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<BackendData: 'static>(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<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| {
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<BackendData: 'static>(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<BackendData: 'static>(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<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 {
surface,
seat,
@ -654,6 +842,7 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
xdg_state: xdg_shell_state,
wl_state: wl_shell_state,
window_map,
output_map,
}
}

View File

@ -31,6 +31,7 @@ pub struct AnvilState<BackendData> {
pub display: Rc<RefCell<Display>>,
pub handle: LoopHandle<'static, AnvilState<BackendData>>,
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 log: slog::Logger,
// input-related fields
@ -76,7 +77,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
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
.borrow_mut()
@ -148,6 +149,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
display,
handle,
window_map: shell_handles.window_map,
output_map: shell_handles.output_map,
dnd_icon,
log,
socket_name,

View File

@ -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<MyOutput>,
output_map: Vec<UdevOutputMap>,
pub session: AutoSession,
#[cfg(feature = "egl")]
primary_gpu: Option<PathBuf>,
@ -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<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>;
struct BackendData {
@ -321,12 +265,12 @@ struct BackendData {
dev_id: u64,
}
pub fn scan_connectors(
fn scan_connectors(
device: &mut DrmDevice<SessionFd>,
gbm: &GbmDevice<SessionFd>,
renderer: &mut Gles2Renderer,
display: &mut Display,
output_map: &mut Vec<MyOutput>,
backend_output_map: &mut Vec<UdevOutputMap>,
output_map: &mut crate::output_map::OutputMap,
signaler: &Signaler<SessionSignal>,
logger: &::slog::Logger,
) -> HashMap<crtc::Handle, Rc<RefCell<RenderSurface>>> {
@ -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<UdevData> {
&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<UdevData> {
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<UdevData> {
&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<UdevData> {
// 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<UdevData> {
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<MyOutput>,
backend_output_map: &[UdevOutputMap],
output_map: &crate::output_map::OutputMap,
pointer_location: &(f64, f64),
pointer_image: &Gles2Texture,
dnd_icon: &Option<wl_surface::WlSurface>,
@ -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() {

View File

@ -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();
}
}

View File

@ -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<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())
}
/// 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)]