use smithay_client_toolkit::reexports::client::{
Connection, Dispatch, QueueHandle, delegate_noop, globals::GlobalList, protocol::wl_surface,
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,
wp_fractional_scale_v1::{self, WpFractionalScaleV1},
};
use wayland_protocols::wp::viewporter::client::{
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
};
use super::wayland::WaylandState;
use crate::surface::SurfaceId;
const SCALE_DENOMINATOR: u32 = 120;
fn scale_from_preferred(preferred: u32) -> f32 {
preferred as f32 / SCALE_DENOMINATOR as f32
}
pub struct Scaling {
manager: WpFractionalScaleManagerV1,
viewporter: WpViewporter,
}
pub struct SurfaceScaling {
fractional: WpFractionalScaleV1,
viewport: WpViewport,
}
impl SurfaceScaling {
pub(super) fn destroy(self) {
self.fractional.destroy();
self.viewport.destroy();
}
}
impl Scaling {
pub(super) fn bind(globals: &GlobalList, qh: &QueueHandle<WaylandState>) -> Option<Self> {
let manager: Option<WpFractionalScaleManagerV1> = globals.bind(qh, 1..=1, ()).ok();
let viewporter: Option<WpViewporter> = globals.bind(qh, 1..=1, ()).ok();
let (Some(manager), Some(viewporter)) = (manager, viewporter) else {
log::info!(
"fractional scaling unavailable - surfaces will render at the \
compositor's preferred integer scale"
);
return None;
};
Some(Self {
manager,
viewporter,
})
}
}
impl WaylandState {
pub(super) fn create_surface_scaling(
&self,
id: SurfaceId,
wl_surface: &wl_surface::WlSurface,
) -> Option<SurfaceScaling> {
let scaling = self.scaling.as_ref()?;
Some(SurfaceScaling {
fractional: scaling
.manager
.get_fractional_scale(wl_surface, &self.qh, id),
viewport: scaling.viewporter.get_viewport(wl_surface, &self.qh, ()),
})
}
pub(crate) fn set_viewport_destination(&self, id: SurfaceId, width: u32, height: u32) {
let Some(scaling) = self.surfaces.get(&id).and_then(|s| s.scaling.as_ref()) else {
return;
};
scaling
.viewport
.set_destination(width as i32, height as i32);
}
}
impl Dispatch<WpFractionalScaleV1, SurfaceId> for WaylandState {
fn event(
state: &mut Self,
_proxy: &WpFractionalScaleV1,
event: wp_fractional_scale_v1::Event,
id: &SurfaceId,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
let wp_fractional_scale_v1::Event::PreferredScale { scale } = event else {
return;
};
let Some(surface_state) = state.surfaces.get_mut(id) else {
return;
};
let scale = scale_from_preferred(scale);
if surface_state.adopt_scale(scale) {
log::info!("Surface {id:?} preferred fractional scale is {scale}");
}
}
}
delegate_noop!(WaylandState: ignore WpFractionalScaleManagerV1);
delegate_noop!(WaylandState: ignore WpViewporter);
delegate_noop!(WaylandState: ignore WpViewport);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_preferred_scale_is_its_numerator_over_a_hundred_and_twenty() {
assert_eq!(scale_from_preferred(120), 1.0);
assert_eq!(scale_from_preferred(180), 1.5);
assert_eq!(scale_from_preferred(240), 2.0);
}
}