diff --git a/Cargo.lock b/Cargo.lock index 557b6fc1..226eab0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,8 @@ dependencies = [ "bitflags", "edid-rs", "egui", + "id_tree", + "indexmap", "serde", "serde_json", "slog", @@ -564,9 +566,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", @@ -605,6 +607,15 @@ dependencies = [ "libc", ] +[[package]] +name = "id_tree" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd9db8dd5be8bde5a2624ed4b2dfb74368fe7999eb9c4940fd3ca344b61071a" +dependencies = [ + "snowflake", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -742,9 +753,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if 1.0.0", ] @@ -1048,9 +1059,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" dependencies = [ "proc-macro2", ] @@ -1096,9 +1107,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" dependencies = [ "bitflags", ] @@ -1207,9 +1218,9 @@ dependencies = [ [[package]] name = "slog-stdlog" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8228ab7302adbf4fcb37e66f3cda78003feb521e7fd9e3847ec117a7784d0f5a" +checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" dependencies = [ "log", "slog", @@ -1238,7 +1249,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/pop-os/smithay?branch=main#7e592376ba708c8c1881f08b6b7dd03bfb855d16" +source = "git+https://github.com/Smithay/smithay.git?rev=bff3f51c#bff3f51cda0789ae4d5585b2ff2eb568b66c8f16" dependencies = [ "appendlist", "bitflags", @@ -1297,7 +1308,7 @@ dependencies = [ [[package]] name = "smithay-egui" version = "0.1.0" -source = "git+https://github.com/Smithay/smithay-egui.git?rev=5cc416ce#5cc416ce2def8737816b3c69c170d9e01581f22c" +source = "git+https://github.com/Smithay/smithay-egui.git?rev=f49044b3#f49044b3ea193eb43087a6884d31071684919f1b" dependencies = [ "cgmath", "egui", @@ -1308,6 +1319,12 @@ dependencies = [ "xkbcommon", ] +[[package]] +name = "snowflake" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1" + [[package]] name = "strsim" version = "0.10.0" @@ -1387,9 +1404,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "itoa", "libc", @@ -1399,9 +1416,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "toml" diff --git a/Cargo.toml b/Cargo.toml index a8603fa1..25d9e165 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,17 +19,19 @@ egui = { version = "0.16", optional = true } edid-rs = { version = "0.1" } thiserror = "1.0.26" xcursor = "0.3.3" +id_tree = "1.8.0" +indexmap = "1.8.0" [dependencies.smithay] version = "0.3" git = "https://github.com/Smithay/smithay.git" -rev = "1151eea4" +rev = "bff3f51c" default-features = false features = ["backend_drm", "backend_gbm", "backend_egl", "backend_libinput", "backend_session_libseat", "backend_udev", "backend_winit", "backend_x11", "desktop", "use_system_lib", "renderer_gl", "renderer_multi", "wayland_frontend", "slog-stdlog"] [dependencies.smithay-egui] git = "https://github.com/Smithay/smithay-egui.git" -rev = "5cc416ce" +rev = "f49044b3" optional = true [build-dependencies] @@ -49,5 +51,5 @@ debug = true [profile.release] lto = "fat" -[patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/pop-os/smithay", branch = "main" } \ No newline at end of file +#[patch."https://github.com/Smithay/smithay.git"] +#smithay = { git = "https://github.com/pop-os/smithay", branch = "main" } \ No newline at end of file diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 1641af89..cf0f6cca 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -102,7 +102,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .handle() .insert_source(libinput_backend, move |event, _, state| { state.common.process_input_event(event); - for output in state.common.spaces.outputs() { + for output in state.common.shell.outputs() { state.backend.kms().schedule_render(output); } }) @@ -246,8 +246,9 @@ impl State { surface.pending = false; state .common - .spaces + .shell .active_space_mut(&surface.output) + .space .send_frames( state.common.start_time.elapsed().as_millis() as u32 ); @@ -300,7 +301,7 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.spaces.map_output(&output), + Ok(output) => self.common.shell.map_output(&output), Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } @@ -319,7 +320,7 @@ impl State { if let Some(token) = surface.render_timer_token.take() { self.common.event_loop_handle.remove(token); } - self.common.spaces.unmap_output(&surface.output); + self.common.shell.unmap_output(&surface.output); } } for (crtc, conn) in changes.added { @@ -331,7 +332,7 @@ impl State { &mut self.common.display.borrow_mut(), &mut self.common.event_loop_handle, ) { - Ok(output) => self.common.spaces.map_output(&output), + Ok(output) => self.common.shell.map_output(&output), Err(err) => slog_scope::warn!("Failed to initialize output: {}", err), }; } @@ -346,7 +347,7 @@ impl State { if let Some(token) = surface.render_timer_token.take() { self.common.event_loop_handle.remove(token); } - self.common.spaces.unmap_output(&surface.output); + self.common.shell.unmap_output(&surface.output); } if let Some(token) = device.event_token.take() { self.common.event_loop_handle.remove(token); @@ -504,8 +505,9 @@ impl Surface { state: &mut Common, ) -> Result<()> { let nodes = state - .spaces + .shell .active_space(&self.output) + .space .windows() .flat_map(|w| { w.toplevel() diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 86aa66f9..9ae94ce8 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -120,17 +120,18 @@ where #[cfg(feature = "debug")] { - let space = state.spaces.active_space(output); - let output_geo = space + let workspace = state.shell.active_space(output); + let output_geo = workspace + .space .output_geometry(output) .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))); - let scale = space.output_scale(output).unwrap(); + let scale = workspace.space.output_scale(output).unwrap(); let fps_overlay = fps_ui(_gpu, state, fps, output_geo, scale); custom_elements.push(fps_overlay.into()); - let mut area = state.spaces.global_space(); - area.loc = state.spaces.space_relative_output_geometry((0, 0), output); + let mut area = state.shell.global_space(); + area.loc = state.shell.space_relative_output_geometry((0, 0), output); if let Some(log_ui) = log_ui(state, area, scale, output_geo.size.w as f32 * 0.6) { custom_elements.push(log_ui.into()); } @@ -145,7 +146,7 @@ where None => continue, }; let location = state - .spaces + .shell .space_relative_output_geometry(pointer.current_location().to_i32_round(), output); if let Some(cursor) = cursor::draw_cursor( @@ -159,7 +160,7 @@ where } } - let res = state.spaces.active_space_mut(output).render_output( + let res = state.shell.active_space_mut(output).space.render_output( renderer, &output, age as usize, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index ade507db..22a96c74 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -56,8 +56,9 @@ impl WinitState { ) { Ok(damage) => { state - .spaces + .shell .active_space_mut(&self.output) + .space .send_frames(state.start_time.elapsed().as_millis() as u32); backend .submit(damage.as_ref().map(|x| &**x), 1.0) @@ -101,7 +102,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res ); output.set_preferred(mode); - state.common.spaces.map_output(&output); + state.common.shell.map_output(&output); let (event_ping, event_source) = ping::make_ping().with_context(|| "Failed to init eventloop timer for winit")?; @@ -133,7 +134,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res } Err(winit::WinitError::WindowClosed) => { let winit_state = state.backend.winit(); - state.common.spaces.unmap_output(&winit_state.output); + state.common.shell.unmap_output(&winit_state.output); if let Some(token) = token.take() { event_loop_handle.remove(token); } diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 040a6d4f..e9a297ca 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -174,8 +174,9 @@ impl Surface { ) { Ok(_) => { state - .spaces + .shell .active_space_mut(&self.output) + .space .send_frames(state.start_time.elapsed().as_millis() as u32); self.surface .submit() @@ -228,7 +229,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .x11() .add_window(&mut *state.common.display.borrow_mut(), event_loop.handle()) .with_context(|| "Failed to create wl_output")?; - state.common.spaces.map_output(&output); + state.common.shell.map_output(&output); event_loop .handle() @@ -243,7 +244,7 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res .filter(|s| s.window.id() == window_id) { surface.window.unmap(); - state.common.spaces.unmap_output(&surface.output); + state.common.shell.unmap_output(&surface.output); } state .backend @@ -353,7 +354,7 @@ impl State { self.common.process_input_event(event); // TODO actually figure out the output - for output in self.common.spaces.outputs() { + for output in self.common.shell.outputs() { self.backend.schedule_render(output); } } diff --git a/src/debug.rs b/src/debug.rs index ac878f9e..82d23aae 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -110,14 +110,14 @@ pub fn debug_ui( .vscroll(true) .collapsible(true) .show(ctx, |ui| { - use crate::shell::workspaces::{ActiveWorkspace, Mode, MAX_WORKSPACES}; + use crate::shell::{ActiveWorkspace, Mode, MAX_WORKSPACES}; ui.set_min_width(250.0); // Mode ui.label(egui::RichText::new("Mode").heading()); - let mut mode = *state.spaces.mode(); + let mut mode = *state.shell.mode(); let active = if let Mode::Global { active } = mode { active } else { @@ -125,12 +125,12 @@ pub fn debug_ui( }; ui.radio_value(&mut mode, Mode::OutputBound, "Output bound"); ui.radio_value(&mut mode, Mode::Global { active }, "Global"); - state.spaces.set_mode(mode); + state.shell.set_mode(mode); - match *state.spaces.mode() { + match *state.shell.mode() { Mode::OutputBound => { ui.label("Workspaces:"); - for output in state.spaces.outputs().cloned().collect::>() { + for output in state.shell.outputs().cloned().collect::>() { ui.horizontal(|ui| { let active = output .user_data() @@ -146,7 +146,7 @@ pub fn debug_ui( .speed(1.0), ); if active != active_val as usize { - state.spaces.activate(&output, active_val as usize); + state.shell.activate(&output, active_val as usize); } }); } @@ -161,29 +161,30 @@ pub fn debug_ui( .speed(1.0), ); if active != active_val as usize { - let output = state.spaces.outputs().next().cloned().unwrap(); - state.spaces.activate(&output, active_val as usize); + let output = state.shell.outputs().next().cloned().unwrap(); + state.shell.activate(&output, active_val as usize); } }); } } // Spaces - for (i, space) in state.spaces.spaces.iter().enumerate() { + for (i, workspace) in state.shell.spaces.iter().enumerate() { ui.collapsing(format!("Space: {}", i), |ui| { ui.collapsing(format!("Windows"), |ui| { - for window in space.windows() { + for window in workspace.space.windows() { ui.collapsing(format!("{:?}", window.toplevel()), |ui| { ui.label(format!("Rect: {:?}", { let mut geo = window.geometry(); - geo.loc += space + geo.loc += workspace + .space .window_location(window) .unwrap_or((0, 0).into()); geo })); ui.label(format!( "Bounding box: {:?}", - space.window_bbox(window) + workspace.space.window_bbox(window) )); }); } @@ -197,9 +198,9 @@ pub fn debug_ui( .hscroll(true) .default_pos([300.0, 300.0]) .show(ctx, |ui| { - ui.label(format!("Global Space: {:?}", state.spaces.global_space())); + ui.label(format!("Global Space: {:?}", state.shell.global_space())); for output in state - .spaces + .shell .outputs() .cloned() .collect::>() @@ -210,15 +211,19 @@ pub fn debug_ui( ui.label(format!("Output: {:#?}", output)); ui.label(format!( "Geometry: {:?}", - state.spaces.output_geometry(&output) + state.shell.output_geometry(&output) )); ui.label(format!( "Local Geometry: {:?}", - state.spaces.active_space(&output).output_geometry(&output) + state + .shell + .active_space(&output) + .space + .output_geometry(&output) )); ui.label(format!( "Relative Geometry: {:?}", - state.spaces.space_relative_output_geometry((0, 0), &output) + state.shell.space_relative_output_geometry((0, 0), &output) )); }); } diff --git a/src/input/mod.rs b/src/input/mod.rs index fa709a7d..21b65990 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -83,7 +83,7 @@ pub fn add_seat(display: &mut Display, name: String) -> Seat { let userdata = seat.user_data(); userdata.insert_if_missing(|| Devices::new()); userdata.insert_if_missing(|| SupressedKeys::new()); - userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Hidden)); + userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); seat } @@ -93,7 +93,7 @@ pub fn active_output(seat: &Seat, state: &Common) -> Output { .map(|x| x.0.borrow().clone()) .unwrap_or_else(|| { state - .spaces + .shell .outputs() .next() .cloned() @@ -137,7 +137,7 @@ impl Common { } DeviceCapability::Pointer => { let output = self - .spaces + .shell .outputs() .next() .expect("Backend initialized without output") @@ -248,7 +248,7 @@ impl Common { 0 => 9, x => x - 1, }; - self.spaces.activate(¤t_output, workspace as usize); + self.shell.activate(¤t_output, workspace as usize); userdata.get::().unwrap().add(&handle); return FilterResult::Intercept(()); } @@ -316,10 +316,10 @@ impl Common { position += event.delta(); let output = self - .spaces + .shell .outputs() .find(|output| { - self.spaces + self.shell .output_geometry(output) .to_f64() .contains(position) @@ -329,7 +329,7 @@ impl Common { if output != current_output { set_active_output(seat, &output); } - let output_geometry = self.spaces.output_geometry(&output); + let output_geometry = self.shell.output_geometry(&output); position.x = 0.0f64 .max(position.x) @@ -339,9 +339,12 @@ impl Common { .min((output_geometry.loc.y + output_geometry.size.h) as f64); let serial = SERIAL_COUNTER.next_serial(); - let space = self.spaces.active_space_mut(&output); - let under = Common::surface_under(position, &output, space); - handle_window_movement(under.as_ref().map(|(s, _)| s), space); + let workspace = self.shell.active_space_mut(&output); + let under = Common::surface_under(position, &output, &workspace.space); + handle_window_movement( + under.as_ref().map(|(s, _)| s), + &mut workspace.space, + ); seat.get_pointer() .unwrap() .motion(position, under, serial, event.time()); @@ -368,13 +371,16 @@ impl Common { let devices = userdata.get::().unwrap(); if devices.has_device(&device) { let output = active_output(seat, &self); - let geometry = self.spaces.output_geometry(&output); - let space = self.spaces.active_space_mut(&output); + let geometry = self.shell.output_geometry(&output); + let workspace = self.shell.active_space_mut(&output); let position = geometry.loc.to_f64() + event.position_transformed(geometry.size); let serial = SERIAL_COUNTER.next_serial(); - let under = Common::surface_under(position, &output, space); - handle_window_movement(under.as_ref().map(|(s, _)| s), space); + let under = Common::surface_under(position, &output, &workspace.space); + handle_window_movement( + under.as_ref().map(|(s, _)| s), + &mut workspace.space, + ); seat.get_pointer() .unwrap() .motion(position, under, serial, event.time()); @@ -437,8 +443,8 @@ impl Common { if !seat.get_pointer().unwrap().is_grabbed() { let output = active_output(seat, &self); let mut pos = seat.get_pointer().unwrap().current_location(); - let output_geo = self.spaces.output_geometry(&output); - let space = self.spaces.active_space_mut(&output); + let output_geo = self.shell.output_geometry(&output); + let workspace = self.shell.active_space_mut(&output); let layers = layer_map_for_output(&output); pos -= output_geo.loc.to_f64(); let mut under = None; @@ -457,8 +463,11 @@ impl Common { ) .map(|(s, _)| s); } - } else if let Some(window) = space.window_under(pos).cloned() { - let window_loc = space.window_location(&window).unwrap(); + } else if let Some(window) = + workspace.space.window_under(pos).cloned() + { + let window_loc = + workspace.space.window_location(&window).unwrap(); under = window .surface_under( pos - window_loc.to_f64(), @@ -466,7 +475,7 @@ impl Common { | WindowSurfaceType::SUBSURFACE, ) .map(|(s, _)| s); - space.raise_window(&window, true); + // space.raise_window(&window, true); } else if let Some(layer) = layers .layer_under(WlrLayer::Bottom, pos) .or_else(|| layers.layer_under(WlrLayer::Background, pos)) @@ -483,9 +492,7 @@ impl Common { } }; - if let Some(keyboard) = seat.get_keyboard() { - keyboard.set_focus(under.as_ref(), serial); - } + self.shell.set_focus(under.as_ref(), seat); } wl_pointer::ButtonState::Pressed } @@ -635,10 +642,11 @@ impl Common { } pub fn handle_window_movement(surface: Option<&WlSurface>, space: &mut Space) { + // TODO: this is why to hardcoded and hacky, but wayland-rs 0.30 will make this unnecessary anyway. if let Some(surface) = surface { if let Some(window) = space.window_for_surface(&surface).cloned() { if let Some(new_position) = - crate::shell::grabs::MoveSurfaceGrab::apply_move_state(&window) + crate::shell::layout::floating::MoveSurfaceGrab::apply_move_state(&window) { space.map_window(&window, new_position, true); } diff --git a/src/main.rs b/src/main.rs index 2622918d..d5af986e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ fn main() -> Result<()> { let signal = event_loop.get_signal(); event_loop.run(None, &mut state, |state| { // shall we shut down? - if state.common.spaces.outputs().next().is_none() || state.common.should_stop { + if state.common.shell.outputs().next().is_none() || state.common.should_stop { slog_scope::info!("Shutting down"); signal.stop(); signal.wakeup(); @@ -46,7 +46,7 @@ fn main() -> Result<()> { // do we need to trigger another render if state.common.dirty_flag.swap(false, Ordering::SeqCst) { - for output in state.common.spaces.outputs() { + for output in state.common.shell.outputs() { state.backend.schedule_render(output) } } @@ -55,7 +55,7 @@ fn main() -> Result<()> { let display = state.common.display.clone(); display.borrow_mut().flush_clients(state); // trigger routines - state.common.spaces.refresh(); + state.common.shell.refresh(); })?; let _log = state.destroy(); diff --git a/src/shell/handler.rs b/src/shell/handler.rs new file mode 100644 index 00000000..16aae0bd --- /dev/null +++ b/src/shell/handler.rs @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{input::active_output, state::State, utils::SurfaceDropNotifier}; +use smithay::{ + backend::renderer::utils::on_commit_buffer_handler, + desktop::{ + layer_map_for_output, Kind, LayerSurface, PopupKeyboardGrab, PopupKind, PopupPointerGrab, + PopupUngrabStrategy, Window, + }, + reexports::{ + wayland_protocols::xdg_shell::server::xdg_toplevel, + wayland_server::{protocol::wl_surface::WlSurface, Display}, + }, + wayland::{ + compositor::{compositor_init, with_states}, + output::Output, + seat::{PointerGrabStartData, Seat}, + shell::{ + wlr_layer::{wlr_layer_shell_init, LayerShellRequest, LayerSurfaceAttributes}, + xdg::{ + xdg_shell_init, Configure, XdgPopupSurfaceRoleAttributes, XdgRequest, + XdgToplevelSurfaceRoleAttributes, + }, + }, + Serial, + }, +}; +use std::{cell::Cell, rc::Rc, sync::Mutex}; + +pub fn init_shell(display: &mut Display) -> super::Shell { + compositor_init( + display, + move |surface, mut ddata| { + on_commit_buffer_handler(&surface); + let state = ddata.get::().unwrap(); + state.common.shell.commit(&surface); + commit(&surface, state) + }, + None, + ); + + let popup_grab = Rc::new(Cell::new(None)); + let popup_grab_clone = popup_grab.clone(); + let (_xdg_shell_state, _xdg_global) = xdg_shell_init( + display, + move |event, mut ddata| { + let state = &mut ddata.get::().unwrap().common; + + 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.shell.active_space_mut(&output); + let window = Window::new(Kind::Xdg(surface)); + space.pending_window(window, seat); + // 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::Move { + surface, + seat, + serial, + } => { + let seat = Seat::from_resource(&seat).unwrap(); + if let Some(start_data) = + check_grab_preconditions(&seat, surface.get_surface(), serial) + { + let workspace = state + .shell + .space_for_surface_mut(surface.get_surface().unwrap()) + .unwrap(); + let window = workspace + .space + .window_for_surface(surface.get_surface().unwrap()) + .unwrap() + .clone(); + + workspace.move_request(&window, &seat, serial, start_data); + } + } + + XdgRequest::Resize { + surface, + seat, + serial, + edges, + } => { + let seat = Seat::from_resource(&seat).unwrap(); + if let Some(start_data) = + check_grab_preconditions(&seat, surface.get_surface(), serial) + { + let workspace = state + .shell + .space_for_surface_mut(surface.get_surface().unwrap()) + .unwrap(); + let window = workspace + .space + .window_for_surface(surface.get_surface().unwrap()) + .unwrap() + .clone(); + + workspace.resize_request(&window, &seat, serial, start_data, edges); + } + } + XdgRequest::AckConfigure { + surface, + configure: Configure::Toplevel(configure), + } => { + // TODO: This is way to hardcoded and hacky, but wayland-rs 0.30 will make this unnecessary so don't bother. + if let Some(window) = state + .shell + .space_for_surface(&surface) + .and_then(|workspace| workspace.space.window_for_surface(&surface)) + { + crate::shell::layout::floating::ResizeSurfaceGrab::ack_configure( + window, configure, + ) + } + } + XdgRequest::Maximize { surface } => { + if let Some(surface) = surface.get_surface() { + let seat = &state.last_active_seat; + let output = active_output(seat, &*state); + + if let Some(workspace) = state.shell.space_for_surface_mut(surface) { + let window = + workspace.space.window_for_surface(surface).unwrap().clone(); + workspace.maximize_request(&window, &output) + } + } + } + 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(); + } + } + XdgRequest::Grab { + serial, + surface, + seat, + } => { + let seat = Seat::from_resource(&seat).unwrap(); + let ret = state.shell.popups.grab_popup(surface.into(), &seat, serial); + + if let Ok(mut grab) = ret { + 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(grab.current_grab().as_ref(), 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(PopupPointerGrab::new(&grab), serial, 0); + } + + popup_grab_clone.set(Some(grab)); + } + } + _ => { /*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 = &mut ddata.get::().unwrap().common; + 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, + ); + + super::Shell::new(popup_grab) +} + +fn check_grab_preconditions( + seat: &Seat, + surface: Option<&WlSurface>, + serial: Serial, +) -> Option { + let surface = if let Some(surface) = surface { + surface + } else { + return None; + }; + + // TODO: touch resize. + let pointer = seat.get_pointer().unwrap(); + + // Check that this surface has a click grab. + if !pointer.has_grab(serial) { + return None; + } + + let start_data = pointer.grab_start_data().unwrap(); + + // If the focus was for a different surface, ignore the request. + if start_data.focus.is_none() + || !start_data + .focus + .as_ref() + .unwrap() + .0 + .as_ref() + .same_client_as(surface.as_ref()) + { + return None; + } + + Some(start_data) +} + +fn commit(surface: &WlSurface, state: &mut State) { + // TODO figure out which output the surface is on. + for output in state.common.shell.outputs() { + //.cloned().collect::>().into_iter() { + state.backend.schedule_render(output); + // let space = state.common.spaces.active_space(output); + // get output for surface + } + + let state = &mut state.common; + let _ = with_states(surface, |states| { + states + .data_map + .insert_if_missing(|| SurfaceDropNotifier::from(&*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(); + } + + return; + } + + // TODO: This is way to hardcoded and hacky, but wayland-rs 0.30 will make this unnecessary so don't bother. + if let Some((space, window)) = + state + .shell + .space_for_surface_mut(surface) + .and_then(|workspace| { + workspace + .space + .window_for_surface(surface) + .cloned() + .map(|window| (&mut workspace.space, window)) + }) + { + let new_location = crate::shell::layout::floating::ResizeSurfaceGrab::apply_resize_state( + &window, + space.window_location(&window).unwrap(), + window.geometry().size, + ); + if let Some(location) = new_location { + space.map_window(&window, location, true); + } + + return; + } + + if let Some(popup) = state.shell.popups.find_popup(surface) { + let PopupKind::Xdg(ref popup) = popup; + let initial_configure_sent = with_states(surface, |states| { + states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }) + .unwrap(); + if !initial_configure_sent { + // NOTE: This should never fail as the initial configure is always + // allowed. + popup.send_configure().expect("initial configure failed"); + } + + return; + } + + if let Some(output) = state.shell.outputs().find(|o| { + let map = layer_map_for_output(o); + map.layer_for_surface(surface).is_some() + }) { + let mut map = layer_map_for_output(output); + let layer = map.layer_for_surface(surface).unwrap(); + + // 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 { + layer.layer_surface().send_configure(); + } + + map.arrange(); + + return; + }; +} diff --git a/src/shell/layout/combined.rs b/src/shell/layout/combined.rs new file mode 100644 index 00000000..87cdda68 --- /dev/null +++ b/src/shell/layout/combined.rs @@ -0,0 +1,126 @@ +use super::Layout; +use smithay::{ + desktop::{Space, Window}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::ResizeEdge, + wayland::{ + compositor::with_states, + seat::{PointerGrabStartData, Seat}, + shell::xdg::XdgToplevelSurfaceRoleAttributes, + Serial, + }, +}; +use std::sync::Mutex; + +struct Filtered; + +pub struct Combined { + first: A, + second: B, + windows_a: Vec, + windows_b: Vec, + filter: Box, Option<&str>) -> bool>, +} + +impl Combined { + pub fn new( + first: A, + second: B, + filter: impl Fn(Option<&str>, Option<&str>) -> bool + 'static, + ) -> Combined { + Combined { + first, + second, + windows_a: Vec::new(), + windows_b: Vec::new(), + filter: Box::new(filter), + } + } +} + +impl Layout for Combined { + fn map_window<'a>( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + focus_stack: Box + 'a>, + ) { + if let Some(surface) = window.toplevel().get_surface() { + let filtered = with_states(surface, |states| { + let attrs = states + .data_map + .get::>() + .unwrap() + .lock() + .unwrap(); + if (self.filter)(attrs.app_id.as_deref(), attrs.title.as_deref()) { + window.user_data().insert_if_missing(|| Filtered); + true + } else { + false + } + }) + .unwrap_or(false); + if filtered { + self.windows_b.push(window.clone()); + return self.second.map_window( + space, + window, + seat, + Box::new(focus_stack.filter(|w| w.user_data().get::().is_some())), + ); + } + } + + self.windows_a.push(window.clone()); + return self.first.map_window( + space, + window, + seat, + Box::new(focus_stack.filter(|w| w.user_data().get::().is_none())), + ); + } + + fn refresh(&mut self, space: &mut Space) { + self.first.refresh(space); + self.second.refresh(space); + self.windows_a.retain(|w| w.toplevel().alive()); + self.windows_b.retain(|w| w.toplevel().alive()); + } + //fn unmap_window(&mut self, space: &mut Space, window: &Window); + + fn move_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + ) { + if self.windows_a.contains(window) { + self.first + .move_request(space, window, seat, serial, start_data) + } else if self.windows_b.contains(window) { + self.second + .move_request(space, window, seat, serial, start_data) + } + } + + fn resize_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + edges: ResizeEdge, + ) { + if self.windows_a.contains(window) { + self.first + .resize_request(space, window, seat, serial, start_data, edges) + } else if self.windows_b.contains(window) { + self.second + .resize_request(space, window, seat, serial, start_data, edges) + } + } +} diff --git a/src/shell/grabs.rs b/src/shell/layout/floating/grabs.rs similarity index 100% rename from src/shell/grabs.rs rename to src/shell/layout/floating/grabs.rs diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs new file mode 100644 index 00000000..6f20fccb --- /dev/null +++ b/src/shell/layout/floating/mod.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::shell::layout::Layout; +use smithay::{ + desktop::{layer_map_for_output, Kind, Space, Window}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::{ + ResizeEdge, State as XdgState, + }, + wayland::{ + output::Output, + seat::{PointerGrabStartData, Seat}, + Serial, + }, +}; + +mod grabs; +pub use self::grabs::*; + +pub struct FloatingLayout; + +impl Layout for FloatingLayout { + fn map_window<'a>( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + _focus_stack: Box + 'a>, + ) { + let output = super::output_from_seat(seat, space); + let win_geo = window.bbox(); + 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), + ); + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + let ret = xdg.with_pending_state(|state| { + state.states.unset(XdgState::TiledLeft); + state.states.unset(XdgState::TiledRight); + state.states.unset(XdgState::TiledTop); + state.states.unset(XdgState::TiledBottom); + }); + if ret.is_ok() { + xdg.send_configure(); + } + } + space.map_window(&window, position, false); + } + + fn refresh(&mut self, _space: &mut Space) { + // TODO make sure all windows are still visible on any output or move them + } + + fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { + let layers = layer_map_for_output(&output); + let geometry = layers.non_exclusive_zone(); + + space.map_window(&window, (-geometry.loc.x, -geometry.loc.y), true); + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(surface) = &window.toplevel() { + let ret = surface.with_pending_state(|state| { + state.states.set(XdgState::Maximized); + state.size = Some(geometry.size); + }); + + if ret.is_ok() { + window.configure(); + } + } + } + + fn move_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + ) { + if let Some(pointer) = seat.get_pointer() { + let mut initial_window_location = space.window_location(&window).unwrap(); + + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(surface) = &window.toplevel() { + // If surface is maximized then unmaximize it + if let Some(current_state) = surface.current_state() { + if current_state.states.contains(XdgState::Maximized) { + let fs_changed = surface.with_pending_state(|state| { + state.states.unset(XdgState::Maximized); + state.size = None; + }); + + if fs_changed.is_ok() { + surface.send_configure(); + + // NOTE: In real compositor mouse location should be mapped to a new window size + // For example, you could: + // 1) transform mouse pointer position from compositor space to window space (location relative) + // 2) divide the x coordinate by width of the window to get the percentage + // - 0.0 would be on the far left of the window + // - 0.5 would be in middle of the window + // - 1.0 would be on the far right of the window + // 3) multiply the percentage by new window width + // 4) by doing that, drag will look a lot more natural + // + // but for anvil needs setting location to pointer location is fine + let pos = pointer.current_location(); + initial_window_location = (pos.x as i32, pos.y as i32).into(); + } + } + } + } + + let grab = MoveSurfaceGrab::new(start_data, window.clone(), initial_window_location); + + pointer.set_grab(grab, serial, 0); + } + } + + fn resize_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + edges: ResizeEdge, + ) { + if let Some(pointer) = seat.get_pointer() { + let location = space.window_location(&window).unwrap(); + let size = window.geometry().size; + + let grab = + grabs::ResizeSurfaceGrab::new(start_data, window.clone(), edges, location, size); + + pointer.set_grab(grab, serial, 0); + } + } +} diff --git a/src/shell/layout/mod.rs b/src/shell/layout/mod.rs new file mode 100644 index 00000000..17feb37c --- /dev/null +++ b/src/shell/layout/mod.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +use crate::input::ActiveOutput; +use smithay::{ + desktop::{Space, Window}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::ResizeEdge, + wayland::{ + output::Output, + seat::{PointerGrabStartData, Seat}, + Serial, + }, +}; + +pub mod combined; +pub mod floating; +pub mod tiling; + +pub trait Layout { + fn map_window<'a>( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + focus_stack: Box + 'a>, + ); //working around object safety.. + fn refresh(&mut self, space: &mut Space); + //fn unmap_window(&mut self, space: &mut Space, window: &Window); + + fn maximize_request(&mut self, space: &mut Space, window: &Window, output: &Output) { + let _ = (space, window, output); + } + fn move_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + ) { + let _ = (space, window, seat, serial, start_data); + } + fn resize_request( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + edges: ResizeEdge, + ) { + let _ = (space, window, seat, serial, start_data, edges); + } +} + +pub fn new_default_layout() -> Box { + Box::new(combined::Combined::new( + tiling::TilingLayout::new(), + floating::FloatingLayout, + |app_id, title| { + slog_scope::debug!( + "New filter input: ({}:{})", + app_id.unwrap_or(""), + title.unwrap_or("") + ); + match (app_id.unwrap_or(""), title.unwrap_or("")) { + ("gcr-prompter", _) => true, + _ => false, + } + }, + )) +} + +fn output_from_seat(seat: &Seat, space: &Space) -> Output { + seat.user_data() + .get::() + .map(|active| active.0.borrow().clone()) + .or_else(|| { + seat.get_pointer() + .map(|ptr| space.output_under(ptr.current_location()).next().unwrap()) + .cloned() + }) + .unwrap_or_else(|| space.outputs().next().cloned().unwrap()) +} diff --git a/src/shell/layout/tiling.rs b/src/shell/layout/tiling.rs new file mode 100644 index 00000000..9ea3e1af --- /dev/null +++ b/src/shell/layout/tiling.rs @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::shell::layout::Layout; +use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; +use smithay::{ + desktop::{layer_map_for_output, Kind, Space, Window}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::State as XdgState, + utils::Rectangle, + wayland::{output::Output, seat::Seat}, +}; +use std::cell::RefCell; + +#[derive(Debug)] +pub struct TilingLayout { + gaps: (i32, i32), +} + +#[derive(Debug)] +pub enum Orientation { + Horizontal, + Vertical, +} + +#[derive(Debug)] +pub enum Data { + Fork { + orientation: Orientation, + ratio: f64, + }, + Stack { + active: usize, + len: usize, + }, + Window(Window), +} + +#[derive(Debug, Clone)] +pub struct WindowInfo { + node: NodeId, + output: Output, +} + +pub struct OutputInfo { + tree: Tree, +} + +impl std::fmt::Debug for OutputInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.tree.write_formatted(f) + } +} + +impl Default for OutputInfo { + fn default() -> OutputInfo { + OutputInfo { tree: Tree::new() } + } +} + +impl Data { + fn fork() -> Data { + Data::Fork { + orientation: Orientation::Vertical, + ratio: 0.5, + } + } +} + +impl TilingLayout { + pub fn new() -> TilingLayout { + TilingLayout { gaps: (0, 4) } + } +} + +impl Layout for TilingLayout { + fn map_window<'a>( + &mut self, + space: &mut Space, + window: &Window, + seat: &Seat, + mut focus_stack: Box + 'a>, + ) { + { + let output = super::output_from_seat(seat, space); + output + .user_data() + .insert_if_missing(|| RefCell::new(OutputInfo::default())); + let output_info = output.user_data().get::>().unwrap(); + let tree = &mut output_info.borrow_mut().tree; + + let new_window = Node::new(Data::Window(window.clone())); + + let last_active = focus_stack + .find_map(|window| tree.root_node_id() + .and_then(|root| tree.traverse_pre_order_ids(root).unwrap() + .find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window)) + ) + ); + let window_id = if let Some(ref node_id) = last_active { + let parent_id = tree.get(node_id).unwrap().parent().cloned(); + if let Some(stack_id) = parent_id + .filter(|id| matches!(tree.get(id).unwrap().data(), Data::Stack { .. })) + { + // we add to the stack + let window_id = tree + .insert(new_window, InsertBehavior::UnderNode(&stack_id)) + .unwrap(); + if let Data::Stack { + ref mut len, + ref mut active, + } = tree.get_mut(&stack_id).unwrap().data_mut() + { + *active = *len; + *len += 1; + } + Ok(window_id) + } else { + // we create a new fork + new_fork(tree, node_id, new_window) + } + } else { + // nothing? then we add to the root + if let Some(root_id) = tree.root_node_id().cloned() { + new_fork(tree, &root_id, new_window) + } else { + tree.insert(new_window, InsertBehavior::AsRoot) + } + } + .unwrap(); + + { + let user_data = window.user_data(); + let window_info = WindowInfo { + node: window_id.clone(), + output: output.clone(), + }; + // insert or update + if !user_data.insert_if_missing(|| RefCell::new(window_info.clone())) { + *user_data.get::>().unwrap().borrow_mut() = window_info; + } + } + } + self.refresh(space); + } + + fn refresh(&mut self, space: &mut Space) { + while let Some(dead_windows) = + Some(update_space_positions(space, self.gaps)).filter(|v| !v.is_empty()) + { + for window in dead_windows { + self.unmap_window(&window); + } + } + } +} + +impl TilingLayout { + fn unmap_window(&mut self, window: &Window) { + if let Some(info) = window.user_data().get::>() { + let output = &info.borrow().output; + let output_info = output.user_data().get::>().unwrap(); + let tree = &mut output_info.borrow_mut().tree; + + let node_id = info.borrow().node.clone(); + let parent_id = tree + .get(&node_id) + .ok() + .and_then(|node| node.parent()) + .cloned(); + let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { + tree.get(parent_id) + .ok() + .and_then(|node| node.parent()) + .cloned() + }); + + // remove self + slog_scope::debug!("Remove window {:?}", window); + let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren); + + // fixup parent node + match parent_id { + Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Fork { .. }) => { + slog_scope::debug!("Removing Fork"); + let other_child = tree.children_ids(&id).unwrap().cloned().next().unwrap(); + let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(parent_id).unwrap().position(|i| i == &id) + }); + let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); + tree.move_node( + &other_child, + parent_parent_id + .as_ref() + .map(|parent_id| MoveBehavior::ToParent(parent_id)) + .unwrap_or(MoveBehavior::ToRoot), + ) + .unwrap(); + if let Some(old_pos) = fork_pos { + tree.make_nth_sibling(&other_child, old_pos).unwrap(); + } + } + Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Stack { .. }) => { + if tree.children_ids(&id).unwrap().count() == 0 { + slog_scope::debug!("Removing Stack"); + // remove stack + let _ = tree.remove_node(id.clone(), RemoveBehavior::DropChildren); + // TODO now we need to untangle the parent_parent + // So we should REFACTOR this unmap function to not only work with windows + } else { + // fixup stack values + if let Data::Stack { + ref mut active, + ref mut len, + } = tree.get_mut(&id).unwrap().data_mut() + { + *len -= 1; + *active = std::cmp::max(*active, *len - 1); + } + } + } + None => {} // root + _ => unreachable!(), + } + // update_space_positions(space, self.gaps); + } + } +} + +fn new_fork( + tree: &mut Tree, + old_id: &NodeId, + new: Node, +) -> Result { + let new_fork = Node::new(Data::fork()); + let old = tree.get(old_id)?; + let parent_id = old.parent().cloned(); + let pos = parent_id.as_ref().and_then(|parent_id| { + tree.children_ids(parent_id) + .unwrap() + .position(|id| id == old_id) + }); + + let fork_id = tree + .insert( + new_fork, + if let Some(parent) = parent_id.as_ref() { + InsertBehavior::UnderNode(parent) + } else { + InsertBehavior::AsRoot + }, + ) + .unwrap(); + + tree.move_node(old_id, MoveBehavior::ToParent(&fork_id)) + .unwrap(); + // keep position + if let Some(old_pos) = pos { + tree.make_nth_sibling(&fork_id, old_pos).unwrap(); + } + tree.insert(new, InsertBehavior::UnderNode(&fork_id)) +} + +fn update_space_positions(space: &mut Space, gaps: (i32, i32)) -> Vec { + let mut dead_windows = Vec::new(); + let (outer, inner) = gaps; + for output in space.outputs().cloned().collect::>().into_iter() { + if let Some(mut info) = output + .user_data() + .get::>() + .map(|x| x.borrow_mut()) + { + slog_scope::trace!("Tree:\n{:?}", info); + let tree = &mut info.tree; + if let Some(root) = tree.root_node_id() { + let mut stack = Vec::new(); + + let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone()); + // TODO saturate? minimum? + if let Some(mut geo) = geo.as_mut() { + geo.loc.x += outer; + geo.loc.y += outer; + geo.size.w -= outer * 2; + geo.size.h -= outer * 2; + } + + for node in tree.traverse_pre_order(root).unwrap() { + let geo = stack.pop().unwrap_or(geo); + match node.data() { + Data::Fork { orientation, ratio } => { + if let Some(geo) = geo { + match orientation { + Orientation::Horizontal => { + let top_size = ( + geo.size.w, + ((geo.size.h as f64) * ratio).ceil() as i32, + ); + stack.push(Some(Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y + top_size.1), + (geo.size.w, geo.size.h - top_size.1), + ))); + stack.push(Some(Rectangle::from_loc_and_size( + geo.loc, top_size, + ))); + } + Orientation::Vertical => { + let left_size = ( + ((geo.size.w as f64) * ratio).ceil() as i32, + geo.size.h, + ); + stack.push(Some(Rectangle::from_loc_and_size( + (geo.loc.x + left_size.0, geo.loc.y), + (geo.size.w - left_size.0, geo.size.h), + ))); + stack.push(Some(Rectangle::from_loc_and_size( + geo.loc, left_size, + ))); + } + } + } else { + stack.push(None); + stack.push(None); + } + } + Data::Stack { active, len } => { + for i in 0..*len { + if i == *active { + stack.push(geo); + } else { + stack.push(None); + } + } + } + Data::Window(window) => { + if window.toplevel().alive() { + if let Some(geo) = geo { + #[allow(irrefutable_let_patterns)] + if let Kind::Xdg(xdg) = &window.toplevel() { + let ret = xdg.with_pending_state(|state| { + state.size = Some( + (geo.size.w - inner * 2, geo.size.h - inner * 2) + .into(), + ); + state.states.set(XdgState::TiledLeft); + state.states.set(XdgState::TiledRight); + state.states.set(XdgState::TiledTop); + state.states.set(XdgState::TiledBottom); + }); + if ret.is_ok() { + xdg.send_configure(); + } + } + let window_geo = window.geometry(); + space.map_window( + &window, + ( + geo.loc.x + inner - window_geo.loc.x, + geo.loc.y + inner - window_geo.loc.y, + ), + false, + ); + } + } else { + dead_windows.push(window.clone()); + } + } + } + } + } + } + } + dead_windows +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 503cdfb9..6cab5985 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,476 +1,392 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{input::active_output, state::State, utils::SurfaceDropNotifier}; -use smithay::{ - backend::renderer::utils::on_commit_buffer_handler, - desktop::{ - layer_map_for_output, Kind, LayerSurface, PopupKeyboardGrab, PopupKind, PopupManager, - PopupPointerGrab, PopupUngrabStrategy, Window, - }, - reexports::{ - wayland_protocols::xdg_shell::server::xdg_toplevel, - wayland_server::{protocol::wl_surface::WlSurface, Display}, - }, - wayland::{ - compositor::{compositor_init, with_states}, - output::Output, - seat::{PointerGrabStartData, PointerHandle, Seat}, - shell::{ - wlr_layer::{ - wlr_layer_shell_init, LayerShellRequest, LayerShellState, LayerSurfaceAttributes, - }, - xdg::{ - xdg_shell_init, Configure, ShellState as XdgShellState, - XdgPopupSurfaceRoleAttributes, XdgRequest, XdgToplevelSurfaceRoleAttributes, - }, - }, - Serial, - }, +pub use smithay::{ + desktop::{PopupGrab, PopupManager, PopupUngrabStrategy, Space, Window}, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Point, Rectangle, Size}, + wayland::{output::Output, seat::Seat, SERIAL_COUNTER}, }; -use std::sync::{Arc, Mutex}; +use std::{cell::Cell, mem::MaybeUninit, rc::Rc}; -pub mod grabs; -pub mod workspaces; +pub const MAX_WORKSPACES: usize = 10; // TODO? -pub struct ShellStates { - popups: PopupManager, - xdg: Arc>, - layer: Arc>, +mod handler; +pub mod layout; +mod workspace; +pub use self::handler::init_shell; +pub use self::layout::Layout; +pub use self::workspace::*; + +pub struct ActiveWorkspace(Cell>); +impl ActiveWorkspace { + fn new() -> Self { + ActiveWorkspace(Cell::new(None)) + } + pub fn get(&self) -> Option { + self.0.get() + } + fn set(&self, active: usize) -> Option { + self.0.replace(Some(active)) + } + fn clear(&self) -> Option { + self.0.replace(None) + } } -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.common.spaces.commit(&surface); - state.common.shell.popups.commit(&surface); - commit(&surface, state) - }, - None, - ); +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Mode { + OutputBound, + Global { active: usize }, +} - let (xdg_shell_state, _xdg_global) = xdg_shell_init( - display, - |event, mut ddata| { - let state = &mut ddata.get::().unwrap().common; +impl Mode { + pub fn output_bound() -> Mode { + Mode::OutputBound + } - match event { - XdgRequest::NewToplevel { surface } => { - state.pending_toplevels.push(surface.clone()); + pub fn global() -> Mode { + Mode::Global { active: 0 } + } +} - 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), true); - // We will position the window after the first commit, when we know its size +pub struct Shell { + popups: PopupManager, + popup_grab: Rc>>, + mode: Mode, + outputs: Vec, + pub spaces: [Workspace; MAX_WORKSPACES], +} + +const UNINIT_SPACE: MaybeUninit = MaybeUninit::uninit(); + +impl Shell { + fn new(popup_grab: Rc>>) -> Self { + Shell { + popups: PopupManager::new(None), + popup_grab, + mode: Mode::global(), + outputs: Vec::new(), + spaces: unsafe { + let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; + for space in &mut spaces { + *space = MaybeUninit::new(Workspace::new()); } - 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; - }); + std::mem::transmute(spaces) + }, + } + } - if result.is_ok() { - surface.send_repositioned(token); - } - } - XdgRequest::Move { - surface, - seat, - serial, - } => { - let seat = Seat::from_resource(&seat).unwrap(); - if let Some((pointer, start_data)) = - check_grab_preconditions(&seat, surface.get_surface(), serial) - { - let space = state - .spaces - .space_for_surface(surface.get_surface().unwrap()) - .unwrap(); - let window = space - .window_for_surface(surface.get_surface().unwrap()) - .unwrap() - .clone(); - let mut initial_window_location = space.window_location(&window).unwrap(); + pub fn map_output(&mut self, output: &Output) { + match self.mode { + Mode::OutputBound => { + output + .user_data() + .insert_if_missing(|| ActiveWorkspace::new()); - // If surface is maximized then unmaximize it - if let Some(current_state) = surface.current_state() { - if current_state - .states - .contains(xdg_toplevel::State::Maximized) - { - let fs_changed = surface.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.size = None; - }); - - if fs_changed.is_ok() { - surface.send_configure(); - - // NOTE: In real compositor mouse location should be mapped to a new window size - // For example, you could: - // 1) transform mouse pointer position from compositor space to window space (location relative) - // 2) divide the x coordinate by width of the window to get the percentage - // - 0.0 would be on the far left of the window - // - 0.5 would be in middle of the window - // - 1.0 would be on the far right of the window - // 3) multiply the percentage by new window width - // 4) by doing that, drag will look a lot more natural - // - // but for anvil needs setting location to pointer location is fine - let pos = pointer.current_location(); - initial_window_location = (pos.x as i32, pos.y as i32).into(); - } - } - } - - let grab = grabs::MoveSurfaceGrab::new( - start_data, - window, - initial_window_location, - ); - - pointer.set_grab(grab, serial, 0); - } - } - - XdgRequest::Resize { - surface, - seat, - serial, - edges, - } => { - let seat = Seat::from_resource(&seat).unwrap(); - if let Some((pointer, start_data)) = - check_grab_preconditions(&seat, surface.get_surface(), serial) - { - let space = state - .spaces - .space_for_surface(surface.get_surface().unwrap()) - .unwrap(); - let window = space - .window_for_surface(surface.get_surface().unwrap()) - .unwrap() - .clone(); - let location = space.window_location(&window).unwrap(); - let size = window.geometry().size; - - let grab = grabs::ResizeSurfaceGrab::new( - start_data, window, edges, location, size, - ); - - pointer.set_grab(grab, serial, 0); - } - } - XdgRequest::AckConfigure { - surface, - configure: Configure::Toplevel(configure), - } => { - if let Some(window) = state - .spaces - .space_for_surface(&surface) - .and_then(|space| space.window_for_surface(&surface)) - { - grabs::ResizeSurfaceGrab::ack_configure(window, 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, true); - 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(); - } - } - XdgRequest::Grab { - serial, - surface, - seat, - } => { - let seat = Seat::from_resource(&seat).unwrap(); - let ret = state.shell.popups.grab_popup(surface.into(), &seat, serial); - - if let Ok(mut grab) = ret { - 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(grab.current_grab().as_ref(), 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(PopupPointerGrab::new(&grab), serial, 0); - } - } - } - _ => { /*TODO*/ } + let (idx, workspace) = self + .spaces + .iter_mut() + .enumerate() + .find(|(_, x)| x.space.outputs().next().is_none()) + .expect("More then 10 outputs?"); + output + .user_data() + .get::() + .unwrap() + .set(idx); + workspace.space.map_output(output, 1.0, (0, 0)); + self.outputs.push(output.clone()); } - }, - None, - ); + Mode::Global { active } => { + // just put new outputs on the right of the previous ones. + // in the future we will only need that as a fallback and need to read saved configurations here + let workspace = &mut self.spaces[active]; + let x = workspace + .space + .outputs() + .map(|output| workspace.space.output_geometry(&output).unwrap()) + .fold(0, |acc, geo| std::cmp::max(acc, geo.loc.x + geo.size.w)); + workspace.space.map_output(output, 1.0, (x, 0)); + self.outputs.push(output.clone()); + } + } + } - 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 = &mut ddata.get::().unwrap().common; - let seat = &state.last_active_seat; - let output = wl_output - .as_ref() - .and_then(Output::from_resource) - .unwrap_or_else(|| active_output(seat, &*state)); + pub fn unmap_output(&mut self, output: &Output) { + match self.mode { + Mode::OutputBound => { + if let Some(idx) = output + .user_data() + .get::() + .and_then(|a| a.get()) + { + self.spaces[idx].space.unmap_output(output); + self.outputs.retain(|o| o != output); + } + } + Mode::Global { active } => { + self.spaces[active].space.unmap_output(output); + self.outputs.retain(|o| o != output); + // TODO move windows and outputs farther on the right / or load save config for remaining monitors + } + } + } - let mut map = layer_map_for_output(&output); - map.map_layer(&LayerSurface::new(surface, namespace)) - .unwrap(); + pub fn output_size(&self, output: &Output) -> Size { + let workspace = self.active_space(output); + workspace + .space + .output_geometry(&output) + .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))) + .size + } + + pub fn global_space(&self) -> Rectangle { + let size = self.outputs.iter().fold((0, 0), |(w, h), output| { + let size = self.output_size(output); + (w + size.w, std::cmp::max(h, size.h)) + }); + + Rectangle::from_loc_and_size((0, 0), size) + } + + pub fn space_relative_output_geometry( + &self, + global_loc: impl Into>, + output: &Output, + ) -> Point { + match self.mode { + Mode::Global { .. } => global_loc.into(), + Mode::OutputBound => global_loc.into() - self.output_geometry(output).loc, + } + } + + pub fn output_geometry(&self, output: &Output) -> Rectangle { + // due to our different modes, we cannot just ask the space for the global output coordinates, + // because for `Mode::OutputBound` the origin will always be (0, 0) + + // TODO: Add a proper grid like structure, for now the outputs just extend to the right + let pos = + self.outputs + .iter() + .take_while(|o| o != &output) + .fold((0, 0), |(x, y), output| { + let size = self.output_size(output); + (x + size.w, y) + }); + + Rectangle::from_loc_and_size(pos, self.output_size(output)) + } + + pub fn activate(&mut self, output: &Output, idx: usize) { + match self.mode { + Mode::OutputBound => { + // TODO check for other outputs already occupying that space + if let Some(active) = output.user_data().get::() { + if let Some(old_idx) = active.set(idx) { + self.spaces[old_idx].space.unmap_output(output); + } + self.spaces[idx].space.map_output(output, 1.0, (0, 0)); + } + // TODO translate windows from previous space size into new size + } + Mode::Global { ref mut active } => { + let old = *active; + *active = idx; + for output in &self.outputs { + let loc = self.spaces[old].space.output_geometry(output).unwrap().loc; + self.spaces[old].space.unmap_output(output); + self.spaces[*active].space.map_output(output, 1.0, loc); + } + } + }; + } + + #[cfg(feature = "debug")] + pub fn mode(&self) -> &Mode { + &self.mode + } + + pub fn set_mode(&mut self, mode: Mode) { + match (&mut self.mode, mode) { + (Mode::OutputBound, Mode::Global { .. }) => { + let active = self + .outputs + .iter() + .next() + .map(|o| { + o.user_data() + .get::() + .unwrap() + .get() + .unwrap() + }) + .unwrap_or(0); + let mut x = 0; + + for output in &self.outputs { + let old_active = output + .user_data() + .get::() + .unwrap() + .clear() + .unwrap(); + let width = self.spaces[old_active] + .space + .output_geometry(output) + .unwrap() + .size + .w; + self.spaces[old_active].space.unmap_output(output); + self.spaces[active].space.map_output(output, 1.0, (x, 0)); + x += width; + } + + self.mode = Mode::Global { active }; + // TODO move windows into new bounds + } + (Mode::Global { active }, new @ Mode::OutputBound) => { + for output in &self.outputs { + self.spaces[*active].space.unmap_output(output); + } + + self.mode = new; + let outputs = self.outputs.drain(..).collect::>(); + for output in &outputs { + self.map_output(output); + } + // TODO move windows into new bounds + // TODO active should probably be mapped somewhere } _ => {} - }, - None, - ); - - ShellStates { - popups: PopupManager::new(None), - xdg: xdg_shell_state, - layer: layer_shell_state, - } -} - -fn check_grab_preconditions( - seat: &Seat, - surface: Option<&WlSurface>, - serial: Serial, -) -> Option<(PointerHandle, PointerGrabStartData)> { - let surface = if let Some(surface) = surface { - surface - } else { - return None; - }; - - // TODO: touch resize. - let pointer = seat.get_pointer().unwrap(); - - // Check that this surface has a click grab. - if !pointer.has_grab(serial) { - return None; + }; } - let start_data = pointer.grab_start_data().unwrap(); - - // If the focus was for a different surface, ignore the request. - if start_data.focus.is_none() - || !start_data - .focus - .as_ref() - .unwrap() - .0 - .as_ref() - .same_client_as(surface.as_ref()) - { - return None; + pub fn outputs(&self) -> impl Iterator { + self.outputs.iter() } - Some((pointer, start_data)) -} - -fn commit(surface: &WlSurface, state: &mut State) { - // TODO figure out which output the surface is on. - for output in state.common.spaces.outputs() { - //.cloned().collect::>().into_iter() { - state.backend.schedule_render(output); - // let space = state.common.spaces.active_space(output); - // get output for surface - } - - let state = &mut state.common; - let _ = with_states(surface, |states| { - states - .data_map - .insert_if_missing(|| SurfaceDropNotifier::from(&*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(); + pub fn active_space(&self, output: &Output) -> &Workspace { + match &self.mode { + Mode::OutputBound => { + let active = output + .user_data() + .get::() + .unwrap() + .get() + .unwrap(); + &self.spaces[active] + } + Mode::Global { active } => &self.spaces[*active], } + } - // 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(); + pub fn active_space_mut(&mut self, output: &Output) -> &mut Workspace { + match &self.mode { + Mode::OutputBound => { + let active = output + .user_data() + .get::() + .unwrap() + .get() + .unwrap(); + &mut self.spaces[active] + } + Mode::Global { active } => &mut self.spaces[*active], + } + } - 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, true); - state.pending_toplevels.retain(|toplevel| { - toplevel - .get_surface() - .map(|s| s != surface) - .unwrap_or(false) - }); + pub fn space_for_surface(&self, surface: &WlSurface) -> Option<&Workspace> { + self.spaces.iter().find(|workspace| { + workspace + .pending_windows + .iter() + .any(|(w, _)| w.toplevel().get_surface() == Some(surface)) + || workspace.space.window_for_surface(surface).is_some() + }) + } + + pub fn space_for_surface_mut(&mut self, surface: &WlSurface) -> Option<&mut Workspace> { + self.spaces + .iter_mut() + .find(|workspace| workspace.space.window_for_surface(surface).is_some()) + } + + pub fn refresh(&mut self) { + for workspace in &mut self.spaces { + workspace.refresh(); + } + } + + pub fn commit(&mut self, surface: &WlSurface) { + let mut new_focus = None; + for (idx, workspace) in self.spaces.iter_mut().enumerate() { + if let Some((window, seat)) = workspace + .pending_windows + .iter() + .find(|(w, _)| w.toplevel().get_surface() == Some(surface)) + .cloned() + { + workspace.map_window(&window, &seat); + if match self.mode { + Mode::OutputBound => self.outputs.iter().any(|o| { + o.user_data() + .get::() + .unwrap() + .get() + .unwrap() + == idx + }), + Mode::Global { active } => active == idx, + } { + new_focus = Some(seat); + } + workspace.pending_windows.retain(|(w, _)| w != &window); + } + workspace.space.commit(surface) + } + if let Some(seat) = new_focus { + self.set_focus(Some(surface), &seat) + } + self.popups.commit(&surface); + } + + pub fn set_focus(&mut self, surface: Option<&WlSurface>, active_seat: &Seat) { + // update FocusStack and notify layouts about new focus (if any window) + if let Some(surface) = surface { + if let Some(workspace) = self.space_for_surface_mut(surface) { + if let Some(window) = workspace.space.window_for_surface(surface) { + if Some(window) != workspace.focus_stack.last().as_ref() { + slog_scope::debug!("Focusing window: {:?}", window); + workspace.focus_stack.append(window); + // also remove popup grabs, if we are switching focus + if let Some(mut popup_grab) = self.popup_grab.take() { + if !popup_grab.has_ended() { + popup_grab.ungrab(PopupUngrabStrategy::All); + } + } + } } } } - return; - } - - if let Some((space, window)) = state - .spaces - .space_for_surface_mut(surface) - .and_then(|space| { - space - .window_for_surface(surface) - .cloned() - .map(|window| (space, window)) - }) - { - let new_location = grabs::ResizeSurfaceGrab::apply_resize_state( - &window, - space.window_location(&window).unwrap(), - window.geometry().size, - ); - if let Some(location) = new_location { - space.map_window(&window, location, true); + // update keyboard focus + if let Some(keyboard) = active_seat.get_keyboard() { + keyboard.set_focus(surface, SERIAL_COUNTER.next_serial()); } - return; - } + // update activate status + let focused_windows = self + .outputs + .iter() + .flat_map(|o| self.active_space(o).focus_stack.last()) + .collect::>(); - if let Some(popup) = state.shell.popups.find_popup(surface) { - let PopupKind::Xdg(ref popup) = popup; - let initial_configure_sent = with_states(surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }) - .unwrap(); - if !initial_configure_sent { - // NOTE: This should never fail as the initial configure is always - // allowed. - popup.send_configure().expect("initial configure failed"); + for workspace in &self.spaces { + for window in workspace.space.windows() { + window.set_activated(focused_windows.contains(window)); + window.configure(); + } } - - return; } - - if let Some(output) = state.spaces.outputs().find(|o| { - let map = layer_map_for_output(o); - map.layer_for_surface(surface).is_some() - }) { - let mut map = layer_map_for_output(output); - let layer = map.layer_for_surface(surface).unwrap(); - - // 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 { - layer.layer_surface().send_configure(); - } - - map.arrange(); - - return; - }; } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs new file mode 100644 index 00000000..dfc4b395 --- /dev/null +++ b/src/shell/workspace.rs @@ -0,0 +1,93 @@ +use super::{layout, Layout}; +use indexmap::IndexSet; +use smithay::{ + desktop::{Space, Window}, + reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::ResizeEdge, + wayland::{ + output::Output, + seat::{PointerGrabStartData, Seat}, + Serial, + }, +}; +pub struct FocusStack(IndexSet); + +impl FocusStack { + pub fn new() -> FocusStack { + FocusStack(IndexSet::new()) + } + + pub fn append(&mut self, window: &Window) { + self.0.retain(|w| w.toplevel().alive()); + self.0.shift_remove(window); + self.0.insert(window.clone()); + } + + pub fn last(&self) -> Option { + self.0.iter().rev().find(|w| w.toplevel().alive()).cloned() + } + + pub fn iter<'a>(&'a self) -> Box + 'a> { + //working around object-safety constraints for trait Layout + Box::new(self.0.iter().rev().filter(|w| w.toplevel().alive())) + } +} + +pub struct Workspace { + pub space: Space, + pub(super) layout: Box, + pub focus_stack: FocusStack, + pub(super) pending_windows: Vec<(Window, Seat)>, +} + +impl Workspace { + pub fn new() -> Workspace { + Workspace { + space: Space::new(None), + layout: layout::new_default_layout(), + focus_stack: FocusStack::new(), + pending_windows: Vec::new(), + } + } + + pub fn pending_window(&mut self, window: Window, seat: &Seat) { + self.pending_windows.push((window, seat.clone())); + } + + pub(super) fn map_window<'a>(&mut self, window: &Window, seat: &Seat) { + self.layout + .map_window(&mut self.space, window, seat, self.focus_stack.iter()) + } + + pub fn refresh(&mut self) { + self.layout.refresh(&mut self.space); + self.space.refresh(); + } + + pub fn maximize_request(&mut self, window: &Window, output: &Output) { + self.layout + .maximize_request(&mut self.space, window, output) + } + + pub fn move_request( + &mut self, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + ) { + self.layout + .move_request(&mut self.space, window, seat, serial, start_data) + } + + pub fn resize_request( + &mut self, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + edges: ResizeEdge, + ) { + self.layout + .resize_request(&mut self.space, window, seat, serial, start_data, edges) + } +} diff --git a/src/shell/workspaces.rs b/src/shell/workspaces.rs deleted file mode 100644 index cfaf68e5..00000000 --- a/src/shell/workspaces.rs +++ /dev/null @@ -1,306 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pub use smithay::{ - desktop::Space, - reexports::wayland_server::protocol::wl_surface::WlSurface, - utils::{Logical, Point, Rectangle, Size}, - wayland::output::Output, -}; -use std::{cell::Cell, mem::MaybeUninit}; - -pub const MAX_WORKSPACES: usize = 10; // TODO? - -pub struct ActiveWorkspace(Cell>); -impl ActiveWorkspace { - fn new() -> Self { - ActiveWorkspace(Cell::new(None)) - } - pub fn get(&self) -> Option { - self.0.get() - } - fn set(&self, active: usize) -> Option { - self.0.replace(Some(active)) - } - fn clear(&self) -> Option { - self.0.replace(None) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum Mode { - OutputBound, - Global { active: usize }, -} - -impl Mode { - pub fn output_bound() -> Mode { - Mode::OutputBound - } - - pub fn global() -> Mode { - Mode::Global { active: 0 } - } -} - -pub struct Workspaces { - mode: Mode, - outputs: Vec, - pub spaces: [Space; MAX_WORKSPACES], -} - -const UNINIT_SPACE: MaybeUninit = MaybeUninit::uninit(); - -impl Workspaces { - pub fn new() -> Self { - Workspaces { - mode: Mode::global(), - outputs: Vec::new(), - spaces: unsafe { - let mut spaces = [UNINIT_SPACE; MAX_WORKSPACES]; - spaces.fill_with(|| MaybeUninit::new(Space::new(None))); - std::mem::transmute(spaces) - }, - } - } - - pub fn map_output(&mut self, output: &Output) { - match self.mode { - Mode::OutputBound => { - output - .user_data() - .insert_if_missing(|| ActiveWorkspace::new()); - - let (idx, space) = self - .spaces - .iter_mut() - .enumerate() - .find(|(_, x)| x.outputs().next().is_none()) - .expect("More then 10 outputs?"); - output - .user_data() - .get::() - .unwrap() - .set(idx); - space.map_output(output, 1.0, (0, 0)); - self.outputs.push(output.clone()); - } - Mode::Global { active } => { - // just put new outputs on the right of the previous ones. - // in the future we will only need that as a fallback and need to read saved configurations here - let space = &mut self.spaces[active]; - let x = space - .outputs() - .map(|output| space.output_geometry(&output).unwrap()) - .fold(0, |acc, geo| std::cmp::max(acc, geo.loc.x + geo.size.w)); - space.map_output(output, 1.0, (x, 0)); - self.outputs.push(output.clone()); - } - } - } - - pub fn unmap_output(&mut self, output: &Output) { - match self.mode { - Mode::OutputBound => { - if let Some(idx) = output - .user_data() - .get::() - .and_then(|a| a.get()) - { - self.spaces[idx].unmap_output(output); - self.outputs.retain(|o| o != output); - } - } - Mode::Global { active } => { - self.spaces[active].unmap_output(output); - self.outputs.retain(|o| o != output); - // TODO move windows and outputs farther on the right / or load save config for remaining monitors - } - } - } - - pub fn output_size(&self, output: &Output) -> Size { - let space = self.active_space(output); - space - .output_geometry(&output) - .unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0))) - .size - } - - pub fn global_space(&self) -> Rectangle { - let size = self.outputs.iter().fold((0, 0), |(w, h), output| { - let size = self.output_size(output); - (w + size.w, std::cmp::max(h, size.h)) - }); - - Rectangle::from_loc_and_size((0, 0), size) - } - - pub fn space_relative_output_geometry( - &self, - global_loc: impl Into>, - output: &Output, - ) -> Point { - match self.mode { - Mode::Global { .. } => global_loc.into(), - Mode::OutputBound => global_loc.into() - self.output_geometry(output).loc, - } - } - - pub fn output_geometry(&self, output: &Output) -> Rectangle { - // due to our different modes, we cannot just ask the space for the global output coordinates, - // because for `Mode::OutputBound` the origin will always be (0, 0) - - // TODO: Add a proper grid like structure, for now the outputs just extend to the right - let pos = - self.outputs - .iter() - .take_while(|o| o != &output) - .fold((0, 0), |(x, y), output| { - let size = self.output_size(output); - (x + size.w, y) - }); - - Rectangle::from_loc_and_size(pos, self.output_size(output)) - } - - pub fn activate(&mut self, output: &Output, idx: usize) { - match self.mode { - Mode::OutputBound => { - // TODO check for other outputs already occupying that space - if let Some(active) = output.user_data().get::() { - if let Some(old_idx) = active.set(idx) { - self.spaces[old_idx].unmap_output(output); - } - self.spaces[idx].map_output(output, 1.0, (0, 0)); - } - // TODO translate windows from previous space size into new size - } - Mode::Global { ref mut active } => { - let old = *active; - *active = idx; - for output in &self.outputs { - let loc = self.spaces[old].output_geometry(output).unwrap().loc; - self.spaces[old].unmap_output(output); - self.spaces[*active].map_output(output, 1.0, loc); - } - } - }; - } - - #[cfg(feature = "debug")] - pub fn mode(&self) -> &Mode { - &self.mode - } - - pub fn set_mode(&mut self, mode: Mode) { - match (&mut self.mode, mode) { - (Mode::OutputBound, Mode::Global { .. }) => { - let active = self - .outputs - .iter() - .next() - .map(|o| { - o.user_data() - .get::() - .unwrap() - .get() - .unwrap() - }) - .unwrap_or(0); - let mut x = 0; - - for output in &self.outputs { - let old_active = output - .user_data() - .get::() - .unwrap() - .clear() - .unwrap(); - let width = self.spaces[old_active] - .output_geometry(output) - .unwrap() - .size - .w; - self.spaces[old_active].unmap_output(output); - self.spaces[active].map_output(output, 1.0, (x, 0)); - x += width; - } - - self.mode = Mode::Global { active }; - // TODO move windows into new bounds - } - (Mode::Global { active }, new @ Mode::OutputBound) => { - for output in &self.outputs { - self.spaces[*active].unmap_output(output); - } - - self.mode = new; - let outputs = self.outputs.drain(..).collect::>(); - for output in &outputs { - self.map_output(output); - } - // TODO move windows into new bounds - // TODO active should probably be mapped somewhere - } - _ => {} - }; - } - - pub fn outputs(&self) -> impl Iterator { - self.outputs.iter() - } - - pub fn active_space(&self, output: &Output) -> &Space { - match &self.mode { - Mode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .get() - .unwrap(); - &self.spaces[active] - } - Mode::Global { active } => &self.spaces[*active], - } - } - - pub fn active_space_mut(&mut self, output: &Output) -> &mut Space { - match &self.mode { - Mode::OutputBound => { - let active = output - .user_data() - .get::() - .unwrap() - .get() - .unwrap(); - &mut self.spaces[active] - } - Mode::Global { active } => &mut self.spaces[*active], - } - } - - 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 4b1a7cdd..b229640f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,7 +3,7 @@ use crate::{ backend::{kms::KmsState, winit::WinitState, x11::X11State}, logger::LogState, - shell::{init_shell, workspaces::Workspaces, ShellStates}, + shell::{init_shell, Shell}, }; use smithay::{ reexports::{ @@ -39,8 +39,7 @@ pub struct Common { pub socket: OsString, pub event_loop_handle: LoopHandle<'static, State>, - pub spaces: Workspaces, - pub shell: ShellStates, + pub shell: Shell, pub pending_toplevels: Vec, pub dirty_flag: Arc, @@ -135,7 +134,7 @@ impl State { ) -> State { init_shm_global(&mut display, vec![], None); init_xdg_output_manager(&mut display, None); - let shell_handles = init_shell(&mut display); + let shell = init_shell(&mut display); let initial_seat = crate::input::add_seat(&mut display, "seat-0".into()); init_data_device( &mut display, @@ -172,8 +171,7 @@ impl State { socket, event_loop_handle: handle, - spaces: Workspaces::new(), - shell: shell_handles, + shell, pending_toplevels: Vec::new(), dirty_flag,