layer-shell: handle added/removed globals

closes #253
This commit is contained in:
Ferdinand Schober
2025-02-13 14:41:02 +01:00
committed by Ferdinand Schober
parent 3e1c3e95b7
commit 21c24f7fa1

View File

@@ -1,8 +1,9 @@
use async_trait::async_trait;
use futures_core::Stream;
use std::{
collections::VecDeque,
collections::{HashSet, VecDeque},
env,
fmt::{self, Display},
io::{self, ErrorKind},
os::fd::{AsFd, RawFd},
pin::Pin,
@@ -46,13 +47,15 @@ use wayland_protocols_wlr::layer_shell::v1::client::{
use wayland_client::{
backend::{ReadEventsGuard, WaylandError},
delegate_noop,
globals::{registry_queue_init, GlobalListContents},
globals::{registry_queue_init, Global, GlobalList, GlobalListContents},
protocol::{
wl_buffer, wl_compositor,
wl_keyboard::{self, WlKeyboard},
wl_output::{self, WlOutput},
wl_pointer::{self, WlPointer},
wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool,
wl_region,
wl_registry::{self, WlRegistry},
wl_seat, wl_shm, wl_shm_pool,
wl_surface::WlSurface,
},
Connection, Dispatch, DispatchError, EventQueue, QueueHandle, WEnum,
@@ -75,28 +78,42 @@ struct Globals {
seat: wl_seat::WlSeat,
shm: wl_shm::WlShm,
layer_shell: ZwlrLayerShellV1,
outputs: Vec<WlOutput>,
xdg_output_manager: ZxdgOutputManagerV1,
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
struct Output {
wl_output: WlOutput,
global: Global,
info: Option<OutputInfo>,
pending_info: OutputInfo,
has_xdg_info: bool,
}
impl Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(info) = &self.info {
write!(
f,
"{} {}x{} @pos {:?} ({})",
info.name, info.size.0, info.size.1, info.position, info.description
)
} else {
write!(f, "unknown output")
}
}
}
#[derive(Clone, Debug, Default)]
struct OutputInfo {
description: String,
name: String,
position: (i32, i32),
size: (i32, i32),
}
impl OutputInfo {
fn new() -> Self {
Self {
name: "".to_string(),
position: (0, 0),
size: (0, 0),
}
}
}
struct State {
active_positions: HashSet<Position>,
pointer: Option<WlPointer>,
keyboard: Option<WlKeyboard>,
pointer_lock: Option<ZwpLockedPointerV1>,
@@ -104,12 +121,13 @@ struct State {
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
active_windows: Vec<Arc<Window>>,
focused: Option<Arc<Window>>,
g: Globals,
global_list: GlobalList,
globals: Globals,
wayland_fd: RawFd,
read_guard: Option<ReadEventsGuard>,
qh: QueueHandle<Self>,
pending_events: VecDeque<(Position, CaptureEvent)>,
output_info: Vec<(WlOutput, OutputInfo)>,
outputs: Vec<Output>,
scroll_discrete_pending: bool,
}
@@ -142,7 +160,7 @@ impl Window {
size: (i32, i32),
) -> Window {
log::debug!("creating window output: {output:?}, size: {size:?}");
let g = &state.g;
let g = &state.globals;
let (width, height) = match pos {
Position::Left | Position::Right => (1, size.1 as u32),
@@ -203,41 +221,36 @@ impl Drop for Window {
}
}
fn get_edges(outputs: &[(WlOutput, OutputInfo)], pos: Position) -> Vec<(WlOutput, i32)> {
fn get_edges(outputs: &[Output], pos: Position) -> Vec<(Output, i32)> {
outputs
.iter()
.map(|(o, i)| {
(
o.clone(),
match pos {
Position::Left => i.position.0,
Position::Right => i.position.0 + i.size.0,
Position::Top => i.position.1,
Position::Bottom => i.position.1 + i.size.1,
},
)
.filter_map(|output| {
output.info.as_ref().map(|info| {
(
output.clone(),
match pos {
Position::Left => info.position.0,
Position::Right => info.position.0 + info.size.0,
Position::Top => info.position.1,
Position::Bottom => info.position.1 + info.size.1,
},
)
})
})
.collect()
}
fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, OutputInfo)> {
fn get_output_configuration(state: &State, pos: Position) -> Vec<Output> {
// get all output edges corresponding to the position
let edges = get_edges(&state.output_info, pos);
log::debug!("edges: {edges:?}");
let opposite_edges = get_edges(&state.output_info, pos.opposite());
let edges = get_edges(&state.outputs, pos);
let opposite_edges = get_edges(&state.outputs, pos.opposite());
// remove those edges that are at the same position
// as an opposite edge of a different output
let outputs: Vec<WlOutput> = edges
edges
.iter()
.filter(|(_, edge)| !opposite_edges.iter().map(|(_, e)| *e).any(|e| &e == edge))
.map(|(o, _)| o.clone())
.collect();
state
.output_info
.iter()
.filter(|(o, _)| outputs.contains(o))
.map(|(o, i)| (o.clone(), i.clone()))
.collect()
}
@@ -259,36 +272,36 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
impl LayerShellInputCapture {
pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
let conn = Connection::connect_to_env()?;
let (g, mut queue) = registry_queue_init::<State>(&conn)?;
let (global_list, mut queue) = registry_queue_init::<State>(&conn)?;
let qh = queue.handle();
let compositor: wl_compositor::WlCompositor = g
let compositor: wl_compositor::WlCompositor = global_list
.bind(&qh, 4..=5, ())
.map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
let xdg_output_manager: ZxdgOutputManagerV1 = g
let xdg_output_manager: ZxdgOutputManagerV1 = global_list
.bind(&qh, 1..=3, ())
.map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
let shm: wl_shm::WlShm = g
let shm: wl_shm::WlShm = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
let layer_shell: ZwlrLayerShellV1 = g
let layer_shell: ZwlrLayerShellV1 = global_list
.bind(&qh, 3..=4, ())
.map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
let seat: wl_seat::WlSeat = g
let seat: wl_seat::WlSeat = global_list
.bind(&qh, 7..=8, ())
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
let pointer_constraints: ZwpPointerConstraintsV1 = g
let pointer_constraints: ZwpPointerConstraintsV1 = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_pointer_constraints_v1"))?;
let relative_pointer_manager: ZwpRelativePointerManagerV1 = g
let relative_pointer_manager: ZwpRelativePointerManagerV1 = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_relative_pointer_manager_v1"))?;
let shortcut_inhibit_manager: Result<
ZwpKeyboardShortcutsInhibitManagerV1,
WaylandBindError,
> = g
> = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_keyboard_shortcuts_inhibit_manager_v1"));
// layer-shell backend still works without this protocol so we make it an optional dependency
@@ -297,65 +310,41 @@ impl LayerShellInputCapture {
to the client");
}
let shortcut_inhibit_manager = shortcut_inhibit_manager.ok();
let outputs = vec![];
let g = Globals {
compositor,
shm,
layer_shell,
seat,
pointer_constraints,
relative_pointer_manager,
shortcut_inhibit_manager,
outputs,
xdg_output_manager,
};
// flush outgoing events
queue.flush()?;
let wayland_fd = queue.as_fd().as_raw_fd();
let mut state = State {
active_positions: Default::default(),
pointer: None,
keyboard: None,
g,
global_list,
globals: Globals {
compositor,
shm,
layer_shell,
seat,
pointer_constraints,
relative_pointer_manager,
shortcut_inhibit_manager,
xdg_output_manager,
},
pointer_lock: None,
rel_pointer: None,
shortcut_inhibitor: None,
active_windows: Vec::new(),
focused: None,
qh,
wayland_fd,
wayland_fd: queue.as_fd().as_raw_fd(),
read_guard: None,
pending_events: VecDeque::new(),
output_info: vec![],
outputs: vec![],
scroll_discrete_pending: false,
};
// dispatch registry to () again, in order to read all wl_outputs
conn.display().get_registry(&state.qh, ());
log::debug!("==============> requested registry");
// roundtrip to read wl_output globals
queue.roundtrip(&mut state)?;
log::debug!("==============> roundtrip 1 done");
// read outputs
for output in state.g.outputs.iter() {
state
.g
.xdg_output_manager
.get_xdg_output(output, &state.qh, output.clone());
for global in state.global_list.contents().clone_list() {
state.register_global(global);
}
// roundtrip to read xdg_output events
queue.roundtrip(&mut state)?;
log::debug!("==============> roundtrip 2 done");
for i in &state.output_info {
log::debug!("{:#?}", i.1);
}
// flush outgoing events
queue.flush()?;
let read_guard = loop {
match queue.prepare_read() {
@@ -379,6 +368,7 @@ impl LayerShellInputCapture {
fn delete_client(&mut self, pos: Position) {
let inner = self.0.get_mut();
inner.state.active_positions.remove(&pos);
// remove all windows corresponding to this client
while let Some(i) = inner.state.active_windows.iter().position(|w| w.pos == pos) {
inner.state.active_windows.remove(i);
@@ -388,6 +378,52 @@ impl LayerShellInputCapture {
}
impl State {
fn update_output_info(&mut self, name: u32) {
let output = self
.outputs
.iter_mut()
.find(|o| o.global.name == name)
.expect("output not found");
if output.has_xdg_info {
output.info.replace(output.pending_info.clone());
self.update_windows();
}
}
fn register_global(&mut self, global: Global) {
if global.interface.as_str() == "wl_output" {
log::debug!("new output global: wl_output {}", global.name);
let wl_output = self.global_list.registry().bind::<WlOutput, _, _>(
global.name,
4,
&self.qh,
global.name,
);
self.globals
.xdg_output_manager
.get_xdg_output(&wl_output, &self.qh, global.name);
self.outputs.push(Output {
wl_output,
global,
info: None,
has_xdg_info: false,
pending_info: Default::default(),
})
}
}
fn deregister_global(&mut self, name: u32) {
self.outputs.retain(|o| {
if o.global.name == name {
log::debug!("{o} (global {:?}) removed", o.global);
o.wl_output.release();
false
} else {
true
}
});
}
fn grab(
&mut self,
surface: &WlSurface,
@@ -408,7 +444,7 @@ impl State {
// lock pointer
if self.pointer_lock.is_none() {
self.pointer_lock = Some(self.g.pointer_constraints.lock_pointer(
self.pointer_lock = Some(self.globals.pointer_constraints.lock_pointer(
surface,
pointer,
None,
@@ -420,7 +456,7 @@ impl State {
// request relative input
if self.rel_pointer.is_none() {
self.rel_pointer = Some(self.g.relative_pointer_manager.get_relative_pointer(
self.rel_pointer = Some(self.globals.relative_pointer_manager.get_relative_pointer(
pointer,
qh,
(),
@@ -428,10 +464,14 @@ impl State {
}
// capture modifier keys
if let Some(shortcut_inhibit_manager) = &self.g.shortcut_inhibit_manager {
if let Some(shortcut_inhibit_manager) = &self.globals.shortcut_inhibit_manager {
if self.shortcut_inhibitor.is_none() {
self.shortcut_inhibitor =
Some(shortcut_inhibit_manager.inhibit_shortcuts(surface, &self.g.seat, qh, ()));
self.shortcut_inhibitor = Some(shortcut_inhibit_manager.inhibit_shortcuts(
surface,
&self.globals.seat,
qh,
(),
));
}
}
}
@@ -469,21 +509,39 @@ impl State {
}
fn add_client(&mut self, pos: Position) {
self.active_positions.insert(pos);
let outputs = get_output_configuration(self, pos);
log::debug!("outputs: {outputs:?}");
outputs.iter().for_each(|(o, i)| {
let window = Window::new(self, &self.qh, o, pos, i.size);
let window = Arc::new(window);
self.active_windows.push(window);
log::info!(
"adding capture for position {pos} - using outputs: {:?}",
outputs
.iter()
.map(|o| o
.info
.as_ref()
.map(|i| i.name.to_owned())
.unwrap_or("unknown output".to_owned()))
.collect::<Vec<_>>()
);
outputs.iter().for_each(|o| {
if let Some(info) = o.info.as_ref() {
let window = Window::new(self, &self.qh, &o.wl_output, pos, info.size);
let window = Arc::new(window);
self.active_windows.push(window);
}
});
}
fn update_windows(&mut self) {
log::debug!("updating windows");
log::debug!("output info: {:?}", self.output_info);
let clients: Vec<_> = self.active_windows.drain(..).map(|w| w.pos).collect();
for pos in clients {
log::info!("active outputs: ");
for output in self.outputs.iter().filter(|o| o.info.is_some()) {
log::info!(" * {}", output);
}
self.active_windows.clear();
let active_positions = self.active_positions.iter().cloned().collect::<Vec<_>>();
for pos in active_positions {
self.add_client(pos);
}
}
@@ -872,94 +930,89 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
}
// delegate wl_registry events to App itself
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
fn event(
_state: &mut Self,
_proxy: &wl_registry::WlRegistry,
_event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<wl_registry::WlRegistry, ()> for State {
impl Dispatch<WlRegistry, GlobalListContents> for State {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
_: &(),
_: &Connection,
qh: &QueueHandle<Self>,
_registry: &WlRegistry,
event: <WlRegistry as wayland_client::Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
match event {
wl_registry::Event::Global {
name,
interface,
version: _,
version,
} => {
if interface.as_str() == "wl_output" {
log::debug!("wl_output global");
state
.g
.outputs
.push(registry.bind::<WlOutput, _, _>(name, 4, qh, ()))
}
state.register_global(Global {
name,
interface,
version,
});
}
wl_registry::Event::GlobalRemove { name } => {
state.deregister_global(name);
}
wl_registry::Event::GlobalRemove { .. } => {}
_ => {}
}
}
}
impl Dispatch<ZxdgOutputV1, WlOutput> for State {
impl Dispatch<ZxdgOutputV1, u32> for State {
fn event(
state: &mut Self,
_: &ZxdgOutputV1,
event: <ZxdgOutputV1 as wayland_client::Proxy>::Event,
wl_output: &WlOutput,
name: &u32,
_: &Connection,
_: &QueueHandle<Self>,
) {
log::debug!("xdg-output - {event:?}");
let output_info = match state.output_info.iter_mut().find(|(o, _)| o == wl_output) {
Some((_, c)) => c,
None => {
let output_info = OutputInfo::new();
state.output_info.push((wl_output.clone(), output_info));
&mut state.output_info.last_mut().unwrap().1
}
};
let output = state
.outputs
.iter_mut()
.find(|o| o.global.name == *name)
.expect("output");
log::debug!("xdg_output {name} - {:?}", event);
match event {
zxdg_output_v1::Event::LogicalPosition { x, y } => {
output_info.position = (x, y);
output.pending_info.position = (x, y);
output.has_xdg_info = true;
}
zxdg_output_v1::Event::LogicalSize { width, height } => {
output_info.size = (width, height);
output.pending_info.size = (width, height);
output.has_xdg_info = true;
}
zxdg_output_v1::Event::Done => {
log::warn!("Use of deprecated xdg-output event \"done\"");
state.update_output_info(*name);
}
zxdg_output_v1::Event::Done => {}
zxdg_output_v1::Event::Name { name } => {
output_info.name = name;
output.pending_info.name = name;
output.has_xdg_info = true;
}
zxdg_output_v1::Event::Description { .. } => {}
_ => {}
zxdg_output_v1::Event::Description { description } => {
output.pending_info.description = description;
output.has_xdg_info = true;
}
_ => todo!(),
}
}
}
impl Dispatch<WlOutput, ()> for State {
impl Dispatch<WlOutput, u32> for State {
fn event(
state: &mut Self,
_proxy: &WlOutput,
_wl_output: &WlOutput,
event: <WlOutput as wayland_client::Proxy>::Event,
_data: &(),
name: &u32,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
log::debug!("wl_output {name} - {:?}", event);
if let wl_output::Event::Done = event {
state.update_windows();
state.update_output_info(*name);
}
}
}