mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Merge pull request #235 from pinnacle-comp/foreign_toplevel_mgmt
Implement wlr-foreign-toplevel-management
This commit is contained in:
commit
35919ac0fa
8 changed files with 782 additions and 29 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
163
src/handlers.rs
163
src/handlers.rs
|
@ -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");
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod foreign_toplevel;
|
||||
pub mod gamma_control;
|
||||
pub mod screencopy;
|
||||
|
|
595
src/protocol/foreign_toplevel.rs
Normal file
595
src/protocol/foreign_toplevel.rs
Normal 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);
|
||||
};
|
||||
}
|
12
src/state.rs
12
src/state.rs
|
@ -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(),
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue