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