Merge pull request #306 from cmeissl/feature/surface-enter-leave

This commit is contained in:
Victor Berger 2021-07-03 17:01:13 +02:00 committed by GitHub
commit f27658b759
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 787 additions and 228 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 .output_map
.backend_data .borrow()
.output_map .find_by_index(num, |_, geometry| geometry)
.iter() .ok();
.take(num)
.fold(0, |acc, output| acc + output.size.0) if let Some(geometry) = geometry {
as f64 let x = geometry.x as f64 + geometry.width as f64 / 2.0;
+ (output.size.0 as f64 / 2.0); let y = geometry.height as f64 / 2.0;
let y = output.size.1 as f64 / 2.0; self.pointer_location = (x, y)
self.pointer_location = (x as f64, y as f64)
} }
} }
}, },
@ -245,47 +260,23 @@ 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 output_map = self.output_map.borrow();
let max_x = self let max_x = output_map.width();
.backend_data let clamped_x = pos_x.max(0.0).min(max_x as f64);
.output_map let max_y = output_map.height(clamped_x as i32);
.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 if let Some(max_y) = max_y {
let max_y = self.current_output_size(x).1; let clamped_y = pos_y.max(0.0).min(max_y as f64);
y = y.max(0.0).min(max_y as f64);
(x, y) (clamped_x, clamped_y)
} } else {
(clamped_x, pos_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] = &[

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

@ -0,0 +1,397 @@
use std::{cell::RefCell, rc::Rc};
use smithay::{
reexports::{
wayland_protocols::xdg_shell::server::xdg_toplevel,
wayland_server::{
protocol::{wl_output, wl_surface::WlSurface},
Display, Global,
},
},
utils::Rectangle,
wayland::{
compositor::{with_surface_tree_downward, SubsurfaceCachedState, TraversalAction},
output::{self, Mode, PhysicalProperties},
},
};
use crate::shell::SurfaceData;
struct Output {
name: String,
output: output::Output,
global: Option<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, x: i32) -> Option<u32> {
// This is a simplification, we only arrange the outputs on the y axis side-by-side
self.outputs
.iter()
.find(|output| x >= output.geometry.x && x < (output.geometry.x + output.geometry.width))
.map(|output| output.geometry.height as u32)
}
pub fn is_empty(&self) -> bool {
self.outputs.is_empty()
}
pub fn with_primary<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,43 @@ 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 { fn fullscreen_output_geometry(
wl_surface: &wl_surface::WlSurface,
wl_output: Option<&wl_output::WlOutput>,
window_map: &WindowMap,
output_map: &OutputMap,
) -> Option<Rectangle> {
// 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<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 +341,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
@ -493,16 +545,39 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
} }
} }
XdgRequest::Fullscreen { surface, output, .. } => { XdgRequest::Fullscreen { surface, output, .. } => {
let ret = surface.with_pending_state(|state| { // NOTE: This is only one part of the solution. We can set the
// TODO: Use size of current output the window is on and set position to (0,0) // location and configure size here, but the surface should be rendered fullscreen
state.states.set(xdg_toplevel::State::Fullscreen); // independently from its buffer size
state.size = Some((800, 600)); let wl_surface = if let Some(surface) = surface.get_surface() {
// TODO: If the provided output is None, use the output where surface
// the toplevel is currently shown } else {
state.fullscreen_output = output; // If there is no underlying surface just ignore the request
}); return;
if ret.is_ok() { };
surface.send_configure();
let output_geometry = fullscreen_output_geometry(
wl_surface,
output.as_ref(),
&xdg_window_map.borrow(),
&xdg_output_map.borrow(),
);
if let Some(geometry) = output_geometry {
if let Some(surface) = surface.get_surface() {
let mut xdg_window_map = xdg_window_map.borrow_mut();
if let Some(kind) = xdg_window_map.find(surface) {
xdg_window_map.set_location(&kind, (geometry.x, geometry.y));
}
}
let ret = surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.size = Some((geometry.width, geometry.height));
state.fullscreen_output = output;
});
if ret.is_ok() {
surface.send_configure();
}
} }
} }
XdgRequest::UnFullscreen { surface } => { XdgRequest::UnFullscreen { surface } => {
@ -516,13 +591,36 @@ pub fn init_shell<BackendData: 'static>(display: &mut Display, log: ::slog::Logg
} }
} }
XdgRequest::Maximize { surface } => { XdgRequest::Maximize { surface } => {
let ret = surface.with_pending_state(|state| { // NOTE: This should use layer-shell when it is implemented to
// TODO: Use size of current output the window is on and set position to (0,0) // get the correct maximum size
state.states.set(xdg_toplevel::State::Maximized); let output_geometry = {
state.size = Some((800, 600)); let xdg_window_map = xdg_window_map.borrow();
}); surface
if ret.is_ok() { .get_surface()
surface.send_configure(); .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 } => { XdgRequest::UnMaximize { surface } => {
@ -541,24 +639,66 @@ 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;
};
let output_geometry = fullscreen_output_geometry(
wl_surface,
output.as_ref(),
&shell_window_map.borrow(),
&shell_output_map.borrow(),
);
if let Some(geometry) = output_geometry {
shell_window_map
.borrow_mut()
.insert(SurfaceKind::Wl(surface), (geometry.x, geometry.y));
}
}
ShellRequest::Move { ShellRequest::Move {
surface, surface,
seat, seat,
@ -669,6 +809,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();
@ -629,7 +613,8 @@ impl AnvilState<UdevData> {
device_backend.dev_id, device_backend.dev_id,
crtc, crtc,
&mut *self.window_map.borrow_mut(), &mut *self.window_map.borrow_mut(),
&mut self.backend_data.output_map, &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: &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,10 +53,13 @@ use std::{
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use wayland_server::protocol::wl_output::{Subpixel, Transform}; use wayland_server::protocol::{
wl_output::{Subpixel, Transform},
wl_surface,
};
use wayland_server::{ use wayland_server::{
protocol::wl_output::{Mode as WMode, WlOutput}, protocol::wl_output::{Mode as WMode, WlOutput},
Display, Filter, Global, Main, Client, Display, Filter, Global, Main,
}; };
use slog::{info, o, trace, warn}; use slog::{info, o, trace, warn};
@ -314,4 +317,38 @@ 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 [FnMut] on every
/// [WlOutput] matching the same [Client] as provided
pub fn with_client_outputs<F>(&self, client: Client, mut f: F)
where
F: FnMut(&WlOutput),
{
self.inner
.lock()
.unwrap()
.instances
.iter()
.filter(|output| match output.as_ref().client() {
Some(output_client) => output_client.equals(&client),
None => false,
})
.for_each(|output| f(output))
}
/// Sends `wl_surface.enter` for the provided surface
/// with the matching client output
pub fn enter(&self, surface: &wl_surface::WlSurface) {
if let Some(client) = surface.as_ref().client() {
self.with_client_outputs(client, |output| surface.enter(output))
}
}
/// Sends `wl_surface.leave` for the provided surface
/// with the matching client output
pub fn leave(&self, surface: &wl_surface::WlSurface) {
if let Some(client) = surface.as_ref().client() {
self.with_client_outputs(client, |output| surface.leave(output))
}
}
} }

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)]