mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2025-01-14 08:01:14 +01:00
Add most of foreign toplevel
Xwayland windows don't show up currently
This commit is contained in:
parent
5d3f66a747
commit
f8fb8fddfb
7 changed files with 715 additions and 26 deletions
|
@ -13,13 +13,13 @@ use pinnacle_api_defs::pinnacle::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{space::SpaceElement, WindowSurface},
|
desktop::space::SpaceElement,
|
||||||
reexports::wayland_protocols::xdg::shell::server,
|
reexports::wayland_protocols::xdg::shell::server,
|
||||||
utils::{Point, Rectangle, SERIAL_COUNTER},
|
utils::{Point, Rectangle, SERIAL_COUNTER},
|
||||||
wayland::seat::WaylandFocus,
|
wayland::seat::WaylandFocus,
|
||||||
};
|
};
|
||||||
use tonic::{Request, Response, Status};
|
use tonic::{Request, Response, Status};
|
||||||
use tracing::{error, warn};
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{output::OutputName, state::WithState, tag::TagId, window::window_state::WindowId};
|
use crate::{output::OutputName, state::WithState, tag::TagId, window::window_state::WindowId};
|
||||||
|
|
||||||
|
@ -51,18 +51,7 @@ impl window_service_server::WindowService for WindowService {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match window.underlying_surface() {
|
window.close();
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
140
src/handlers.rs
140
src/handlers.rs
|
@ -69,9 +69,10 @@ use tracing::{error, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
delegate_gamma_control, delegate_screencopy,
|
delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy,
|
||||||
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
|
focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget},
|
||||||
protocol::{
|
protocol::{
|
||||||
|
foreign_toplevel::{ForeignToplevelHandler, ForeignToplevelManagerState},
|
||||||
gamma_control::{GammaControlHandler, GammaControlManagerState},
|
gamma_control::{GammaControlHandler, GammaControlManagerState},
|
||||||
screencopy::{Screencopy, ScreencopyHandler},
|
screencopy::{Screencopy, ScreencopyHandler},
|
||||||
},
|
},
|
||||||
|
@ -678,19 +679,136 @@ impl PointerConstraintsHandler for State {
|
||||||
self.pinnacle
|
self.pinnacle
|
||||||
.maybe_activate_pointer_constraint(pointer.current_location());
|
.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);
|
delegate_pointer_constraints!(State);
|
||||||
|
|
||||||
|
// FIXME: duplicated code with api calls
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
output.with_state_mut(|state| state.focus_stack.set_focus(window));
|
||||||
|
self.update_keyboard_focus(&output);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self, wl_surface: WlSurface) {
|
||||||
|
let Some(window) = self.pinnacle.window_for_surface(&wl_surface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fullscreen on specified output? maybe
|
||||||
|
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_fullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_fullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Pinnacle {
|
||||||
fn position_popup(&self, popup: &PopupSurface) {
|
fn position_popup(&self, popup: &PopupSurface) {
|
||||||
trace!("State::position_popup");
|
trace!("State::position_popup");
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod foreign_toplevel;
|
||||||
pub mod gamma_control;
|
pub mod gamma_control;
|
||||||
pub mod screencopy;
|
pub mod screencopy;
|
||||||
|
|
552
src/protocol/foreign_toplevel.rs
Normal file
552
src/protocol/foreign_toplevel.rs
Normal file
|
@ -0,0 +1,552 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// Hands down plagiarized from Niri
|
||||||
|
|
||||||
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
|
|
||||||
|
use smithay::{
|
||||||
|
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::{ToplevelStateSet, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::state::{State, WithState};
|
||||||
|
|
||||||
|
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_and_op = state.pinnacle.focused_output().map(|op| {
|
||||||
|
(
|
||||||
|
op.with_state(|state| state.focus_stack.stack.last().cloned()),
|
||||||
|
op.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// OH GOD THE BORROW CHECKER IS HAVING A SEIZURE
|
||||||
|
|
||||||
|
for window in state.pinnacle.windows.clone().iter() {
|
||||||
|
let Some(surface) = window.wl_surface() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
compositor::with_states(&surface, |states| {
|
||||||
|
// FIXME: xwayland
|
||||||
|
let Some(role) = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.map(|mutex| mutex.lock().expect("mutex should be lockable"))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((win, op)) = focused_win_and_op.as_ref() {
|
||||||
|
if win.as_ref() == Some(window) {
|
||||||
|
focused = Some((window.clone(), op.clone()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: this will use the tags the window has to determine
|
||||||
|
// output, not overlap.
|
||||||
|
|
||||||
|
let win_op = window.output(&state.pinnacle);
|
||||||
|
|
||||||
|
refresh_toplevel(
|
||||||
|
&mut state.pinnacle.foreign_toplevel_manager_state,
|
||||||
|
&surface,
|
||||||
|
&role,
|
||||||
|
win_op.as_ref(),
|
||||||
|
window.with_state(|state| state.minimized),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, refresh the focused window.
|
||||||
|
if let Some((window, op)) = focused {
|
||||||
|
let Some(surface) = window.wl_surface() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
compositor::with_states(&surface, |states| {
|
||||||
|
// FIXME: xwayland
|
||||||
|
let Some(role) = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.map(|mutex| mutex.lock().expect("mutex should be lockable"))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
refresh_toplevel(
|
||||||
|
&mut state.pinnacle.foreign_toplevel_manager_state,
|
||||||
|
&surface,
|
||||||
|
&role,
|
||||||
|
Some(&op),
|
||||||
|
window.with_state(|state| state.minimized),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh foreign toplevel handle state.
|
||||||
|
fn refresh_toplevel(
|
||||||
|
protocol_state: &mut ForeignToplevelManagerState,
|
||||||
|
wl_surface: &WlSurface,
|
||||||
|
role: &XdgToplevelSurfaceRoleAttributes,
|
||||||
|
output: Option<&Output>,
|
||||||
|
is_minimized: bool,
|
||||||
|
has_focus: bool,
|
||||||
|
) {
|
||||||
|
let states = to_state_vec(&role.current.states, is_minimized, has_focus);
|
||||||
|
|
||||||
|
match protocol_state.toplevels.entry(wl_surface.clone()) {
|
||||||
|
Entry::Occupied(entry) => {
|
||||||
|
let data = entry.into_mut();
|
||||||
|
|
||||||
|
let mut new_title = None;
|
||||||
|
if data.title != role.title {
|
||||||
|
data.title.clone_from(&role.title);
|
||||||
|
new_title = role.title.as_deref();
|
||||||
|
|
||||||
|
if new_title.is_none() {
|
||||||
|
error!("toplevel title changed to None");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_app_id = None;
|
||||||
|
if data.app_id != role.app_id {
|
||||||
|
data.app_id.clone_from(&role.app_id);
|
||||||
|
new_app_id = role.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.as_ref() != output {
|
||||||
|
data.output = output.cloned();
|
||||||
|
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: role.title.clone(),
|
||||||
|
app_id: role.app_id.clone(),
|
||||||
|
states,
|
||||||
|
output: output.cloned(),
|
||||||
|
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(
|
||||||
|
states: &ToplevelStateSet,
|
||||||
|
is_minimized: bool,
|
||||||
|
has_focus: bool,
|
||||||
|
) -> Vec<zwlr_foreign_toplevel_handle_v1::State> {
|
||||||
|
let mut state_vec = Vec::new();
|
||||||
|
if states.contains(xdg_toplevel::State::Maximized) {
|
||||||
|
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Maximized);
|
||||||
|
}
|
||||||
|
if states.contains(xdg_toplevel::State::Fullscreen) {
|
||||||
|
state_vec.push(zwlr_foreign_toplevel_handle_v1::State::Fullscreen);
|
||||||
|
}
|
||||||
|
if is_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,
|
focus::OutputFocusStack,
|
||||||
grab::resize_grab::ResizeSurfaceState,
|
grab::resize_grab::ResizeSurfaceState,
|
||||||
layout::LayoutState,
|
layout::LayoutState,
|
||||||
protocol::{gamma_control::GammaControlManagerState, screencopy::ScreencopyManagerState},
|
protocol::{
|
||||||
|
foreign_toplevel::{self, ForeignToplevelManagerState},
|
||||||
|
gamma_control::GammaControlManagerState,
|
||||||
|
screencopy::ScreencopyManagerState,
|
||||||
|
},
|
||||||
window::WindowElement,
|
window::WindowElement,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
@ -89,6 +93,7 @@ pub struct Pinnacle {
|
||||||
pub security_context_state: SecurityContextState,
|
pub security_context_state: SecurityContextState,
|
||||||
pub relative_pointer_manager_state: RelativePointerManagerState,
|
pub relative_pointer_manager_state: RelativePointerManagerState,
|
||||||
pub pointer_constraints_state: PointerConstraintsState,
|
pub pointer_constraints_state: PointerConstraintsState,
|
||||||
|
pub foreign_toplevel_manager_state: ForeignToplevelManagerState,
|
||||||
|
|
||||||
/// The state of key and mousebinds along with libinput settings
|
/// The state of key and mousebinds along with libinput settings
|
||||||
pub input_state: InputState,
|
pub input_state: InputState,
|
||||||
|
@ -129,6 +134,7 @@ impl State {
|
||||||
self.pinnacle.space.refresh();
|
self.pinnacle.space.refresh();
|
||||||
self.pinnacle.popup_manager.cleanup();
|
self.pinnacle.popup_manager.cleanup();
|
||||||
self.update_pointer_focus();
|
self.update_pointer_focus();
|
||||||
|
foreign_toplevel::refresh(self);
|
||||||
|
|
||||||
self.pinnacle
|
self.pinnacle
|
||||||
.display_handle
|
.display_handle
|
||||||
|
@ -252,6 +258,10 @@ impl Pinnacle {
|
||||||
&display_handle,
|
&display_handle,
|
||||||
),
|
),
|
||||||
pointer_constraints_state: PointerConstraintsState::new::<State>(&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(),
|
input_state: InputState::new(),
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ use smithay::{
|
||||||
utils::{IsAlive, Logical, Point, Rectangle},
|
utils::{IsAlive, Logical, Point, Rectangle},
|
||||||
wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData},
|
wayland::{compositor, seat::WaylandFocus, shell::xdg::XdgToplevelSurfaceData},
|
||||||
};
|
};
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::state::{Pinnacle, WithState};
|
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.
|
/// Get the output this window is on.
|
||||||
///
|
///
|
||||||
/// This method gets the first tag the window has and returns its output.
|
/// 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 floating_or_tiled: FloatingOrTiled,
|
||||||
pub fullscreen_or_maximized: FullscreenOrMaximized,
|
pub fullscreen_or_maximized: FullscreenOrMaximized,
|
||||||
pub target_loc: Option<Point<i32, Logical>>,
|
pub target_loc: Option<Point<i32, Logical>>,
|
||||||
|
pub minimized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowElement {
|
impl WindowElement {
|
||||||
|
@ -295,6 +296,7 @@ impl WindowElementState {
|
||||||
floating_or_tiled: FloatingOrTiled::Tiled(None),
|
floating_or_tiled: FloatingOrTiled::Tiled(None),
|
||||||
fullscreen_or_maximized: FullscreenOrMaximized::Neither,
|
fullscreen_or_maximized: FullscreenOrMaximized::Neither,
|
||||||
target_loc: None,
|
target_loc: None,
|
||||||
|
minimized: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue