feat(state): support compositors without xdg-output via wlr-output-management
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
230
src/state.rs
230
src/state.rs
@@ -1,3 +1,4 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::os::fd::{AsFd, OwnedFd};
|
||||
use std::os::unix::io::FromRawFd;
|
||||
@@ -5,11 +6,12 @@ use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use wayland_client::backend::ObjectId;
|
||||
use wayland_client::globals::{GlobalList, GlobalListContents};
|
||||
use wayland_client::protocol::wl_buffer::WlBuffer;
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::{Dispatch, Proxy, QueueHandle};
|
||||
use wayland_client::{event_created_child, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::{
|
||||
Event as BufferParamsEvent, Flags as BufferParamsFlags, ZwpLinuxBufferParamsV1,
|
||||
};
|
||||
@@ -23,6 +25,13 @@ use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_manager_v1::Zxd
|
||||
use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{
|
||||
Event as XdgOutputEvent, ZxdgOutputV1,
|
||||
};
|
||||
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{
|
||||
self, Event as WlrHeadEvent, ZwlrOutputHeadV1,
|
||||
};
|
||||
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::{
|
||||
self, Event as WlrOutputManagerEvent, ZwlrOutputManagerV1,
|
||||
};
|
||||
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1;
|
||||
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{
|
||||
Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1,
|
||||
};
|
||||
@@ -72,6 +81,8 @@ pub struct OutputInfo {
|
||||
|
||||
pub struct PartialOutputInfo {
|
||||
pub name: Option<String>,
|
||||
/// Name from wl_output::Name (v4) — used to match wlr-output-management heads
|
||||
pub wl_name: Option<String>,
|
||||
pub transform: Option<Transform>,
|
||||
pub physical_size: Option<(i32, i32)>,
|
||||
pub logical_position: Option<(i32, i32)>,
|
||||
@@ -84,6 +95,7 @@ impl Default for PartialOutputInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
wl_name: None,
|
||||
transform: None,
|
||||
physical_size: None,
|
||||
logical_position: None,
|
||||
@@ -93,6 +105,11 @@ impl Default for PartialOutputInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores head info from wlr-output-management for name-based matching with wl_output.
|
||||
struct WlrHeadInfo {
|
||||
position: Option<(i32, i32)>,
|
||||
}
|
||||
|
||||
/// User data for XdgOutput dispatch to identify which WlOutput it belongs to.
|
||||
pub struct OutputId(pub u32);
|
||||
|
||||
@@ -109,6 +126,10 @@ pub enum EncConstructionStage<S: CaptureSource> {
|
||||
dmabuf: Option<ZwpLinuxDmabufV1>,
|
||||
dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
|
||||
xdg_output_manager: Option<ZxdgOutputManagerV1>,
|
||||
wlr_output_manager: Option<ZwlrOutputManagerV1>,
|
||||
wlr_manager_done: bool,
|
||||
wlr_heads: HashMap<String, WlrHeadInfo>,
|
||||
wlr_head_proxy_to_name: HashMap<ObjectId, String>,
|
||||
},
|
||||
EverythingButFmt {
|
||||
output_info: OutputInfo,
|
||||
@@ -213,6 +234,10 @@ impl<S: CaptureSource> State<S> {
|
||||
dmabuf: None,
|
||||
dmabuf_feedback: None,
|
||||
xdg_output_manager: None,
|
||||
wlr_output_manager: None,
|
||||
wlr_manager_done: false,
|
||||
wlr_heads: HashMap::new(),
|
||||
wlr_head_proxy_to_name: HashMap::new(),
|
||||
},
|
||||
in_flight_surface: InFlightSurface::None,
|
||||
starting_timestamp: None,
|
||||
@@ -468,8 +493,30 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
|
||||
fn try_finalize_output(&mut self, _idx: usize) -> bool {
|
||||
// Merge wlr head position info into outputs (needed for niri path)
|
||||
if let EncConstructionStage::ProbingOutputs {
|
||||
outputs, wlr_heads, ..
|
||||
} = &mut self.stage
|
||||
{
|
||||
for info in outputs.iter_mut() {
|
||||
if info.logical_position.is_none() {
|
||||
if let Some(ref wl_name) = info.wl_name {
|
||||
if let Some(head_info) = wlr_heads.get(wl_name) {
|
||||
info.logical_position = head_info.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (target_idx, output_count) = match &self.stage {
|
||||
EncConstructionStage::ProbingOutputs { outputs, .. } => {
|
||||
EncConstructionStage::ProbingOutputs {
|
||||
outputs,
|
||||
xdg_output_manager,
|
||||
wlr_manager_done,
|
||||
..
|
||||
} => {
|
||||
let has_xdg = xdg_output_manager.is_some();
|
||||
let output_count = outputs.len();
|
||||
let idx = if let Some(ref name) = self.args.output_name {
|
||||
let pos = outputs
|
||||
@@ -503,13 +550,25 @@ impl<S: CaptureSource> State<S> {
|
||||
match idx {
|
||||
Some(i) => {
|
||||
let info = &outputs[i];
|
||||
if info.done_count < 2
|
||||
|| info.name.is_none()
|
||||
|| info.transform.is_none()
|
||||
|| info.physical_size.is_none()
|
||||
|| info.logical_position.is_none()
|
||||
{
|
||||
return false;
|
||||
if has_xdg {
|
||||
// xdg-output path (Sway/Hyprland) — strict checks
|
||||
if info.done_count < 2
|
||||
|| info.name.is_none()
|
||||
|| info.transform.is_none()
|
||||
|| info.physical_size.is_none()
|
||||
|| info.logical_position.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// wlr-output-management path (niri) — relaxed checks
|
||||
if info.done_count < 1 || !wlr_manager_done {
|
||||
return false;
|
||||
}
|
||||
if info.transform.is_none() || info.physical_size.is_none() {
|
||||
return false;
|
||||
}
|
||||
// name and logical_position can use defaults
|
||||
}
|
||||
(i, output_count)
|
||||
}
|
||||
@@ -530,11 +589,15 @@ impl<S: CaptureSource> State<S> {
|
||||
let (
|
||||
outputs,
|
||||
bound_outputs,
|
||||
_output_names,
|
||||
output_names,
|
||||
screencopy_manager,
|
||||
dmabuf,
|
||||
dmabuf_feedback,
|
||||
_xdg_output_manager,
|
||||
_wlr_output_manager,
|
||||
_wlr_manager_done,
|
||||
_wlr_heads,
|
||||
_wlr_head_proxy_to_name,
|
||||
) = match probing {
|
||||
EncConstructionStage::ProbingOutputs {
|
||||
outputs,
|
||||
@@ -544,6 +607,10 @@ impl<S: CaptureSource> State<S> {
|
||||
dmabuf,
|
||||
dmabuf_feedback,
|
||||
xdg_output_manager,
|
||||
wlr_output_manager,
|
||||
wlr_manager_done,
|
||||
wlr_heads,
|
||||
wlr_head_proxy_to_name,
|
||||
} => (
|
||||
outputs,
|
||||
bound_outputs,
|
||||
@@ -552,6 +619,10 @@ impl<S: CaptureSource> State<S> {
|
||||
dmabuf,
|
||||
dmabuf_feedback,
|
||||
xdg_output_manager,
|
||||
wlr_output_manager,
|
||||
wlr_manager_done,
|
||||
wlr_heads,
|
||||
wlr_head_proxy_to_name,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@@ -562,10 +633,14 @@ impl<S: CaptureSource> State<S> {
|
||||
|
||||
let info = &outputs[target_idx];
|
||||
let output_info = OutputInfo {
|
||||
name: info.name.clone().unwrap(),
|
||||
name: info
|
||||
.name
|
||||
.clone()
|
||||
.or(info.wl_name.clone())
|
||||
.unwrap_or_else(|| format!("output-{}", output_names[target_idx])),
|
||||
transform: info.transform.unwrap(),
|
||||
physical_size: info.physical_size.unwrap(),
|
||||
logical_position: info.logical_position.unwrap(),
|
||||
logical_position: info.logical_position.unwrap_or((0, 0)),
|
||||
};
|
||||
let output = bound_outputs[target_idx].clone();
|
||||
|
||||
@@ -716,6 +791,17 @@ impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
|
||||
*xdg_output_manager = Some(xdg_mgr);
|
||||
}
|
||||
}
|
||||
"zwlr_output_manager_v1" => {
|
||||
let v = version.min(4);
|
||||
tracing::debug!("Binding zwlr_output_manager_v1 v{v} (name={name})");
|
||||
let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ());
|
||||
if let EncConstructionStage::ProbingOutputs {
|
||||
wlr_output_manager, ..
|
||||
} = &mut state.stage
|
||||
{
|
||||
*wlr_output_manager = Some(mgr);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
RegistryEvent::GlobalRemove { name } => {
|
||||
@@ -805,6 +891,13 @@ impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
|
||||
}
|
||||
}
|
||||
}
|
||||
OutputEvent::Name { name } => {
|
||||
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
|
||||
if let Some(info) = outputs.get_mut(idx) {
|
||||
info.wl_name = Some(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1050,6 +1143,119 @@ impl<S: CaptureSource> Dispatch<ZxdgOutputManagerV1, ()> for State<S> {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dispatch<ZwlrOutputManagerV1, ()>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
impl<S: CaptureSource> Dispatch<ZwlrOutputManagerV1, ()> for State<S> {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &ZwlrOutputManagerV1,
|
||||
event: <ZwlrOutputManagerV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
qhandle: &QueueHandle<State<S>>,
|
||||
) {
|
||||
match event {
|
||||
WlrOutputManagerEvent::Head { head } => {
|
||||
let _head: ZwlrOutputHeadV1 = head;
|
||||
tracing::debug!("wlr output head advertised");
|
||||
}
|
||||
WlrOutputManagerEvent::Done { .. } => {
|
||||
if let EncConstructionStage::ProbingOutputs {
|
||||
wlr_manager_done,
|
||||
outputs,
|
||||
..
|
||||
} = &mut state.stage
|
||||
{
|
||||
*wlr_manager_done = true;
|
||||
let count = outputs.len();
|
||||
for idx in 0..count {
|
||||
state.try_finalize_output(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
WlrOutputManagerEvent::Finished { .. } => {
|
||||
tracing::warn!("zwlr_output_manager_v1::Finished received during probing");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State<S>, ZwlrOutputManagerV1, [
|
||||
zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()),
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dispatch<ZwlrOutputHeadV1, ()>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
impl<S: CaptureSource> Dispatch<ZwlrOutputHeadV1, ()> for State<S> {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
proxy: &ZwlrOutputHeadV1,
|
||||
event: <ZwlrOutputHeadV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &QueueHandle<State<S>>,
|
||||
) {
|
||||
match event {
|
||||
WlrHeadEvent::Name { name } => {
|
||||
if let EncConstructionStage::ProbingOutputs {
|
||||
wlr_heads,
|
||||
wlr_head_proxy_to_name,
|
||||
..
|
||||
} = &mut state.stage
|
||||
{
|
||||
wlr_heads
|
||||
.entry(name.clone())
|
||||
.or_insert(WlrHeadInfo { position: None });
|
||||
wlr_head_proxy_to_name.insert(proxy.id(), name);
|
||||
}
|
||||
}
|
||||
WlrHeadEvent::Position { x, y } => {
|
||||
if let EncConstructionStage::ProbingOutputs {
|
||||
wlr_heads,
|
||||
wlr_head_proxy_to_name,
|
||||
..
|
||||
} = &mut state.stage
|
||||
{
|
||||
if let Some(name) = wlr_head_proxy_to_name.get(&proxy.id()) {
|
||||
if let Some(head) = wlr_heads.get_mut(name) {
|
||||
head.position = Some((x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WlrHeadEvent::Finished { .. } => {
|
||||
tracing::debug!("zwlr_output_head_v1::Finished received");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
event_created_child!(State<S>, ZwlrOutputHeadV1, [
|
||||
zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()),
|
||||
]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dispatch<ZwlrOutputModeV1, ()>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
impl<S: CaptureSource> Dispatch<ZwlrOutputModeV1, ()> for State<S> {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &ZwlrOutputModeV1,
|
||||
_event: <ZwlrOutputModeV1 as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &wayland_client::Connection,
|
||||
_qhandle: &QueueHandle<State<S>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dispatch<ZwlrScreencopyManagerV1, ()>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user