Speed up per-frame layout, hit testing, and stacking paths
Split ScrollingLayout validation so the per-frame geometry solve only checks scalars and keeps the full tree validation behind debug_assert, removing a HashSet build from every frame. Replace the per-region Vec in decode_regions with a stack array via as_chunks, and skip the window hit test in PointerMotion when Overview is not presenting, where visual_scale_for_window returns 1.0 regardless. Guard sync_window_stacking behind a check that the space already matches the target order with uniform z_index, avoiding an O(n^2) raise_element loop on every redraw. Narrow window_under_visual to the space iterator outside Overview, keeping the collected and sorted candidate list for the Overview branch where front-to-back order is load-bearing. Raise rust-version to 1.98 and clear the resulting Clippy findings across the workspace: let-chain collapses, clamp, and while-let loops.
This commit is contained in:
1 parent
391f6b4e37
commit
67192c0c04
25 files changed
+886
-700
No files matched your search
@@ -1101,7 +1101,7 @@ fn select_outputs(drm: &DrmDevice, profiles: &[OutputProfile]) -> io::Result<Out
|
||||
enabled: settings.enabled,
|
||||
profile: active_profile.map(|profile| profile.name.clone()),
|
||||
physical_size: connector.size(),
|
||||
current_mode: selected_mode.map(|mode| connected_mode_info(mode)),
|
||||
current_mode: selected_mode.map(connected_mode_info),
|
||||
available_modes: connector
|
||||
.modes()
|
||||
.iter()
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Runner {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TERM/grace/reaping must not pause the compositor's event loop.
|
||||
for mut removed in previous {
|
||||
if let Some(mut child) = removed.child.take() {
|
||||
|
||||
@@ -127,21 +127,21 @@ pub(crate) fn surface_regions(surface: &WlSurface) -> Option<Vec<[i32; 5]>> {
|
||||
}
|
||||
|
||||
fn decode_regions(bytes: &[u8]) -> Option<Vec<[i32; 5]>> {
|
||||
if bytes.len() % 20 != 0 || bytes.len() > 32 * 20 {
|
||||
if !bytes.len().is_multiple_of(20) || bytes.len() > 32 * 20 {
|
||||
return None;
|
||||
}
|
||||
bytes
|
||||
.chunks_exact(20)
|
||||
.as_chunks::<20>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|tuple| {
|
||||
let values: Vec<_> = tuple
|
||||
.chunks_exact(4)
|
||||
.map(|v| i32::from_ne_bytes(v.try_into().unwrap()))
|
||||
.collect();
|
||||
let values: [i32; 5] =
|
||||
std::array::from_fn(|index| i32::from_ne_bytes(tuple.as_chunks::<4>().0[index]));
|
||||
(values[2] > 0
|
||||
&& values[3] > 0
|
||||
&& values[4] >= 0
|
||||
&& values.iter().all(|v| v.abs_diff(0) <= 32768))
|
||||
.then(|| [values[0], values[1], values[2], values[3], values[4]])
|
||||
.then_some(values)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -110,19 +110,6 @@ impl CompositorHandler for Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use smithay::wayland::shell::wlr_layer::Layer;
|
||||
#[test]
|
||||
fn panel_commits_do_not_invalidate_their_own_backdrop() {
|
||||
assert!(!layer_affects_backdrop(Layer::Top));
|
||||
assert!(!layer_affects_backdrop(Layer::Overlay));
|
||||
assert!(layer_affects_backdrop(Layer::Bottom));
|
||||
assert!(layer_affects_backdrop(Layer::Background));
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferHandler for Ferese {
|
||||
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
|
||||
}
|
||||
@@ -148,3 +135,16 @@ impl ShmHandler for Ferese {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use smithay::wayland::shell::wlr_layer::Layer;
|
||||
#[test]
|
||||
fn panel_commits_do_not_invalidate_their_own_backdrop() {
|
||||
assert!(!layer_affects_backdrop(Layer::Top));
|
||||
assert!(!layer_affects_backdrop(Layer::Overlay));
|
||||
assert!(layer_affects_backdrop(Layer::Bottom));
|
||||
assert!(layer_affects_backdrop(Layer::Background));
|
||||
}
|
||||
}
|
||||
@@ -107,10 +107,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let reload_handle = event_loop.handle();
|
||||
let result = event_loop.run(None, &mut state, |state| {
|
||||
if let Some(monitor) = &mut monitor {
|
||||
if let Some(result) = monitor.poll(std::time::Instant::now()) {
|
||||
if let Err(error) = result.and_then(|source| state.reload_config_source(source)) {
|
||||
warn!(%error, "config reload rejected; retaining last working config");
|
||||
}
|
||||
if let Some(result) = monitor.poll(std::time::Instant::now())
|
||||
&& let Err(error) = result.and_then(|source| state.reload_config_source(source))
|
||||
{
|
||||
warn!(%error, "config reload rejected; retaining last working config");
|
||||
}
|
||||
// At most one timer exists. Edits arriving during debounce move the
|
||||
// deadline; the old timer wakes once and is replaced if necessary.
|
||||
|
||||
@@ -148,7 +148,7 @@ mod tests {
|
||||
Duration::from_millis(2),
|
||||
&[Rectangle::new((0, 0).into(), (100, 50).into())],
|
||||
0,
|
||||
FrameEffectMetrics::default(),
|
||||
FrameEffectMetrics,
|
||||
);
|
||||
|
||||
assert_eq!(metrics.rendered_frames, 0);
|
||||
|
||||
@@ -92,6 +92,14 @@ pub(crate) fn init_font_loader(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type OverviewStateLabel = HashMap<
|
||||
(String, u64, [u32; 4]),
|
||||
(
|
||||
MemoryRenderBuffer,
|
||||
smithay::utils::Size<i32, smithay::utils::Buffer>,
|
||||
),
|
||||
>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct OverviewState {
|
||||
active: bool,
|
||||
@@ -103,13 +111,7 @@ pub(crate) struct OverviewState {
|
||||
font_family: String,
|
||||
font_requests: Option<FontRequests>,
|
||||
font: Option<FontArc>,
|
||||
labels: HashMap<
|
||||
(String, u64, [u32; 4]),
|
||||
(
|
||||
MemoryRenderBuffer,
|
||||
smithay::utils::Size<i32, smithay::utils::Buffer>,
|
||||
),
|
||||
>,
|
||||
labels: OverviewStateLabel,
|
||||
}
|
||||
|
||||
impl Default for OverviewState {
|
||||
@@ -480,6 +482,7 @@ impl Ferese {
|
||||
let selected = self
|
||||
.window_under_visual(point)
|
||||
.and_then(|w| self.window_ids.get(&w).copied());
|
||||
|
||||
if selected.is_some() && self.overview.selected != selected {
|
||||
self.overview.selected = selected;
|
||||
crate::backends::direct::render_all(self);
|
||||
@@ -655,8 +658,7 @@ impl Ferese {
|
||||
let card_width =
|
||||
((strip.width - 24.0 - WORKSPACE_CARD_GAP * capacity.saturating_sub(1) as f64)
|
||||
/ capacity as f64)
|
||||
.min(160.0)
|
||||
.max(1.0);
|
||||
.clamp(1.0, 160.0);
|
||||
let visible_count = workspaces.len().saturating_sub(offset).min(capacity);
|
||||
let start_x = centered_row_start(strip, visible_count, card_width);
|
||||
workspaces
|
||||
|
||||
@@ -74,14 +74,12 @@ impl Ferese {
|
||||
.space
|
||||
.outputs()
|
||||
.all(|output| self.session_lock.presented.contains(output))
|
||||
&& let Some(confirmation) = self.session_lock.confirmation.take()
|
||||
&& confirmation.ext_session_lock().is_alive()
|
||||
{
|
||||
if let Some(confirmation) = self.session_lock.confirmation.take() {
|
||||
if confirmation.ext_session_lock().is_alive() {
|
||||
confirmation.lock();
|
||||
self.session_lock.confirmed = true;
|
||||
tracing::info!("session lock confirmed after safe output frames");
|
||||
}
|
||||
}
|
||||
confirmation.lock();
|
||||
self.session_lock.confirmed = true;
|
||||
tracing::info!("session lock confirmed after safe output frames");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,14 +161,14 @@ impl SessionLockHandler for Ferese {
|
||||
}
|
||||
|
||||
fn new_surface(&mut self, surface: LockSurface, output: WlOutput) {
|
||||
if let Some(output) = Output::from_resource(&output) {
|
||||
if let Some(geometry) = self.space.output_geometry(&output) {
|
||||
surface.with_pending_state(|state| {
|
||||
state.size = Some((geometry.size.w as u32, geometry.size.h as u32).into())
|
||||
});
|
||||
self.session_lock.surfaces.insert(output, surface);
|
||||
self.focus_lock_surface();
|
||||
}
|
||||
if let Some(output) = Output::from_resource(&output)
|
||||
&& let Some(geometry) = self.space.output_geometry(&output)
|
||||
{
|
||||
surface.with_pending_state(|state| {
|
||||
state.size = Some((geometry.size.w as u32, geometry.size.h as u32).into())
|
||||
});
|
||||
self.session_lock.surfaces.insert(output, surface);
|
||||
self.focus_lock_surface();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+302
-229
@@ -24,7 +24,7 @@ use smithay::{
|
||||
},
|
||||
desktop::{
|
||||
LayerSurface, PopupManager, Space, Window, WindowSurfaceType, layer_map_for_output,
|
||||
utils::send_frames_surface_tree,
|
||||
space::SpaceElement, utils::send_frames_surface_tree,
|
||||
},
|
||||
input::{
|
||||
Seat, SeatState,
|
||||
@@ -241,17 +241,205 @@ pub struct RuntimeConfig {
|
||||
}
|
||||
|
||||
impl Ferese {
|
||||
pub fn new(
|
||||
event_loop: &mut EventLoop<'static, Self>,
|
||||
display: Display<Self>,
|
||||
config: RuntimeConfig,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let display_handle = display.handle();
|
||||
crate::handlers::screencopy::init_global(&display_handle);
|
||||
crate::effects::init_global(&display_handle);
|
||||
crate::shell_control::init_global(&display_handle);
|
||||
let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle);
|
||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
|
||||
let xdg_shell_state = XdgShellState::new::<Self>(&display_handle);
|
||||
let decoration_state = XdgDecorationState::new::<Self>(&display_handle);
|
||||
let dmabuf_state = DmabufState::new();
|
||||
let fractional_scale_state = FractionalScaleManagerState::new::<Self>(&display_handle);
|
||||
let idle_inhibit_state = IdleInhibitManagerState::new::<Self>(&display_handle);
|
||||
let idle_notifier_state = IdleNotifierState::new(&display_handle, event_loop.handle());
|
||||
let session_lock_state = smithay::wayland::session_lock::SessionLockManagerState::new::<
|
||||
Self,
|
||||
_,
|
||||
>(&display_handle, |_| true);
|
||||
let input_method_enabled =
|
||||
std::env::var_os("FERESE_ENABLE_INPUT_METHOD").is_some_and(|value| value == "1");
|
||||
let input_method_manager_state =
|
||||
InputMethodManagerState::new::<Self, _>(&display_handle, move |_| input_method_enabled);
|
||||
let keyboard_shortcuts_inhibit_state =
|
||||
KeyboardShortcutsInhibitState::new::<Self>(&display_handle);
|
||||
let shortcut_inhibit_enabled =
|
||||
std::env::var_os("FERESE_ENABLE_SHORTCUT_INHIBIT").is_some_and(|value| value == "1");
|
||||
if !shortcut_inhibit_enabled {
|
||||
display_handle.disable_global::<Self>(keyboard_shortcuts_inhibit_state.global());
|
||||
}
|
||||
let shm_state = ShmState::new::<Self>(&display_handle, Vec::new());
|
||||
let single_pixel_buffer_state = SinglePixelBufferState::new::<Self>(&display_handle);
|
||||
let text_input_manager_state = TextInputManagerState::new::<Self>(&display_handle);
|
||||
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
|
||||
let pointer_constraints_state = PointerConstraintsState::new::<Self>(&display_handle);
|
||||
let presentation_state =
|
||||
PresentationState::new::<Self>(&display_handle, libc::CLOCK_MONOTONIC as u32);
|
||||
let data_device_state = DataDeviceState::new::<Self>(&display_handle);
|
||||
let primary_selection_state = PrimarySelectionState::new::<Self>(&display_handle);
|
||||
let relative_pointer_state = RelativePointerManagerState::new::<Self>(&display_handle);
|
||||
let viewporter_state = ViewporterState::new::<Self>(&display_handle);
|
||||
let layer_shell_state = WlrLayerShellState::new::<Self>(&display_handle);
|
||||
let xdg_activation_state = XdgActivationState::new::<Self>(&display_handle);
|
||||
let xdg_foreign_state = XdgForeignState::new::<Self>(&display_handle);
|
||||
let xdg_toplevel_icon_manager = XdgToplevelIconManager::new::<Self>(&display_handle);
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit");
|
||||
let xkb_options = (!config.input_settings.xkb_options.is_empty())
|
||||
.then(|| config.input_settings.xkb_options.join(","));
|
||||
seat.add_keyboard(
|
||||
XkbConfig {
|
||||
layout: &config.input_settings.xkb_layout,
|
||||
variant: &config.input_settings.xkb_variant,
|
||||
options: xkb_options,
|
||||
..XkbConfig::default()
|
||||
},
|
||||
config.input_settings.repeat_delay_ms,
|
||||
config.input_settings.repeat_rate,
|
||||
)?;
|
||||
seat.add_pointer();
|
||||
seat.add_touch();
|
||||
let socket_name = Self::init_wayland_listener(display, event_loop)?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
let cursor_theme = crate::cursor::cursor_theme();
|
||||
let default_cursor = crate::cursor::load_named_cursor(&cursor_theme, CursorIcon::Default);
|
||||
let named_cursors = HashMap::from([(CursorIcon::Default, default_cursor)]);
|
||||
|
||||
let mut state = Self {
|
||||
session_lock_state,
|
||||
session_lock: crate::session_lock::Lock::default(),
|
||||
config_source: None,
|
||||
start_time,
|
||||
socket_name,
|
||||
display_handle,
|
||||
loop_signal: event_loop.get_signal(),
|
||||
space: Space::default(),
|
||||
workspaces: WorkspaceSet::new(
|
||||
config.layout_mode,
|
||||
config.default_column_width,
|
||||
config.scrolling_focus_strategy,
|
||||
),
|
||||
output_workspaces: OutputWorkspaceMap::default(),
|
||||
output_ids: HashMap::new(),
|
||||
output_identity_ids: HashMap::new(),
|
||||
window_ids: HashMap::new(),
|
||||
window_geometry: HashMap::new(),
|
||||
resize_transactions: HashMap::new(),
|
||||
resize_snapshots: HashMap::new(),
|
||||
nested_backend: None,
|
||||
wallpaper: crate::wallpaper::WallpaperState::with_wakeup(
|
||||
config.wallpaper,
|
||||
Some(event_loop.get_signal()),
|
||||
),
|
||||
maximized_windows: HashSet::new(),
|
||||
maximized_column_widths: HashMap::new(),
|
||||
window_stack: crate::stacking::WindowStack::default(),
|
||||
natural_floating_pending: HashSet::new(),
|
||||
window_borders: HashMap::new(),
|
||||
window_dims: HashMap::new(),
|
||||
window_resize_fills: HashMap::new(),
|
||||
window_dimming: HashMap::new(),
|
||||
window_focus: HashMap::new(),
|
||||
window_shadows: HashMap::new(),
|
||||
rounded_clip_programs: HashMap::new(),
|
||||
overview_scrims: HashMap::new(),
|
||||
material_programs: HashMap::new(),
|
||||
material_buffers: HashMap::new(),
|
||||
blur_programs: HashMap::new(),
|
||||
backdrop_generation: 0,
|
||||
closing_windows: HashMap::new(),
|
||||
viewport_animations: HashMap::new(),
|
||||
scrolling_world_x: HashMap::new(),
|
||||
viewport_coupled_widths: HashMap::new(),
|
||||
pending_column_width_cycles: HashSet::new(),
|
||||
focused_window: None,
|
||||
column_width_presets: config.column_width_presets,
|
||||
gap_config: config.gap_config,
|
||||
input_settings: config.input_settings,
|
||||
bindings: config.bindings,
|
||||
window_rules: config.window_rules,
|
||||
window_rules_applied: HashSet::new(),
|
||||
theme_settings: config.theme_settings,
|
||||
animations_enabled: config.animations_enabled,
|
||||
animation_speed: config.animation_speed,
|
||||
spring_config: config.spring_config,
|
||||
viewport_spring_config: config.viewport_spring_config,
|
||||
output_profiles: config.output_profiles,
|
||||
autostart: config.autostart,
|
||||
cursor_status: CursorImageStatus::default_named(),
|
||||
cursor_redraw_pending: false,
|
||||
cursor_theme,
|
||||
named_cursors,
|
||||
intercepted_keys: HashSet::new(),
|
||||
swipe: crate::gestures::Swipe::default(),
|
||||
idle_inhibitors: HashMap::new(),
|
||||
active_shortcuts_inhibitor: None,
|
||||
direct_backend: None,
|
||||
_ipc_socket: None,
|
||||
pending_dmabuf_imports: Vec::new(),
|
||||
pending_screencopies: Vec::new(),
|
||||
shell_resources: Vec::new(),
|
||||
shell_snapshot_serial: 0,
|
||||
last_shell_snapshot: None,
|
||||
overview: crate::overview::OverviewState::with_font_family(config.overview_font_family),
|
||||
next_window_id: 1,
|
||||
next_output_id: 1,
|
||||
last_animation_tick: start_time,
|
||||
popups: PopupManager::default(),
|
||||
dismissing_popups: Vec::new(),
|
||||
seat,
|
||||
alpha_modifier_state,
|
||||
compositor_state,
|
||||
cursor_shape_state,
|
||||
data_device_state,
|
||||
decoration_state,
|
||||
dmabuf_state,
|
||||
fractional_scale_state,
|
||||
idle_inhibit_state,
|
||||
idle_notifier_state,
|
||||
input_method_manager_state,
|
||||
keyboard_shortcuts_inhibit_state,
|
||||
output_manager_state,
|
||||
pointer_constraints_state,
|
||||
presentation_state,
|
||||
primary_selection_state,
|
||||
relative_pointer_state,
|
||||
seat_state,
|
||||
shm_state,
|
||||
single_pixel_buffer_state,
|
||||
text_input_manager_state,
|
||||
viewporter_state,
|
||||
layer_shell_state,
|
||||
xdg_activation_state,
|
||||
xdg_foreign_state,
|
||||
xdg_shell_state,
|
||||
xdg_toplevel_icon_manager,
|
||||
};
|
||||
state._ipc_socket = Some(crate::ipc::init(event_loop)?);
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub(crate) fn apply_runtime_config(&mut self, config: RuntimeConfig) -> Result<(), String> {
|
||||
if self.output_profiles != config.output_profiles {
|
||||
crate::backends::direct::validate_live_outputs(self, &config.output_profiles)?;
|
||||
}
|
||||
|
||||
let keyboard_changed = self.input_settings.xkb_layout != config.input_settings.xkb_layout
|
||||
|| self.input_settings.xkb_variant != config.input_settings.xkb_variant
|
||||
|| self.input_settings.xkb_options != config.input_settings.xkb_options;
|
||||
|
||||
if let Some(keyboard) = self.seat.get_keyboard() {
|
||||
if keyboard_changed {
|
||||
let options = (!config.input_settings.xkb_options.is_empty())
|
||||
.then(|| config.input_settings.xkb_options.join(","));
|
||||
|
||||
keyboard
|
||||
.set_xkb_config(
|
||||
self,
|
||||
@@ -269,7 +457,9 @@ impl Ferese {
|
||||
config.input_settings.repeat_delay_ms,
|
||||
);
|
||||
}
|
||||
|
||||
self.advance_animations(Instant::now());
|
||||
|
||||
let touchpad_changed = self.input_settings.touchpad != config.input_settings.touchpad;
|
||||
let outputs_changed = self.output_profiles != config.output_profiles;
|
||||
let bounds = self
|
||||
@@ -287,6 +477,7 @@ impl Ferese {
|
||||
(workspace.id, rect)
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.workspaces
|
||||
.reconfigure_live(
|
||||
config.layout_mode,
|
||||
@@ -307,20 +498,25 @@ impl Ferese {
|
||||
self.viewport_spring_config = config.viewport_spring_config;
|
||||
self.output_profiles = config.output_profiles;
|
||||
self.autostart = config.autostart;
|
||||
|
||||
if touchpad_changed {
|
||||
crate::backends::direct::reload_input_devices(self);
|
||||
}
|
||||
|
||||
if outputs_changed {
|
||||
crate::backends::direct::reload_outputs(self);
|
||||
}
|
||||
|
||||
if old_rules != self.window_rules {
|
||||
self.reapply_window_rules(&old_rules);
|
||||
}
|
||||
|
||||
self.overview.set_font_family(config.overview_font_family);
|
||||
self.wallpaper.reload(config.wallpaper);
|
||||
self.backdrop_generation = self.backdrop_generation.wrapping_add(1);
|
||||
self.relayout();
|
||||
crate::backends::direct::render_all(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -509,191 +705,6 @@ impl Ferese {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
event_loop: &mut EventLoop<'static, Self>,
|
||||
display: Display<Self>,
|
||||
config: RuntimeConfig,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let display_handle = display.handle();
|
||||
crate::handlers::screencopy::init_global(&display_handle);
|
||||
crate::effects::init_global(&display_handle);
|
||||
crate::shell_control::init_global(&display_handle);
|
||||
let alpha_modifier_state = AlphaModifierState::new::<Self>(&display_handle);
|
||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
|
||||
let xdg_shell_state = XdgShellState::new::<Self>(&display_handle);
|
||||
let decoration_state = XdgDecorationState::new::<Self>(&display_handle);
|
||||
let dmabuf_state = DmabufState::new();
|
||||
let fractional_scale_state = FractionalScaleManagerState::new::<Self>(&display_handle);
|
||||
let idle_inhibit_state = IdleInhibitManagerState::new::<Self>(&display_handle);
|
||||
let idle_notifier_state = IdleNotifierState::new(&display_handle, event_loop.handle());
|
||||
let session_lock_state = smithay::wayland::session_lock::SessionLockManagerState::new::<
|
||||
Self,
|
||||
_,
|
||||
>(&display_handle, |_| true);
|
||||
let input_method_enabled =
|
||||
std::env::var_os("FERESE_ENABLE_INPUT_METHOD").is_some_and(|value| value == "1");
|
||||
let input_method_manager_state =
|
||||
InputMethodManagerState::new::<Self, _>(&display_handle, move |_| input_method_enabled);
|
||||
let keyboard_shortcuts_inhibit_state =
|
||||
KeyboardShortcutsInhibitState::new::<Self>(&display_handle);
|
||||
let shortcut_inhibit_enabled =
|
||||
std::env::var_os("FERESE_ENABLE_SHORTCUT_INHIBIT").is_some_and(|value| value == "1");
|
||||
if !shortcut_inhibit_enabled {
|
||||
display_handle.disable_global::<Self>(keyboard_shortcuts_inhibit_state.global());
|
||||
}
|
||||
let shm_state = ShmState::new::<Self>(&display_handle, Vec::new());
|
||||
let single_pixel_buffer_state = SinglePixelBufferState::new::<Self>(&display_handle);
|
||||
let text_input_manager_state = TextInputManagerState::new::<Self>(&display_handle);
|
||||
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
|
||||
let pointer_constraints_state = PointerConstraintsState::new::<Self>(&display_handle);
|
||||
let presentation_state =
|
||||
PresentationState::new::<Self>(&display_handle, libc::CLOCK_MONOTONIC as u32);
|
||||
let data_device_state = DataDeviceState::new::<Self>(&display_handle);
|
||||
let primary_selection_state = PrimarySelectionState::new::<Self>(&display_handle);
|
||||
let relative_pointer_state = RelativePointerManagerState::new::<Self>(&display_handle);
|
||||
let viewporter_state = ViewporterState::new::<Self>(&display_handle);
|
||||
let layer_shell_state = WlrLayerShellState::new::<Self>(&display_handle);
|
||||
let xdg_activation_state = XdgActivationState::new::<Self>(&display_handle);
|
||||
let xdg_foreign_state = XdgForeignState::new::<Self>(&display_handle);
|
||||
let xdg_toplevel_icon_manager = XdgToplevelIconManager::new::<Self>(&display_handle);
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit");
|
||||
let xkb_options = (!config.input_settings.xkb_options.is_empty())
|
||||
.then(|| config.input_settings.xkb_options.join(","));
|
||||
seat.add_keyboard(
|
||||
XkbConfig {
|
||||
layout: &config.input_settings.xkb_layout,
|
||||
variant: &config.input_settings.xkb_variant,
|
||||
options: xkb_options,
|
||||
..XkbConfig::default()
|
||||
},
|
||||
config.input_settings.repeat_delay_ms,
|
||||
config.input_settings.repeat_rate,
|
||||
)?;
|
||||
seat.add_pointer();
|
||||
seat.add_touch();
|
||||
let socket_name = Self::init_wayland_listener(display, event_loop)?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
let cursor_theme = crate::cursor::cursor_theme();
|
||||
let default_cursor = crate::cursor::load_named_cursor(&cursor_theme, CursorIcon::Default);
|
||||
let named_cursors = HashMap::from([(CursorIcon::Default, default_cursor)]);
|
||||
|
||||
let mut state = Self {
|
||||
session_lock_state,
|
||||
session_lock: crate::session_lock::Lock::default(),
|
||||
config_source: None,
|
||||
start_time,
|
||||
socket_name,
|
||||
display_handle,
|
||||
loop_signal: event_loop.get_signal(),
|
||||
space: Space::default(),
|
||||
workspaces: WorkspaceSet::new(
|
||||
config.layout_mode,
|
||||
config.default_column_width,
|
||||
config.scrolling_focus_strategy,
|
||||
),
|
||||
output_workspaces: OutputWorkspaceMap::default(),
|
||||
output_ids: HashMap::new(),
|
||||
output_identity_ids: HashMap::new(),
|
||||
window_ids: HashMap::new(),
|
||||
window_geometry: HashMap::new(),
|
||||
resize_transactions: HashMap::new(),
|
||||
resize_snapshots: HashMap::new(),
|
||||
nested_backend: None,
|
||||
wallpaper: crate::wallpaper::WallpaperState::with_wakeup(
|
||||
config.wallpaper,
|
||||
Some(event_loop.get_signal()),
|
||||
),
|
||||
maximized_windows: HashSet::new(),
|
||||
maximized_column_widths: HashMap::new(),
|
||||
window_stack: crate::stacking::WindowStack::default(),
|
||||
natural_floating_pending: HashSet::new(),
|
||||
window_borders: HashMap::new(),
|
||||
window_dims: HashMap::new(),
|
||||
window_resize_fills: HashMap::new(),
|
||||
window_dimming: HashMap::new(),
|
||||
window_focus: HashMap::new(),
|
||||
window_shadows: HashMap::new(),
|
||||
rounded_clip_programs: HashMap::new(),
|
||||
overview_scrims: HashMap::new(),
|
||||
material_programs: HashMap::new(),
|
||||
material_buffers: HashMap::new(),
|
||||
blur_programs: HashMap::new(),
|
||||
backdrop_generation: 0,
|
||||
closing_windows: HashMap::new(),
|
||||
viewport_animations: HashMap::new(),
|
||||
scrolling_world_x: HashMap::new(),
|
||||
viewport_coupled_widths: HashMap::new(),
|
||||
pending_column_width_cycles: HashSet::new(),
|
||||
focused_window: None,
|
||||
column_width_presets: config.column_width_presets,
|
||||
gap_config: config.gap_config,
|
||||
input_settings: config.input_settings,
|
||||
bindings: config.bindings,
|
||||
window_rules: config.window_rules,
|
||||
window_rules_applied: HashSet::new(),
|
||||
theme_settings: config.theme_settings,
|
||||
animations_enabled: config.animations_enabled,
|
||||
animation_speed: config.animation_speed,
|
||||
spring_config: config.spring_config,
|
||||
viewport_spring_config: config.viewport_spring_config,
|
||||
output_profiles: config.output_profiles,
|
||||
autostart: config.autostart,
|
||||
cursor_status: CursorImageStatus::default_named(),
|
||||
cursor_redraw_pending: false,
|
||||
cursor_theme,
|
||||
named_cursors,
|
||||
intercepted_keys: HashSet::new(),
|
||||
swipe: crate::gestures::Swipe::default(),
|
||||
idle_inhibitors: HashMap::new(),
|
||||
active_shortcuts_inhibitor: None,
|
||||
direct_backend: None,
|
||||
_ipc_socket: None,
|
||||
pending_dmabuf_imports: Vec::new(),
|
||||
pending_screencopies: Vec::new(),
|
||||
shell_resources: Vec::new(),
|
||||
shell_snapshot_serial: 0,
|
||||
last_shell_snapshot: None,
|
||||
overview: crate::overview::OverviewState::with_font_family(config.overview_font_family),
|
||||
next_window_id: 1,
|
||||
next_output_id: 1,
|
||||
last_animation_tick: start_time,
|
||||
popups: PopupManager::default(),
|
||||
dismissing_popups: Vec::new(),
|
||||
seat,
|
||||
alpha_modifier_state,
|
||||
compositor_state,
|
||||
cursor_shape_state,
|
||||
data_device_state,
|
||||
decoration_state,
|
||||
dmabuf_state,
|
||||
fractional_scale_state,
|
||||
idle_inhibit_state,
|
||||
idle_notifier_state,
|
||||
input_method_manager_state,
|
||||
keyboard_shortcuts_inhibit_state,
|
||||
output_manager_state,
|
||||
pointer_constraints_state,
|
||||
presentation_state,
|
||||
primary_selection_state,
|
||||
relative_pointer_state,
|
||||
seat_state,
|
||||
shm_state,
|
||||
single_pixel_buffer_state,
|
||||
text_input_manager_state,
|
||||
viewporter_state,
|
||||
layer_shell_state,
|
||||
xdg_activation_state,
|
||||
xdg_foreign_state,
|
||||
xdg_shell_state,
|
||||
xdg_toplevel_icon_manager,
|
||||
};
|
||||
state._ipc_socket = Some(crate::ipc::init(event_loop)?);
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
fn init_wayland_listener(
|
||||
display: Display<Self>,
|
||||
event_loop: &mut EventLoop<'static, Self>,
|
||||
@@ -836,35 +847,38 @@ impl Ferese {
|
||||
}
|
||||
|
||||
pub fn window_under_visual(&self, position: Point<f64, Logical>) -> Option<Window> {
|
||||
let overview_active = self.overview.is_active();
|
||||
let workspace = self.workspace_under_pointer(position)?;
|
||||
let mut candidates = if self.overview.is_active() {
|
||||
self.window_ids.keys().cloned().collect::<Vec<_>>()
|
||||
} else {
|
||||
self.space.elements().rev().cloned().collect::<Vec<_>>()
|
||||
};
|
||||
if self.overview.is_active() {
|
||||
// Match Overview's front-to-back render order during overlapping motion.
|
||||
candidates.sort_by_key(|window| {
|
||||
std::cmp::Reverse(self.window_ids.get(window).map_or(0, |id| id.0))
|
||||
});
|
||||
}
|
||||
candidates.iter().find_map(|window| {
|
||||
|
||||
let hit = |window: &Window| -> Option<Window> {
|
||||
if !self.window_content_ready(window) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let id = self.window_ids.get(window)?;
|
||||
if self.workspaces.workspace_for_window(*id) != Some(workspace) {
|
||||
return None;
|
||||
}
|
||||
let visual = self.presented_window_rect(*id)?;
|
||||
|
||||
let caption_height = if self.overview.is_active() { 34.0 } else { 0.0 };
|
||||
let visual = self.presented_window_rect(*id)?;
|
||||
let caption_height = if overview_active { 34.0 } else { 0.0 };
|
||||
(position.x >= visual.x
|
||||
&& position.y >= visual.y
|
||||
&& position.x < visual.x + visual.width
|
||||
&& position.y < visual.y + visual.height + caption_height)
|
||||
.then(|| window.clone())
|
||||
})
|
||||
.then_some(window.clone())
|
||||
};
|
||||
|
||||
if overview_active {
|
||||
let mut candidates = self.window_ids.keys().cloned().collect::<Vec<_>>();
|
||||
// Match Overview's front-to-back render order during overlapping motion.
|
||||
candidates.sort_by_key(|window| {
|
||||
std::cmp::Reverse(self.window_ids.get(window).map_or(0, |id| id.0))
|
||||
});
|
||||
candidates.iter().find_map(hit)
|
||||
} else {
|
||||
self.space.elements().rev().find_map(hit)
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_under_pointer(&self, position: Point<f64, Logical>) -> Option<WorkspaceId> {
|
||||
@@ -909,6 +923,9 @@ impl Ferese {
|
||||
id.map_or(usize::MAX, |id| self.window_stack.rank(id)),
|
||||
)
|
||||
});
|
||||
if stacking_order_settled(self.space.elements(), &windows, |window| window.z_index()) {
|
||||
return;
|
||||
}
|
||||
for window in windows {
|
||||
self.space.raise_element(&window, false);
|
||||
}
|
||||
@@ -1571,18 +1588,17 @@ impl Ferese {
|
||||
fullscreen_changed || maximized_changed || tiled_changed || decoration_changed
|
||||
});
|
||||
|
||||
if natural_pending || requested_size.is_some() || state_changed {
|
||||
if let Some(serial) = toplevel.send_pending_configure()
|
||||
&& requested_size.is_some()
|
||||
&& had_geometry
|
||||
&& self.animations_enabled
|
||||
{
|
||||
self.resize_transactions.insert(
|
||||
id,
|
||||
crate::resize_transaction::ResizeTransaction::new(serial, now)
|
||||
.with_source_geometry(window.geometry()),
|
||||
);
|
||||
}
|
||||
if (natural_pending || requested_size.is_some() || state_changed)
|
||||
&& let Some(serial) = toplevel.send_pending_configure()
|
||||
&& requested_size.is_some()
|
||||
&& had_geometry
|
||||
&& self.animations_enabled
|
||||
{
|
||||
self.resize_transactions.insert(
|
||||
id,
|
||||
crate::resize_transaction::ResizeTransaction::new(serial, now)
|
||||
.with_source_geometry(window.geometry()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2341,22 +2357,22 @@ impl Ferese {
|
||||
}
|
||||
if self.workspaces.placement(window) == Some(WindowPlacement::Tiled) {
|
||||
let workspace_id = self.workspaces.workspace_for_window(window).unwrap();
|
||||
if let Some(workspace) = self.workspaces.workspace_mut(workspace_id) {
|
||||
if let WorkspaceLayout::Scrolling(layout) = &mut workspace.layout {
|
||||
if enabled {
|
||||
if let Some(column) = layout
|
||||
.columns()
|
||||
.iter()
|
||||
.find(|column| column.windows.contains(&window))
|
||||
{
|
||||
self.maximized_column_widths
|
||||
.entry(window)
|
||||
.or_insert(column.width);
|
||||
}
|
||||
let _ = layout.set_column_width(window, ColumnWidth::Proportion(1.0));
|
||||
} else if let Some(width) = self.maximized_column_widths.remove(&window) {
|
||||
let _ = layout.set_column_width(window, width);
|
||||
if let Some(workspace) = self.workspaces.workspace_mut(workspace_id)
|
||||
&& let WorkspaceLayout::Scrolling(layout) = &mut workspace.layout
|
||||
{
|
||||
if enabled {
|
||||
if let Some(column) = layout
|
||||
.columns()
|
||||
.iter()
|
||||
.find(|column| column.windows.contains(&window))
|
||||
{
|
||||
self.maximized_column_widths
|
||||
.entry(window)
|
||||
.or_insert(column.width);
|
||||
}
|
||||
let _ = layout.set_column_width(window, ColumnWidth::Proportion(1.0));
|
||||
} else if let Some(width) = self.maximized_column_widths.remove(&window) {
|
||||
let _ = layout.set_column_width(window, width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2870,6 +2886,18 @@ fn window_has_buffer(window: &Window) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn stacking_order_settled<'a, T, I>(current: I, target: &'a [T], z_index: impl Fn(&T) -> u8) -> bool
|
||||
where
|
||||
I: IntoIterator<Item = &'a T>,
|
||||
T: PartialEq,
|
||||
{
|
||||
let Some((first, rest)) = target.split_first() else {
|
||||
return current.into_iter().next().is_none();
|
||||
};
|
||||
let expected = z_index(first);
|
||||
rest.iter().all(|window| z_index(window) == expected) && current.into_iter().eq(target)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClientState {
|
||||
pub compositor_state: CompositorClientState,
|
||||
@@ -3016,4 +3044,49 @@ mod tests {
|
||||
1_000.0
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
struct StackedWindow {
|
||||
id: u32,
|
||||
z_index: u8,
|
||||
}
|
||||
|
||||
fn stacked(id: u32, z_index: u8) -> StackedWindow {
|
||||
StackedWindow { id, z_index }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacking_is_settled_when_order_matches_and_z_index_is_uniform() {
|
||||
let current = [stacked(1, 30), stacked(2, 30), stacked(3, 30)];
|
||||
assert!(stacking_order_settled(current.iter(), ¤t, |window| {
|
||||
window.z_index
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacking_is_not_settled_when_order_differs() {
|
||||
let current = [stacked(2, 30), stacked(1, 30)];
|
||||
let target = [stacked(1, 30), stacked(2, 30)];
|
||||
assert!(!stacking_order_settled(current.iter(), &target, |window| {
|
||||
window.z_index
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacking_is_not_settled_when_z_index_is_mixed_even_with_matching_order() {
|
||||
let current = [stacked(1, 30), stacked(2, 40)];
|
||||
assert!(!stacking_order_settled(
|
||||
current.iter(),
|
||||
¤t,
|
||||
|window| window.z_index
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacking_is_settled_for_an_empty_space() {
|
||||
let empty: [StackedWindow; 0] = [];
|
||||
assert!(stacking_order_settled(empty.iter(), &empty, |window| {
|
||||
window.z_index
|
||||
}));
|
||||
}
|
||||
}
|
||||
+39
-40
@@ -1017,7 +1017,7 @@ pub(crate) fn frame_effect_metrics(
|
||||
_elements: &[AnimatedWindowRenderElement],
|
||||
_scale: f64,
|
||||
) -> FrameEffectMetrics {
|
||||
FrameEffectMetrics::default()
|
||||
FrameEffectMetrics
|
||||
}
|
||||
|
||||
fn output_elements(
|
||||
@@ -1220,45 +1220,44 @@ fn output_elements(
|
||||
output,
|
||||
&programs,
|
||||
);
|
||||
if !scale_content {
|
||||
if let Some(snapshot) = state.resize_snapshots.get(&id)
|
||||
&& snapshot.context == renderer.context_id().erased()
|
||||
&& (snapshot.scale - scale).abs() < 0.001
|
||||
{
|
||||
let size = snapshot.texture.size();
|
||||
let visible =
|
||||
Rectangle::new(pixels.loc, (size.w, size.h).into()).intersection(pixels);
|
||||
if let Some(visible) = visible {
|
||||
let clip = framebuffer_clip_rect(
|
||||
pixels,
|
||||
output.current_mode().unwrap().size,
|
||||
output.current_transform().invert(),
|
||||
);
|
||||
elements.push(
|
||||
NativeTextureElement {
|
||||
id: snapshot.id.clone(),
|
||||
commit: snapshot.commit,
|
||||
texture: snapshot.texture.clone(),
|
||||
geometry: visible,
|
||||
source: Rectangle::from_size(Size::from((
|
||||
f64::from(visible.size.w),
|
||||
f64::from(visible.size.h),
|
||||
))),
|
||||
alpha: crate::presentation::handoff_alpha(snapshot.elapsed)
|
||||
* close_alpha,
|
||||
program: Some(programs.texture.clone()),
|
||||
uniforms: vec![
|
||||
Uniform::new("clip_rect", clip).into_owned(),
|
||||
Uniform::new(
|
||||
"radius",
|
||||
scaled_effect_value(window_radius, constrain, scale),
|
||||
)
|
||||
.into_owned(),
|
||||
],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if !scale_content
|
||||
&& let Some(snapshot) = state.resize_snapshots.get(&id)
|
||||
&& snapshot.context == renderer.context_id().erased()
|
||||
&& (snapshot.scale - scale).abs() < 0.001
|
||||
{
|
||||
let size = snapshot.texture.size();
|
||||
let visible =
|
||||
Rectangle::new(pixels.loc, (size.w, size.h).into()).intersection(pixels);
|
||||
if let Some(visible) = visible {
|
||||
let clip = framebuffer_clip_rect(
|
||||
pixels,
|
||||
output.current_mode().unwrap().size,
|
||||
output.current_transform().invert(),
|
||||
);
|
||||
elements.push(
|
||||
NativeTextureElement {
|
||||
id: snapshot.id.clone(),
|
||||
commit: snapshot.commit,
|
||||
texture: snapshot.texture.clone(),
|
||||
geometry: visible,
|
||||
source: Rectangle::from_size(Size::from((
|
||||
f64::from(visible.size.w),
|
||||
f64::from(visible.size.h),
|
||||
))),
|
||||
alpha: crate::presentation::handoff_alpha(snapshot.elapsed)
|
||||
* close_alpha,
|
||||
program: Some(programs.texture.clone()),
|
||||
uniforms: vec![
|
||||
Uniform::new("clip_rect", clip).into_owned(),
|
||||
Uniform::new(
|
||||
"radius",
|
||||
scaled_effect_value(window_radius, constrain, scale),
|
||||
)
|
||||
.into_owned(),
|
||||
],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
elements.extend(rounded_window_elements(
|
||||
|
||||
Reference in new issue
Block a user