Make x11 popups float, fix x11 windows not reappearing

Popups currently appear behind all other windows and apps like Steam show up as black on the winit backend, but stuff like Discord should work.
This commit is contained in:
Ottatop 2023-07-26 16:01:53 -05:00
parent c2190f2e0c
commit fe2a08c704
6 changed files with 176 additions and 76 deletions

View file

@ -121,6 +121,12 @@ impl<B: Backend> CompositorHandler for State<B> {
window.with_state(|state| {
if let WindowResizeState::Acknowledged(new_pos) = state.resize_state {
state.resize_state = WindowResizeState::Idle;
if let WindowElement::X11(surface) = &window {
tracing::debug!("setting x11 win to mapped");
if !surface.is_override_redirect() {
surface.set_mapped(true).expect("failed to map x11 win");
}
}
self.space.map_element(window.clone(), new_pos, false);
}
});
@ -296,7 +302,7 @@ impl<B: Backend> XdgShellHandler for State<B> {
first_tag.layout().layout(
self.windows.clone(),
state.focused_tags().cloned().collect(),
&self.space,
self,
&focused_output,
);
}
@ -350,15 +356,15 @@ impl<B: Backend> XdgShellHandler for State<B> {
.wl_surface()
.is_some_and(|surf| &surf != surface.wl_surface())
});
if let Some(focused_output) = self.focus_state.focused_output.as_ref() {
if let Some(focused_output) = self.focus_state.focused_output.as_ref().cloned() {
focused_output.with_state(|state| {
let first_tag = state.focused_tags().next();
if let Some(first_tag) = first_tag {
first_tag.layout().layout(
self.windows.clone(),
state.focused_tags().cloned().collect(),
&self.space,
focused_output,
self,
&focused_output,
);
}
});

View file

@ -4,8 +4,10 @@
//
// SPDX-License-Identifier: MPL-2.0
use std::time::Duration;
use smithay::{
desktop::space::SpaceElement,
desktop::{space::SpaceElement, utils::surface_primary_scanout_output},
input::pointer::Focus,
reexports::wayland_server::Resource,
utils::{Logical, Rectangle, SERIAL_COUNTER},
@ -20,7 +22,7 @@ use crate::{
backend::Backend,
grab::resize_grab::{ResizeSurfaceGrab, ResizeSurfaceState},
state::{CalloopData, WithState},
window::{WindowBlocker, WindowElement, BLOCKER_COUNTER},
window::{WindowBlocker, WindowElement, BLOCKER_COUNTER, window_state::Float},
};
impl<B: Backend> XwmHandler for CalloopData<B> {
@ -34,16 +36,15 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
fn map_window_request(&mut self, xwm: XwmId, window: X11Surface) {
tracing::debug!("new x11 window from map_window_request");
tracing::debug!("window popup is {}", window.is_popup());
window.set_mapped(true).expect("failed to map x11 window");
let window = WindowElement::X11(window);
// TODO: place the window in the space
self.state.space.map_element(window.clone(), (0, 0), true);
let bbox = self
.state
.space
.element_bbox(&window)
.expect("failed to get x11 bbox");
// let geo = window.geometry();
let WindowElement::X11(surface) = &window else { unreachable!() };
let bbox = self.state.space.element_bbox(&window).unwrap();
tracing::debug!("map_window_request, configuring with bbox {bbox:?}");
surface
.configure(Some(bbox))
.expect("failed to configure x11 window");
@ -73,6 +74,21 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
tracing::debug!("new window, tags are {:?}", state.tags);
});
window.with_state(|state| {
let WindowElement::X11(surface) = &window else { unreachable!() };
let is_popup = surface.window_type().is_some_and(|typ| !matches!(typ, smithay::xwayland::xwm::WmWindowType::Normal));
if surface.is_popup() || is_popup || surface.min_size() == surface.max_size() {
state.floating = Float::Floating;
}
});
// self.state.space.map_element(window.clone(), (0, 0), true);
// self.state.space.raise_element(&window, true);
// let WindowElement::X11(surface) = &window else { unreachable!() };
// self.state.xwm.as_mut().unwrap().raise_window(surface).unwrap();
let windows_on_output = self
.state
.windows
@ -96,7 +112,6 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
.collect::<Vec<_>>();
self.state.windows.push(window.clone());
// self.space.map_element(window.clone(), (0, 0), true);
if let Some(focused_output) = self.state.focus_state.focused_output.clone() {
focused_output.with_state(|state| {
let first_tag = state.focused_tags().next();
@ -104,7 +119,7 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
first_tag.layout().layout(
self.state.windows.clone(),
state.focused_tags().cloned().collect(),
&self.state.space,
&mut self.state,
&focused_output,
);
}
@ -120,8 +135,8 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
}
}
let clone = window.clone();
self.state.loop_handle.insert_idle(|data| {
crate::state::schedule_on_commit(data, vec![clone], move |data| {
self.state.loop_handle.insert_idle(move |data| {
crate::state::schedule_on_commit(data, vec![clone.clone()], move |data| {
BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst);
tracing::debug!(
"blocker {}",
@ -135,6 +150,20 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
.client_compositor_state(&client)
.blocker_cleared(&mut data.state, &data.display.handle())
}
// data.state.loop_handle.insert_idle(move |dt| {
//
// let WindowElement::X11(surface) = &clone else { unreachable!() };
// let is_popup = surface.window_type().is_some_and(|typ| !matches!(typ, smithay::xwayland::xwm::WmWindowType::Normal));
// if surface.is_popup() || is_popup || surface.min_size() == surface.max_size() {
// if let Some(xwm) = dt.state.xwm.as_mut() {
// tracing::debug!("raising x11 modal");
// xwm.raise_window(surface).expect("failed to raise x11 win");
// dt.state.space.raise_element(&clone, true);
// }
// }
// });
})
});
}
@ -152,28 +181,36 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
}
}
fn mapped_override_redirect_window(&mut self, xwm: XwmId, window: X11Surface) {
// fn map_window_notify(&mut self, xwm: XwmId, window: X11Surface) {
// //
// }
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
let loc = window.geometry().loc;
let window = WindowElement::X11(window);
tracing::debug!("mapped_override_redirect_window to loc {loc:?}");
self.state.space.map_element(window, loc, true);
}
fn unmapped_window(&mut self, xwm: XwmId, window: X11Surface) {
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
tracing::debug!("unmapped x11 window");
let win = self
.state
.space
.elements()
.find(|elem| matches!(elem, WindowElement::X11(surface) if surface == &window))
.find(|elem| {
matches!(elem,
WindowElement::X11(surface) if surface == &window)
})
.cloned();
if let Some(win) = win {
self.state.space.unmap_elem(&win);
// self.state.windows.retain(|elem| &win != elem);
if win.with_state(|state| state.floating.is_tiled()) {
if let Some(output) = win.output(&self.state) {
self.state.re_layout(&output);
}
}
// if win.with_state(|state| state.floating.is_tiled()) {
// if let Some(output) = win.output(&self.state) {
// self.state.re_layout(&output);
// }
// }
}
if !window.is_override_redirect() {
window.set_mapped(false).expect("failed to unmap x11 win");
@ -232,6 +269,7 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
geometry: Rectangle<i32, Logical>,
above: Option<smithay::reexports::x11rb::protocol::xproto::Window>,
) {
tracing::debug!("x11 configure_notify");
let Some(win) = self
.state
.space
@ -241,7 +279,11 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
else {
return;
};
tracing::debug!("geo: {geometry:?}");
self.state.space.map_element(win, geometry.loc, false);
// for output in self.state.space.outputs_for_element(&win) {
// win.send_frame(&output, self.state.clock.now(), Some(Duration::ZERO), surface_primary_scanout_output);
// }
// TODO: anvil has a TODO here
}
@ -249,13 +291,16 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
// TODO: unmaximize request
// TODO: fullscreen request
fn fullscreen_request(&mut self, xwm: XwmId, window: X11Surface) {
// TODO:
window.set_fullscreen(true).unwrap();
}
// TODO: unfullscreen request
fn resize_request(
&mut self,
xwm: XwmId,
_xwm: XwmId,
window: X11Surface,
button: u32,
resize_edge: smithay::xwayland::xwm::ResizeEdge,
@ -296,7 +341,7 @@ impl<B: Backend> XwmHandler for CalloopData<B> {
win.clone(),
resize_edge.into(),
Rectangle::from_loc_and_size(initial_window_location, initial_window_size),
0x110, // BUTTON_LEFT
button, // BUTTON_LEFT
);
if let Some(grab) = grab {

View file

@ -2,7 +2,6 @@
use itertools::{Either, Itertools};
use smithay::{
desktop::Space,
output::Output,
utils::{Logical, Size},
};
@ -27,35 +26,30 @@ pub enum Layout {
}
impl Layout {
pub fn layout(
pub fn layout<B: Backend>(
&self,
windows: Vec<WindowElement>,
tags: Vec<Tag>,
space: &Space<WindowElement>,
state: &mut State<B>,
output: &Output,
) {
let windows = filter_windows(&windows, tags);
let Some(output_geo) = space.output_geometry(output) else {
tracing::error!("could not get output geometry");
return;
};
let output_loc = output.current_location();
match self {
Layout::MasterStack => master_stack(windows, space, output),
Layout::Dwindle => dwindle(windows, space, output),
Layout::Spiral => spiral(windows, space, output),
Layout::MasterStack => master_stack(windows, state, output),
Layout::Dwindle => dwindle(windows, state, output),
Layout::Spiral => spiral(windows, state, output),
layout @ (Layout::CornerTopLeft
| Layout::CornerTopRight
| Layout::CornerBottomLeft
| Layout::CornerBottomRight) => corner(layout, windows, space, output),
| Layout::CornerBottomRight) => corner(layout, windows, state, output),
}
}
}
fn master_stack(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &Output) {
fn master_stack<B: Backend>(windows: Vec<WindowElement>, state: &mut State<B>, output: &Output) {
let space = &mut state.space;
let Some(output_geo) = space.output_geometry(output) else {
tracing::error!("could not get output geometry");
return;
@ -72,11 +66,11 @@ fn master_stack(windows: Vec<WindowElement>, space: &Space<WindowElement>, outpu
if stack_count == 0 {
// one window
master.request_size_change(output_loc, output_geo.size);
master.request_size_change(state, output_loc, output_geo.size);
} else {
let loc = (output_loc.x, output_loc.y).into();
let new_master_size: Size<i32, Logical> = (output_geo.size.w / 2, output_geo.size.h).into();
master.request_size_change(loc, new_master_size);
master.request_size_change(state, loc, new_master_size);
let stack_count = stack_count;
@ -93,6 +87,7 @@ fn master_stack(windows: Vec<WindowElement>, space: &Space<WindowElement>, outpu
for (i, win) in stack.enumerate() {
win.request_size_change(
state,
(output_geo.size.w / 2 + output_loc.x, y_s[i] + output_loc.y).into(),
(output_geo.size.w / 2, i32::max(heights[i], 40)).into(),
);
@ -100,7 +95,9 @@ fn master_stack(windows: Vec<WindowElement>, space: &Space<WindowElement>, outpu
}
}
fn dwindle(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &Output) {
fn dwindle<B: Backend>(windows: Vec<WindowElement>, state: &mut State<B>, output: &Output) {
let space = &state.space;
let Some(output_geo) = space.output_geometry(output) else {
tracing::error!("could not get output geometry");
return;
@ -112,7 +109,7 @@ fn dwindle(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &O
if iter.peek().is_none() {
if let Some(window) = windows.first() {
window.request_size_change(output_loc, output_geo.size);
window.request_size_change(state, output_loc, output_geo.size);
}
} else {
let mut win1_size = output_geo.size;
@ -137,6 +134,7 @@ fn dwindle(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &O
let width_partition = win1_size.w / 2;
win1.request_size_change(
state,
win1_loc,
(win1_size.w - width_partition, i32::max(win1_size.h, 40)).into(),
);
@ -144,12 +142,13 @@ fn dwindle(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &O
win1_loc = (win1_loc.x + (win1_size.w - width_partition), win1_loc.y).into();
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
Slice::Below => {
let height_partition = win1_size.h / 2;
win1.request_size_change(
state,
win1_loc,
(win1_size.w, i32::max(win1_size.h - height_partition, 40)).into(),
);
@ -157,14 +156,15 @@ fn dwindle(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &O
win1_loc = (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)).into();
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
}
}
}
}
fn spiral(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &Output) {
fn spiral<B: Backend>(windows: Vec<WindowElement>, state: &mut State<B>, output: &Output) {
let space = &state.space;
let Some(output_geo) = space.output_geometry(output) else {
tracing::error!("could not get output geometry");
return;
@ -176,7 +176,7 @@ fn spiral(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &Ou
if iter.peek().is_none() {
if let Some(window) = windows.first() {
window.request_size_change(output_loc, output_geo.size);
window.request_size_change(state, output_loc, output_geo.size);
}
} else {
let mut win1_loc = output_loc;
@ -206,59 +206,64 @@ fn spiral(windows: Vec<WindowElement>, space: &Space<WindowElement>, output: &Ou
let height_partition = win1_size.h / 2;
win1.request_size_change(
state,
(win1_loc.x, win1_loc.y + height_partition).into(),
(win1_size.w, i32::max(win1_size.h - height_partition, 40)).into(),
);
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
Slice::Below => {
let height_partition = win1_size.h / 2;
win1.request_size_change(
state,
win1_loc,
(win1_size.w, win1_size.h - i32::max(height_partition, 40)).into(),
);
win1_loc = (win1_loc.x, win1_loc.y + (win1_size.h - height_partition)).into();
win1_size = (win1_size.w, i32::max(height_partition, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
Slice::Left => {
let width_partition = win1_size.w / 2;
win1.request_size_change(
state,
(win1_loc.x + width_partition, win1_loc.y).into(),
(win1_size.w - width_partition, i32::max(win1_size.h, 40)).into(),
);
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
Slice::Right => {
let width_partition = win1_size.w / 2;
win1.request_size_change(
state,
win1_loc,
(win1_size.w - width_partition, i32::max(win1_size.h, 40)).into(),
);
win1_loc = (win1_loc.x + (win1_size.w - width_partition), win1_loc.y).into();
win1_size = (width_partition, i32::max(win1_size.h, 40)).into();
win2.request_size_change(win1_loc, win1_size);
win2.request_size_change(state, win1_loc, win1_size);
}
}
}
}
}
fn corner(
fn corner<B: Backend>(
layout: &Layout,
windows: Vec<WindowElement>,
space: &Space<WindowElement>,
state: &mut State<B>,
output: &Output,
) {
let space = &state.space;
let Some(output_geo) = space.output_geometry(output) else {
tracing::error!("could not get output geometry");
return;
@ -268,15 +273,17 @@ fn corner(
match windows.len() {
0 => (),
1 => {
windows[0].request_size_change(output_loc, output_geo.size);
windows[0].request_size_change(state, output_loc, output_geo.size);
}
2 => {
windows[0].request_size_change(
state,
output_loc,
(output_geo.size.w / 2, output_geo.size.h).into(),
);
windows[1].request_size_change(
state,
(output_loc.x + output_geo.size.w / 2, output_loc.y).into(),
(output_geo.size.w / 2, output_geo.size.h).into(),
);
@ -296,6 +303,7 @@ fn corner(
let div_factor = 2;
corner.request_size_change(
state,
match layout {
Layout::CornerTopLeft => (output_loc.x, output_loc.y),
Layout::CornerTopRight => (
@ -335,6 +343,7 @@ fn corner(
for (i, win) in vert_stack.iter().enumerate() {
win.request_size_change(
state,
(
match layout {
Layout::CornerTopLeft | Layout::CornerBottomLeft => {
@ -367,6 +376,7 @@ fn corner(
for (i, win) in horiz_stack.iter().enumerate() {
win.request_size_change(
state,
match layout {
Layout::CornerTopLeft => {
(x_s[i] + output_loc.x, output_loc.y + output_geo.size.h / 2)
@ -392,16 +402,15 @@ fn corner(
fn filter_windows(windows: &[WindowElement], tags: Vec<Tag>) -> Vec<WindowElement> {
windows
.iter()
.filter(|window| window.with_state(|state| state.floating.is_tiled()))
.filter(|window| {
window.with_state(|state| {
state.floating.is_tiled() && {
for tag in state.tags.iter() {
if tags.iter().any(|tg| tg == tag) {
return true;
}
for tag in state.tags.iter() {
if tags.iter().any(|tg| tg == tag) {
return true;
}
false
}
false
})
})
.cloned()

View file

@ -8,7 +8,7 @@
//! While Pinnacle is not a library, this documentation serves to guide those who want to
//! contribute or learn how building something like this works.
#![deny(unused_imports)] // gonna force myself to keep stuff clean
// #![deny(unused_imports)] // gonna force myself to keep stuff clean
#![warn(clippy::unwrap_used)]
mod api;

View file

@ -159,7 +159,7 @@ impl<B: Backend> State<B> {
.element_location(&window)
.unwrap_or((0, 0).into());
let window_size = window.geometry().size;
window.request_size_change(window_loc, window_size);
window.request_size_change(self, window_loc, window_size);
}
Msg::MoveWindowToTag { window_id, tag_id } => {
let Some(window) = window_id.window(self) else { return };
@ -623,11 +623,6 @@ impl<B: Backend> State<B> {
.any(|tag| tag.output(self).is_some_and(|op| &op == output))
})
})
.filter(|win| match win {
WindowElement::Wayland(win) => !win.with_state(|state| state.minimized),
WindowElement::X11(surf) => !surf.is_minimized(),
})
.filter(|win| win.alive())
.cloned()
.collect::<Vec<_>>();
let (render, do_not_render) = output.with_state(|state| {
@ -636,7 +631,7 @@ impl<B: Backend> State<B> {
first_tag.layout().layout(
self.windows.clone(),
state.focused_tags().cloned().collect(),
&self.space,
self,
output,
);
}
@ -656,11 +651,22 @@ impl<B: Backend> State<B> {
})
});
tracing::debug!(
"{} to render, {} to not render",
render.len(),
do_not_render.len()
);
let clone = render.clone();
self.loop_handle.insert_idle(|data| {
schedule_on_commit(data, clone, |dt| {
for win in do_not_render {
dt.state.space.unmap_elem(&win);
if let WindowElement::X11(surface) = win {
if !surface.is_override_redirect() {
surface.set_mapped(false).expect("failed to unmap x11 win");
}
}
}
})
});

View file

@ -181,7 +181,12 @@ impl WindowElement {
}
/// Request a size and loc change.
pub fn request_size_change(&self, new_loc: Point<i32, Logical>, new_size: Size<i32, Logical>) {
pub fn request_size_change<B: Backend>(
&self,
state: &mut State<B>,
new_loc: Point<i32, Logical>,
new_size: Size<i32, Logical>,
) {
match self {
WindowElement::Wayland(window) => {
window.toplevel().with_pending_state(|state| {
@ -193,12 +198,25 @@ impl WindowElement {
});
}
WindowElement::X11(surface) => {
tracing::debug!("sending size change to x11 win");
surface
.configure(Rectangle::from_loc_and_size(new_loc, new_size))
.expect("failed to configure x11 win");
self.with_state(|state| {
state.resize_state = WindowResizeState::Acknowledged(new_loc);
});
// self.with_state(|state| {
// state.resize_state = WindowResizeState::Acknowledged(new_loc);
// });
if !surface.is_override_redirect() {
surface.set_mapped(true).unwrap();
}
state.space.map_element(self.clone(), new_loc, false);
// if let Some(focused_output) = state.focus_state.focused_output.clone() {
// self.send_frame(
// &focused_output,
// state.clock.now(),
// Some(Duration::ZERO),
// surface_primary_scanout_output,
// );
// }
}
}
}
@ -209,6 +227,22 @@ impl WindowElement {
pub fn output<B: Backend>(&self, state: &State<B>) -> Option<Output> {
self.with_state(|st| st.tags.first().and_then(|tag| tag.output(state)))
}
/// Returns `true` if the window element is [`Wayland`].
///
/// [`Wayland`]: WindowElement::Wayland
#[must_use]
pub fn is_wayland(&self) -> bool {
matches!(self, Self::Wayland(..))
}
/// Returns `true` if the window element is [`X11`].
///
/// [`X11`]: WindowElement::X11
#[must_use]
pub fn is_x11(&self) -> bool {
matches!(self, Self::X11(..))
}
}
impl IsAlive for WindowElement {
@ -483,7 +517,7 @@ pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &WindowElement)
});
if let Some((prev_loc, prev_size)) = resize {
window.request_size_change(prev_loc, prev_size);
window.request_size_change(state, prev_loc, prev_size);
}
let output = state.focus_state.focused_output.clone().unwrap();