diff --git a/.github/workflows/ldoc.yml b/.github/workflows/ldoc.yml index e8bbc90c5..ae6be9e3c 100644 --- a/.github/workflows/ldoc.yml +++ b/.github/workflows/ldoc.yml @@ -31,9 +31,9 @@ jobs: repository: Ottatop/ldoc_gen path: ./ldoc_gen - name: Setup Lua - uses: leafo/gh-actions-lua@v8 + uses: leafo/gh-actions-lua@v10 with: - luaVersion: 5.4 + luaVersion: "5.4" - name: Setup Lua Rocks uses: leafo/gh-actions-luarocks@v4 - name: Setup dependencies diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 143b64ab4..72cf9b2b5 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -42,55 +42,49 @@ require("pinnacle").setup(function(pinnacle) -- Mousebinds -------------------------------------------------------------------- - input.mousebind({ "Ctrl" }, buttons.left, "Press", function() - window.begin_move(buttons.left) - end) - input.mousebind({ "Ctrl" }, buttons.right, "Press", function() - window.begin_resize(buttons.right) - end) + input.mousebind({"Ctrl"}, buttons.left, "Press", + function() window.begin_move(buttons.left) end) + input.mousebind({"Ctrl"}, buttons.right, "Press", + function() window.begin_resize(buttons.right) end) -- Keybinds ---------------------------------------------------------------------- -- mod_key + Alt + q quits the compositor - input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit) + input.keybind({mod_key, "Alt"}, keys.q, pinnacle.quit) -- mod_key + Alt + c closes the focused window - input.keybind({ mod_key, "Alt" }, keys.c, function() - window.get_focused():close() - end) + input.keybind({mod_key, "Alt"}, keys.c, + function() window.get_focused():close() end) -- mod_key + return spawns a terminal - input.keybind({ mod_key }, keys.Return, function() + input.keybind({mod_key}, keys.Return, function() process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg) -- do something with the output here end) end) -- mod_key + Alt + Space toggle floating on the focused window - input.keybind({ mod_key, "Alt" }, keys.space, function() - window.get_focused():toggle_floating() - end) + input.keybind({mod_key, "Alt"}, keys.space, + function() window.get_focused():toggle_floating() end) -- mod_key + f toggles fullscreen on the focused window - input.keybind({ mod_key }, keys.f, function() - window.get_focused():toggle_fullscreen() - end) + input.keybind({mod_key}, keys.f, + function() window.get_focused():toggle_fullscreen() end) -- mod_key + m toggles maximized on the focused window - input.keybind({ mod_key }, keys.m, function() - window.get_focused():toggle_maximized() - end) + input.keybind({mod_key}, keys.m, + function() window.get_focused():toggle_maximized() end) -- Tags --------------------------------------------------------------------------- - local tags = { "1", "2", "3", "4", "5" } + local tags = {"1", "2", "3", "4", "5"} output.connect_for_all(function(op) -- Add tags 1, 2, 3, 4 and 5 on all monitors, and toggle tag 1 active by default op:add_tags(tags) -- Same as tag.add(op, "1", "2", "3", "4", "5") - tag.toggle({ name = "1", output = op }) + tag.toggle({name = "1", output = op}) -- Window rules -- Add your own window rules here. Below is an example. @@ -117,35 +111,30 @@ require("pinnacle").setup(function(pinnacle) -- Create a layout cycler to cycle your tag layouts. This will store which layout each tag has -- and change to the next or previous one in the array when the respective function is called. local layout_cycler = tag.layout_cycler({ - "MasterStack", - "Dwindle", - "Spiral", - "CornerTopLeft", - "CornerTopRight", - "CornerBottomLeft", - "CornerBottomRight", + "MasterStack", "Dwindle", "Spiral", "CornerTopLeft", "CornerTopRight", + "CornerBottomLeft", "CornerBottomRight" }) - input.keybind({ mod_key }, keys.space, layout_cycler.next) - input.keybind({ mod_key, "Shift" }, keys.space, layout_cycler.prev) + input.keybind({mod_key}, keys.space, layout_cycler.next) + input.keybind({mod_key, "Shift"}, keys.space, layout_cycler.prev) -- Tag manipulation for _, tag_name in pairs(tags) do -- mod_key + 1-5 switches tags - input.keybind({ mod_key }, tag_name, function() - tag.switch_to(tag_name) - end) + input.keybind({mod_key}, tag_name, + function() tag.switch_to(tag_name) end) -- mod_key + Shift + 1-5 toggles tags - input.keybind({ mod_key, "Shift" }, tag_name, function() - tag.toggle(tag_name) - end) + input.keybind({mod_key, "Shift"}, tag_name, + function() tag.toggle(tag_name) end) -- mod_key + Alt + 1-5 moves windows to tags - input.keybind({ mod_key, "Alt" }, tag_name, function() + input.keybind({mod_key, "Alt"}, tag_name, + function() window:get_focused():move_to_tag(tag_name) end) -- mod_key + Shift + Alt + 1-5 toggles tags on windows - input.keybind({ mod_key, "Shift", "Alt" }, tag_name, function() + input.keybind({mod_key, "Shift", "Alt"}, tag_name, + function() window.get_focused():toggle_tag(tag_name) end) end diff --git a/src/backend.rs b/src/backend.rs index 272ef35f7..3b6647986 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -204,7 +204,7 @@ impl DmabufHandler for State { .map_err(|_| ()), }; - if let Ok(_) = res { + if res.is_ok() { let _ = notifier.successful::(); } else { notifier.failed(); diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 43080380b..6ab9677b9 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -3,7 +3,6 @@ use std::{ collections::{HashMap, HashSet}, ffi::OsString, - os::fd::FromRawFd, path::Path, time::Duration, }; @@ -243,7 +242,7 @@ pub fn run_udev() -> anyhow::Result<()> { let (session, notifier) = LibSeatSession::new()?; // Get the primary gpu - let primary_gpu = udev::primary_gpu(&session.seat()) + let primary_gpu = udev::primary_gpu(session.seat()) .context("unable to get primary gpu path")? .and_then(|x| { DrmNode::from_path(x) @@ -1341,61 +1340,6 @@ fn render_surface( clock: &Clock, ) -> Result { - use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; - - let pending_wins = windows - .iter() - .filter(|win| win.alive()) - .filter(|win| { - let pending_size = if let WindowElement::Wayland(win) = win { - let current_state = win.toplevel().current_state(); - win.toplevel() - .with_pending_state(|state| state.size != current_state.size) - } else { - false - }; - pending_size || win.with_state(|state| !state.loc_request_state.is_idle()) - }) - .filter(|win| { - if let WindowElement::Wayland(win) = win { - !win.toplevel() - .current_state() - .states - .contains(xdg_toplevel::State::Resizing) - } else { - true - } - }) - .map(|win| { - ( - win.class().unwrap_or("None".to_string()), - win.title().unwrap_or("None".to_string()), - win.with_state(|state| state.loc_request_state.clone()), - ) - }) - .collect::>(); - - if !pending_wins.is_empty() { - tracing::debug!("Skipping frame, waiting on {pending_wins:?}"); - for win in windows.iter() { - win.send_frame(output, clock.now(), Some(Duration::ZERO), |_, _| { - Some(output.clone()) - }); - } - - surface - .compositor - .queue_frame(None) - .map_err(Into::::into)?; - - tracing::debug!("queued no frame"); - - // TODO: still draw the cursor here - surface.render_state = RenderState::WaitingForVblank { dirty: false }; - - return Ok(true); - } - let output_render_elements = crate::render::generate_render_elements( output, renderer, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 0cbb05edc..609d701cf 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -12,10 +12,7 @@ use smithay::{ }, winit::{WinitEvent, WinitGraphicsBackend}, }, - desktop::{ - layer_map_for_output, - utils::{send_frames_surface_tree, surface_primary_scanout_output}, - }, + desktop::{layer_map_for_output, utils::send_frames_surface_tree}, input::pointer::CursorImageStatus, output::{Output, Subpixel}, reexports::{ @@ -23,10 +20,7 @@ use smithay::{ timer::{TimeoutAction, Timer}, EventLoop, }, - wayland_protocols::{ - wp::presentation_time::server::wp_presentation_feedback, - xdg::shell::server::xdg_toplevel, - }, + wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, wayland_server::{protocol::wl_surface::WlSurface, Display}, winit::platform::pump_events::PumpStatus, }, @@ -36,8 +30,7 @@ use smithay::{ use crate::{ render::{pointer::PointerElement, take_presentation_feedback}, - state::{CalloopData, State, WithState}, - window::WindowElement, + state::{CalloopData, State}, }; use super::{Backend, BackendData}; @@ -264,57 +257,6 @@ impl State { fn render_winit_window(&mut self, output: &Output) { let winit = self.backend.winit_mut(); - let pending_wins = self - .windows - .iter() - .filter(|win| win.alive()) - .filter(|win| { - let pending_size = if let WindowElement::Wayland(win) = win { - let current_state = win.toplevel().current_state(); - win.toplevel() - .with_pending_state(|state| state.size != current_state.size) - } else { - false - }; - pending_size || win.with_state(|state| !state.loc_request_state.is_idle()) - }) - .filter(|win| { - if let WindowElement::Wayland(win) = win { - !win.toplevel() - .current_state() - .states - .contains(xdg_toplevel::State::Resizing) - } else { - true - } - }) - .map(|win| { - ( - win.class().unwrap_or("None".to_string()), - win.title().unwrap_or("None".to_string()), - win.with_state(|state| state.loc_request_state.clone()), - ) - }) - .collect::>(); - - if !pending_wins.is_empty() { - // tracing::debug!("Skipping frame, waiting on {pending_wins:?}"); - let op_clone = output.clone(); - self.loop_handle.insert_idle(move |dt| { - for win in dt.state.windows.iter() { - win.send_frame( - &op_clone, - dt.state.clock.now(), - Some(Duration::ZERO), - surface_primary_scanout_output, - ); - } - }); - - // TODO: still draw the cursor here - - return; - } let full_redraw = &mut winit.full_redraw; *full_redraw = full_redraw.saturating_sub(1); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 4b77bfe18..d05390f89 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -88,6 +88,7 @@ impl XdgShellHandler for State { if let Some(focused_output) = data.state.focus_state.focused_output.clone() { data.state.update_windows(&focused_output); } + data.state.loop_handle.insert_idle(move |data| { data.state .seat diff --git a/src/layout.rs b/src/layout.rs index e7be84ca1..bf00833dd 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,14 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-or-later +use std::sync::atomic::AtomicU32; + use smithay::{ + backend::renderer::utils::with_renderer_surface_state, desktop::layer_map_for_output, output::Output, - utils::{IsAlive, Logical, Point, Rectangle, Size}, + reexports::wayland_server::Resource, + utils::{Logical, Point, Rectangle, Serial, Size}, + wayland::compositor::{self, CompositorHandler}, }; use crate::{ state::{State, WithState}, window::{ + blocker::TiledWindowBlocker, window_state::{FloatingOrTiled, FullscreenOrMaximized, LocationRequestState}, WindowElement, }, @@ -51,6 +57,15 @@ impl State { /// Compute tiled window locations and sizes, resize maximized and fullscreen windows correctly, /// and send configures and that cool stuff. pub fn update_windows(&mut self, output: &Output) { + // HACK: With the blocker implementation, if I opened up a bunch of windows quickly they + // | would freeze up. + // | So instead of being smart and figuring something out I instead decided "f*** it" + // | and stuck a static here to detect if update_windows was called again, causing + // | previous blockers to be unblocked. + static UPDATE_COUNT: AtomicU32 = AtomicU32::new(0); + UPDATE_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let current_update_count = UPDATE_COUNT.load(std::sync::atomic::Ordering::Relaxed); + tracing::debug!("Updating windows"); let Some(layout) = output.with_state(|state| state.focused_tags().next().map(|tag| tag.layout())) @@ -117,7 +132,7 @@ impl State { } } - let mut pending_wins = Vec::<(Point<_, _>, WindowElement)>::new(); + let mut pending_wins = Vec::<(WindowElement, Serial)>::new(); let mut non_pending_wins = Vec::<(Point<_, _>, WindowElement)>::new(); // TODO: completely refactor how tracking state changes works @@ -143,7 +158,7 @@ impl State { let serial = win.toplevel().send_configure(); state.loc_request_state = LocationRequestState::Requested(serial, loc); - pending_wins.push((loc, window.clone())); + pending_wins.push((window.clone(), serial)); } } WindowElement::X11(surface) => { @@ -165,18 +180,69 @@ impl State { }); } - self.schedule( - move |_dt| { - let all_idle = pending_wins - .iter() - .filter(|(_, win)| win.alive()) - .all(|(_, win)| win.with_state(|state| state.loc_request_state.is_idle())); + let tiling_blocker = TiledWindowBlocker::new(pending_wins.clone()); + + for (win, _) in pending_wins.iter() { + if let Some(surface) = win.wl_surface() { + let client = surface + .client() + .expect("Surface has no client/is no longer alive"); + self.client_compositor_state(&client) + .blocker_cleared(self, &self.display_handle.clone()); - all_idle + tracing::debug!("blocker cleared"); + compositor::add_blocker(&surface, tiling_blocker.clone()); + } + } + + let pending_wins_clone = pending_wins.clone(); + let pending_wins_clone2 = pending_wins.clone(); + + self.schedule( + move |data| { + if UPDATE_COUNT.load(std::sync::atomic::Ordering::Relaxed) > current_update_count { + for (win, _) in pending_wins_clone2.iter() { + let Some(surface) = win.wl_surface() else { + continue; + }; + + let client = surface + .client() + .expect("Surface has no client/is no longer alive"); + data.state + .client_compositor_state(&client) + .blocker_cleared(&mut data.state, &data.display_handle); + + tracing::debug!("blocker cleared"); + } + } + tiling_blocker.ready() + && pending_wins_clone2.iter().all(|(win, _)| { + if let Some(surface) = win.wl_surface() { + with_renderer_surface_state(&surface, |state| state.buffer().is_some()) + } else { + true + } + }) }, - move |dt| { + move |data| { + for (win, _) in pending_wins_clone { + let Some(surface) = win.wl_surface() else { + continue; + }; + + let client = surface + .client() + .expect("Surface has no client/is no longer alive"); + data.state + .client_compositor_state(&client) + .blocker_cleared(&mut data.state, &data.display_handle); + + tracing::debug!("blocker cleared"); + } + for (loc, win) in non_pending_wins { - dt.state.space.map_element(win, loc, false); + data.state.space.map_element(win, loc, false); } }, ); @@ -215,8 +281,6 @@ fn master_stack(windows: Vec, rect: Rectangle) { let new_master_size: Size = (size.w / 2, size.h).into(); master.change_geometry(Rectangle::from_loc_and_size(loc, new_master_size)); - let stack_count = stack_count; - let height = size.h as f32 / stack_count as f32; let mut y_s = vec![]; for i in 0..stack_count { diff --git a/src/window.rs b/src/window.rs index 89179dc58..54f9330a3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later +pub mod blocker; pub mod rules; use std::{cell::RefCell, time::Duration}; diff --git a/src/window/blocker.rs b/src/window/blocker.rs new file mode 100644 index 000000000..550edb171 --- /dev/null +++ b/src/window/blocker.rs @@ -0,0 +1,74 @@ +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; + +use smithay::{ + utils::Serial, + wayland::{ + compositor::{self, Blocker, BlockerState}, + shell::xdg::XdgToplevelSurfaceData, + }, +}; + +use super::WindowElement; + +#[derive(Debug, Clone)] +pub struct TiledWindowBlocker { + wins_and_serials: Arc>, + start_time: Instant, +} + +impl TiledWindowBlocker { + pub fn new(wins_and_serials: impl IntoIterator) -> Self { + let wins_and_serials = wins_and_serials.into_iter().collect::>(); + + Self { + wins_and_serials: Arc::new(wins_and_serials), + start_time: Instant::now(), + } + } + + // From cosmic-comp + pub fn ready(&self) -> bool { + let too_long_since_start = + Instant::now().duration_since(self.start_time) >= Duration::from_millis(500); + + let all_windows_acked = self.wins_and_serials.iter().all(|(win, serial)| match win { + WindowElement::Wayland(win) => { + compositor::with_states(win.toplevel().wl_surface(), |states| { + let attrs = states + .data_map + .get::() + .expect("no XdgToplevelSurfaceData") + .lock() + .expect("failed to lock mutex"); + + attrs + .configure_serial + .as_ref() + .map(|s| s >= serial) + .unwrap_or(false) + }) + } + WindowElement::X11(_) | WindowElement::X11OverrideRedirect(_) => true, + }); + + tracing::debug!( + "blocker ready is {}", + too_long_since_start || all_windows_acked + ); + + too_long_since_start || all_windows_acked + } +} + +impl Blocker for TiledWindowBlocker { + fn state(&self) -> BlockerState { + if self.ready() { + BlockerState::Released + } else { + BlockerState::Pending + } + } +}