Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adaptive VRR Support #35

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 100 additions & 27 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod align;
use clap::{Parser, ValueEnum};
use cosmic_randr::context::HeadConfiguration;
use cosmic_randr::Message;
use cosmic_randr::{AdaptiveSyncState, Context};
use cosmic_randr::{AdaptiveSyncAvailability, AdaptiveSyncStateExt, Context};
use nu_ansi_term::{Color, Style};
use std::fmt::{Display, Write as FmtWrite};
use std::io::Write;
Expand All @@ -33,6 +33,9 @@ struct Mode {
/// Specifies the refresh rate to apply to the output.
#[arg(long)]
refresh: Option<f32>,
/// Specfies the adaptive sync mode to apply to the output.
#[arg(long, value_enum)]
adaptive_sync: Option<AdaptiveSync>,
/// Position the output within this x pixel coordinate.
#[arg(long, allow_hyphen_values(true))]
pos_x: Option<i32>,
Expand All @@ -55,6 +58,9 @@ impl Mode {
HeadConfiguration {
size: Some((self.width as u32, self.height as u32)),
refresh: self.refresh,
adaptive_sync: self
.adaptive_sync
.map(|adaptive_sync| adaptive_sync.adaptive_sync_state_ext()),
pos: (self.pos_x.is_some() || self.pos_y.is_some()).then(|| {
(
self.pos_x.unwrap_or_default(),
Expand Down Expand Up @@ -159,6 +165,50 @@ impl Transform {
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum AdaptiveSync {
#[value(name = "true")]
Always,
#[value(name = "automatic")]
Automatic,
#[value(name = "false")]
Disabled,
}

impl Display for AdaptiveSync {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
AdaptiveSync::Always => "true",
AdaptiveSync::Automatic => "automatic",
AdaptiveSync::Disabled => "false",
})
}
}

impl TryFrom<AdaptiveSyncStateExt> for AdaptiveSync {
type Error = &'static str;

fn try_from(value: AdaptiveSyncStateExt) -> Result<Self, Self::Error> {
Ok(match value {
AdaptiveSyncStateExt::Always => AdaptiveSync::Always,
AdaptiveSyncStateExt::Automatic => AdaptiveSync::Automatic,
AdaptiveSyncStateExt::Disabled => AdaptiveSync::Disabled,
_ => return Err("unknown adaptive_sync_state_ext variant"),
})
}
}

impl AdaptiveSync {
#[must_use]
pub fn adaptive_sync_state_ext(self) -> AdaptiveSyncStateExt {
match self {
AdaptiveSync::Always => AdaptiveSyncStateExt::Always,
AdaptiveSync::Automatic => AdaptiveSyncStateExt::Automatic,
AdaptiveSync::Disabled => AdaptiveSyncStateExt::Disabled,
}
}
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
Expand Down Expand Up @@ -357,20 +407,20 @@ impl App {
align::display(active_output, other_outputs);

// Calculate how much to offset the position of each display to be aligned against (0,0)
let mut offset =
self.context
.output_heads
.values()
.filter(|head| head.enabled && head.mirroring.is_none())
.fold((i32::MAX, i32::MAX), |offset, head| {
let (x, y) = if output == head.name {
(active_output.x as i32, active_output.y as i32)
} else {
(head.position_x, head.position_y)
};

(offset.0.min(x), offset.1.min(y))
});
let mut offset = self
.context
.output_heads
.values()
.filter(|head| head.enabled && head.mirroring.is_none())
.fold((i32::MAX, i32::MAX), |offset, head| {
let (x, y) = if output == head.name {
(active_output.x as i32, active_output.y as i32)
} else {
(head.position_x, head.position_y)
};

(offset.0.min(x), offset.1.min(y))
});

// Reposition each display with that offset
let updates = self
Expand Down Expand Up @@ -491,13 +541,26 @@ fn list(context: &Context) {
(Color::Yellow.bold().paint("\n Transform: ")) (transform)
}
}
if let Some(available) = head.adaptive_sync_support {
(Color::Yellow.bold().paint("\n Adaptive Sync Support: "))
(match available {
AdaptiveSyncAvailability::Supported | AdaptiveSyncAvailability::RequiresModeset => Color::Green.paint("true"),
_ => Color::Red.paint("false"),
})
}
if let Some(sync) = head.adaptive_sync {
(Color::Yellow.bold().paint("\n Adaptive Sync: "))
if let AdaptiveSyncState::Enabled = sync {
(Color::Green.paint("true\n"))
} else {
(Color::Red.paint("false\n"))
}
(match sync {
AdaptiveSyncStateExt::Always => {
Color::Green.paint("true\n")
},
AdaptiveSyncStateExt::Automatic => {
Color::Green.paint("automatic\n")
},
_ => {
Color::Red.paint("false\n")
}
})
}
(Color::Yellow.bold().paint("\n Modes:"))
);
Expand Down Expand Up @@ -556,13 +619,23 @@ fn list_kdl(context: &Context) {
" transform \"" (transform) "\"\n"
}
}
if let Some(available) = head.adaptive_sync_support {
" adaptive_sync_support \""
(match available {
AdaptiveSyncAvailability::Supported => "true",
AdaptiveSyncAvailability::RequiresModeset => "requires_modeset",
_ => "false",
})
"\"\n"
}
if let Some(sync) = head.adaptive_sync {
" adaptive_sync "
if let AdaptiveSyncState::Enabled = sync {
"true\n"
} else {
"false\n"
}
" adaptive_sync \""
(match sync {
AdaptiveSyncStateExt::Always => "true",
AdaptiveSyncStateExt::Automatic => "automatic",
_ => "false",
})
"\"\n"
}
if !head.serial_number.is_empty() {
" serial_number=\"" (head.serial_number) "\"\n"
Expand Down Expand Up @@ -611,7 +684,7 @@ fn set_mode(context: &mut Context, args: &Mode) -> Result<(), Box<dyn std::error
if let Some(mirroring_from) = mirroring.filter(|_| head_config.pos.is_none()) {
config.mirror_head(&args.output, &mirroring_from, Some(head_config))?;
} else {
config.enable_head(&args.output, Some(args.to_head_config()))?;
config.enable_head(&args.output, Some(head_config))?;
}

if args.test {
Expand Down
34 changes: 31 additions & 3 deletions lib/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@

use crate::output_head::OutputHead;
use crate::{Error, Message};
use cosmic_protocols::output_management::v1::client::zcosmic_output_configuration_head_v1::ZcosmicOutputConfigurationHeadV1;
use cosmic_protocols::output_management::v1::client::zcosmic_output_configuration_head_v1::{
self, ZcosmicOutputConfigurationHeadV1,
};
use cosmic_protocols::output_management::v1::client::zcosmic_output_configuration_v1::ZcosmicOutputConfigurationV1;
use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::AdaptiveSyncStateExt;
use cosmic_protocols::output_management::v1::client::zcosmic_output_manager_v1::ZcosmicOutputManagerV1;
use std::collections::HashMap;
use std::fmt;
Expand All @@ -16,7 +19,9 @@ use wayland_client::{backend::ObjectId, Connection, Proxy, QueueHandle};
use wayland_client::{DispatchError, EventQueue};
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1;
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_configuration_v1::ZwlrOutputConfigurationV1;
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::ZwlrOutputHeadV1;
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{
AdaptiveSyncState, ZwlrOutputHeadV1,
};
use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1;

#[derive(Debug)]
Expand Down Expand Up @@ -54,6 +59,8 @@ pub struct HeadConfiguration {
pub size: Option<(u32, u32)>,
/// Specifies the refresh rate to apply to the output.
pub refresh: Option<f32>,
/// Specifies the adaptive_sync mode to apply to the output.
pub adaptive_sync: Option<AdaptiveSyncStateExt>,
/// Position the output within this x pixel coordinate.
pub pos: Option<(i32, i32)>,
/// Changes the dimensions of the output picture.
Expand All @@ -70,6 +77,7 @@ pub enum ConfigurationError {
NoCosmicExtension,
PositionForMirroredOutput,
MirroringItself,
UnsupportedVrrState,
}

impl fmt::Display for ConfigurationError {
Expand All @@ -81,6 +89,9 @@ impl fmt::Display for ConfigurationError {
Self::NoCosmicExtension => f.write_str("Mirroring isn't available outside COSMIC"),
Self::PositionForMirroredOutput => f.write_str("You cannot position a mirrored output"),
Self::MirroringItself => f.write_str("Output mirroring itself"),
Self::UnsupportedVrrState => {
f.write_str("Automatic VRR state management isn't available outside COSMIC")
}
}
}
}
Expand Down Expand Up @@ -222,7 +233,7 @@ fn send_mode_to_config_head(
args: HeadConfiguration,
) -> Result<(), ConfigurationError> {
if let Some(scale) = args.scale {
if let Some(cosmic_obj) = cosmic_head_config {
if let Some(cosmic_obj) = cosmic_head_config.as_ref() {
cosmic_obj.set_scale_1000((scale * 1000.0) as i32);
} else {
head_config.set_scale(scale);
Expand All @@ -249,6 +260,23 @@ fn send_mode_to_config_head(
})
};

if let Some(vrr) = args.adaptive_sync {
if let Some(cosmic_obj) = cosmic_head_config.as_ref().filter(|obj| {
obj.version() >= zcosmic_output_configuration_head_v1::REQ_SET_ADAPTIVE_SYNC_EXT_SINCE
}) {
cosmic_obj.set_adaptive_sync_ext(vrr);
} else {
head_config.set_adaptive_sync(match vrr {
AdaptiveSyncStateExt::Always => AdaptiveSyncState::Enabled,
AdaptiveSyncStateExt::Disabled => AdaptiveSyncState::Disabled,
AdaptiveSyncStateExt::Automatic => {
return Err(ConfigurationError::UnsupportedVrrState)
}
_ => panic!("Unknown AdaptiveSyncStatExt variant"),
});
}
}

if let Some(refresh) = args.refresh {
#[allow(clippy::cast_possible_truncation)]
let refresh = (refresh * 1000.0) as i32;
Expand Down
4 changes: 3 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub mod output_manager;
pub mod output_mode;
pub use output_mode::OutputMode;

pub use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::AdaptiveSyncState;
pub use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::{
AdaptiveSyncAvailability, AdaptiveSyncStateExt,
};
pub mod wl_registry;

use tachyonix::Sender;
Expand Down
22 changes: 20 additions & 2 deletions lib/src/output_head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::sync::Mutex;

use crate::{Context, OutputMode};

use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::AdaptiveSyncAvailability;
use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::AdaptiveSyncStateExt;
use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::Event as ZcosmicOutputHeadEvent;
use cosmic_protocols::output_management::v1::client::zcosmic_output_head_v1::ZcosmicOutputHeadV1;
use indexmap::IndexMap;
Expand All @@ -21,7 +23,8 @@ use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::Z

#[derive(Clone, Debug, PartialEq)]
pub struct OutputHead {
pub adaptive_sync: Option<AdaptiveSyncState>,
pub adaptive_sync: Option<AdaptiveSyncStateExt>,
pub adaptive_sync_support: Option<AdaptiveSyncAvailability>,
pub current_mode: Option<ObjectId>,
pub description: String,
pub enabled: bool,
Expand Down Expand Up @@ -116,7 +119,11 @@ impl Dispatch<ZwlrOutputHeadV1, ()> for Context {
}

ZwlrOutputHeadEvent::AdaptiveSync { state } => {
head.adaptive_sync = state.into_result().ok();
head.adaptive_sync = match state.into_result().ok() {
Some(AdaptiveSyncState::Enabled) => Some(AdaptiveSyncStateExt::Always),
Some(AdaptiveSyncState::Disabled) => Some(AdaptiveSyncStateExt::Disabled),
Some(_) | None => None,
};
}

_ => tracing::debug!(?event, "unknown event"),
Expand Down Expand Up @@ -149,6 +156,16 @@ impl Dispatch<ZcosmicOutputHeadV1, ObjectId> for Context {
ZcosmicOutputHeadEvent::Mirroring { name } => {
head.mirroring = name;
}
ZcosmicOutputHeadEvent::AdaptiveSyncAvailable { available } => {
head.adaptive_sync_support = Some(
available
.into_result()
.unwrap_or(AdaptiveSyncAvailability::Unsupported),
);
}
ZcosmicOutputHeadEvent::AdaptiveSyncExt { state } => {
head.adaptive_sync = state.into_result().ok();
}
_ => tracing::debug!(?event, "unknown event"),
}
}
Expand All @@ -159,6 +176,7 @@ impl OutputHead {
pub fn new(wlr_head: ZwlrOutputHeadV1) -> Self {
Self {
adaptive_sync: None,
adaptive_sync_support: None,
current_mode: None,
description: String::new(),
enabled: false,
Expand Down
8 changes: 6 additions & 2 deletions lib/src/wl_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ impl Dispatch<wl_registry::WlRegistry, ()> for Context {
));
}
if "zcosmic_output_manager_v1" == &interface[..] {
state.cosmic_output_manager =
Some(registry.bind::<ZcosmicOutputManagerV1, _, _>(name, 1, handle, ()))
state.cosmic_output_manager = Some(registry.bind::<ZcosmicOutputManagerV1, _, _>(
name,
version.min(2),
handle,
(),
))
}
}
}
Expand Down
Loading