From 748dadae01382a241033396634e766660f5d7053 Mon Sep 17 00:00:00 2001 From: Seaotatop Date: Fri, 9 Jun 2023 20:29:17 -0500 Subject: [PATCH] Copied Anvil's udev support --- Cargo.toml | 22 +- resources/cursor.rgba | Bin 0 -> 16384 bytes resources/numbers.png | Bin 0 -> 1770 bytes src/backend.rs | 6 +- src/backend/udev.rs | 1525 ++++++++++++++++++++++++++++++++++++++++- src/backend/winit.rs | 426 +++++------- src/cursor.rs | 89 +++ src/handlers.rs | 30 +- src/input.rs | 626 ++++++++++------- src/layout/manual.rs | 1 + src/main.rs | 4 +- src/state.rs | 136 +++- 12 files changed, 2308 insertions(+), 557 deletions(-) create mode 100644 resources/cursor.rgba create mode 100644 resources/numbers.png create mode 100644 src/cursor.rs diff --git a/Cargo.toml b/Cargo.toml index ce25820..e80af25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,24 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } smithay = { git = "https://github.com/Smithay/smithay" } smithay-drm-extras = { git = "https://github.com/Smithay/smithay", optional = true } thiserror = "1.0.40" -xcursor = { version = "0.3.4", optional = true } +xcursor = {version = "0.3.4", optional = true } +image = {version = "0.24.0", default-features = false, optional = true} + +[features] +default = ["egl", "winit", "udev"] +egl = ["smithay/use_system_lib", "smithay/backend_egl"] +udev = [ + "smithay-drm-extras", + "smithay/backend_libinput", + "smithay/backend_udev", + "smithay/backend_drm", + "smithay/backend_gbm", + "smithay/backend_vulkan", + "smithay/backend_egl", + "smithay/backend_session_libseat", + "image", + "smithay/renderer_gl", + "smithay/renderer_multi", + "xcursor", +] +winit = ["smithay/backend_winit", "smithay/backend_drm"] diff --git a/resources/cursor.rgba b/resources/cursor.rgba new file mode 100644 index 0000000000000000000000000000000000000000..729c1cc466b85bb2b1e9923d4a0bb7db559df4be GIT binary patch literal 16384 zcmeI1O-NKx6vxx~)X~vYbjq=Ws3dTaLKuP~m=FrJuvI9bL!v{}AX>ICKS>b^7eP@g zaT_j75OiUpE)+i&I*Qt0f{G}SV4*Xkr~kS51{afAtKOS?;mn;m^9KC=-nk#|y0NiQ zw6wHz{|s7TVPP2eq14=N_X8sGPfkv{z->Z=l!K|MDaYaAp%>vFA0K}N{>XxYf{<~5 z`!4vsUN6dk{P6J<4s_Ys*&*S8`N8e2udgTEe|A9A)6;cu5T(=UH19y+uc)Y?q@*O8 znVDe)e)P}az*JmZ9FAVIP2qPsodig-SS&O#F~JUKc6PQIIfKn+)2su9zpAQA@Uw&X z_;_}3c;uiFIcs8Kq9z>#<7Wp62?;bdHpU7Lutv5bXDliz(wu|e@v{TWe>5^O!VYL| zZmu13GZPyFn_Y&O&2;Gn1jIOssml$n{S z2?wX)X9rARH_#EWAc87NL3vr{!k{R;!iz`}>6hJTrA7XT?5IhhDYqZ}gwvKwzgJ8 zO-&6oH#gJn?yjhVm6eqb;J2owrRmYDss-ccxz1!V1@?n_ygsC2$uE_al>rCJbJ70({!cJoN7xWN?m6NSF$gniM^$s-aemxa!M?q{ zy}7fq^KEZ$kD{Za0}e_{O9|&hG(SJj<2?$&tQ>q!pakBkrz!mW+>h~0o12?ov$C=r zot>R6ETg@>J;2Y;17Ihg&-cBlsVNtfM)i;e|4W&l&;OWpZEfuf*0MXe@-iZIYirBB zzP?T=DJcO5d3kw!DSLR2b)V~A)n`OU_n%f*SKS#I8TY_*5n)C|mzS5{W!Bc#R>9Bf z0`fcua{7nE;kb#S7%3o?{NUifh#Tf0L8eE&eVNXJI#Z-Q$Lu z_ux#}h5ca;zDq?Y>mc50uNjR-u7S9|zP=aT-QBP7J{xffYa#Dqc>6-dj literal 0 HcmV?d00001 diff --git a/resources/numbers.png b/resources/numbers.png new file mode 100644 index 0000000000000000000000000000000000000000..bc01532968ec538ef14d6ee569c6fdd0d760d9b6 GIT binary patch literal 1770 zcmZ`)eK_0K9*>&0TAF7q4KC?C)>U)cM7`9@i1wnGtwcqWbqQhKrj1ndMuL?~rmU(@ zHFQ>{Rw;SvtzBvFgOO%w4(12#zxspay_BP$AB8|#%dFp$&46r04I`X`Kz8&P zhl#E0r{4gj9W4Y;JCTu0W0EtIAXIW1g@&Re(LVG@Hz`*!<3&+BU5HT-^L{pxJ#WsT;*#A}ZWfd&Vi_lCNi1zYjvpvC$w%UCizWjA9| zG_EY%FlWZB_V4BAPur4~w=@q`IXq^pX2&uvYoI{CabT2#Gj0W1AC9XS@n@-J`2#0k zCBY`x1LM)>_3UJ|u+xLfuFM#o4`8Wz7WV`Rh}jGc@gP#pgP#@&`b2_p%{yi{I|CN` zRO~nSG9I>1oLxqsMyR_*D=pIW?@d&%zQU`U(LUmJPV6C=&b~)aa2M=D<0YI5lKM_e zJW926kdqNnq(!t&KBS;^m30$&S;MACCHoqDJp!xl>YOvNT@Dy1dG`}vA4k%DbK&>v zO36m_&^o&LU9Dqud|XBQD-n)zj-0oDPQe?=Z4pE-cyx}mUC3(bve8cTr3aSQ_lDj3 zk#XPq8baQ^5p9$5dg(dL<+npm&+v(h-`(WWfuUq=n1@_8YOZGw$KIlTWVW0`o?-p4h z8(ok%fFv_W+W4ZZMh+-VehHMe+-Tv^e`4B$!EPPk*-TX@}h zO2ZqN3c$1nqofBW3g|;(o}dRMMV7ZG*)qsZ+8AiMW-xLo!AYxfSWP1dZd@mt1`d?a zhuYI88?st2W;39j=@#hng6<2@W;wRZkFRY*>Dwql3z-?70RrIp#`{lN zNb+ft{4dxqW?yl3koR@G=<9&q?F*11mEC@Gw;YDtYUXgl7bn(`${rv){!GRUH!<$> z=%H?VQNk2Ok2DN zOfB}CYqfS~RU)>cZ!-A+aL71n4trL>CKwsRqyOVUjoCcE7xie8oJ~c~9|r7byPdBG z$c&odG=qi?UXC#dQ*YMnzAZT3dZIUztWiq*=El4<&pLsD&^-4Ve4gKsnaVgDTU>px zsl|Gp&qb$IN!Xm^;xDTSofaTU+zJukw2P>7Dxj;JJE(gHF;Zd0>{Qi}t)7JpRaw#2 zi%~UJG&q)9@VsPJXjW0e_t-V7+ez%)nVQu6JSRMFSI#M`JM68tUgsy0>(pEBxoAQm zv6{(O@JHIV3tKK`Hx_xZQYHTvS)qBX=xZLEzeF`25 zLDOUoUV?dBh_y+Ux~ep7uyh}bxQRMCakYPTtW-hrUw6C#Ykd%DD-^MG zzQ+&=3Rj#;H#IgBwI@Nuyp_Nk{}Sl0gyMU-vU@SVU;v}C0@glyY$_0Uzd>752$J<< z;GMEtcXSaGs4InroAseXC!hVP2{B#r_6n%W?E^dcZS_WF%Jl zqksL^|0x$ej6wJg&-T&6l<2+^8 do;_WVVcE`1&d*+lz<&u69&$YR_P^ru{{TSbhzS4y literal 0 HcmV?d00001 diff --git a/src/backend.rs b/src/backend.rs index 65a2aef..b8bfcbd 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,4 +1,8 @@ -use smithay::{output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface}; +use smithay::{ + backend::input::{InputBackend, InputEvent}, + output::Output, + reexports::wayland_server::protocol::wl_surface::WlSurface, +}; pub mod udev; pub mod winit; diff --git a/src/backend/udev.rs b/src/backend/udev.rs index c863c05..d738231 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -1,53 +1,549 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + error::Error, + os::fd::FromRawFd, + path::Path, + sync::{atomic::Ordering, Mutex}, + time::Duration, +}; use smithay::{ backend::{ - allocator::gbm::{GbmAllocator, GbmDevice}, - drm::{compositor::DrmCompositor, DrmDeviceFd, DrmNode, GbmBufferedSurface}, - renderer::{ - damage::OutputDamageTracker, - gles::GlesRenderer, - multigpu::{gbm::GbmGlesBackend, GpuManager}, + allocator::{ + dmabuf::{AnyError, Dmabuf, DmabufAllocator}, + gbm::{GbmAllocator, GbmBufferFlags, GbmDevice}, + vulkan::{ImageUsageFlags, VulkanAllocator}, + Allocator, Fourcc, }, - session::libseat::LibSeatSession, + drm::{ + compositor::DrmCompositor, CreateDrmNodeError, DrmDevice, DrmDeviceFd, DrmError, + DrmEvent, DrmEventMetadata, DrmNode, DrmSurface, GbmBufferedSurface, NodeType, + }, + egl::{self, EGLDevice, EGLDisplay}, + libinput::{LibinputInputBackend, LibinputSessionInterface}, + renderer::{ + damage::{self, OutputDamageTracker}, + element::{ + self, texture::TextureBuffer, utils::select_dmabuf_feedback, AsRenderElements, + RenderElement, RenderElementStates, + }, + gles::{GlesRenderer, GlesTexture}, + multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture}, + Bind, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen, Renderer, + }, + session::{ + self, + libseat::{self, LibSeatSession}, + Session, + }, + udev::{self, UdevBackend, UdevEvent}, + vulkan::{self, version::Version, PhysicalDevice}, + SwapBuffersError, }, - desktop::utils::OutputPresentationFeedback, - output::Output, + delegate_dmabuf, + desktop::{ + space::{self, SurfaceTree}, + utils::{self, surface_primary_scanout_output, OutputPresentationFeedback}, + Space, Window, + }, + input::pointer::{CursorImageAttributes, CursorImageStatus}, + output::{Output, PhysicalProperties, Subpixel}, reexports::{ - drm::control::crtc, - wayland_server::{backend::GlobalId, protocol::wl_surface::WlSurface, DisplayHandle}, + ash::vk::ExtPhysicalDeviceDrmFn, + calloop::{ + timer::{TimeoutAction, Timer}, + EventLoop, LoopHandle, RegistrationToken, + }, + drm::{ + self, + control::{connector, crtc, ModeTypeFlags}, + Device, + }, + gbm, + input::Libinput, + nix::fcntl::OFlag, + wayland_protocols::wp::{ + linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1, + presentation_time::server::wp_presentation_feedback, + }, + wayland_server::{ + backend::GlobalId, + protocol::wl_surface::{self, WlSurface}, + Display, DisplayHandle, + }, + }, + utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Point, Scale, Transform}, + wayland::{ + compositor, + dmabuf::{ + DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, + ImportError, + }, + fractional_scale, + input_method::{InputMethodHandle, InputMethodSeat}, }, }; +use smithay_drm_extras::{ + drm_scanner::{DrmScanEvent, DrmScanner}, + edid::EdidInfo, +}; -use crate::state::State; +use crate::{ + render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, + state::{take_presentation_feedback, CalloopData, State, SurfaceDmabufFeedback}, +}; use super::Backend; +const SUPPORTED_FORMATS: &[Fourcc] = &[ + Fourcc::Abgr2101010, + Fourcc::Argb2101010, + Fourcc::Abgr8888, + Fourcc::Argb8888, +]; +const SUPPORTED_FORMATS_8BIT_ONLY: &[Fourcc] = &[Fourcc::Abgr8888, Fourcc::Argb8888]; + +type UdevRenderer<'a, 'b> = + MultiRenderer<'a, 'a, 'b, GbmGlesBackend, GbmGlesBackend>; + +#[derive(Debug, PartialEq)] +struct UdevOutputId { + device_id: DrmNode, + crtc: crtc::Handle, +} + pub struct UdevData { session: LibSeatSession, display_handle: DisplayHandle, + dmabuf_state: Option<(DmabufState, DmabufGlobal)>, primary_gpu: DrmNode, + allocator: Option>>, gpu_manager: GpuManager>, backends: HashMap, + pointer_images: Vec<(xcursor::parser::Image, TextureBuffer)>, + pointer_element: PointerElement, + pointer_image: crate::cursor::Cursor, } +impl DmabufHandler for State { + fn dmabuf_state(&mut self) -> &mut DmabufState { + &mut self.backend_data.dmabuf_state.as_mut().unwrap().0 + } + + fn dmabuf_imported( + &mut self, + _global: &DmabufGlobal, + dmabuf: Dmabuf, + ) -> Result<(), ImportError> { + self.backend_data + .gpu_manager + .single_renderer(&self.backend_data.primary_gpu) + .and_then(|mut renderer| renderer.import_dmabuf(&dmabuf, None)) + .map(|_| ()) + .map_err(|_| ImportError::Failed) + } +} +delegate_dmabuf!(State); + impl Backend for UdevData { fn seat_name(&self) -> String { - todo!() + self.session.seat() } fn reset_buffers(&mut self, output: &Output) { - todo!() + if let Some(id) = output.user_data().get::() { + if let Some(gpu) = self.backends.get_mut(&id.device_id) { + if let Some(surface) = gpu.surfaces.get_mut(&id.crtc) { + surface.compositor.reset_buffers(); + } + } + } } fn early_import(&mut self, surface: &WlSurface) { - todo!() + if let Err(err) = + self.gpu_manager + .early_import(Some(self.primary_gpu), self.primary_gpu, surface) + { + tracing::warn!("early buffer import failed: {}", err); + } } } +pub fn run_udev() -> Result<(), Box> { + let mut event_loop = EventLoop::try_new().unwrap(); + let mut display = Display::new().unwrap(); + + /* + * Initialize session + */ + let (session, notifier) = LibSeatSession::new()?; + + /* + * Initialize the compositor + */ + let primary_gpu = if let Ok(var) = std::env::var("ANVIL_DRM_DEVICE") { + DrmNode::from_path(var).expect("Invalid drm device path") + } else { + udev::primary_gpu(&session.seat()) + .unwrap() + .and_then(|x| { + DrmNode::from_path(x) + .ok()? + .node_with_type(NodeType::Render)? + .ok() + }) + .unwrap_or_else(|| { + udev::all_gpus(session.seat()) + .unwrap() + .into_iter() + .find_map(|x| DrmNode::from_path(x).ok()) + .expect("No GPU!") + }) + }; + tracing::info!("Using {} as primary gpu.", primary_gpu); + + let gpu_manager = GpuManager::new(GbmGlesBackend::default()).unwrap(); + + let data = UdevData { + display_handle: display.handle(), + dmabuf_state: None, + session, + primary_gpu, + gpu_manager, + allocator: None, + backends: HashMap::new(), + pointer_image: crate::cursor::Cursor::load(), + pointer_images: Vec::new(), + pointer_element: PointerElement::default(), + }; + let mut state = State::init( + data, + &mut display, + event_loop.get_signal(), + event_loop.handle(), + )?; + + /* + * Initialize the udev backend + */ + let udev_backend = UdevBackend::new(state.seat.name())?; + /* + * Initialize libinput backend + */ + let mut libinput_context = Libinput::new_with_udev::>( + state.backend_data.session.clone().into(), + ); + libinput_context + .udev_assign_seat(state.seat.name()) + .unwrap(); + let libinput_backend = LibinputInputBackend::new(libinput_context.clone()); + + /* + * Bind all our objects that get driven by the event loop + */ + event_loop + .handle() + .insert_source(libinput_backend, move |event, _, data| { + // println!("event: {:?}", event); + data.state.process_input_event(event); + })?; + + let handle = event_loop.handle(); + event_loop + .handle() + .insert_source(notifier, move |event, &mut (), data| match event { + session::Event::PauseSession => { + libinput_context.suspend(); + tracing::info!("pausing session"); + + for backend in data.state.backend_data.backends.values() { + backend.drm.pause(); + } + } + session::Event::ActivateSession => { + tracing::info!("resuming session"); + + if let Err(err) = libinput_context.resume() { + tracing::error!("Failed to resume libinput context: {:?}", err); + } + for (node, backend) in data + .state + .backend_data + .backends + .iter_mut() + .map(|(handle, backend)| (*handle, backend)) + { + backend.drm.activate(); + for surface in backend.surfaces.values_mut() { + if let Err(err) = surface.compositor.surface().reset_state() { + tracing::warn!("Failed to reset drm surface state: {}", err); + } + // reset the buffers after resume to trigger a full redraw + // this is important after a vt switch as the primary plane + // has no content and damage tracking may prevent a redraw + // otherwise + surface.compositor.reset_buffers(); + } + handle.insert_idle(move |data| data.state.render(node, None)); + } + } + }) + .unwrap(); + + for (device_id, path) in udev_backend.device_list() { + if let Err(err) = DrmNode::from_dev_id(device_id) + .map_err(DeviceAddError::DrmNode) + .and_then(|node| state.device_added(node, path)) + { + tracing::error!("Skipping device {device_id}: {err}"); + } + } + state.shm_state.update_formats( + state + .backend_data + .gpu_manager + .single_renderer(&primary_gpu) + .unwrap() + .shm_formats(), + ); + + let skip_vulkan = std::env::var("ANVIL_NO_VULKAN") + .map(|x| { + x == "1" + || x.to_lowercase() == "true" + || x.to_lowercase() == "yes" + || x.to_lowercase() == "y" + }) + .unwrap_or(false); + + if !skip_vulkan { + if let Ok(instance) = vulkan::Instance::new(Version::VERSION_1_2, None) { + if let Some(physical_device) = + PhysicalDevice::enumerate(&instance) + .ok() + .and_then(|devices| { + devices + .filter(|phd| phd.has_device_extension(ExtPhysicalDeviceDrmFn::name())) + .find(|phd| { + phd.primary_node().unwrap() == Some(primary_gpu) + || phd.render_node().unwrap() == Some(primary_gpu) + }) + }) + { + match VulkanAllocator::new( + &physical_device, + ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::SAMPLED, + ) { + Ok(allocator) => { + state.backend_data.allocator = Some(Box::new(DmabufAllocator(allocator)) + as Box>); + } + Err(err) => { + tracing::warn!("Failed to create vulkan allocator: {}", err); + } + } + } + } + } + + if state.backend_data.allocator.is_none() { + tracing::info!("No vulkan allocator found, using GBM."); + let gbm = state + .backend_data + .backends + .get(&primary_gpu) + // If the primary_gpu failed to initialize, we likely have a kmsro device + .or_else(|| state.backend_data.backends.values().next()) + // Don't fail, if there is no allocator. There is a chance, that this a single gpu system and we don't need one. + .map(|backend| backend.gbm.clone()); + state.backend_data.allocator = gbm.map(|gbm| { + Box::new(DmabufAllocator(GbmAllocator::new( + gbm, + GbmBufferFlags::RENDERING, + ))) as Box<_> + }); + } + + #[cfg_attr(not(feature = "egl"), allow(unused_mut))] + let mut renderer = state + .backend_data + .gpu_manager + .single_renderer(&primary_gpu) + .unwrap(); + + { + tracing::info!( + ?primary_gpu, + "Trying to initialize EGL Hardware Acceleration", + ); + match renderer.bind_wl_display(&display.handle()) { + Ok(_) => tracing::info!("EGL hardware-acceleration enabled"), + Err(err) => tracing::info!(?err, "Failed to initialize EGL hardware-acceleration"), + } + } + + // init dmabuf support with format list from our primary gpu + let dmabuf_formats = renderer.dmabuf_formats().collect::>(); + let default_feedback = DmabufFeedbackBuilder::new(primary_gpu.dev_id(), dmabuf_formats) + .build() + .unwrap(); + let mut dmabuf_state = DmabufState::new(); + let global = dmabuf_state.create_global_with_default_feedback::>( + &display.handle(), + &default_feedback, + ); + state.backend_data.dmabuf_state = Some((dmabuf_state, global)); + + let gpu_manager = &mut state.backend_data.gpu_manager; + state + .backend_data + .backends + .values_mut() + .for_each(|backend_data| { + // Update the per drm surface dmabuf feedback + backend_data.surfaces.values_mut().for_each(|surface_data| { + surface_data.dmabuf_feedback = surface_data.dmabuf_feedback.take().or_else(|| { + get_surface_dmabuf_feedback( + primary_gpu, + surface_data.render_node, + gpu_manager, + &surface_data.compositor, + ) + }); + }); + }); + + event_loop + .handle() + .insert_source(udev_backend, move |event, _, data| match event { + UdevEvent::Added { device_id, path } => { + if let Err(err) = DrmNode::from_dev_id(device_id) + .map_err(DeviceAddError::DrmNode) + .and_then(|node| data.state.device_added(node, &path)) + { + tracing::error!("Skipping device {device_id}: {err}"); + } + } + UdevEvent::Changed { device_id } => { + if let Ok(node) = DrmNode::from_dev_id(device_id) { + data.state.device_changed(node) + } + } + UdevEvent::Removed { device_id } => { + if let Ok(node) = DrmNode::from_dev_id(device_id) { + data.state.device_removed(node) + } + } + }) + .unwrap(); + + event_loop.run( + Some(Duration::from_millis(6)), + &mut CalloopData { state, display }, + |data| { + data.state.space.refresh(); + data.state.popup_manager.cleanup(); + data.display.flush_clients().unwrap(); + }, + )?; + + Ok(()) +} + struct BackendData { surfaces: HashMap, - gbm_device: GbmDevice, + gbm: GbmDevice, + drm: DrmDevice, + drm_scanner: DrmScanner, + render_node: DrmNode, + registration_token: RegistrationToken, +} + +#[derive(Debug, thiserror::Error)] +enum DeviceAddError { + #[error("Failed to open device using libseat: {0}")] + DeviceOpen(libseat::Error), + #[error("Failed to initialize drm device: {0}")] + DrmDevice(DrmError), + #[error("Failed to initialize gbm device: {0}")] + GbmDevice(std::io::Error), + #[error("Failed to access drm node: {0}")] + DrmNode(CreateDrmNodeError), + #[error("Failed to add device to GpuManager: {0}")] + AddNode(egl::Error), +} + +fn get_surface_dmabuf_feedback( + primary_gpu: DrmNode, + render_node: DrmNode, + gpu_manager: &mut GpuManager>, + composition: &SurfaceComposition, +) -> Option { + let primary_formats = gpu_manager + .single_renderer(&primary_gpu) + .ok()? + .dmabuf_formats() + .collect::>(); + + let render_formats = gpu_manager + .single_renderer(&render_node) + .ok()? + .dmabuf_formats() + .collect::>(); + + let all_render_formats = primary_formats + .iter() + .chain(render_formats.iter()) + .copied() + .collect::>(); + + let surface = composition.surface(); + let planes = surface.planes().unwrap(); + // We limit the scan-out trache to formats we can also render from + // so that there is always a fallback render path available in case + // the supplied buffer can not be scanned out directly + let planes_formats = surface + .supported_formats(planes.primary.handle) + .unwrap() + .into_iter() + .chain( + planes + .overlay + .iter() + .flat_map(|p| surface.supported_formats(p.handle).unwrap()), + ) + .collect::>() + .intersection(&all_render_formats) + .copied() + .collect::>(); + + let builder = DmabufFeedbackBuilder::new(primary_gpu.dev_id(), primary_formats); + let render_feedback = builder + .clone() + .add_preference_tranche(render_node.dev_id(), None, render_formats.clone()) + .build() + .unwrap(); + + let scanout_feedback = builder + .add_preference_tranche( + surface.device_fd().dev_id().unwrap(), + Some(zwp_linux_dmabuf_feedback_v1::TrancheFlags::Scanout), + planes_formats, + ) + .add_preference_tranche(render_node.dev_id(), None, render_formats) + .build() + .unwrap(); + + Some(DrmSurfaceDmabufFeedback { + render_feedback, + scanout_feedback, + }) +} + +struct DrmSurfaceDmabufFeedback { + render_feedback: DmabufFeedback, + scanout_feedback: DmabufFeedback, } struct SurfaceData { @@ -56,7 +552,7 @@ struct SurfaceData { device_id: DrmNode, render_node: DrmNode, compositor: SurfaceComposition, - // TODO: dmabuf_feedback + dmabuf_feedback: Option, } impl Drop for SurfaceData { @@ -84,3 +580,996 @@ enum SurfaceComposition { }, Compositor(GbmDrmCompositor), } + +impl SurfaceComposition { + fn frame_submitted(&mut self) -> Result, SwapBuffersError> { + match self { + SurfaceComposition::Surface { surface, .. } => surface + .frame_submitted() + .map(Option::flatten) + .map_err(SwapBuffersError::from), + SurfaceComposition::Compositor(comp) => comp + .frame_submitted() + .map(Option::flatten) + .map_err(SwapBuffersError::from), + } + } + + fn format(&self) -> gbm::Format { + match self { + SurfaceComposition::Surface { surface, .. } => surface.format(), + SurfaceComposition::Compositor(comp) => comp.format(), + } + } + + fn surface(&self) -> &DrmSurface { + match self { + SurfaceComposition::Compositor(c) => c.surface(), + SurfaceComposition::Surface { surface, .. } => surface.surface(), + } + } + + fn reset_buffers(&mut self) { + match self { + SurfaceComposition::Compositor(c) => c.reset_buffers(), + SurfaceComposition::Surface { surface, .. } => surface.reset_buffers(), + } + } + + fn queue_frame( + &mut self, + user_data: Option, + ) -> Result<(), SwapBuffersError> { + match self { + SurfaceComposition::Surface { surface, .. } => surface + .queue_buffer(None, user_data) + .map_err(Into::::into), + SurfaceComposition::Compositor(c) => c + .queue_frame(user_data) + .map_err(Into::::into), + } + } + + fn render_frame( + &mut self, + renderer: &mut R, + elements: &[E], + clear_color: [f32; 4], + ) -> Result<(bool, RenderElementStates), SwapBuffersError> + where + R: Renderer + Bind + Bind + Offscreen + ExportMem, + ::TextureId: 'static, + ::Error: Into, + E: RenderElement, + { + match self { + SurfaceComposition::Surface { + surface, + damage_tracker, + } => { + let (dmabuf, age) = surface + .next_buffer() + .map_err(Into::::into)?; + renderer + .bind(dmabuf) + .map_err(Into::::into)?; + let current_debug_flags = renderer.debug_flags(); + + tracing::info!("surface damage_tracker render_output"); + + let res = damage_tracker + .render_output(renderer, age.into(), elements, clear_color) + .map(|(damage, states)| (damage.is_some(), states)) + .map_err(|err| match err { + damage::Error::Rendering(err) => err.into(), + _ => unreachable!(), + }); + renderer.set_debug_flags(current_debug_flags); + res + } + SurfaceComposition::Compositor(compositor) => { + tracing::info!("compositor.render_frame"); + compositor + .render_frame(renderer, elements, clear_color) + .map(|render_frame_result| { + ( + render_frame_result.damage.is_some(), + render_frame_result.states, + ) + }) + .map_err(|err| match err { + smithay::backend::drm::compositor::RenderFrameError::PrepareFrame(err) => { + err.into() + } + smithay::backend::drm::compositor::RenderFrameError::RenderFrame( + damage::Error::Rendering(err), + ) => err.into(), + _ => unreachable!(), + }) + } + } + } +} + +impl State { + fn device_added(&mut self, node: DrmNode, path: &Path) -> Result<(), DeviceAddError> { + // Try to open the device + let fd = self + .backend_data + .session + .open( + path, + OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK, + ) + .map_err(DeviceAddError::DeviceOpen)?; + + let fd = DrmDeviceFd::new(unsafe { DeviceFd::from_raw_fd(fd) }); + + let (drm, notifier) = + DrmDevice::new(fd.clone(), true).map_err(DeviceAddError::DrmDevice)?; + let gbm = GbmDevice::new(fd).map_err(DeviceAddError::GbmDevice)?; + + let registration_token = self + .loop_handle + .insert_source( + notifier, + move |event, metadata, data: &mut CalloopData<_>| match event { + DrmEvent::VBlank(crtc) => { + data.state.frame_finish(node, crtc, metadata); + } + DrmEvent::Error(error) => { + tracing::error!("{:?}", error); + } + }, + ) + .unwrap(); + + let render_node = EGLDevice::device_for_display(&EGLDisplay::new(gbm.clone()).unwrap()) + .ok() + .and_then(|x| x.try_get_render_node().ok().flatten()) + .unwrap_or(node); + + self.backend_data + .gpu_manager + .as_mut() + .add_node(render_node, gbm.clone()) + .map_err(DeviceAddError::AddNode)?; + + self.backend_data.backends.insert( + node, + BackendData { + registration_token, + gbm, + drm, + drm_scanner: DrmScanner::new(), + render_node, + surfaces: HashMap::new(), + }, + ); + + self.device_changed(node); + + Ok(()) + } + + fn connector_connected( + &mut self, + node: DrmNode, + connector: connector::Info, + crtc: crtc::Handle, + ) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + let mut renderer = self + .backend_data + .gpu_manager + .single_renderer(&device.render_node) + .unwrap(); + let render_formats = renderer + .as_mut() + .egl_context() + .dmabuf_render_formats() + .clone(); + + tracing::info!( + ?crtc, + "Trying to setup connector {:?}-{}", + connector.interface(), + connector.interface_id(), + ); + + let mode_id = connector + .modes() + .iter() + .position(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED)) + .unwrap_or(0); + + let drm_mode = connector.modes()[mode_id]; + let wl_mode = smithay::output::Mode::from(drm_mode); + + let surface = match device + .drm + .create_surface(crtc, drm_mode, &[connector.handle()]) + { + Ok(surface) => surface, + Err(err) => { + tracing::warn!("Failed to create drm surface: {}", err); + return; + } + }; + + let output_name = format!( + "{}-{}", + connector.interface().as_str(), + connector.interface_id() + ); + + let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle()) + .map(|info| (info.manufacturer, info.model)) + .unwrap_or_else(|| ("Unknown".into(), "Unknown".into())); + + let (phys_w, phys_h) = connector.size().unwrap_or((0, 0)); + let output = Output::new( + output_name, + PhysicalProperties { + size: (phys_w as i32, phys_h as i32).into(), + subpixel: Subpixel::Unknown, + make, + model, + }, + ); + let global = output.create_global::>(&self.backend_data.display_handle); + + let x = self.space.outputs().fold(0, |acc, o| { + acc + self.space.output_geometry(o).unwrap().size.w + }); + let position = (x, 0).into(); + + output.set_preferred(wl_mode); + output.change_current_state(Some(wl_mode), None, None, Some(position)); + self.space.map_output(&output, position); + + output.user_data().insert_if_missing(|| UdevOutputId { + crtc, + device_id: node, + }); + + let allocator = GbmAllocator::new( + device.gbm.clone(), + GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT, + ); + + let color_formats = if std::env::var("ANVIL_DISABLE_10BIT").is_ok() { + SUPPORTED_FORMATS_8BIT_ONLY + } else { + SUPPORTED_FORMATS + }; + + let compositor = if std::env::var("ANVIL_DISABLE_DRM_COMPOSITOR").is_ok() { + let gbm_surface = + match GbmBufferedSurface::new(surface, allocator, color_formats, render_formats) { + Ok(renderer) => renderer, + Err(err) => { + tracing::warn!("Failed to create rendering surface: {}", err); + return; + } + }; + tracing::info!("***** surface composition :: SURFACE *****"); + SurfaceComposition::Surface { + surface: gbm_surface, + damage_tracker: OutputDamageTracker::from_output(&output), + } + } else { + let driver = match device.drm.get_driver() { + Ok(driver) => driver, + Err(err) => { + tracing::warn!("Failed to query drm driver: {}", err); + return; + } + }; + + let mut planes = match surface.planes() { + Ok(planes) => planes, + Err(err) => { + tracing::warn!("Failed to query surface planes: {}", err); + return; + } + }; + + // Using an overlay plane on a nvidia card breaks + if driver + .name() + .to_string_lossy() + .to_lowercase() + .contains("nvidia") + || driver + .description() + .to_string_lossy() + .to_lowercase() + .contains("nvidia") + { + planes.overlay = vec![]; + } + + let compositor = match DrmCompositor::new( + &output, + surface, + Some(planes), + allocator, + device.gbm.clone(), + color_formats, + render_formats, + device.drm.cursor_size(), + Some(device.gbm.clone()), + ) { + Ok(compositor) => compositor, + Err(err) => { + tracing::warn!("Failed to create drm compositor: {}", err); + return; + } + }; + tracing::info!("***** surface composition :: COMPOSITOR *****"); + SurfaceComposition::Compositor(compositor) + }; + + let dmabuf_feedback = get_surface_dmabuf_feedback( + self.backend_data.primary_gpu, + device.render_node, + &mut self.backend_data.gpu_manager, + &compositor, + ); + + let surface = SurfaceData { + display_handle: self.backend_data.display_handle.clone(), + device_id: node, + render_node: device.render_node, + global: Some(global), + compositor, + dmabuf_feedback, + }; + + device.surfaces.insert(crtc, surface); + + self.schedule_initial_render(node, crtc, self.loop_handle.clone()); + } + + fn connector_disconnected( + &mut self, + node: DrmNode, + _connector: connector::Info, + crtc: crtc::Handle, + ) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + device.surfaces.remove(&crtc); + + let output = self + .space + .outputs() + .find(|o| { + o.user_data() + .get::() + .map(|id| id.device_id == node && id.crtc == crtc) + .unwrap_or(false) + }) + .cloned(); + + if let Some(output) = output { + self.space.unmap_output(&output); + } + } + + fn device_changed(&mut self, node: DrmNode) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + for event in device.drm_scanner.scan_connectors(&device.drm) { + match event { + DrmScanEvent::Connected { + connector, + crtc: Some(crtc), + } => { + self.connector_connected(node, connector, crtc); + } + DrmScanEvent::Disconnected { + connector, + crtc: Some(crtc), + } => { + self.connector_disconnected(node, connector, crtc); + } + _ => {} + } + } + + // fixup window coordinates + // crate::shell::fixup_positions(&mut self.space); + } + + fn device_removed(&mut self, node: DrmNode) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + let crtcs: Vec<_> = device + .drm_scanner + .crtcs() + .map(|(info, crtc)| (info.clone(), crtc)) + .collect(); + + for (connector, crtc) in crtcs { + self.connector_disconnected(node, connector, crtc); + } + + tracing::debug!("Surfaces dropped"); + + // drop the backends on this side + if let Some(backend_data) = self.backend_data.backends.remove(&node) { + self.backend_data + .gpu_manager + .as_mut() + .remove_node(&backend_data.render_node); + + self.loop_handle.remove(backend_data.registration_token); + + tracing::debug!("Dropping device"); + } + + // crate::shell::fixup_positions(&mut self.space); + } + + fn frame_finish( + &mut self, + dev_id: DrmNode, + crtc: crtc::Handle, + metadata: &mut Option, + ) { + let device_backend = match self.backend_data.backends.get_mut(&dev_id) { + Some(backend) => backend, + None => { + tracing::error!("Trying to finish frame on non-existent backend {}", dev_id); + return; + } + }; + + let surface = match device_backend.surfaces.get_mut(&crtc) { + Some(surface) => surface, + None => { + tracing::error!("Trying to finish frame on non-existent crtc {:?}", crtc); + return; + } + }; + + let output = if let Some(output) = self.space.outputs().find(|o| { + o.user_data().get::() + == Some(&UdevOutputId { + device_id: surface.device_id, + crtc, + }) + }) { + output.clone() + } else { + // somehow we got called with an invalid output + return; + }; + + let schedule_render = match surface + .compositor + .frame_submitted() + .map_err(Into::::into) + { + Ok(user_data) => { + if let Some(mut feedback) = user_data { + let tp = metadata.as_ref().and_then(|metadata| match metadata.time { + smithay::backend::drm::DrmEventTime::Monotonic(tp) => Some(tp), + smithay::backend::drm::DrmEventTime::Realtime(_) => None, + }); + let seq = metadata + .as_ref() + .map(|metadata| metadata.sequence) + .unwrap_or(0); + + let (clock, flags) = if let Some(tp) = tp { + ( + tp.into(), + wp_presentation_feedback::Kind::Vsync + | wp_presentation_feedback::Kind::HwClock + | wp_presentation_feedback::Kind::HwCompletion, + ) + } else { + (self.clock.now(), wp_presentation_feedback::Kind::Vsync) + }; + + feedback.presented( + clock, + output + .current_mode() + .map(|mode| mode.refresh as u32) + .unwrap_or_default(), + seq as u64, + flags, + ); + } + + true + } + Err(err) => { + tracing::warn!("Error during rendering: {:?}", err); + match err { + SwapBuffersError::AlreadySwapped => true, + // If the device has been deactivated do not reschedule, this will be done + // by session resume + SwapBuffersError::TemporaryFailure(err) + if matches!( + err.downcast_ref::(), + Some(&DrmError::DeviceInactive) + ) => + { + false + } + SwapBuffersError::TemporaryFailure(err) => matches!( + err.downcast_ref::(), + Some(&DrmError::Access { + source: drm::SystemError::PermissionDenied, + .. + }) + ), + SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), + } + } + }; + + if schedule_render { + let output_refresh = match output.current_mode() { + Some(mode) => mode.refresh, + None => return, + }; + // What are we trying to solve by introducing a delay here: + // + // Basically it is all about latency of client provided buffers. + // A client driven by frame callbacks will wait for a frame callback + // to repaint and submit a new buffer. As we send frame callbacks + // as part of the repaint in the compositor the latency would always + // be approx. 2 frames. By introducing a delay before we repaint in + // the compositor we can reduce the latency to approx. 1 frame + the + // remaining duration from the repaint to the next VBlank. + // + // With the delay it is also possible to further reduce latency if + // the client is driven by presentation feedback. As the presentation + // feedback is directly sent after a VBlank the client can submit a + // new buffer during the repaint delay that can hit the very next + // VBlank, thus reducing the potential latency to below one frame. + // + // Choosing a good delay is a topic on its own so we just implement + // a simple strategy here. We just split the duration between two + // VBlanks into two steps, one for the client repaint and one for the + // compositor repaint. Theoretically the repaint in the compositor should + // be faster so we give the client a bit more time to repaint. On a typical + // modern system the repaint in the compositor should not take more than 2ms + // so this should be safe for refresh rates up to at least 120 Hz. For 120 Hz + // this results in approx. 3.33ms time for repainting in the compositor. + // A too big delay could result in missing the next VBlank in the compositor. + // + // A more complete solution could work on a sliding window analyzing past repaints + // and do some prediction for the next repaint. + let repaint_delay = + Duration::from_millis(((1_000_000f32 / output_refresh as f32) * 0.6f32) as u64); + + let timer = if self.backend_data.primary_gpu != surface.render_node { + // However, if we need to do a copy, that might not be enough. + // (And without actual comparision to previous frames we cannot really know.) + // So lets ignore that in those cases to avoid thrashing performance. + tracing::trace!("scheduling repaint timer immediately on {:?}", crtc); + Timer::immediate() + } else { + tracing::trace!( + "scheduling repaint timer with delay {:?} on {:?}", + repaint_delay, + crtc + ); + Timer::from_duration(repaint_delay) + }; + + self.loop_handle + .insert_source(timer, move |_, _, data| { + data.state.render(dev_id, Some(crtc)); + TimeoutAction::Drop + }) + .expect("failed to schedule frame timer"); + } + } + + // If crtc is `Some()`, render it, else render all crtcs + fn render(&mut self, node: DrmNode, crtc: Option) { + tracing::info!("********* DATA.STATE.RENDER()"); + let device_backend = match self.backend_data.backends.get_mut(&node) { + Some(backend) => backend, + None => { + tracing::error!("Trying to render on non-existent backend {}", node); + return; + } + }; + + if let Some(crtc) = crtc { + self.render_surface(node, crtc); + } else { + let crtcs: Vec<_> = device_backend.surfaces.keys().copied().collect(); + for crtc in crtcs { + self.render_surface(node, crtc); + } + }; + } + + fn render_surface(&mut self, node: DrmNode, crtc: crtc::Handle) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + let surface = if let Some(surface) = device.surfaces.get_mut(&crtc) { + surface + } else { + return; + }; + + // TODO get scale from the rendersurface when supporting HiDPI + let frame = self + .backend_data + .pointer_image + .get_image(1 /*scale*/, self.clock.now().try_into().unwrap()); + + let render_node = surface.render_node; + let primary_gpu = self.backend_data.primary_gpu; + let mut renderer = if primary_gpu == render_node { + self.backend_data.gpu_manager.single_renderer(&render_node) + } else { + let format = surface.compositor.format(); + self.backend_data.gpu_manager.renderer( + &primary_gpu, + &render_node, + self.backend_data + .allocator + .as_mut() + // TODO: We could build some kind of `GLAllocator` using Renderbuffers in theory for this case. + // That would work for memcpy's of offscreen contents. + .expect("We need an allocator for multigpu systems") + .as_mut(), + format, + ) + } + .unwrap(); + + let pointer_images = &mut self.backend_data.pointer_images; + let pointer_image = pointer_images + .iter() + .find_map(|(image, texture)| { + if image == &frame { + Some(texture.clone()) + } else { + None + } + }) + .unwrap_or_else(|| { + let texture = TextureBuffer::from_memory( + &mut renderer, + &frame.pixels_rgba, + Fourcc::Abgr8888, + (frame.width as i32, frame.height as i32), + false, + 1, + Transform::Normal, + None, + ) + .expect("Failed to import cursor bitmap"); + pointer_images.push((frame, texture.clone())); + texture + }); + + let output = if let Some(output) = self.space.outputs().find(|o| { + o.user_data().get::() + == Some(&UdevOutputId { + device_id: surface.device_id, + crtc, + }) + }) { + output.clone() + } else { + // somehow we got called with an invalid output + return; + }; + + let result = render_surface( + surface, + &mut renderer, + &self.space, + &output, + self.seat.input_method(), + self.pointer_location, + &pointer_image, + &mut self.backend_data.pointer_element, + &mut self.cursor_status, + &self.clock, + ); + let reschedule = match &result { + Ok(has_rendered) => !has_rendered, + Err(err) => { + tracing::warn!("Error during rendering: {:?}", err); + match err { + SwapBuffersError::AlreadySwapped => false, + SwapBuffersError::TemporaryFailure(err) => !matches!( + err.downcast_ref::(), + Some(&DrmError::DeviceInactive) + | Some(&DrmError::Access { + source: drm::SystemError::PermissionDenied, + .. + }) + ), + SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), + } + } + }; + + if reschedule { + let output_refresh = match output.current_mode() { + Some(mode) => mode.refresh, + None => { + return; + } + }; + // If reschedule is true we either hit a temporary failure or more likely rendering + // did not cause any damage on the output. In this case we just re-schedule a repaint + // after approx. one frame to re-test for damage. + let reschedule_duration = + Duration::from_millis((1_000_000f32 / output_refresh as f32) as u64); + tracing::trace!( + "reschedule repaint timer with delay {:?} on {:?}", + reschedule_duration, + crtc, + ); + let timer = Timer::from_duration(reschedule_duration); + self.loop_handle + .insert_source(timer, move |_, _, data| { + data.state.render(node, Some(crtc)); + TimeoutAction::Drop + }) + .expect("failed to schedule frame timer"); + } + } + + fn schedule_initial_render( + &mut self, + node: DrmNode, + crtc: crtc::Handle, + evt_handle: LoopHandle<'static, CalloopData>, + ) { + let device = if let Some(device) = self.backend_data.backends.get_mut(&node) { + device + } else { + return; + }; + + let surface = if let Some(surface) = device.surfaces.get_mut(&crtc) { + surface + } else { + return; + }; + + let node = surface.render_node; + let result = { + let mut renderer = self + .backend_data + .gpu_manager + .single_renderer(&node) + .unwrap(); + initial_render(surface, &mut renderer) + }; + + if let Err(err) = result { + match err { + SwapBuffersError::AlreadySwapped => {} + SwapBuffersError::TemporaryFailure(err) => { + // TODO dont reschedule after 3(?) retries + tracing::warn!("Failed to submit page_flip: {}", err); + let handle = evt_handle.clone(); + evt_handle.insert_idle(move |data| { + data.state.schedule_initial_render(node, crtc, handle) + }); + } + SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), + } + } + } +} + +#[allow(clippy::too_many_arguments)] +fn render_surface<'a>( + surface: &'a mut SurfaceData, + renderer: &mut UdevRenderer<'a, '_>, + space: &Space, + output: &Output, + input_method: &InputMethodHandle, + pointer_location: Point, + pointer_image: &TextureBuffer, + pointer_element: &mut PointerElement, + cursor_status: &mut CursorImageStatus, + clock: &Clock, +) -> Result { + let output_geometry = space.output_geometry(output).unwrap(); + let scale = Scale::from(output.current_scale().fractional_scale()); + + let mut custom_elements: Vec> = Vec::new(); + // draw input method surface if any + let rectangle = input_method.coordinates(); + let position = Point::from(( + rectangle.loc.x + rectangle.size.w, + rectangle.loc.y + rectangle.size.h, + )); + input_method.with_surface(|surface| { + custom_elements.extend(AsRenderElements::>::render_elements( + &SurfaceTree::from_surface(surface), + renderer, + position.to_physical_precise_round(scale), + scale, + 1.0, + )); + }); + + if output_geometry.to_f64().contains(pointer_location) { + let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = cursor_status { + compositor::with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot + }) + } else { + (0, 0).into() + }; + let cursor_pos = pointer_location - output_geometry.loc.to_f64() - cursor_hotspot.to_f64(); + let cursor_pos_scaled = cursor_pos.to_physical(scale).to_i32_round(); + + // set cursor + pointer_element.set_texture(pointer_image.clone()); + + // draw the cursor as relevant + { + // reset the cursor if the surface is no longer alive + let mut reset = false; + if let CursorImageStatus::Surface(ref surface) = *cursor_status { + reset = !surface.alive(); + } + if reset { + *cursor_status = CursorImageStatus::Default; + } + + pointer_element.set_status(cursor_status.clone()); + } + + custom_elements.extend(pointer_element.render_elements( + renderer, + cursor_pos_scaled, + scale, + 1.0, + )); + + // TODO: dnd icon + } + + let mut output_render_elements = custom_elements + .into_iter() + .map(OutputRenderElements::from) + .collect::>(); + + let space_render_elements = + space::space_render_elements(renderer, [space], output, 1.0).unwrap(); + + output_render_elements.extend( + space_render_elements + .into_iter() + .map(OutputRenderElements::Space), + ); + + let (rendered, states) = surface.compositor.render_frame::<_, _, GlesTexture>( + renderer, + &output_render_elements, + [0.6, 0.6, 0.6, 1.0], + )?; + + // post_repaint + { + let throttle = Some(Duration::from_secs(1)); + space.elements().for_each(|window| { + window.with_surfaces(|surface, states_inner| { + let primary_scanout_output = utils::update_surface_primary_scanout_output( + surface, + output, + states_inner, + &states, + element::default_primary_scanout_output_compare, + ); + + if let Some(output) = primary_scanout_output { + fractional_scale::with_fractional_scale(states_inner, |fraction_scale| { + fraction_scale + .set_preferred_scale(output.current_scale().fractional_scale()); + }); + } + }); + + if space.outputs_for_element(window).contains(output) { + window.send_frame( + output, + clock.now(), + throttle, + utils::surface_primary_scanout_output, + ); + + if let Some(dmabuf_feedback) = + surface + .dmabuf_feedback + .as_ref() + .map(|feedback| SurfaceDmabufFeedback { + render_feedback: &feedback.render_feedback, + scanout_feedback: &feedback.scanout_feedback, + }) + { + window.send_dmabuf_feedback( + output, + surface_primary_scanout_output, + |surface, _| { + select_dmabuf_feedback( + surface, + &states, + dmabuf_feedback.render_feedback, + dmabuf_feedback.scanout_feedback, + ) + }, + ); + } + } + }); + } + + if rendered { + let output_presentation_feedback = take_presentation_feedback(output, space, &states); + surface + .compositor + .queue_frame(Some(output_presentation_feedback)) + .map_err(Into::::into)?; + } + + Ok(rendered) +} + +fn initial_render( + surface: &mut SurfaceData, + renderer: &mut UdevRenderer<'_, '_>, +) -> Result<(), SwapBuffersError> { + surface + .compositor + .render_frame::<_, CustomRenderElements<_>, GlesTexture>( + renderer, + &[], + [0.6, 0.6, 0.6, 1.0], + )?; + surface.compositor.queue_frame(None)?; + surface.compositor.reset_buffers(); + + Ok(()) +} diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 9282026..a036feb 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,9 +1,4 @@ -use std::{ - error::Error, - os::fd::AsRawFd, - sync::{Arc, Mutex}, - time::Duration, -}; +use std::{error::Error, sync::Mutex, time::Duration}; use smithay::{ backend::{ @@ -24,42 +19,31 @@ use smithay::{ desktop::{ space, utils::{surface_primary_scanout_output, update_surface_primary_scanout_output}, - PopupManager, Space, Window, - }, - input::{ - pointer::{CursorImageAttributes, CursorImageStatus}, - SeatState, }, + input::pointer::{CursorImageAttributes, CursorImageStatus}, output::{Output, Subpixel}, reexports::{ calloop::{ - generic::Generic, timer::{TimeoutAction, Timer}, - EventLoop, Interest, Mode, PostAction, + EventLoop, }, wayland_server::{protocol::wl_surface::WlSurface, Display}, }, - utils::{Clock, IsAlive, Monotonic, Physical, Point, Scale, Transform}, + utils::{IsAlive, Scale, Transform}, wayland::{ - compositor::{self, CompositorState}, - data_device::DataDeviceState, + compositor::{self}, dmabuf::{ DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportError, }, - fractional_scale::{with_fractional_scale, FractionalScaleManagerState}, - output::OutputManagerState, - shell::xdg::XdgShellState, - shm::ShmState, - socket::ListeningSocketSource, - viewporter::ViewporterState, + fractional_scale::with_fractional_scale, }, }; use crate::{ layout::{Direction, Layout}, render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, - state::{CalloopData, ClientState, State}, + state::{CalloopData, State}, }; use super::Backend; @@ -104,41 +88,12 @@ impl DmabufHandler for State { delegate_dmabuf!(State); pub fn run_winit() -> Result<(), Box> { - let mut event_loop: EventLoop = EventLoop::try_new()?; + let mut event_loop: EventLoop> = EventLoop::try_new()?; let mut display: Display> = Display::new()?; - - let socket = ListeningSocketSource::new_auto()?; - let socket_name = socket.socket_name().to_os_string(); - - let evt_loop_handle = event_loop.handle(); - - evt_loop_handle.insert_source(socket, |stream, _metadata, data| { - data.display - .handle() - .insert_client(stream, Arc::new(ClientState::default())) - .unwrap(); - })?; - - evt_loop_handle.insert_source( - Generic::new( - display.backend().poll_fd().as_raw_fd(), - Interest::READ, - Mode::Level, - ), - |_readiness, _metadata, data| { - data.display.dispatch_clients(&mut data.state)?; - Ok(PostAction::Continue) - }, - )?; - let display_handle = display.handle(); - let mut seat_state = SeatState::>::new(); - let mut seat = seat_state.new_wl_seat(&display_handle, "seat1"); - - seat.add_keyboard(Default::default(), 500, 50)?; - seat.add_pointer(); + let evt_loop_handle = event_loop.handle(); let (mut winit_backend, mut winit_evt_loop) = smithay::backend::winit::init::()?; @@ -214,39 +169,17 @@ pub fn run_winit() -> Result<(), Box> { } }; - let mut state = State { - // TODO: move winit_backend and damage_tracker into their own scope so I can't access them - // | from after this - backend_data: WinitData { + let mut state = State::init( + WinitData { backend: winit_backend, damage_tracker: OutputDamageTracker::from_output(&output), dmabuf_state, full_redraw: 0, }, - loop_signal: event_loop.get_signal(), - loop_handle: event_loop.handle(), - clock: Clock::::new()?, - compositor_state: CompositorState::new::>(&display_handle), - data_device_state: DataDeviceState::new::>(&display_handle), - seat_state, - pointer_location: (0.0, 0.0).into(), - shm_state: ShmState::new::>(&display_handle, vec![]), - space: Space::::default(), - cursor_status: CursorImageStatus::Default, - output_manager_state: OutputManagerState::new_with_xdg_output::>( - &display_handle, - ), - xdg_shell_state: XdgShellState::new::>(&display_handle), - viewporter_state: ViewporterState::new::>(&display_handle), - fractional_scale_manager_state: FractionalScaleManagerState::new::>( - &display_handle, - ), - - move_mode: false, - socket_name: socket_name.clone(), - - popup_manager: PopupManager::default(), - }; + &mut display, + event_loop.get_signal(), + evt_loop_handle, + )?; state .shm_state @@ -254,194 +187,191 @@ pub fn run_winit() -> Result<(), Box> { state.space.map_output(&output, (0, 0)); - std::env::set_var("WAYLAND_DISPLAY", socket_name); - let mut pointer_element = PointerElement::::new(); // TODO: pointer - evt_loop_handle.insert_source(Timer::immediate(), move |_instant, _metadata, data| { - let display = &mut data.display; - let state = &mut data.state; + state + .loop_handle + .insert_source(Timer::immediate(), move |_instant, _metadata, data| { + let display = &mut data.display; + let state = &mut data.state; - let result = winit_evt_loop.dispatch_new_events(|event| match event { - WinitEvent::Resized { - size, - scale_factor: _, - } => { - output.change_current_state( - Some(smithay::output::Mode { - size, - refresh: 144_000, - }), - None, - None, - None, - ); - Layout::master_stack( - state, - state.space.elements().cloned().collect(), - Direction::Left, - ); - } - WinitEvent::Focus(_) => {} - WinitEvent::Input(input_evt) => { - state.process_input_event(&seat, input_evt); - } - WinitEvent::Refresh => {} - }); + let result = winit_evt_loop.dispatch_new_events(|event| match event { + WinitEvent::Resized { + size, + scale_factor: _, + } => { + output.change_current_state( + Some(smithay::output::Mode { + size, + refresh: 144_000, + }), + None, + None, + None, + ); + Layout::master_stack( + state, + state.space.elements().cloned().collect(), + Direction::Left, + ); + } + WinitEvent::Focus(_) => {} + WinitEvent::Input(input_evt) => { + state.process_input_event(input_evt); + } + WinitEvent::Refresh => {} + }); - match result { - Ok(_) => {} - Err(WinitError::WindowClosed) => { - state.loop_signal.stop(); - } - }; - - if let CursorImageStatus::Surface(ref surface) = state.cursor_status { - if !surface.alive() { - state.cursor_status = CursorImageStatus::Default; - } - } - - let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_)); - - pointer_element.set_status(state.cursor_status.clone()); - - let full_redraw = &mut state.backend_data.full_redraw; - *full_redraw = full_redraw.saturating_sub(1); - - let scale = Scale::from(output.current_scale().fractional_scale()); - let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status { - compositor::with_states(surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .hotspot - }) - } else { - (0, 0).into() - }; - let cursor_pos = state.pointer_location - cursor_hotspot.to_f64(); - let cursor_pos_scaled = cursor_pos.to_physical(scale).to_i32_round::(); - - let mut custom_render_elements = Vec::>::new(); - - custom_render_elements.extend(pointer_element.render_elements( - state.backend_data.backend.renderer(), - cursor_pos_scaled, - scale, - 1.0, - )); - - tracing::info!( - "custom_render_elements len = {}", - custom_render_elements.len() - ); - - let render_res = state.backend_data.backend.bind().and_then(|_| { - let age = if *full_redraw > 0 { - 0 - } else { - state.backend_data.backend.buffer_age().unwrap_or(0) + match result { + Ok(_) => {} + Err(WinitError::WindowClosed) => { + state.loop_signal.stop(); + } }; - let renderer = state.backend_data.backend.renderer(); - - // render_output() - let space_render_elements = - space::space_render_elements(renderer, [&state.space], &output, 1.0).unwrap(); - - let mut output_render_elements = Vec::< - OutputRenderElements>, - >::new(); - - output_render_elements.extend( - custom_render_elements - .into_iter() - .map(OutputRenderElements::from), - ); - output_render_elements.extend( - space_render_elements - .into_iter() - .map(OutputRenderElements::from), - ); - - state - .backend_data - .damage_tracker - .render_output(renderer, age, &output_render_elements, [0.5, 0.5, 0.5, 1.0]) - .map_err(|err| match err { - damage::Error::Rendering(err) => err.into(), - damage::Error::OutputNoMode(_) => todo!(), - }) - }); - - match render_res { - Ok((damage, states)) => { - let has_rendered = damage.is_some(); - if let Some(damage) = damage { - if let Err(err) = state.backend_data.backend.submit(Some(&damage)) { - tracing::warn!("{}", err); - } + if let CursorImageStatus::Surface(ref surface) = state.cursor_status { + if !surface.alive() { + state.cursor_status = CursorImageStatus::Default; } + } + + let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_)); + + pointer_element.set_status(state.cursor_status.clone()); + + let full_redraw = &mut state.backend_data.full_redraw; + *full_redraw = full_redraw.saturating_sub(1); + + let scale = Scale::from(output.current_scale().fractional_scale()); + let cursor_hotspot = + if let CursorImageStatus::Surface(ref surface) = state.cursor_status { + compositor::with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .hotspot + }) + } else { + (0, 0).into() + }; + let cursor_pos = state.pointer_location - cursor_hotspot.to_f64(); + let cursor_pos_scaled = cursor_pos.to_physical(scale).to_i32_round::(); + + let mut custom_render_elements = Vec::>::new(); + + custom_render_elements.extend(pointer_element.render_elements( + state.backend_data.backend.renderer(), + cursor_pos_scaled, + scale, + 1.0, + )); + + let render_res = state.backend_data.backend.bind().and_then(|_| { + let age = if *full_redraw > 0 { + 0 + } else { + state.backend_data.backend.buffer_age().unwrap_or(0) + }; + + let renderer = state.backend_data.backend.renderer(); + + // render_output() + let space_render_elements = + space::space_render_elements(renderer, [&state.space], &output, 1.0).unwrap(); + + let mut output_render_elements = Vec::< + OutputRenderElements>, + >::new(); + + output_render_elements.extend( + custom_render_elements + .into_iter() + .map(OutputRenderElements::from), + ); + output_render_elements.extend( + space_render_elements + .into_iter() + .map(OutputRenderElements::from), + ); state .backend_data - .backend - .window() - .set_cursor_visible(cursor_visible); + .damage_tracker + .render_output(renderer, age, &output_render_elements, [0.5, 0.5, 0.5, 1.0]) + .map_err(|err| match err { + damage::Error::Rendering(err) => err.into(), + damage::Error::OutputNoMode(_) => todo!(), + }) + }); - let throttle = Some(Duration::from_secs(1)); + match render_res { + Ok((damage, states)) => { + let has_rendered = damage.is_some(); + if let Some(damage) = damage { + if let Err(err) = state.backend_data.backend.submit(Some(&damage)) { + tracing::warn!("{}", err); + } + } - state.space.elements().for_each(|window| { - window.with_surfaces(|surface, states_inner| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - &output, - states_inner, - &states, - default_primary_scanout_output_compare, - ); + state + .backend_data + .backend + .window() + .set_cursor_visible(cursor_visible); - if let Some(output) = primary_scanout_output { - with_fractional_scale(states_inner, |fraction_scale| { - fraction_scale - .set_preferred_scale(output.current_scale().fractional_scale()); - }); + let throttle = Some(Duration::from_secs(1)); + + state.space.elements().for_each(|window| { + window.with_surfaces(|surface, states_inner| { + let primary_scanout_output = update_surface_primary_scanout_output( + surface, + &output, + states_inner, + &states, + default_primary_scanout_output_compare, + ); + + if let Some(output) = primary_scanout_output { + with_fractional_scale(states_inner, |fraction_scale| { + fraction_scale.set_preferred_scale( + output.current_scale().fractional_scale(), + ); + }); + } + }); + + if state.space.outputs_for_element(window).contains(&output) { + window.send_frame( + &output, + state.clock.now(), + throttle, + surface_primary_scanout_output, + ); + // TODO: dmabuf_feedback } }); - if state.space.outputs_for_element(window).contains(&output) { - window.send_frame( - &output, - state.clock.now(), - throttle, - surface_primary_scanout_output, - ); - // TODO: dmabuf_feedback + if has_rendered { + // TODO: } - }); - - if has_rendered { - // TODO: + } + Err(err) => { + tracing::warn!("{}", err); } } - Err(err) => { - tracing::warn!("{}", err); - } - } - state.space.refresh(); - state.popup_manager.cleanup(); - display - .flush_clients() - .expect("failed to flush client buffers"); + state.space.refresh(); + state.popup_manager.cleanup(); + display + .flush_clients() + .expect("failed to flush client buffers"); - TimeoutAction::ToDuration(Duration::from_millis(6)) - })?; + TimeoutAction::ToDuration(Duration::from_millis(6)) + })?; event_loop.run(None, &mut CalloopData { display, state }, |_data| {})?; diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 0000000..6fe1114 --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,89 @@ +use std::{io::Read, time::Duration}; + +use xcursor::{parser::Image, CursorTheme}; + +static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba"); + +pub struct Cursor { + icons: Vec, + size: u32, +} + +impl Cursor { + pub fn load() -> Self { + let name = std::env::var("XCURSOR_THEME") + .ok() + .unwrap_or_else(|| "default".into()); + let size = std::env::var("XCURSOR_SIZE") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(24); + + let theme = CursorTheme::load(&name); + let icons = load_icon(&theme) + .map_err(|err| tracing::warn!("Unable to load xcursor: {}, using fallback cursor", err)) + .unwrap_or_else(|_| { + vec![Image { + size: 32, + width: 64, + height: 64, + xhot: 1, + yhot: 1, + delay: 1, + pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), + pixels_argb: vec![], //unused + }] + }); + + Cursor { icons, size } + } + + pub fn get_image(&self, scale: u32, time: Duration) -> Image { + let size = self.size * scale; + frame(time.as_millis() as u32, size, &self.icons) + } +} + +fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { + // Follow the nominal size of the cursor to choose the nearest + let nearest_image = images + .iter() + .min_by_key(|image| (size as i32 - image.size as i32).abs()) + .unwrap(); + + images.iter().filter(move |image| { + image.width == nearest_image.width && image.height == nearest_image.height + }) +} + +fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { + let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); + millis %= total; + + for img in nearest_images(size, images) { + if millis < img.delay { + return img.clone(); + } + millis -= img.delay; + } + + unreachable!() +} + +#[derive(thiserror::Error, Debug)] +enum Error { + #[error("Theme has no default cursor")] + NoDefaultCursor, + #[error("Error opening xcursor file: {0}")] + File(#[from] std::io::Error), + #[error("Failed to parse XCursor file")] + Parse, +} + +fn load_icon(theme: &CursorTheme) -> Result, Error> { + let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?; + let mut cursor_file = std::fs::File::open(icon_path)?; + let mut cursor_data = Vec::new(); + cursor_file.read_to_end(&mut cursor_data)?; + xcursor::parser::parse_xcursor(&cursor_data).ok_or(Error::Parse) +} diff --git a/src/handlers.rs b/src/handlers.rs index 47754fe..c978ca9 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,7 +1,8 @@ use smithay::{ backend::renderer::utils, delegate_compositor, delegate_data_device, delegate_fractional_scale, delegate_output, - delegate_seat, delegate_shm, delegate_viewporter, delegate_xdg_shell, + delegate_presentation, delegate_relative_pointer, delegate_seat, delegate_shm, + delegate_viewporter, delegate_xdg_shell, desktop::{ find_popup_root_surface, PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Space, Window, @@ -171,7 +172,7 @@ impl SeatHandler for State { } fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { - tracing::info!("new cursor image: {:?}", image); + // tracing::info!("new cursor image: {:?}", image); self.cursor_status = image; } @@ -305,27 +306,6 @@ delegate_viewporter!(@ State); impl FractionalScaleHandler for State { fn new_fractional_scale(&mut self, surface: WlSurface) { // ripped straight from anvil - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣤⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣦⣤⣤⣤⣀⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⢿⣟⣛⣭⡽⠶⠶⠶⠮⠭⠭⣭⣭⣭⣭⣭⣭⣭⣿⣿⣯⣭⣥⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⢟⣫⣶⠿⣫⣭⣶⠿⠿⣿⣿⣿⠿⢿⣷⣶⣮⣭⣭⣭⣭⣭⣷⣶⣶⣶⣾⣽⣿⣷⣦⡀⠀⠀⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣫⣾⣟⣩⣞⣫⣵⣿⣿⣿⣿⣿⣿⣿⣯⢻⣿⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣶⣍⡻⣿⣿⣿⣷⠀⠀⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣵⣿⡿⠿⠛⠛⠛⠛⠿⣿⣿⣿⡇⣿⣿⣿⣿⣿⣣⣿⣿⣿⣿⠿⢿⣿⣷⣼⣿⣿⣿⣇⠀⠀⠀⠀ - // ⠀⠀⠀⠀⢀⣚⣯⣽⣿⣿⣿⣿⢻⣿⣏⡅⠀⠀⠀⠀⠀⠠⣿⣷⣯⡛⣿⣿⣿⣿⣿⣿⡿⠟⠉⠁⠐⣿⣿⣶⣽⣿⣟⣛⡻⠿⣦⡀⠀⠀ - // ⠀⢀⣴⣞⣯⣷⠶⣒⣛⣛⡻⢿⣷⣿⣷⣾⣶⣾⢟⣿⣿⣿⣶⣯⣟⣫⣿⣿⣿⣿⣿⣍⠀⣀⣤⣤⣬⣭⣽⣿⣿⣿⣿⣿⣿⣟⢶⡝⣦⠀ - // ⠀⣿⡿⣾⣿⣵⣿⣿⣿⣿⣿⣷⣾⣭⣽⣿⣭⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⡿⠿⢟⣫⣭⣭⣽⣿⣷⣿⢸⠀ - // ⠀⣿⡇⣿⣿⣿⡿⠿⢟⣴⣬⣛⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣷⣝⣛⢿⣿⣿⣿⣿⣿⣿⡟⣿⣿⣿⢟⣿⢸⠀ - // ⠀⢿⣧⣿⣿⣿⣿⣿⣧⢻⣿⣿⣿⣷⣮⢙⡻⠿⣿⣿⣯⣭⣾⡇⣿⣿⣟⣭⣻⣿⣿⣿⣿⣿⡿⣸⣿⠿⢿⣿⣿⡿⡁⢹⣿⣷⢿⣱⠇⠀ - // ⠀⠀⠻⢷⣝⣿⣿⣿⣿⣧⠉⠻⢿⣿⣿⢸⣿⣿⣷⣶⣭⣝⢛⠿⢿⣿⣿⣿⣿⣿⣯⣙⣛⣭⣾⣿⣿⣿⣿⠿⡋⣾⣿⡈⣿⣿⣿⡏⠀⠀ - // ⠀⠀⠀⠀⠸⣽⣿⣿⣿⣿⣷⡽⣿⣷⣆⢘⠿⣿⣿⣿⣿⣿⢸⣿⣿⣶⣶⣶⡎⣭⣭⣭⣭⡩⣭⣭⣽⣦⣰⣿⣧⢿⣿⡇⣿⣿⣿⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣮⣻⣏⣿⣿⣾⣯⣍⠛⠋⠻⢿⣿⣿⣿⣿⡇⣿⣿⣿⣿⡇⣿⣿⣿⣿⡟⣿⠟⠈⠉⠀⣿⣿⡏⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣾⡿⣿⣿⣿⣿⣿⢦⣴⣦⣬⣍⡛⠛⠈⠛⠛⠛⠛⠁⠙⠛⠛⠉⠀⠀⠀⠀⢠⡆⣿⣿⡇⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣮⣿⡻⣿⢏⣾⣿⣿⣿⣿⣿⣷⣶⣾⣷⣶⣄⣴⣶⣤⡤⣶⣶⡆⣾⡿⡸⣱⣿⣿⡇⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣷⣝⡻⢶⣽⣻⢿⣿⣷⣭⣝⣻⣿⡿⠿⠿⠏⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⡿⠱⣿⣃⣵⣿⣿⣿⣧⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢿⣶⣝⡻⢷⣮⣝⡻⢿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⡿⣿⣿⣿⣿⣿⡄⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⢷⣮⣝⡻⢿⣷⣮⣭⣛⣻⠿⠿⣿⣶⣶⣶⣶⣿⣿⣿⠿⢿⣛⣽⣾⣿⡿⣹⣿⣿⡇⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⢿⣾⣶⣭⣽⣛⣛⠿⠿⠶⢶⣶⣶⣶⣶⡿⠿⠿⢟⣛⣭⣷⣿⣿⣿⣿⠇⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀ - // ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠛⠛⠛⠛⠿⠿⠛⠛⠛⠉⠁⠀⠀⠀⠀⠀ // Here we can set the initial fractional scale // @@ -375,3 +355,7 @@ impl FractionalScaleHandler for State { } delegate_fractional_scale!(@ State); + +delegate_relative_pointer!(@ State); + +delegate_presentation!(@ State); diff --git a/src/input.rs b/src/input.rs index cf7747e..fa15188 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,279 +1,395 @@ use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, - KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, + KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent, }, desktop::WindowSurfaceType, input::{ keyboard::{keysyms, FilterResult}, - pointer::{AxisFrame, ButtonEvent, MotionEvent}, - Seat, + pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent}, }, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, - utils::{Point, SERIAL_COUNTER}, + utils::{Logical, Point, SERIAL_COUNTER}, }; -use crate::{backend::winit::WinitData, state::State}; +use crate::{ + backend::{udev::UdevData, winit::WinitData, Backend}, + state::State, +}; + +impl State { + fn pointer_button(&mut self, event: I::PointerButtonEvent) { + let pointer = self.seat.get_pointer().unwrap(); + let keyboard = self.seat.get_keyboard().unwrap(); + + // A serial is a number sent with a event that is sent back to the + // server by the clients in further requests. This allows the server to + // keep track of which event caused which requests. It is an AtomicU32 + // that increments when next_serial is called. + let serial = SERIAL_COUNTER.next_serial(); + + // Returns which button on the pointer was used. + let button = event.button_code(); + + // The state, either released or pressed. + let button_state = event.state(); + + let pointer_loc = pointer.current_location(); + + // If the button was clicked, focus on the window below if exists, else + // unfocus on windows. + if ButtonState::Pressed == button_state { + if let Some((window, window_loc)) = self + .space + .element_under(pointer_loc) + .map(|(w, l)| (w.clone(), l)) + { + const BUTTON_LEFT: u32 = 0x110; + const BUTTON_RIGHT: u32 = 0x111; + if self.move_mode { + if event.button_code() == BUTTON_LEFT { + crate::xdg::request::move_request_force( + self, + window.toplevel(), + &self.seat.clone(), + serial, + ); + return; // TODO: kinda ugly return here + } else if event.button_code() == BUTTON_RIGHT { + let window_geometry = window.geometry(); + let window_x = window_loc.x as f64; + let window_y = window_loc.y as f64; + let window_width = window_geometry.size.w as f64; + let window_height = window_geometry.size.h as f64; + let half_width = window_x + window_width / 2.0; + let half_height = window_y + window_height / 2.0; + let full_width = window_x + window_width; + let full_height = window_y + window_height; + + let edges = match pointer_loc { + Point { x, y, .. } + if (window_x..=half_width).contains(&x) + && (window_y..=half_height).contains(&y) => + { + ResizeEdge::TopLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (window_y..=half_height).contains(&y) => + { + ResizeEdge::TopRight + } + Point { x, y, .. } + if (window_x..=half_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + ResizeEdge::BottomLeft + } + Point { x, y, .. } + if (half_width..=full_width).contains(&x) + && (half_height..=full_height).contains(&y) => + { + ResizeEdge::BottomRight + } + _ => ResizeEdge::None, + }; + + crate::xdg::request::resize_request_force( + self, + window.toplevel(), + &self.seat.clone(), + serial, + edges, + BUTTON_RIGHT, + ); + } + } else { + // Move window to top of stack. + self.space.raise_element(&window, true); + + // Focus on window. + keyboard.set_focus(self, Some(window.toplevel().wl_surface().clone()), serial); + self.space.elements().for_each(|window| { + window.toplevel().send_configure(); + }); + } + } else { + self.space.elements().for_each(|window| { + window.set_activated(false); + window.toplevel().send_configure(); + }); + keyboard.set_focus(self, None, serial); + } + }; + + // Send the button event to the client. + pointer.button( + self, + &ButtonEvent { + button, + state: button_state, + serial, + time: event.time_msec(), + }, + ); + } + + fn pointer_axis(&mut self, event: I::PointerAxisEvent) { + let source = event.source(); + + let horizontal_amount = event + .amount(Axis::Horizontal) + .unwrap_or_else(|| event.amount_discrete(Axis::Horizontal).unwrap_or(0.0) * 3.0); + + let vertical_amount = event + .amount(Axis::Vertical) + .unwrap_or_else(|| event.amount_discrete(Axis::Vertical).unwrap_or(0.0) * 3.0); + + let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal); + let vertical_amount_discrete = event.amount_discrete(Axis::Vertical); + + let mut frame = AxisFrame::new(event.time_msec()).source(source); + + if horizontal_amount != 0.0 { + frame = frame.value(Axis::Horizontal, horizontal_amount); + if let Some(discrete) = horizontal_amount_discrete { + frame = frame.discrete(Axis::Horizontal, discrete as i32); + } + } else if source == AxisSource::Finger { + frame = frame.stop(Axis::Horizontal); + } + + if vertical_amount != 0.0 { + frame = frame.value(Axis::Vertical, vertical_amount); + if let Some(discrete) = vertical_amount_discrete { + frame = frame.discrete(Axis::Vertical, discrete as i32); + } + } else if source == AxisSource::Finger { + frame = frame.stop(Axis::Vertical); + } + + self.seat.get_pointer().unwrap().axis(self, frame); + } + + fn keyboard(&mut self, event: I::KeyboardKeyEvent) { + let serial = SERIAL_COUNTER.next_serial(); + let time = event.time_msec(); + let press_state = event.state(); + let mut move_mode = false; + let action = self.seat.get_keyboard().unwrap().input( + self, + event.key_code(), + press_state, + serial, + time, + |state, _modifiers, keysym| { + if press_state == KeyState::Pressed { + match keysym.modified_sym() { + keysyms::KEY_L => return FilterResult::Intercept(1), + keysyms::KEY_K => return FilterResult::Intercept(2), + keysyms::KEY_J => return FilterResult::Intercept(3), + keysyms::KEY_H => return FilterResult::Intercept(4), + keysyms::KEY_Escape => { + state.loop_signal.stop(); + return FilterResult::Intercept(0); + } + _ => {} + } + } + + if keysym.modified_sym() == keysyms::KEY_Control_L { + match press_state { + KeyState::Pressed => { + move_mode = true; + } + KeyState::Released => { + move_mode = false; + } + } + FilterResult::Forward + } else { + FilterResult::Forward + } + }, + ); + + self.move_mode = move_mode; + + let program = match action { + Some(1) => "alacritty", + Some(2) => "nautilus", + Some(3) => "kitty", + Some(4) => "foot", + Some(_) | None => return, + }; + + tracing::info!("Spawning {}", program); + std::process::Command::new(program) + .env("WAYLAND_DISPLAY", self.socket_name.clone()) + .spawn() + .unwrap(); + } +} impl State { - pub fn process_input_event( - &mut self, - seat: &Seat>, - event: InputEvent, - ) { + pub fn process_input_event(&mut self, event: InputEvent) { match event { // TODO: rest of input events // InputEvent::DeviceAdded { device } => todo!(), // InputEvent::DeviceRemoved { device } => todo!(), - InputEvent::Keyboard { event } => { - let serial = SERIAL_COUNTER.next_serial(); - let time = event.time_msec(); - let press_state = event.state(); - let mut move_mode = false; - let action = seat.get_keyboard().unwrap().input( - self, - event.key_code(), - press_state, - serial, - time, - |_state, _modifiers, keysym| { - if press_state == KeyState::Pressed { - match keysym.modified_sym() { - keysyms::KEY_L => return FilterResult::Intercept(1), - keysyms::KEY_K => return FilterResult::Intercept(2), - keysyms::KEY_J => return FilterResult::Intercept(3), - keysyms::KEY_H => return FilterResult::Intercept(4), - _ => {} - } - } - - if keysym.modified_sym() == keysyms::KEY_Control_L { - match press_state { - KeyState::Pressed => { - move_mode = true; - } - KeyState::Released => { - move_mode = false; - } - } - FilterResult::Forward - } else { - FilterResult::Forward - } - }, - ); - - self.move_mode = move_mode; - - std::process::Command::new(match action { - Some(1) => "alacritty", - Some(2) => "nautilus", - Some(3) => "kitty", - Some(4) => "foot", - Some(_) | None => return, - }) - .env("WAYLAND_DISPLAY", self.socket_name.clone()) - .spawn() - .unwrap(); - } + InputEvent::Keyboard { event } => self.keyboard::(event), InputEvent::PointerMotion { event } => {} - InputEvent::PointerMotionAbsolute { event } => { - let output = self.space.outputs().next().unwrap(); - let output_geo = self.space.output_geometry(output).unwrap(); - let pointer_loc = - event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); - let serial = SERIAL_COUNTER.next_serial(); - let pointer = seat.get_pointer().unwrap(); - - self.pointer_location = pointer_loc; - - let surface_under_pointer = - self.space - .element_under(pointer_loc) - .and_then(|(window, location)| { - window - .surface_under( - pointer_loc - location.to_f64(), - WindowSurfaceType::ALL, - ) - .map(|(s, p)| (s, p + location)) - }); - - pointer.motion( - self, - surface_under_pointer, - &MotionEvent { - location: pointer_loc, - serial, - time: event.time_msec(), - }, - ); - } - InputEvent::PointerButton { event } => { - let pointer = seat.get_pointer().unwrap(); - let keyboard = seat.get_keyboard().unwrap(); - - // A serial is a number sent with a event that is sent back to the - // server by the clients in further requests. This allows the server to - // keep track of which event caused which requests. It is an AtomicU32 - // that increments when next_serial is called. - let serial = SERIAL_COUNTER.next_serial(); - - // Returns which button on the pointer was used. - let button = event.button_code(); - - // The state, either released or pressed. - let button_state = event.state(); - - let pointer_loc = pointer.current_location(); - - // If the button was clicked, focus on the window below if exists, else - // unfocus on windows. - if ButtonState::Pressed == button_state { - if let Some((window, window_loc)) = self - .space - .element_under(pointer_loc) - .map(|(w, l)| (w.clone(), l)) - { - const BUTTON_LEFT: u32 = 0x110; - const BUTTON_RIGHT: u32 = 0x111; - if self.move_mode { - if event.button_code() == BUTTON_LEFT { - crate::xdg::request::move_request_force( - self, - window.toplevel(), - seat, - serial, - ); - return; // TODO: kinda ugly return here - } else if event.button_code() == BUTTON_RIGHT { - let window_geometry = window.geometry(); - let window_x = window_loc.x as f64; - let window_y = window_loc.y as f64; - let window_width = window_geometry.size.w as f64; - let window_height = window_geometry.size.h as f64; - let half_width = window_x + window_width / 2.0; - let half_height = window_y + window_height / 2.0; - let full_width = window_x + window_width; - let full_height = window_y + window_height; - - println!( - "window loc: {}, {} | window size: {}, {}", - window_x, window_y, window_width, window_height - ); - - let edges = match pointer_loc { - Point { x, y, .. } - if (window_x..=half_width).contains(&x) - && (window_y..=half_height).contains(&y) => - { - ResizeEdge::TopLeft - } - Point { x, y, .. } - if (half_width..=full_width).contains(&x) - && (window_y..=half_height).contains(&y) => - { - ResizeEdge::TopRight - } - Point { x, y, .. } - if (window_x..=half_width).contains(&x) - && (half_height..=full_height).contains(&y) => - { - ResizeEdge::BottomLeft - } - Point { x, y, .. } - if (half_width..=full_width).contains(&x) - && (half_height..=full_height).contains(&y) => - { - ResizeEdge::BottomRight - } - _ => ResizeEdge::None, - }; - - crate::xdg::request::resize_request_force( - self, - window.toplevel(), - seat, - serial, - edges, - BUTTON_RIGHT, - ); - } - } else { - // Move window to top of stack. - self.space.raise_element(&window, true); - - // Focus on window. - keyboard.set_focus( - self, - Some(window.toplevel().wl_surface().clone()), - serial, - ); - self.space.elements().for_each(|window| { - window.toplevel().send_configure(); - }); - } - } else { - self.space.elements().for_each(|window| { - window.set_activated(false); - window.toplevel().send_configure(); - }); - keyboard.set_focus(self, None, serial); - } - }; - - // Send the button event to the client. - pointer.button( - self, - &ButtonEvent { - button, - state: button_state, - serial, - time: event.time_msec(), - }, - ); - } - InputEvent::PointerAxis { event } => { - let pointer = seat.get_pointer().unwrap(); - - let source = event.source(); - - let horizontal_amount = event - .amount(Axis::Horizontal) - .unwrap_or_else(|| event.amount_discrete(Axis::Horizontal).unwrap() * 3.0); - - let vertical_amount = event - .amount(Axis::Vertical) - .unwrap_or_else(|| event.amount_discrete(Axis::Vertical).unwrap() * 3.0); - - let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal); - let vertical_amount_discrete = event.amount_discrete(Axis::Vertical); - - let mut frame = AxisFrame::new(event.time_msec()).source(source); - - if horizontal_amount != 0.0 { - frame = frame.value(Axis::Horizontal, horizontal_amount); - if let Some(discrete) = horizontal_amount_discrete { - frame = frame.discrete(Axis::Horizontal, discrete as i32); - } - } else if source == AxisSource::Finger { - frame = frame.stop(Axis::Horizontal); - } - - if vertical_amount != 0.0 { - frame = frame.value(Axis::Vertical, vertical_amount); - if let Some(discrete) = vertical_amount_discrete { - frame = frame.discrete(Axis::Vertical, discrete as i32); - } - } else if source == AxisSource::Finger { - frame = frame.stop(Axis::Vertical); - } - - pointer.axis(self, frame); - } + InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::(event), + InputEvent::PointerButton { event } => self.pointer_button::(event), + InputEvent::PointerAxis { event } => self.pointer_axis::(event), _ => (), } } + + fn pointer_motion_absolute(&mut self, event: I::PointerMotionAbsoluteEvent) { + let output = self.space.outputs().next().unwrap(); + let output_geo = self.space.output_geometry(output).unwrap(); + let pointer_loc = event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); + let serial = SERIAL_COUNTER.next_serial(); + let pointer = self.seat.get_pointer().unwrap(); + + // tracing::info!("pointer_loc: {:?}", pointer_loc); + + self.pointer_location = pointer_loc; + + let surface_under_pointer = + self.space + .element_under(pointer_loc) + .and_then(|(window, location)| { + window + .surface_under(pointer_loc - location.to_f64(), WindowSurfaceType::ALL) + .map(|(s, p)| (s, p + location)) + }); + + pointer.motion( + self, + surface_under_pointer, + &MotionEvent { + location: pointer_loc, + serial, + time: event.time_msec(), + }, + ); + } +} + +impl State { + pub fn process_input_event(&mut self, event: InputEvent) { + match event { + // TODO: rest of input events + + // InputEvent::DeviceAdded { device } => todo!(), + // InputEvent::DeviceRemoved { device } => todo!(), + InputEvent::Keyboard { event } => self.keyboard::(event), + InputEvent::PointerMotion { event } => self.pointer_motion::(event), + InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::(event), + InputEvent::PointerButton { event } => self.pointer_button::(event), + InputEvent::PointerAxis { event } => self.pointer_axis::(event), + + _ => (), + } + } + + fn pointer_motion(&mut self, event: I::PointerMotionEvent) { + let serial = SERIAL_COUNTER.next_serial(); + self.pointer_location += event.delta(); + + // clamp to screen limits + // this event is never generated by winit + self.pointer_location = self.clamp_coords(self.pointer_location); + + // tracing::info!("{:?}", self.pointer_location); + if let Some(ptr) = self.seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: self.pointer_location, + serial, + time: event.time_msec(), + }, + ); + + // ptr.relative_motion( + // self, + // under, + // &RelativeMotionEvent { + // delta: event.delta(), + // delta_unaccel: event.delta_unaccel(), + // utime: event.time(), + // }, + // ) + } + } + + fn pointer_motion_absolute(&mut self, event: I::PointerMotionAbsoluteEvent) { + let serial = SERIAL_COUNTER.next_serial(); + + let max_x = self.space.outputs().fold(0, |acc, o| { + acc + self.space.output_geometry(o).unwrap().size.w + }); + + let max_h_output = self + .space + .outputs() + .max_by_key(|o| self.space.output_geometry(o).unwrap().size.h) + .unwrap(); + + let max_y = self.space.output_geometry(max_h_output).unwrap().size.h; + + self.pointer_location.x = event.x_transformed(max_x); + self.pointer_location.y = event.y_transformed(max_y); + + // clamp to screen limits + self.pointer_location = self.clamp_coords(self.pointer_location); + + // tracing::info!("{:?}", self.pointer_location); + + // let under = self.surface_under(); + if let Some(ptr) = self.seat.get_pointer() { + ptr.motion( + self, + None, + &MotionEvent { + location: self.pointer_location, + serial, + time: event.time_msec(), + }, + ); + } + } + + fn clamp_coords(&self, pos: Point) -> Point { + if self.space.outputs().next().is_none() { + return pos; + } + + let (pos_x, pos_y) = pos.into(); + let max_x = self.space.outputs().fold(0, |acc, o| { + acc + self.space.output_geometry(o).unwrap().size.w + }); + let clamped_x = pos_x.clamp(0.0, max_x as f64); + let max_y = self + .space + .outputs() + .find(|o| { + let geo = self.space.output_geometry(o).unwrap(); + geo.contains((clamped_x as i32, 0)) + }) + .map(|o| self.space.output_geometry(o).unwrap().size.h); + + if let Some(max_y) = max_y { + let clamped_y = pos_y.clamp(0.0, max_y as f64); + (clamped_x, clamped_y).into() + } else { + (clamped_x, pos_y).into() + } + } } diff --git a/src/layout/manual.rs b/src/layout/manual.rs index e69de29..8b13789 100644 --- a/src/layout/manual.rs +++ b/src/layout/manual.rs @@ -0,0 +1 @@ + diff --git a/src/main.rs b/src/main.rs index 9b2b59d..835be08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod backend; +mod cursor; mod grab; mod handlers; mod input; @@ -25,6 +26,7 @@ fn main() -> Result<(), Box> { } tracing::info!("Starting winit backend"); - crate::backend::winit::run_winit()?; + // crate::backend::winit::run_winit()?; + crate::backend::udev::run_udev()?; Ok(()) } diff --git a/src/state.rs b/src/state.rs index 83d87af..8dd53dc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,10 +1,18 @@ -use std::ffi::OsString; +use std::{error::Error, os::fd::AsRawFd, sync::Arc}; use smithay::{ - desktop::{PopupManager, Space, Window}, - input::{pointer::CursorImageStatus, SeatState}, + backend::renderer::element::RenderElementStates, + desktop::{ + utils::{ + surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, + OutputPresentationFeedback, + }, + PopupManager, Space, Window, + }, + input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState}, + output::Output, reexports::{ - calloop::{LoopHandle, LoopSignal}, + calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction}, wayland_server::{ backend::{ClientData, ClientId, DisconnectReason}, protocol::wl_surface::WlSurface, @@ -15,27 +23,31 @@ use smithay::{ wayland::{ compositor::{CompositorClientState, CompositorState}, data_device::DataDeviceState, + dmabuf::DmabufFeedback, fractional_scale::FractionalScaleManagerState, output::OutputManagerState, seat::WaylandFocus, shell::xdg::XdgShellState, shm::ShmState, + socket::ListeningSocketSource, viewporter::ViewporterState, }, }; -use crate::backend::{winit::WinitData, Backend}; +use crate::backend::Backend; pub struct State { pub backend_data: B, pub loop_signal: LoopSignal, - pub loop_handle: LoopHandle<'static, CalloopData>, + pub loop_handle: LoopHandle<'static, CalloopData>, pub clock: Clock, pub space: Space, pub move_mode: bool, - pub socket_name: OsString, + pub socket_name: String, + + pub seat: Seat>, pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, @@ -53,6 +65,72 @@ pub struct State { } impl State { + pub fn init( + backend_data: B, + display: &mut Display>, + loop_signal: LoopSignal, + loop_handle: LoopHandle<'static, CalloopData>, + ) -> Result> { + let socket = ListeningSocketSource::new_auto()?; + let socket_name = socket.socket_name().to_os_string(); + + std::env::set_var("WAYLAND_DISPLAY", socket_name.clone()); + + loop_handle.insert_source(socket, |stream, _metadata, data| { + data.display + .handle() + .insert_client(stream, Arc::new(ClientState::default())) + .unwrap(); + })?; + + loop_handle.insert_source( + Generic::new( + display.backend().poll_fd().as_raw_fd(), + Interest::READ, + Mode::Level, + ), + |_readiness, _metadata, data| { + data.display.dispatch_clients(&mut data.state)?; + Ok(PostAction::Continue) + }, + )?; + + let display_handle = display.handle(); + let mut seat_state = SeatState::new(); + let mut seat = seat_state.new_wl_seat(&display_handle, backend_data.seat_name()); + seat.add_pointer(); + seat.add_keyboard(XkbConfig::default(), 200, 25)?; + + Ok(Self { + backend_data, + loop_signal, + loop_handle, + clock: Clock::::new()?, + compositor_state: CompositorState::new::>(&display_handle), + data_device_state: DataDeviceState::new::>(&display_handle), + seat_state, + pointer_location: (0.0, 0.0).into(), + shm_state: ShmState::new::>(&display_handle, vec![]), + space: Space::::default(), + cursor_status: CursorImageStatus::Default, + output_manager_state: OutputManagerState::new_with_xdg_output::>( + &display_handle, + ), + xdg_shell_state: XdgShellState::new::>(&display_handle), + viewporter_state: ViewporterState::new::>(&display_handle), + fractional_scale_manager_state: FractionalScaleManagerState::new::>( + &display_handle, + ), + + seat, + + move_mode: false, + socket_name: socket_name.to_string_lossy().to_string(), + + popup_manager: PopupManager::default(), + }) + } + /// Returns the [Window] associated with a given [WlSurface]. pub fn window_for_surface(&self, surface: &WlSurface) -> Option { self.space @@ -62,9 +140,9 @@ impl State { } } -pub struct CalloopData { - pub display: Display>, - pub state: State, +pub struct CalloopData { + pub display: Display>, + pub state: State, } #[derive(Default)] @@ -78,3 +156,41 @@ impl ClientData for ClientState { // fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {} } + +#[derive(Debug, Copy, Clone)] +pub struct SurfaceDmabufFeedback<'a> { + pub render_feedback: &'a DmabufFeedback, + pub scanout_feedback: &'a DmabufFeedback, +} + +pub fn take_presentation_feedback( + output: &Output, + space: &Space, + render_element_states: &RenderElementStates, +) -> OutputPresentationFeedback { + let mut output_presentation_feedback = OutputPresentationFeedback::new(output); + + space.elements().for_each(|window| { + if space.outputs_for_element(window).contains(output) { + window.take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + } + }); + let map = smithay::desktop::layer_map_for_output(output); + for layer_surface in map.layers() { + layer_surface.take_presentation_feedback( + &mut output_presentation_feedback, + surface_primary_scanout_output, + |surface, _| { + surface_presentation_feedback_flags_from_states(surface, render_element_states) + }, + ); + } + + output_presentation_feedback +}