use std::sync::atomic::{AtomicBool, Ordering};
use super::dbus::{Arg, BusKind, CallError, Connection, parse_variant_int};
use crate::{BrokerError, FallbackReason};
const RTKIT_DEST: &str = "org.freedesktop.RealtimeKit1";
const RTKIT_PATH: &str = "/org/freedesktop/RealtimeKit1";
const RTKIT_IFACE: &str = "org.freedesktop.RealtimeKit1";
const PORTAL_DEST: &str = "org.freedesktop.portal.Desktop";
const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop";
const PORTAL_IFACE: &str = "org.freedesktop.portal.Realtime";
const PROPERTIES_IFACE: &str = "org.freedesktop.DBus.Properties";
pub(crate) const DEFAULT_MIN_NICE_LEVEL: i64 = -15;
pub(crate) const DEFAULT_RTTIME_USEC_MAX: i64 = 200_000;
pub(crate) const DEFAULT_MAX_REALTIME_PRIORITY: i64 = 20;
static RTKIT_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
static PORTAL_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
pub(crate) struct Broker {
conn: Connection,
dest: &'static str,
path: &'static str,
iface: &'static str,
unavailable_flag: &'static AtomicBool,
}
pub(crate) fn classify(e: &CallError) -> (FallbackReason, bool) {
match e {
CallError::Absent => (FallbackReason::NoBroker, true),
CallError::TimedOut => (FallbackReason::BrokerTimedOut, false),
CallError::Refused { .. } => (FallbackReason::BrokerRefused, false),
CallError::Io(_) => (FallbackReason::NoBroker, false),
}
}
pub(crate) fn broker_error(e: &CallError) -> Option<BrokerError> {
match e {
CallError::Refused { name } => Some(BrokerError::from_dbus_name(name)),
_ => None,
}
}
impl Broker {
pub(crate) fn rtkit() -> Result<Self, FallbackReason> {
Self::open(
BusKind::System,
RTKIT_DEST,
RTKIT_PATH,
RTKIT_IFACE,
&RTKIT_UNAVAILABLE,
)
}
pub(crate) fn portal() -> Result<Self, FallbackReason> {
Self::open(
BusKind::Session,
PORTAL_DEST,
PORTAL_PATH,
PORTAL_IFACE,
&PORTAL_UNAVAILABLE,
)
}
fn open(
kind: BusKind,
dest: &'static str,
path: &'static str,
iface: &'static str,
unavailable_flag: &'static AtomicBool,
) -> Result<Self, FallbackReason> {
if unavailable_flag.load(Ordering::Relaxed) {
return Err(FallbackReason::NoBroker);
}
match Connection::open(kind) {
Ok(conn) => Ok(Broker {
conn,
dest,
path,
iface,
unavailable_flag,
}),
Err(e) => {
let (reason, absent) = classify(&e);
if absent {
unavailable_flag.store(true, Ordering::Relaxed);
}
Err(reason)
}
}
}
fn property(&mut self, name: &str) -> Option<i64> {
match self.conn.call(
self.dest,
self.path,
PROPERTIES_IFACE,
"Get",
&[Arg::Str(self.iface), Arg::Str(name)],
) {
Ok(body) => parse_variant_int(&body),
Err(e) => {
if matches!(e, CallError::Absent) {
self.unavailable_flag.store(true, Ordering::Relaxed);
}
None
}
}
}
pub(crate) fn min_nice_level(&mut self) -> i64 {
self.property("MinNiceLevel")
.unwrap_or(DEFAULT_MIN_NICE_LEVEL)
}
pub(crate) fn max_realtime_priority(&mut self) -> i64 {
self.property("MaxRealtimePriority")
.unwrap_or(DEFAULT_MAX_REALTIME_PRIORITY)
}
pub(crate) fn rttime_usec_max(&mut self) -> i64 {
self.property("RTTimeUSecMax")
.unwrap_or(DEFAULT_RTTIME_USEC_MAX)
}
fn method(&mut self, member: &str, args: &[Arg<'_>]) -> Result<(), CallError> {
match self
.conn
.call(self.dest, self.path, self.iface, member, args)
{
Ok(_) => Ok(()),
Err(e) => {
if matches!(e, CallError::Absent) {
self.unavailable_flag.store(true, Ordering::Relaxed);
}
Err(e)
}
}
}
pub(crate) fn make_thread_high_priority(
&mut self,
tid: u64,
nice: i32,
) -> Result<(), CallError> {
self.method("MakeThreadHighPriority", &[Arg::U64(tid), Arg::I32(nice)])
}
pub(crate) fn make_thread_realtime(
&mut self,
tid: u64,
priority: u32,
) -> Result<(), CallError> {
self.method("MakeThreadRealtime", &[Arg::U64(tid), Arg::U32(priority)])
}
pub(crate) fn make_thread_realtime_with_pid(
&mut self,
pid: u64,
tid: u64,
priority: u32,
) -> Result<(), CallError> {
self.method(
"MakeThreadRealtimeWithPID",
&[Arg::U64(pid), Arg::U64(tid), Arg::U32(priority)],
)
}
}
pub(crate) fn try_high_priority(
tid: u64,
requested_nice: i32,
) -> Result<i32, (FallbackReason, Option<BrokerError>)> {
let mut broker = Broker::rtkit().map_err(|r| (r, None))?;
let min_nice = broker.min_nice_level().clamp(-20, 0) as i32;
let nice = requested_nice.max(min_nice);
match broker.make_thread_high_priority(tid, nice) {
Ok(()) => Ok(nice),
Err(e) => {
let (reason, _absent) = classify(&e);
Err((reason, broker_error(&e)))
}
}
}