1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
// Copyright 2017 Lyndon Brown
//
// This file is part of the PulseAudio Rust language binding.
//
// Licensed under the MIT license or the Apache license (version 2.0), at your option. You may not
// copy, modify, or distribute this file except in compliance with said license. You can find copies
// of these licenses either in the LICENSE-MIT and LICENSE-APACHE files, or alternatively at
// <http://opensource.org/licenses/MIT> and <http://www.apache.org/licenses/LICENSE-2.0>
// respectively.
//
// Portions of documentation are copied from the LGPL 2.1+ licensed PulseAudio C headers on a
// fair-use basis, as discussed in the overall project readme (available in the git repository).
//! Version related constants and functions.
//!
//! This module contains functions and constants relating to the version of the PulseAudio (PA)
//! client system library.
//!
//! # Dynamic compatibility
//!
//! As discussed in the project `COMPATIBILITY.md` file, compatibility is offered for multiple
//! versions of the PA client system library, with feature flags adapting the crate to changes made
//! in the API of newer PA versions.
//!
//! Note that the minimum supported version of PA is v5.0.
//!
//! # Runtime checking
//!
//! The following functions are provided to retrieve and compare the version of the actual PA client
//! system library in use at runtime:
//!
//! - The [`get_library_version()`] function obtains the version string the system library
//! provides.
//! - The [`get_library_version_numbers()`] function uses the previous function and attempts to
//! parse the version string it returns into numeric form for comparison purposes.
//! - The [`compare_with_library_version()`] function uses the previous function and allows
//! comparing a provided major and minor version number with what it returned.
//! - The [`library_version_is_too_old()`] function uses the previous function to compare against
//! the [`TARGET_VERSION`] constant version numbers. This constant varies depending upon PA
//! version feature flags, and thus this can be used to check that a program is not being run on
//! a system with too old of a version of PA, helping combat the “forward” compatibility problem
//! discussed in the project `COMPATIBILITY.md` documentation.
//!
//! # Dynamic constants
//!
//! The version constants defined here mostly relate to those provided in the PA C headers, and are
//! likely of little use to most projects. They are set dynamically, depending upon the feature
//! flags used, or in other words the level of minimum compatibility support selected. Note that PA
//! version feature flags are only introduced when new versions of PA introduce changes to its API
//! that would require one. The version numbers associated with each PA version feature flag are
//! those from the PA version that required introduction of that feature flag.
//!
//! As an example to clarify, if the “newest” PA version feature flag enabled is `pa_v8` (which
//! obviously corresponds to a minimum compatibility level of PA version 8.0), then the
//! [`TARGET_VERSION`] constant is set to `(8, 0)`. The “next-newest” feature flag is `pa_v11`,
//! which if enabled would bump it up to `(11, 0)`.
use capi;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::ffi::CStr;
// Re-export from sys
pub use capi::version::{Compatibility, get_compatibility};
pub use capi::version::{TARGET_VERSION_STRING, TARGET_VERSION};
pub use capi::version::{PA_API_VERSION as API_VERSION, PA_PROTOCOL_VERSION as PROTOCOL_VERSION};
/// Kinds of errors from trying to parse the runtime PulseAudio system library version string.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
enum ErrorKind {
/// Error parsing part as integer.
ParseIntError,
/// Missing version part.
MissingPart,
/// Too many parts found in the string (unexpected; something is wrong).
ExtraParts,
}
/// Error from trying to parse the runtime PulseAudio system library version string.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Error {
/// The problematic version sring which could not be parsed.
ver_str: Cow<'static, str>,
}
impl Error {
#[inline]
fn new(ver_str: Cow<'static, str>) -> Self {
Self { ver_str }
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
format!("failed to parse PulseAudio system library version string '{}'", &self.ver_str)
.fmt(f)
}
}
/// Checks whether the version of the running system library is older than the version corresponding
/// to the compatibility level selected via the available feature flags.
///
/// Returns `Ok(true)` if the library version is older, `Ok(false)` if equal or newer, or `Err` if a
/// problem occurred processing the version string.
#[inline]
pub fn library_version_is_too_old() -> Result<bool, Error> {
match compare_with_library_version(TARGET_VERSION.0, TARGET_VERSION.1)? {
Ordering::Less | Ordering::Equal => Ok(false),
Ordering::Greater => Ok(true),
}
}
/// Compares the supplied version with that of the runtime system library.
///
/// Returns the comparison, or `Err` if a problem occurred parsing the library version string. The
/// comparison will represent `supplied.cmp(&library)`.
#[inline]
pub fn compare_with_library_version(major: u8, minor: u8) -> Result<std::cmp::Ordering, Error> {
let (lib_major, lib_minor, _) = get_library_version_numbers()?;
Ok((major).cmp(&lib_major).then_with(|| minor.cmp(&lib_minor)))
}
/// Tries to convert the runtime system library version to numeric major, minor and micro form, for
/// comparison purposes.
///
/// Note, currently micro is always zero. This is the case even in beta/rc versions (like 13.99.1)
/// due to the fact that the version string returned by PA always has micro fixed to zero.
///
/// Returns `Err` if parsing the version number string fails.
#[inline]
pub fn get_library_version_numbers() -> Result<(u8, u8, u8), Error> {
let ver = get_library_version().to_string_lossy();
pa_version_str_to_num(&ver).or_else(|_e| Err(Error::new(ver)))
}
/// Convert PulseAudio version string to major, minor and micro numbers.
///
/// The version number string should come from `pa_get_library_version()` and thus currently will
/// always consist of exactly `$MAJOR.$MINOR.0` per the compiled version.h header. Note that the
/// micro number is fixed to zero.
#[inline]
fn pa_version_str_to_num(ver: &str) -> Result<(u8, u8, u8), ErrorKind> {
let mut parts = ver.split('.');
let major: u8 =
parts.next().ok_or(ErrorKind::MissingPart)?.parse().or(Err(ErrorKind::ParseIntError))?;
let minor: u8 =
parts.next().ok_or(ErrorKind::MissingPart)?.parse().or(Err(ErrorKind::ParseIntError))?;
// Note, we want to be very strict about accepting only properly formatted values, as anything
// otherwise suggests a wierd problem, thus we do parse the micro number even though it will
// always be zero.
let micro: u8 =
parts.next().ok_or(ErrorKind::MissingPart)?.parse().or(Err(ErrorKind::ParseIntError))?;
match parts.next().is_some() {
true => Err(ErrorKind::ExtraParts), // Something isn’t right
false => Ok((major, minor, micro)),
}
}
/// Gets the version string of the (PulseAudio client system) library actually in use at runtime.
#[inline]
pub fn get_library_version() -> &'static CStr {
unsafe { CStr::from_ptr(capi::pa_get_library_version()) }
}
/// Just compares given version with that defined in `TARGET_VERSION` and returns `true` if the
/// `TARGET_VERSION` version is greater. This does **not** involve talking to the PulseAudio client
/// library at runtime. This is not very useful!
#[deprecated(since = "2.22.0")]
#[inline(always)]
pub fn check_version(major: u8, minor: u8, micro: u8) -> bool {
#[allow(deprecated)]
capi::pa_check_version(major, minor, micro)
}
#[test]
fn test_ver_str_to_num() {
assert_eq!(pa_version_str_to_num(""), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num(" "), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("."), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("a"), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("a.a"), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("a.1"), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("14"), Err(ErrorKind::MissingPart));
assert_eq!(pa_version_str_to_num("14.0"), Err(ErrorKind::MissingPart));
assert_eq!(pa_version_str_to_num("14.0.0"), Ok((14, 0, 0)));
assert_eq!(pa_version_str_to_num("14.1.0"), Ok((14, 1, 0)));
assert_eq!(pa_version_str_to_num("14.2.0."), Err(ErrorKind::ExtraParts));
assert_eq!(pa_version_str_to_num("14.2.0.0"), Err(ErrorKind::ExtraParts));
assert_eq!(pa_version_str_to_num("12.2a"), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("12.a"), Err(ErrorKind::ParseIntError));
assert_eq!(pa_version_str_to_num("12.a.1"), Err(ErrorKind::ParseIntError));
}
#[test]
fn test_getting_pa_version() {
let actual_ver_str =
unsafe { CStr::from_ptr(capi::pa_get_library_version()).to_string_lossy() };
let (major, minor, micro) = get_library_version_numbers().unwrap();
assert_eq!(format!("{}.{}.{}", major, minor, micro), actual_ver_str);
}
#[test]
fn test_comparing_pa_version() {
let (major, minor, _micro) = get_library_version_numbers().unwrap();
assert_eq!(compare_with_library_version(major, minor).unwrap(), Ordering::Equal);
assert_eq!(compare_with_library_version(major + 1, minor).unwrap(), Ordering::Greater);
assert_eq!(compare_with_library_version(major - 1, minor).unwrap(), Ordering::Less);
assert_eq!(compare_with_library_version(major, minor + 1).unwrap(), Ordering::Greater);
assert_eq!(compare_with_library_version(major - 1, minor + 1).unwrap(), Ordering::Less);
if minor > 0 {
assert_eq!(compare_with_library_version(major, minor - 1).unwrap(), Ordering::Less);
assert_eq!(compare_with_library_version(major + 1, minor - 1).unwrap(), Ordering::Greater);
}
}
#[test]
fn test_lib_ver_not_too_old() {
assert_eq!(library_version_is_too_old(), Ok(false));
}