diff --git a/anvil/src/output_map.rs b/anvil/src/output_map.rs index 6ced0e1..98583b8 100644 --- a/anvil/src/output_map.rs +++ b/anvil/src/output_map.rs @@ -51,7 +51,7 @@ impl Output { let output_scale = scale.round() as i32; - output.change_current_state(Some(mode), None, Some(output_scale)); + output.change_current_state(Some(mode), None, Some(output_scale), Some(location)); output.set_preferred(mode); Self { @@ -159,6 +159,11 @@ impl OutputMap { output.location.x = output_x; output.location.y = 0; + + output + .output + .change_current_state(None, None, None, Some(output.location)); + output_x += output.size().w; } @@ -314,7 +319,7 @@ impl OutputMap { output.output.delete_mode(output.current_mode); output .output - .change_current_state(Some(mode), None, Some(output.output_scale)); + .change_current_state(Some(mode), None, Some(output.output_scale), None); output.output.set_preferred(mode); output.current_mode = mode; } @@ -356,9 +361,12 @@ impl OutputMap { if output.output_scale != output_scale { output.output_scale = output_scale; - output - .output - .change_current_state(Some(output.current_mode), None, Some(output_scale)); + output.output.change_current_state( + Some(output.current_mode), + None, + Some(output_scale), + None, + ); } } } diff --git a/anvil/src/state.rs b/anvil/src/state.rs index eb917ad..4e032a9 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -15,6 +15,7 @@ use smithay::{ utils::{Logical, Point}, wayland::{ data_device::{default_action_chooser, init_data_device, set_data_device_focus, DataDeviceEvent}, + output::xdg::init_xdg_output_manager, seat::{CursorImageStatus, KeyboardHandle, PointerHandle, Seat, XkbConfig}, shm::init_shm_global, tablet_manager::{init_tablet_manager_global, TabletSeatTrait}, @@ -82,6 +83,8 @@ impl AnvilState { let shell_handles = init_shell::(display.clone(), log.clone()); + init_xdg_output_manager(&mut display.borrow_mut(), log.clone()); + let socket_name = display .borrow_mut() .add_socket_auto() diff --git a/src/wayland/output/mod.rs b/src/wayland/output/mod.rs index bbb313e..6e1170c 100644 --- a/src/wayland/output/mod.rs +++ b/src/wayland/output/mod.rs @@ -39,6 +39,7 @@ //! Some(Mode { size: (1920, 1080).into(), refresh: 60000 }), // the resolution mode, //! Some(wl_output::Transform::Normal), // global screen transformation //! Some(1), // global screen scaling factor +//! Some((0,0).into()) // output position //! ); //! // set the preferred mode //! output.set_preferred(Mode { size: (1920, 1080).into(), refresh: 60000 }); @@ -47,6 +48,8 @@ //! output.add_mode(Mode { size: (1024, 768).into(), refresh: 60000 }); //! ``` +pub mod xdg; + use std::{ ops::Deref as _, sync::{Arc, Mutex}, @@ -65,6 +68,8 @@ use slog::{info, o, trace, warn}; use crate::utils::{Logical, Physical, Point, Raw, Size}; +use self::xdg::XdgOutput; + /// An output mode /// /// A possible combination of dimensions and refresh rate for an output. @@ -106,6 +111,8 @@ struct Inner { modes: Vec, current_mode: Option, preferred_mode: Option, + + xdg_output: Option, } impl Inner { @@ -194,6 +201,7 @@ impl Output { modes: Vec::new(), current_mode: None, preferred_mode: None, + xdg_output: None, })); let output = Output { inner: inner.clone() }; @@ -220,6 +228,16 @@ impl Output { (output, global) } + /// Attempt to retrieve a [`Output`] from an existing resource + pub fn from_resource(output: &WlOutput) -> Option { + output + .as_ref() + .user_data() + .get::>>() + .cloned() + .map(|inner| Output { inner }) + } + /// Sets the preferred mode of this output /// /// If the provided mode was not previously known to this output, it is added to its @@ -257,7 +275,7 @@ impl Output { /// Change the current state of this output /// - /// You can changed the current mode, transform status or scale of this output. Providing + /// You can changed the current mode, transform status, location or scale of this output. Providing /// `None` to any of these field means that the value does not change. /// /// If the provided mode was not previously known to this output, it is added to its @@ -269,6 +287,7 @@ impl Output { new_mode: Option, new_transform: Option, new_scale: Option, + new_location: Option>, ) { let mut inner = self.inner.lock().unwrap(); if let Some(mode) = new_mode { @@ -287,11 +306,21 @@ impl Output { if inner.preferred_mode == new_mode { flags |= WMode::Preferred; } + if let Some(new_location) = new_location { + inner.location = new_location; + } + + // XdgOutput has to be updated before WlOutput + // Because WlOutput::done() has to allways be called last + if let Some(xdg_output) = inner.xdg_output.as_ref() { + xdg_output.change_current_state(new_mode, new_scale, new_location); + } + for output in &inner.instances { if let Some(mode) = new_mode { output.mode(flags, mode.size.w, mode.size.h, mode.refresh); } - if new_transform.is_some() { + if new_transform.is_some() || new_location.is_some() { inner.send_geometry(output); } if let Some(scale) = new_scale { diff --git a/src/wayland/output/xdg.rs b/src/wayland/output/xdg.rs new file mode 100644 index 0000000..29a7887 --- /dev/null +++ b/src/wayland/output/xdg.rs @@ -0,0 +1,177 @@ +//! XDG Output advertising capabilities +//! +//! This protocol is ment for describing outputs in a way +//! which is more in line with the concept of an output on desktop oriented systems. + +use std::{ + ops::Deref as _, + sync::{Arc, Mutex}, +}; + +use slog::{o, trace}; +use wayland_protocols::unstable::xdg_output::v1::server::{ + zxdg_output_manager_v1::{self, ZxdgOutputManagerV1}, + zxdg_output_v1::ZxdgOutputV1, +}; +use wayland_server::{protocol::wl_output::WlOutput, Display, Filter, Global, Main}; + +use crate::utils::{Logical, Physical, Point, Size}; + +use super::{Mode, Output}; + +#[derive(Debug)] +struct Inner { + name: String, + description: String, + logical_position: Point, + + physical_size: Option>, + scale: i32, + + instances: Vec, + log: ::slog::Logger, +} + +#[derive(Debug, Clone)] +pub(super) struct XdgOutput { + inner: Arc>, +} + +impl XdgOutput { + fn new(output: &super::Inner, log: ::slog::Logger) -> Self { + trace!(log, "Creating new xdg_output"; "name" => &output.name); + + let description = format!( + "{} - {} - {}", + output.physical.make, output.physical.model, output.name + ); + + let physical_size = output.current_mode.map(|mode| mode.size); + + Self { + inner: Arc::new(Mutex::new(Inner { + name: output.name.clone(), + description, + logical_position: output.location, + + physical_size, + scale: output.scale, + + instances: Vec::new(), + log, + })), + } + } + + fn add_instance(&self, xdg_output: Main, wl_output: &WlOutput) { + let mut inner = self.inner.lock().unwrap(); + + xdg_output.logical_position(inner.logical_position.x, inner.logical_position.y); + + if let Some(size) = inner.physical_size { + let logical_size = size.to_logical(inner.scale); + xdg_output.logical_size(logical_size.w, logical_size.h); + } + + if xdg_output.as_ref().version() >= 2 { + xdg_output.name(inner.name.clone()); + xdg_output.description(inner.description.clone()); + } + + // xdg_output.done() is deprecated since version 3 + if xdg_output.as_ref().version() < 3 { + xdg_output.done(); + } + + wl_output.done(); + + xdg_output.quick_assign(|_, _, _| {}); + xdg_output.assign_destructor(Filter::new(|xdg_output: ZxdgOutputV1, _, _| { + let inner = &xdg_output.as_ref().user_data().get::().unwrap().inner; + inner + .lock() + .unwrap() + .instances + .retain(|o| !o.as_ref().equals(&xdg_output.as_ref())); + })); + xdg_output.as_ref().user_data().set_threadsafe({ + let xdg_output = self.clone(); + move || xdg_output + }); + + inner.instances.push(xdg_output.deref().clone()); + } + + pub(super) fn change_current_state( + &self, + new_mode: Option, + new_scale: Option, + new_location: Option>, + ) { + let mut output = self.inner.lock().unwrap(); + + if let Some(new_mode) = new_mode { + output.physical_size = Some(new_mode.size); + } + if let Some(new_scale) = new_scale { + output.scale = new_scale; + } + if let Some(new_location) = new_location { + output.logical_position = new_location; + } + + for instance in output.instances.iter() { + if new_mode.is_some() | new_scale.is_some() { + if let Some(size) = output.physical_size { + let logical_size = size.to_logical(output.scale); + instance.logical_size(logical_size.w, logical_size.h); + } + } + + if new_location.is_some() { + instance.logical_position(output.logical_position.x, output.logical_position.y); + } + + // xdg_output.done() is deprecated since version 3 + if instance.as_ref().version() < 3 { + instance.done(); + } + + // No need for wl_output.done() here, it will be called by caller (super::Output::change_current_state) + } + } +} + +/// Initialize a xdg output manager global. +pub fn init_xdg_output_manager(display: &mut Display, logger: L) -> Global +where + L: Into>, +{ + let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "xdg_output_handler")); + + display.create_global( + 3, + Filter::new(move |(manager, _version): (Main, _), _, _| { + let log = log.clone(); + manager.quick_assign(move |_, req, _| match req { + zxdg_output_manager_v1::Request::GetXdgOutput { + id, + output: wl_output, + } => { + let output = Output::from_resource(&wl_output).unwrap(); + let mut inner = output.inner.lock().unwrap(); + + if inner.xdg_output.is_none() { + inner.xdg_output = Some(XdgOutput::new(&inner, log.clone())); + } + + inner.xdg_output.as_ref().unwrap().add_instance(id, &wl_output); + } + zxdg_output_manager_v1::Request::Destroy => { + // Nothing to do + } + _ => {} + }); + }), + ) +}