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:
216
src/state.rs
216
src/state.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::fd::{AsFd, OwnedFd};
|
use std::os::fd::{AsFd, OwnedFd};
|
||||||
use std::os::unix::io::FromRawFd;
|
use std::os::unix::io::FromRawFd;
|
||||||
@@ -5,11 +6,12 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use wayland_client::backend::ObjectId;
|
||||||
use wayland_client::globals::{GlobalList, GlobalListContents};
|
use wayland_client::globals::{GlobalList, GlobalListContents};
|
||||||
use wayland_client::protocol::wl_buffer::WlBuffer;
|
use wayland_client::protocol::wl_buffer::WlBuffer;
|
||||||
use wayland_client::protocol::wl_output::WlOutput;
|
use wayland_client::protocol::wl_output::WlOutput;
|
||||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
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::{
|
use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1::{
|
||||||
Event as BufferParamsEvent, Flags as BufferParamsFlags, ZwpLinuxBufferParamsV1,
|
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::{
|
use wayland_protocols::xdg::xdg_output::zv1::client::zxdg_output_v1::{
|
||||||
Event as XdgOutputEvent, ZxdgOutputV1,
|
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::{
|
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{
|
||||||
Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1,
|
Event as ScreencopyFrameEvent, ZwlrScreencopyFrameV1,
|
||||||
};
|
};
|
||||||
@@ -72,6 +81,8 @@ pub struct OutputInfo {
|
|||||||
|
|
||||||
pub struct PartialOutputInfo {
|
pub struct PartialOutputInfo {
|
||||||
pub name: Option<String>,
|
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 transform: Option<Transform>,
|
||||||
pub physical_size: Option<(i32, i32)>,
|
pub physical_size: Option<(i32, i32)>,
|
||||||
pub logical_position: Option<(i32, i32)>,
|
pub logical_position: Option<(i32, i32)>,
|
||||||
@@ -84,6 +95,7 @@ impl Default for PartialOutputInfo {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
name: None,
|
||||||
|
wl_name: None,
|
||||||
transform: None,
|
transform: None,
|
||||||
physical_size: None,
|
physical_size: None,
|
||||||
logical_position: 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.
|
/// User data for XdgOutput dispatch to identify which WlOutput it belongs to.
|
||||||
pub struct OutputId(pub u32);
|
pub struct OutputId(pub u32);
|
||||||
|
|
||||||
@@ -109,6 +126,10 @@ pub enum EncConstructionStage<S: CaptureSource> {
|
|||||||
dmabuf: Option<ZwpLinuxDmabufV1>,
|
dmabuf: Option<ZwpLinuxDmabufV1>,
|
||||||
dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
|
dmabuf_feedback: Option<ZwpLinuxDmabufFeedbackV1>,
|
||||||
xdg_output_manager: Option<ZxdgOutputManagerV1>,
|
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 {
|
EverythingButFmt {
|
||||||
output_info: OutputInfo,
|
output_info: OutputInfo,
|
||||||
@@ -213,6 +234,10 @@ impl<S: CaptureSource> State<S> {
|
|||||||
dmabuf: None,
|
dmabuf: None,
|
||||||
dmabuf_feedback: None,
|
dmabuf_feedback: None,
|
||||||
xdg_output_manager: 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,
|
in_flight_surface: InFlightSurface::None,
|
||||||
starting_timestamp: None,
|
starting_timestamp: None,
|
||||||
@@ -468,8 +493,30 @@ impl<S: CaptureSource> State<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_finalize_output(&mut self, _idx: usize) -> bool {
|
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 {
|
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 output_count = outputs.len();
|
||||||
let idx = if let Some(ref name) = self.args.output_name {
|
let idx = if let Some(ref name) = self.args.output_name {
|
||||||
let pos = outputs
|
let pos = outputs
|
||||||
@@ -503,6 +550,8 @@ impl<S: CaptureSource> State<S> {
|
|||||||
match idx {
|
match idx {
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let info = &outputs[i];
|
let info = &outputs[i];
|
||||||
|
if has_xdg {
|
||||||
|
// xdg-output path (Sway/Hyprland) — strict checks
|
||||||
if info.done_count < 2
|
if info.done_count < 2
|
||||||
|| info.name.is_none()
|
|| info.name.is_none()
|
||||||
|| info.transform.is_none()
|
|| info.transform.is_none()
|
||||||
@@ -511,6 +560,16 @@ impl<S: CaptureSource> State<S> {
|
|||||||
{
|
{
|
||||||
return false;
|
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)
|
(i, output_count)
|
||||||
}
|
}
|
||||||
None => return false,
|
None => return false,
|
||||||
@@ -530,11 +589,15 @@ impl<S: CaptureSource> State<S> {
|
|||||||
let (
|
let (
|
||||||
outputs,
|
outputs,
|
||||||
bound_outputs,
|
bound_outputs,
|
||||||
_output_names,
|
output_names,
|
||||||
screencopy_manager,
|
screencopy_manager,
|
||||||
dmabuf,
|
dmabuf,
|
||||||
dmabuf_feedback,
|
dmabuf_feedback,
|
||||||
_xdg_output_manager,
|
_xdg_output_manager,
|
||||||
|
_wlr_output_manager,
|
||||||
|
_wlr_manager_done,
|
||||||
|
_wlr_heads,
|
||||||
|
_wlr_head_proxy_to_name,
|
||||||
) = match probing {
|
) = match probing {
|
||||||
EncConstructionStage::ProbingOutputs {
|
EncConstructionStage::ProbingOutputs {
|
||||||
outputs,
|
outputs,
|
||||||
@@ -544,6 +607,10 @@ impl<S: CaptureSource> State<S> {
|
|||||||
dmabuf,
|
dmabuf,
|
||||||
dmabuf_feedback,
|
dmabuf_feedback,
|
||||||
xdg_output_manager,
|
xdg_output_manager,
|
||||||
|
wlr_output_manager,
|
||||||
|
wlr_manager_done,
|
||||||
|
wlr_heads,
|
||||||
|
wlr_head_proxy_to_name,
|
||||||
} => (
|
} => (
|
||||||
outputs,
|
outputs,
|
||||||
bound_outputs,
|
bound_outputs,
|
||||||
@@ -552,6 +619,10 @@ impl<S: CaptureSource> State<S> {
|
|||||||
dmabuf,
|
dmabuf,
|
||||||
dmabuf_feedback,
|
dmabuf_feedback,
|
||||||
xdg_output_manager,
|
xdg_output_manager,
|
||||||
|
wlr_output_manager,
|
||||||
|
wlr_manager_done,
|
||||||
|
wlr_heads,
|
||||||
|
wlr_head_proxy_to_name,
|
||||||
),
|
),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
@@ -562,10 +633,14 @@ impl<S: CaptureSource> State<S> {
|
|||||||
|
|
||||||
let info = &outputs[target_idx];
|
let info = &outputs[target_idx];
|
||||||
let output_info = OutputInfo {
|
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(),
|
transform: info.transform.unwrap(),
|
||||||
physical_size: info.physical_size.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();
|
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);
|
*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 } => {
|
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, ()>
|
// Dispatch<ZwlrScreencopyManagerV1, ()>
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user