diff --git a/Cargo.toml b/Cargo.toml index 0c414a9..ce25820 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,6 @@ edition = "2021" tracing = "0.1.37" 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 } diff --git a/src/backend.rs b/src/backend.rs index 1467f10..65a2aef 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,5 +1,6 @@ use smithay::{output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface}; +pub mod udev; pub mod winit; /// A trait defining common methods for each available backend: winit and tty-udev diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 22666a2..9282026 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -1,4 +1,9 @@ -use std::{error::Error, os::fd::AsRawFd, sync::Arc, time::Duration}; +use std::{ + error::Error, + os::fd::AsRawFd, + sync::{Arc, Mutex}, + time::Duration, +}; use smithay::{ backend::{ @@ -6,8 +11,11 @@ use smithay::{ egl::EGLDevice, renderer::{ damage::{self, OutputDamageTracker}, - element::default_primary_scanout_output_compare, - gles::GlesRenderer, + element::{ + default_primary_scanout_output_compare, surface::WaylandSurfaceRenderElement, + AsRenderElements, + }, + gles::{GlesRenderer, GlesTexture}, ImportDma, ImportMemWl, }, winit::{WinitError, WinitEvent, WinitGraphicsBackend}, @@ -16,9 +24,12 @@ use smithay::{ desktop::{ space, utils::{surface_primary_scanout_output, update_surface_primary_scanout_output}, - Space, Window, + PopupManager, Space, Window, + }, + input::{ + pointer::{CursorImageAttributes, CursorImageStatus}, + SeatState, }, - input::{pointer::CursorImageStatus, SeatState}, output::{Output, Subpixel}, reexports::{ calloop::{ @@ -28,9 +39,9 @@ use smithay::{ }, wayland_server::{protocol::wl_surface::WlSurface, Display}, }, - utils::{Clock, Monotonic, Physical, Point, Scale, Transform}, + utils::{Clock, IsAlive, Monotonic, Physical, Point, Scale, Transform}, wayland::{ - compositor::CompositorState, + compositor::{self, CompositorState}, data_device::DataDeviceState, dmabuf::{ DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, @@ -45,7 +56,11 @@ use smithay::{ }, }; -use crate::state::{CalloopData, ClientState, State}; +use crate::{ + layout::{Direction, Layout}, + render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements}, + state::{CalloopData, ClientState, State}, +}; use super::Backend; @@ -214,10 +229,10 @@ pub fn run_winit() -> Result<(), Box> { compositor_state: CompositorState::new::>(&display_handle), data_device_state: DataDeviceState::new::>(&display_handle), seat_state, - shm_state: ShmState::new::>(&display_handle, Vec::new()), + pointer_location: (0.0, 0.0).into(), + shm_state: ShmState::new::>(&display_handle, vec![]), space: Space::::default(), cursor_status: CursorImageStatus::Default, - pointer_location: (0.0, 0.0).into(), output_manager_state: OutputManagerState::new_with_xdg_output::>( &display_handle, ), @@ -229,6 +244,8 @@ pub fn run_winit() -> Result<(), Box> { move_mode: false, socket_name: socket_name.clone(), + + popup_manager: PopupManager::default(), }; state @@ -239,6 +256,8 @@ pub fn run_winit() -> Result<(), Box> { 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; @@ -258,6 +277,11 @@ pub fn run_winit() -> Result<(), Box> { None, None, ); + Layout::master_stack( + state, + state.space.elements().cloned().collect(), + Direction::Left, + ); } WinitEvent::Focus(_) => {} WinitEvent::Input(input_evt) => { @@ -273,9 +297,50 @@ pub fn run_winit() -> Result<(), Box> { } }; + 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 @@ -286,9 +351,24 @@ pub fn run_winit() -> Result<(), Box> { let renderer = state.backend_data.backend.renderer(); // render_output() - let output_render_elements = + 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 @@ -307,6 +387,13 @@ pub fn run_winit() -> Result<(), Box> { tracing::warn!("{}", err); } } + + state + .backend_data + .backend + .window() + .set_cursor_visible(cursor_visible); + let throttle = Some(Duration::from_secs(1)); state.space.elements().for_each(|window| { @@ -347,13 +434,11 @@ pub fn run_winit() -> Result<(), Box> { } } - let scale = Scale::from(output.current_scale().fractional_scale()); - let cursor_pos = state.pointer_location; - let _cursor_pos_scaled: Point = cursor_pos.to_physical(scale).to_i32_round(); - state.space.refresh(); - - display.flush_clients().unwrap(); + state.popup_manager.cleanup(); + display + .flush_clients() + .expect("failed to flush client buffers"); TimeoutAction::ToDuration(Duration::from_millis(6)) })?; diff --git a/src/handlers.rs b/src/handlers.rs index a8bc4be..47754fe 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -2,8 +2,14 @@ use smithay::{ backend::renderer::utils, delegate_compositor, delegate_data_device, delegate_fractional_scale, delegate_output, delegate_seat, delegate_shm, delegate_viewporter, delegate_xdg_shell, - desktop::Window, - input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState}, + desktop::{ + find_popup_root_surface, PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, + PopupUngrabStrategy, Space, Window, + }, + input::{ + pointer::{CursorImageStatus, Focus}, + Seat, SeatHandler, SeatState, + }, reexports::{ calloop::Interest, wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, @@ -25,8 +31,8 @@ use smithay::{ dmabuf, fractional_scale::{self, FractionalScaleHandler}, shell::xdg::{ - Configure, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, - XdgShellState, XdgToplevelSurfaceData, + Configure, PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, + XdgShellHandler, XdgShellState, XdgToplevelSurfaceData, }, shm::{ShmHandler, ShmState}, }, @@ -93,27 +99,9 @@ impl CompositorHandler for State { } }; - if let Some(window) = self.window_for_surface(surface) { - let initial_configure_sent = compositor::with_states(surface, |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - // println!("initial_configure_sent is {}", initial_configure_sent); + self.popup_manager.commit(surface); - if !initial_configure_sent { - println!("initial configure"); - window.toplevel().send_configure(); - // println!( - // "ensured_configured: {}", - // window.toplevel().ensure_configured() - // ); - } - } + ensure_initial_configure(surface, self); crate::grab::resize_grab::handle_commit(self, surface); } @@ -123,6 +111,44 @@ impl CompositorHandler for State { } } delegate_compositor!(@ State); +fn ensure_initial_configure(surface: &WlSurface, state: &mut State) { + if let Some(window) = state.window_for_surface(surface) { + let initial_configure_sent = compositor::with_states(surface, |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + // println!("initial_configure_sent is {}", initial_configure_sent); + + if !initial_configure_sent { + window.toplevel().send_configure(); + } + return; + } + + if let Some(popup) = state.popup_manager.find_popup(surface) { + let PopupKind::Xdg(ref popup) = popup; + let initial_configure_sent = compositor::with_states(surface, |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + if !initial_configure_sent { + popup + .send_configure() + .expect("popup initial configure failed"); + } + } + // TODO: layer map thingys +} impl ClientDndGrabHandler for State {} impl ServerDndGrabHandler for State {} @@ -136,7 +162,7 @@ impl DataDeviceHandler for State { } delegate_data_device!(@ State); -impl SeatHandler for State { +impl SeatHandler for State { type KeyboardFocus = WlSurface; type PointerFocus = WlSurface; @@ -145,6 +171,7 @@ impl SeatHandler for State { } fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { + tracing::info!("new cursor image: {:?}", image); self.cursor_status = image; } @@ -177,7 +204,11 @@ impl XdgShellHandler for State { Layout::master_stack(self, windows, crate::layout::Direction::Left); } - fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {} + fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { + if let Err(err) = self.popup_manager.track_popup(PopupKind::from(surface)) { + tracing::warn!("failed to track popup: {}", err); + } + } fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { crate::xdg::request::move_request( @@ -206,7 +237,58 @@ impl XdgShellHandler for State { ); } - fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) {} + fn reposition_request( + &mut self, + surface: PopupSurface, + positioner: PositionerState, + token: u32, + ) { + surface.with_pending_state(|state| { + state.geometry = positioner.get_geometry(); + state.positioner = positioner; + }); + surface.send_repositioned(token); + } + + fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) { + let seat: Seat> = Seat::from_resource(&seat).unwrap(); + let popup_kind = PopupKind::Xdg(surface); + if let Some(root) = find_popup_root_surface(&popup_kind) + .ok() + .and_then(|root| self.window_for_surface(&root)) + { + if let Ok(mut grab) = self.popup_manager.grab_popup( + root.toplevel().wl_surface().clone(), + popup_kind, + &seat, + serial, + ) { + if let Some(keyboard) = seat.get_keyboard() { + if keyboard.is_grabbed() + && !(keyboard.has_grab(serial) + || keyboard.has_grab(grab.previous_serial().unwrap_or(serial))) + { + grab.ungrab(PopupUngrabStrategy::All); + return; + } + + keyboard.set_focus(self, grab.current_grab(), serial); + keyboard.set_grab(PopupKeyboardGrab::new(&grab), serial); + } + if let Some(pointer) = seat.get_pointer() { + if pointer.is_grabbed() + && !(pointer.has_grab(serial) + || pointer + .has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial()))) + { + grab.ungrab(PopupUngrabStrategy::All); + return; + } + pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep); + } + } + } + } fn ack_configure(&mut self, surface: WlSurface, configure: Configure) { // println!("surface ack_configure: {:?}", configure); diff --git a/src/input.rs b/src/input.rs index 450374f..cf7747e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -86,6 +86,8 @@ impl State { 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) @@ -268,7 +270,6 @@ impl State { frame = frame.stop(Axis::Vertical); } - println!("axisframe: {:?}", frame); pointer.axis(self, frame); } diff --git a/src/main.rs b/src/main.rs index 55b0f0c..9f500c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod handlers; mod input; mod layout; mod pointer; -mod shell; +mod render; mod state; mod tag; mod window; diff --git a/src/state.rs b/src/state.rs index 2b41177..83d87af 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use smithay::{ - desktop::{Space, Window}, + desktop::{PopupManager, Space, Window}, input::{pointer::CursorImageStatus, SeatState}, reexports::{ calloop::{LoopHandle, LoopSignal}, @@ -28,23 +28,28 @@ use crate::backend::{winit::WinitData, Backend}; pub struct State { pub backend_data: B, + pub loop_signal: LoopSignal, pub loop_handle: LoopHandle<'static, CalloopData>, pub clock: Clock, + + pub space: Space, + pub move_mode: bool, + pub socket_name: OsString, + pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub seat_state: SeatState, pub shm_state: ShmState, - pub space: Space, - pub cursor_status: CursorImageStatus, - pub pointer_location: Point, pub output_manager_state: OutputManagerState, pub xdg_shell_state: XdgShellState, pub viewporter_state: ViewporterState, pub fractional_scale_manager_state: FractionalScaleManagerState, - pub move_mode: bool, - pub socket_name: OsString, + pub popup_manager: PopupManager, + + pub cursor_status: CursorImageStatus, + pub pointer_location: Point, } impl State {