Merge pull request #235 from pinnacle-comp/foreign_toplevel_mgmt

Implement wlr-foreign-toplevel-management
This commit is contained in:
Ottatop 2024-05-08 22:40:56 -05:00 committed by GitHub
commit 35919ac0fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 782 additions and 29 deletions

View file

@ -772,8 +772,8 @@ impl tag_service_server::TagService for TagService {
return;
};
output.with_state_mut(|op_state| {
for op_tag in op_state.tags.iter_mut() {
output.with_state(|op_state| {
for op_tag in op_state.tags.iter() {
op_tag.set_active(false, state);
}
tag.set_active(true, state);

View file

@ -13,13 +13,13 @@ use pinnacle_api_defs::pinnacle::{
},
};
use smithay::{
desktop::{space::SpaceElement, WindowSurface},
desktop::space::SpaceElement,
reexports::wayland_protocols::xdg::shell::server,
utils::{Point, Rectangle, SERIAL_COUNTER},
wayland::seat::WaylandFocus,
};
use tonic::{Request, Response, Status};
use tracing::{error, warn};
use tracing::warn;
use crate::{output::OutputName, state::WithState, tag::TagId, window::window_state::WindowId};
@ -51,18 +51,7 @@ impl window_service_server::WindowService for WindowService {
return;
};
match window.underlying_surface() {
WindowSurface::Wayland(toplevel) => toplevel.send_close(),
WindowSurface::X11(surface) => {
if !surface.is_override_redirect() {
if let Err(err) = surface.close() {
error!("failed to close x11 window: {err}");
}
} else {
warn!("tried to close OR window");
}
}
}
window.close();
})
.await
}

View file

@ -69,9 +69,10 @@ use tracing::{error, trace, warn};
use crate::{
backend::Backend,
delegate_gamma_control, delegate_screencopy,
delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy,
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
protocol::{
foreign_toplevel::{self, ForeignToplevelHandler, ForeignToplevelManagerState},
gamma_control::{GammaControlHandler, GammaControlManagerState},
screencopy::{Screencopy, ScreencopyHandler},
},
@ -457,7 +458,11 @@ impl ShmHandler for State {
}
delegate_shm!(State);
impl OutputHandler for State {}
impl OutputHandler for State {
fn output_bound(&mut self, output: Output, wl_output: WlOutput) {
foreign_toplevel::on_output_bound(self, &output, &wl_output);
}
}
delegate_output!(State);
delegate_viewporter!(State);
@ -678,19 +683,153 @@ impl PointerConstraintsHandler for State {
self.pinnacle
.maybe_activate_pointer_constraint(pointer.current_location());
}
// Testing a smithay patch
// fn constraint_removed(
// &mut self,
// surface: &WlSurface,
// pointer: &PointerHandle<Self>,
// constraint: smithay::wayland::pointer_constraints::PointerConstraint,
// ) {
// // tracing::info!(?constraint);
// }
}
delegate_pointer_constraints!(State);
impl ForeignToplevelHandler for State {
fn foreign_toplevel_manager_state(&mut self) -> &mut ForeignToplevelManagerState {
&mut self.pinnacle.foreign_toplevel_manager_state
}
fn activate(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
let Some(output) = window.output(&self.pinnacle) else {
return;
};
if !window.is_on_active_tag() {
let new_active_tag =
window.with_state(|state| state.tags.iter().min_by_key(|tag| tag.id().0).cloned());
if let Some(tag) = new_active_tag {
output.with_state(|state| {
if state.tags.contains(&tag) {
for op_tag in state.tags.iter() {
op_tag.set_active(false, self);
}
tag.set_active(true, self);
}
});
}
}
output.with_state_mut(|state| state.focus_stack.set_focus(window.clone()));
self.pinnacle.raise_window(window, true);
self.update_keyboard_focus(&output);
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn close(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
window.close();
}
fn set_fullscreen(&mut self, wl_surface: WlSurface, _wl_output: Option<WlOutput>) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn unset_fullscreen(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.toggle_fullscreen();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn set_maximized(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn unset_maximized(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.toggle_maximized();
}
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn set_minimized(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
window.with_state_mut(|state| state.minimized = true);
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
fn unset_minimized(&mut self, wl_surface: WlSurface) {
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
return;
};
window.with_state_mut(|state| state.minimized = false);
let Some(output) = window.output(&self.pinnacle) else {
return;
};
self.pinnacle.request_layout(&output);
self.schedule_render(&output);
}
}
delegate_foreign_toplevel!(State);
impl Pinnacle {
fn position_popup(&self, popup: &PopupSurface) {
trace!("State::position_popup");

View file

@ -1,2 +1,3 @@
pub mod foreign_toplevel;
pub mod gamma_control;
pub mod screencopy;

View file

@ -0,0 +1,595 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Hands down plagiarized from Niri
use std::collections::{hash_map::Entry, HashMap};
use smithay::{
desktop::WindowSurface,
output::Output,
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_protocols_wlr::foreign_toplevel::v1::server::{
zwlr_foreign_toplevel_handle_v1::{self, ZwlrForeignToplevelHandleV1},
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
},
wayland_server::{
self,
backend::ClientId,
protocol::{wl_output::WlOutput, wl_surface::WlSurface},
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, Resource,
},
},
wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData},
};
use tracing::error;
use crate::{
state::{Pinnacle, State, WithState},
window::WindowElement,
};
const VERSION: u32 = 3;
pub struct ForeignToplevelManagerState {
display: DisplayHandle,
instances: Vec<ZwlrForeignToplevelManagerV1>,
toplevels: HashMap<WlSurface, ToplevelData>,
}
#[derive(Default)]
struct ToplevelData {
title: Option<String>,
app_id: Option<String>,
states: Vec<zwlr_foreign_toplevel_handle_v1::State>,
output: Option<Output>,
instances: HashMap<ZwlrForeignToplevelHandleV1, Vec<WlOutput>>,
// TODO:
// parent: Option<ZwlrForeignToplevelHandleV1>,
}
pub trait ForeignToplevelHandler {
fn foreign_toplevel_manager_state(&mut self) -> &mut ForeignToplevelManagerState;
fn activate(&mut self, wl_surface: WlSurface);
fn close(&mut self, wl_surface: WlSurface);
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>);
fn unset_fullscreen(&mut self, wl_surface: WlSurface);
fn set_maximized(&mut self, wl_surface: WlSurface);
fn unset_maximized(&mut self, wl_surface: WlSurface);
fn set_minimized(&mut self, wl_surface: WlSurface);
fn unset_minimized(&mut self, wl_surface: WlSurface);
}
pub struct ForeignToplevelGlobalData {
filter: Box<dyn Fn(&Client) -> bool + Send + Sync>,
}
impl ForeignToplevelManagerState {
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
where
D: GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData>
+ Dispatch<ZwlrForeignToplevelManagerV1, ()>
+ 'static,
F: Fn(&Client) -> bool + Send + Sync + 'static,
{
let global_data = ForeignToplevelGlobalData {
filter: Box::new(filter),
};
display.create_global::<D, ZwlrForeignToplevelManagerV1, _>(VERSION, global_data);
Self {
display: display.clone(),
instances: Vec::new(),
toplevels: HashMap::new(),
}
}
}
pub fn refresh(state: &mut State) {
state
.pinnacle
.foreign_toplevel_manager_state
.toplevels
.retain(|surface, data| {
if state
.pinnacle
.windows
.iter()
.any(|win| win.wl_surface().as_ref() == Some(surface))
{
return true;
}
for instance in data.instances.keys() {
instance.closed();
}
false
});
let mut focused = None;
// FIXME: Initial window mapping bypasses `state.update_keyboard_focus`
// and sets it manually without updating the output keyboard focus stack,
// fix that
let focused_win = state
.pinnacle
.focused_output()
.map(|op| state.pinnacle.focused_window(op));
// OH GOD THE BORROW CHECKER IS HAVING A SEIZURE
// PERF: We sacrifice performance to the borrow checker with this clone
for window in state
.pinnacle
.windows
.clone()
.iter()
.filter(|win| !win.is_x11_override_redirect())
{
if let Some(win) = focused_win.as_ref() {
if win.as_ref() == Some(window) {
focused = Some(window.clone());
continue;
}
}
if let Some(pending_data) = pending_toplevel_data_for(&state.pinnacle, window) {
let Some(surface) = window.wl_surface() else {
continue;
};
refresh_toplevel(
&mut state.pinnacle.foreign_toplevel_manager_state,
&surface,
pending_data,
);
}
}
// Finally, refresh the focused window.
if let Some(window) = focused {
if let Some(pending_data) = pending_toplevel_data_for(&state.pinnacle, &window) {
let Some(surface) = window.wl_surface() else {
return;
};
refresh_toplevel(
&mut state.pinnacle.foreign_toplevel_manager_state,
&surface,
pending_data,
);
}
}
}
fn pending_toplevel_data_for(
pinnacle: &Pinnacle,
win: &WindowElement,
) -> Option<PendingToplevelData> {
let focused_win_and_op = pinnacle
.focused_output()
.map(|op| (pinnacle.focused_window(op), op.clone()));
let surface = win.wl_surface()?;
let output = win.output(pinnacle);
let focused = if let Some((foc_win, _)) = focused_win_and_op.as_ref() {
foc_win.as_ref() == Some(win)
} else {
false
};
compositor::with_states(&surface, |states| match win.underlying_surface() {
WindowSurface::Wayland(_toplevel) => {
let role = states
.data_map
.get::<XdgToplevelSurfaceData>()?
.lock()
.ok()?;
Some(PendingToplevelData {
title: role.title.clone(),
app_id: role.app_id.clone(),
maximized: role.current.states.contains(xdg_toplevel::State::Maximized),
minimized: win.with_state(|state| state.minimized),
fullscreen: role
.current
.states
.contains(xdg_toplevel::State::Fullscreen),
_activated: role.current.states.contains(xdg_toplevel::State::Activated),
focused,
output,
})
}
WindowSurface::X11(x11_surface) => Some(PendingToplevelData {
title: Some(x11_surface.title()),
app_id: Some(x11_surface.class()),
maximized: x11_surface.is_maximized(),
minimized: x11_surface.is_minimized(),
fullscreen: x11_surface.is_fullscreen(),
_activated: x11_surface.is_activated(),
focused,
output,
}),
})
}
pub fn on_output_bound(state: &mut State, output: &Output, wl_output: &WlOutput) {
let Some(client) = wl_output.client() else {
return;
};
let protocol_state = &mut state.pinnacle.foreign_toplevel_manager_state;
for data in protocol_state.toplevels.values_mut() {
if data.output.as_ref() != Some(output) {
continue;
}
for (instance, outputs) in &mut data.instances {
if instance.client().as_ref() != Some(&client) {
continue;
}
instance.output_enter(wl_output);
instance.done();
outputs.push(wl_output.clone());
}
}
}
struct PendingToplevelData {
title: Option<String>,
app_id: Option<String>,
maximized: bool,
minimized: bool,
fullscreen: bool,
_activated: bool,
focused: bool,
output: Option<Output>,
}
/// Refresh foreign toplevel handle state.
fn refresh_toplevel(
protocol_state: &mut ForeignToplevelManagerState,
wl_surface: &WlSurface,
pending_data: PendingToplevelData,
) {
let states = to_state_vec(
pending_data.maximized,
pending_data.minimized,
pending_data.fullscreen,
pending_data.focused,
);
match protocol_state.toplevels.entry(wl_surface.clone()) {
Entry::Occupied(entry) => {
let data = entry.into_mut();
let mut new_title = None;
if data.title != pending_data.title {
data.title.clone_from(&pending_data.title);
new_title = pending_data.title.as_deref();
if new_title.is_none() {
error!("toplevel title changed to None");
}
}
let mut new_app_id = None;
if data.app_id != pending_data.app_id {
data.app_id.clone_from(&pending_data.app_id);
new_app_id = pending_data.app_id.as_deref();
if new_app_id.is_none() {
error!("toplevel app_id changed to None");
}
}
let mut states_changed = false;
if data.states != states {
data.states = states;
states_changed = true;
}
let mut output_changed = false;
if data.output != pending_data.output {
data.output.clone_from(&pending_data.output);
output_changed = true;
}
// TODO:
// let mut parent_changed = false;
// while let Some(parent) = compositor::get_parent(wl_surface) {}
let something_changed =
new_title.is_some() || new_app_id.is_some() || states_changed || output_changed;
if something_changed {
for (instance, outputs) in &mut data.instances {
if let Some(new_title) = new_title {
instance.title(new_title.to_owned());
}
if let Some(new_app_id) = new_app_id {
instance.app_id(new_app_id.to_owned());
}
if states_changed {
instance.state(
data.states
.iter()
.flat_map(|state| (*state as u32).to_ne_bytes())
.collect(),
);
}
if output_changed {
for wl_output in outputs.drain(..) {
instance.output_leave(&wl_output);
}
if let Some(output) = &data.output {
if let Some(client) = instance.client() {
for wl_output in output.client_outputs(&client) {
instance.output_enter(&wl_output);
outputs.push(wl_output);
}
}
}
}
instance.done();
}
}
for outputs in data.instances.values_mut() {
// Clean up dead wl_outputs.
outputs.retain(|x| x.is_alive());
}
}
Entry::Vacant(entry) => {
let mut data = ToplevelData {
title: pending_data.title.clone(),
app_id: pending_data.app_id.clone(),
states,
output: pending_data.output.clone(),
instances: HashMap::new(),
// parent: TODO:
};
for manager in protocol_state.instances.iter() {
if let Some(client) = manager.client() {
data.add_instance::<State>(&protocol_state.display, &client, manager);
}
}
entry.insert(data);
}
}
}
impl ToplevelData {
fn add_instance<D>(
&mut self,
display: &DisplayHandle,
client: &Client,
manager: &ZwlrForeignToplevelManagerV1,
) where
D: Dispatch<ZwlrForeignToplevelHandleV1, ()> + 'static,
{
let toplevel = client
.create_resource::<ZwlrForeignToplevelHandleV1, _, D>(display, manager.version(), ())
.expect("TODO");
manager.toplevel(&toplevel);
if let Some(title) = self.title.clone() {
toplevel.title(title);
}
if let Some(app_id) = self.app_id.clone() {
toplevel.app_id(app_id);
}
// TODO:
// toplevel.parent(self.parent.as_ref());
toplevel.state(
self.states
.iter()
.flat_map(|state| (*state as u32).to_ne_bytes())
.collect(),
);
let mut outputs = Vec::new();
if let Some(output) = self.output.as_ref() {
for wl_output in output.client_outputs(client) {
toplevel.output_enter(&wl_output);
outputs.push(wl_output);
}
}
toplevel.done();
self.instances.insert(toplevel, outputs);
}
}
impl<D> GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData, D>
for ForeignToplevelManagerState
where
D: GlobalDispatch<ZwlrForeignToplevelManagerV1, ForeignToplevelGlobalData>
+ Dispatch<ZwlrForeignToplevelManagerV1, ()>
+ Dispatch<ZwlrForeignToplevelHandleV1, ()>
+ ForeignToplevelHandler,
{
fn bind(
state: &mut D,
handle: &DisplayHandle,
client: &Client,
resource: wayland_server::New<ZwlrForeignToplevelManagerV1>,
_global_data: &ForeignToplevelGlobalData,
data_init: &mut DataInit<'_, D>,
) {
let manager = data_init.init(resource, ());
let state = state.foreign_toplevel_manager_state();
for data in state.toplevels.values_mut() {
data.add_instance::<D>(handle, client, &manager);
}
state.instances.push(manager);
}
fn can_view(client: Client, global_data: &ForeignToplevelGlobalData) -> bool {
(global_data.filter)(&client)
}
}
impl<D> Dispatch<ZwlrForeignToplevelManagerV1, (), D> for ForeignToplevelManagerState
where
D: Dispatch<ZwlrForeignToplevelManagerV1, ()> + ForeignToplevelHandler,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZwlrForeignToplevelManagerV1,
request: <ZwlrForeignToplevelManagerV1 as Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
match request {
zwlr_foreign_toplevel_manager_v1::Request::Stop => {
resource.finished();
state
.foreign_toplevel_manager_state()
.instances
.retain(|instance| instance != resource);
}
_ => unreachable!(),
}
}
fn destroyed(
state: &mut D,
_client: ClientId,
resource: &ZwlrForeignToplevelManagerV1,
_data: &(),
) {
state
.foreign_toplevel_manager_state()
.instances
.retain(|instance| instance != resource);
}
}
impl<D> Dispatch<ZwlrForeignToplevelHandleV1, (), D> for ForeignToplevelManagerState
where
D: Dispatch<ZwlrForeignToplevelHandleV1, ()> + ForeignToplevelHandler,
{
fn request(
state: &mut D,
_client: &Client,
resource: &ZwlrForeignToplevelHandleV1,
request: <ZwlrForeignToplevelHandleV1 as Resource>::Request,
_data: &(),
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
let Some((surface, _)) = state
.foreign_toplevel_manager_state()
.toplevels
.iter()
.find(|(_, data)| data.instances.contains_key(resource))
else {
return;
};
let surface = surface.clone();
match request {
zwlr_foreign_toplevel_handle_v1::Request::SetMaximized => state.set_maximized(surface),
zwlr_foreign_toplevel_handle_v1::Request::UnsetMaximized => {
state.unset_maximized(surface);
}
zwlr_foreign_toplevel_handle_v1::Request::SetMinimized => state.set_minimized(surface),
zwlr_foreign_toplevel_handle_v1::Request::UnsetMinimized => {
state.unset_minimized(surface);
}
zwlr_foreign_toplevel_handle_v1::Request::Activate { seat: _ } => {
state.activate(surface);
}
zwlr_foreign_toplevel_handle_v1::Request::Close => state.close(surface),
zwlr_foreign_toplevel_handle_v1::Request::SetRectangle { .. } => (),
zwlr_foreign_toplevel_handle_v1::Request::Destroy => (),
zwlr_foreign_toplevel_handle_v1::Request::SetFullscreen { output } => {
state.set_fullscreen(surface, output);
}
zwlr_foreign_toplevel_handle_v1::Request::UnsetFullscreen => {
state.unset_fullscreen(surface);
}
_ => unreachable!(),
}
}
fn destroyed(
state: &mut D,
_client: ClientId,
resource: &ZwlrForeignToplevelHandleV1,
_data: &(),
) {
for data in state
.foreign_toplevel_manager_state()
.toplevels
.values_mut()
{
data.instances.retain(|instance, _| instance != resource);
}
}
}
fn to_state_vec(
maximized: bool,
minimized: bool,
fullscreen: bool,
// activated
has_focus: bool,
) -> Vec<zwlr_foreign_toplevel_handle_v1::State> {
let mut state_vec = Vec::new();
if maximized {
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Maximized);
}
if fullscreen {
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen);
}
if minimized {
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Minimized);
}
// HACK: wlr-foreign-toplevel-management states:
//
// These have the same meaning as the states with the same names defined in xdg-toplevel
//
// However, clients such as sfwbar and fcitx seem to treat the activated state as keyboard
// focus, i.e. they don't expect multiple windows to have it set at once. Even Waybar which
// handles multiple activated windows correctly uses it in its design in such a way that
// keyboard focus would make more sense. Let's do what the clients expect.
if has_focus {
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Activated);
}
state_vec
}
#[macro_export]
macro_rules! delegate_foreign_toplevel {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: $crate::protocol::foreign_toplevel::ForeignToplevelGlobalData
] => $crate::protocol::foreign_toplevel::ForeignToplevelManagerState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: ()
] => $crate::protocol::foreign_toplevel::ForeignToplevelManagerState);
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
smithay::reexports::wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: ()
] => $crate::protocol::foreign_toplevel::ForeignToplevelManagerState);
};
}

