diff --git a/Cargo.lock b/Cargo.lock index 6a4eb73a..d28e09d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -592,7 +592,7 @@ checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/Smithay/smithay.git?rev=cb0bc33#cb0bc3379faf183087c90355edd80d1bb5e33841" +source = "git+https://github.com/Smithay/smithay.git?rev=7e2affd#7e2affdf3106b22cb17ef48c087d3f0229a0abdf" dependencies = [ "appendlist", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 2ae351ef..0a182b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,6 @@ slog-stdlog = "4.1" [dependencies.smithay] version = "0.3" git = "https://github.com/Smithay/smithay.git" -rev = "cb0bc33" +rev = "7e2affd" default-features = false features = ["backend_x11", "backend_egl", "desktop", "use_system_lib", "renderer_gl", "wayland_frontend"] \ No newline at end of file diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 00000000..ed03f897 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::State; +use smithay::{ + backend::input::{Device, DeviceCapability, InputBackend, InputEvent}, + reexports::wayland_server::Display, + wayland::{ + data_device::set_data_device_focus, + output::Output, + seat::{CursorImageStatus, Keysym, Seat, XkbConfig}, + }, +}; +use std::{cell::RefCell, collections::HashMap}; + +pub struct ActiveOutput(pub RefCell); + +pub fn add_seat(display: &mut Display, name: String) -> Seat { + let (seat, _) = Seat::new(display, name, None); + seat +} + +pub fn active_output(seat: &Seat, state: &State) -> Output { + seat.user_data() + .get::() + .map(|x| x.0.borrow().clone()) + .unwrap_or_else(|| { + state + .spaces + .outputs() + .next() + .cloned() + .expect("Backend has no outputs?") + }) +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index c735ede3..e9fd11b8 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,3 +1,214 @@ // SPDX-License-Identifier: GPL-3.0-only +use crate::{input::active_output, state::State}; +use smithay::{ + backend::renderer::utils::on_commit_buffer_handler, + desktop::{layer_map_for_output, Kind, LayerSurface, PopupKind, PopupManager, Window}, + reexports::{ + wayland_protocols::xdg_shell::server::xdg_toplevel, + wayland_server::{protocol::wl_surface::WlSurface, Display}, + }, + wayland::{ + compositor::{compositor_init, with_states}, + output::Output, + shell::{ + wlr_layer::{wlr_layer_shell_init, LayerShellRequest, LayerShellState}, + xdg::{ + xdg_shell_init, Configure, ShellState as XdgShellState, XdgRequest, + XdgToplevelSurfaceRoleAttributes, + }, + }, + }, +}; +use std::sync::{Arc, Mutex}; + pub mod workspaces; + +pub struct ShellStates { + popups: PopupManager, + xdg: Arc>, + layer: Arc>, +} + +pub fn init_shell(display: &mut Display) -> ShellStates { + compositor_init( + display, + move |surface, mut ddata| { + on_commit_buffer_handler(&surface); + let state = ddata.get::().unwrap(); + state.spaces.commit(&surface); + state.shell.popups.commit(&surface); + commit(&surface, state) + }, + None, + ); + + let (xdg_shell_state, _xdg_global) = xdg_shell_init( + display, + |event, mut ddata| { + let state = ddata.get::().unwrap(); + + match event { + XdgRequest::NewToplevel { surface } => { + state.pending_toplevels.push(surface.clone()); + + let seat = &state.last_active_seat; + let output = active_output(seat, &state); + let space = state.spaces.active_space_mut(&output); + let window = Window::new(Kind::Xdg(surface)); + space.map_window(&window, (0, 0)); + // We will position the window after the first commit, when we know its size + } + XdgRequest::NewPopup { surface, .. } => { + state + .shell + .popups + .track_popup(PopupKind::from(surface)) + .unwrap(); + } + XdgRequest::RePosition { + surface, + positioner, + token, + } => { + let result = surface.with_pending_state(|state| { + // TODO: This is a simplification, a proper compositor would + // calculate the geometry of the popup here. + // For now we just use the default implementation here that does not take the + // window position and output constraints into account. + let geometry = positioner.get_geometry(); + state.geometry = geometry; + state.positioner = positioner; + }); + + if result.is_ok() { + surface.send_repositioned(token); + } + } + /* + XdgRequest::AckConfigure { surface, configure: Configure::Toplevel(configure) } => { + + }, + */ + XdgRequest::Maximize { surface } => { + let seat = &state.last_active_seat; + let output = active_output(seat, &state); + let space = state.spaces.active_space_mut(&output); + let window = space + .window_for_surface(surface.get_surface().unwrap()) + .unwrap() + .clone(); + let layers = layer_map_for_output(&output); + let geometry = layers.non_exclusive_zone(); + + space.map_window(&window, geometry.loc); + let ret = surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Maximized); + state.size = Some(geometry.size); + }); + + if ret.is_ok() { + window.configure(); + } + } + XdgRequest::UnMaximize { surface } => { + let ret = surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Maximized); + state.size = None; + }); + + if ret.is_ok() { + surface.send_configure(); + } + } + _ => { /*TODO*/ } + } + }, + None, + ); + + let (layer_shell_state, _layer_global) = wlr_layer_shell_init( + display, + |event, mut ddata| match event { + LayerShellRequest::NewLayerSurface { + surface, + output: wl_output, + namespace, + .. + } => { + let state = ddata.get::().unwrap(); + let seat = &state.last_active_seat; + let output = wl_output + .as_ref() + .and_then(Output::from_resource) + .unwrap_or_else(|| active_output(seat, &state)); + + let mut map = layer_map_for_output(&output); + map.map_layer(&LayerSurface::new(surface, namespace)) + .unwrap(); + } + _ => {} + }, + None, + ); + + ShellStates { + popups: PopupManager::new(), + xdg: xdg_shell_state, + layer: layer_shell_state, + } +} + +fn commit(surface: &WlSurface, state: &mut State) { + if let Some(toplevel) = state.pending_toplevels.iter().find(|toplevel| { + toplevel + .get_surface() + .map(|s| s == surface) + .unwrap_or(false) + }) { + // send the initial configure if relevant + let initial_configure_sent = with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }) + .unwrap(); + if !initial_configure_sent { + toplevel.send_configure(); + } + + // position our new window + if let Some(space) = toplevel + .get_surface() + .and_then(|surface| state.spaces.space_for_surface_mut(surface)) + { + let window = space + .window_for_surface(toplevel.get_surface().unwrap()) + .unwrap() + .clone(); + if let Some(output) = space.outputs_for_window(&window).iter().next() { + let win_geo = window.bbox(); + if win_geo.size.w > 0 && win_geo.size.h > 0 { + let layers = layer_map_for_output(&output); + let geometry = layers.non_exclusive_zone(); + + let position = ( + geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2), + geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2), + ); + space.map_window(&window, position); + state.pending_toplevels.retain(|toplevel| { + toplevel + .get_surface() + .map(|s| s != surface) + .unwrap_or(false) + }); + } + } + } + } +} diff --git a/src/shell/workspaces.rs b/src/shell/workspaces.rs index 4167f340..5b0392c3 100644 --- a/src/shell/workspaces.rs +++ b/src/shell/workspaces.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only -pub use smithay::{desktop::Space, wayland::output::Output}; +pub use smithay::{ + desktop::Space, reexports::wayland_server::protocol::wl_surface::WlSurface, + wayland::output::Output, +}; use std::{cell::Cell, mem::MaybeUninit}; const MAX_WORKSPACES: usize = 10; // TODO? @@ -224,9 +227,27 @@ impl Workspaces { } } + pub fn space_for_surface(&self, surface: &WlSurface) -> Option<&Space> { + self.spaces + .iter() + .find(|space| space.window_for_surface(surface).is_some()) + } + + pub fn space_for_surface_mut(&mut self, surface: &WlSurface) -> Option<&mut Space> { + self.spaces + .iter_mut() + .find(|space| space.window_for_surface(surface).is_some()) + } + pub fn refresh(&mut self) { for space in &mut self.spaces { space.refresh() } } + + pub fn commit(&mut self, surface: &WlSurface) { + for space in &mut self.spaces { + space.commit(surface) + } + } } diff --git a/src/state.rs b/src/state.rs index 71e51340..fc0df49d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,12 +1,30 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{backend::x11::X11State, shell::workspaces::Workspaces}; -use smithay::reexports::wayland_server::Display; +use crate::{ + backend::x11::X11State, + shell::{init_shell, workspaces::Workspaces, ShellStates}, +}; +use smithay::{ + reexports::wayland_server::Display, + wayland::{ + data_device::{default_action_chooser, init_data_device}, + output::xdg::init_xdg_output_manager, + seat::Seat, + shell::xdg::ToplevelSurface, + shm::init_shm_global, + }, +}; use std::{cell::RefCell, rc::Rc, time::Instant}; pub struct State { pub display: Rc>, + pub spaces: Workspaces, + pub shell: ShellStates, + pub pending_toplevels: Vec, + + pub seats: Vec, + pub last_active_seat: Seat, pub start_time: Instant, pub should_stop: bool, @@ -31,10 +49,27 @@ impl BackendData { } impl State { - pub fn new(display: Display) -> State { + pub fn new(mut display: Display) -> State { + init_shm_global(&mut display, vec![], None); + init_xdg_output_manager(&mut display, None); + let shell_handles = init_shell(&mut display); + let initial_seat = crate::input::add_seat(&mut display, "seat-0".into()); + init_data_device( + &mut display, + |_dnd_event| { /* TODO */ }, + default_action_chooser, + None, + ); + State { display: Rc::new(RefCell::new(display)), + spaces: Workspaces::new(), + shell: shell_handles, + pending_toplevels: Vec::new(), + + seats: vec![initial_seat.clone()], + last_active_seat: initial_seat, start_time: Instant::now(), should_stop: false,