Skip to content

Commit

Permalink
feat: support adaptive-sync (VRR)
Browse files Browse the repository at this point in the history
* cli/lib: Add basic adaptive-sync support
* cli/lib: Support cosmic-extensions for adaptive-sync
* shell: Parse adaptive_sync keys
  • Loading branch information
Drakulix authored Nov 26, 2024
1 parent 8d09380 commit 311b944
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 37 deletions.
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

0 comments on commit 311b944

Please sign in to comment.