View file

@ -8,7 +8,11 @@ use crate::{
focus::OutputFocusStack,
grab::resize_grab::ResizeSurfaceState,
layout::LayoutState,
protocol::{gamma_control::GammaControlManagerState, screencopy::ScreencopyManagerState},
protocol::{
foreign_toplevel::{self, ForeignToplevelManagerState},
gamma_control::GammaControlManagerState,
screencopy::ScreencopyManagerState,
},
window::WindowElement,
};
use anyhow::Context;
@ -89,6 +93,7 @@ pub struct Pinnacle {
pub security_context_state: SecurityContextState,
pub relative_pointer_manager_state: RelativePointerManagerState,
pub pointer_constraints_state: PointerConstraintsState,
pub foreign_toplevel_manager_state: ForeignToplevelManagerState,
/// The state of key and mousebinds along with libinput settings
pub input_state: InputState,
@ -129,6 +134,7 @@ impl State {
self.pinnacle.space.refresh();
self.pinnacle.popup_manager.cleanup();
self.update_pointer_focus();
foreign_toplevel::refresh(self);
self.pinnacle
.display_handle
@ -252,6 +258,10 @@ impl Pinnacle {
&display_handle,
),
pointer_constraints_state: PointerConstraintsState::new::<State>(&display_handle),
foreign_toplevel_manager_state: ForeignToplevelManagerState::new::<State, _>(
&display_handle,
filter_restricted_client,
),
input_state: InputState::new(),

View file

@ -11,6 +11,7 @@ use smithay::{
utils::{IsAlive, Logical, Point, Rectangle},
wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData},
};
use tracing::{error, warn};
use crate::state::{Pinnacle, WithState};
@ -98,6 +99,22 @@ impl WindowElement {
}
}
/// Send a close request to this window.
pub fn close(&self) {
match self.underlying_surface() {
WindowSurface::Wayland(toplevel) => toplevel.send_close(),
WindowSurface::X11(surface) => {
if !surface.is_override_redirect() {
if let Err(err) = surface.close() {
error!("failed to close x11 window: {err}");
}
} else {
warn!("tried to close OR window");
}
}
}
}
/// Get the output this window is on.
///
/// This method gets the first tag the window has and returns its output.

View file

@ -47,6 +47,7 @@ pub struct WindowElementState {
pub floating_or_tiled: FloatingOrTiled,
pub fullscreen_or_maximized: FullscreenOrMaximized,
pub target_loc: Option<Point<i32, Logical>>,
pub minimized: bool,
}
impl WindowElement {
@ -295,6 +296,7 @@ impl WindowElementState {
floating_or_tiled: FloatingOrTiled::Tiled(None),
fullscreen_or_maximized: FullscreenOrMaximized::Neither,
target_loc: None,
minimized: false,
}
}
}