#![allow(clippy::unused_self)]
use {
crate::{ffi, String},
::std::{
ffi::{CStr, CString},
fmt::{Display, Write as _},
marker::PhantomData,
os::{raw::c_int, unix::ffi::OsStrExt},
path::Path,
ptr, slice, str,
},
};
#[derive(Debug)]
pub struct Api<'rofi> {
display_name: ptr::NonNull<*mut u8>,
display_name_len: usize,
display_name_capacity: usize,
lifetime: PhantomData<&'rofi ()>,
}
unsafe impl Send for Api<'_> {}
unsafe impl Sync for Api<'_> {}
impl Api<'_> {
pub(crate) unsafe fn new(display_name: ptr::NonNull<*mut u8>) -> Self {
assert_eq!(*unsafe { display_name.as_ref() }, ptr::null_mut());
Self {
display_name,
display_name_len: 0,
display_name_capacity: 0,
lifetime: PhantomData,
}
}
#[must_use]
pub fn display_name(&self) -> Option<&str> {
let ptr = *unsafe { self.display_name.as_ref() };
if ptr.is_null() {
return None;
}
let slice = unsafe { slice::from_raw_parts(ptr, self.display_name_len) };
Some(unsafe { str::from_utf8_unchecked(slice) })
}
fn change_display_name(&mut self, display_name: Option<String>) -> Option<String> {
let ptr = unsafe { self.display_name.as_mut() };
let old_len = self.display_name_len;
let old_capacity = self.display_name_capacity;
let old_ptr = *ptr;
if let Some(display_name) = &display_name {
self.display_name_len = display_name.len();
self.display_name_capacity = display_name.capacity();
}
*ptr = display_name.map_or_else(ptr::null_mut, String::into_raw);
if old_ptr.is_null() {
None
} else {
Some(unsafe { String::from_raw_parts(old_ptr, old_len, old_capacity) })
}
}
pub fn take_display_name(&mut self) -> Option<String> {
self.change_display_name(None)
}
pub fn replace_display_name(&mut self, display_name: String) -> Option<String> {
self.change_display_name(Some(display_name))
}
pub fn set_display_name<T: Display>(&mut self, display_name: T) {
let mut buf = self.take_display_name().unwrap_or_default();
buf.clear();
write!(buf, "{display_name}").unwrap();
self.replace_display_name(buf);
}
#[must_use]
pub fn supports_image<P: AsRef<Path>>(&self, path: P) -> bool {
let mut path = path.as_ref().as_os_str().as_bytes().to_owned();
path.push(b'\0');
let res = unsafe { ffi::icon_fetcher::file_is_image(path.as_ptr().cast()) };
res != 0
}
#[must_use]
pub fn query_icon(&mut self, name: &str, size: u32) -> IconRequest {
let name = CString::new(name).expect("name contained nul bytes");
self.query_icon_cstr(&*name, size)
}
#[must_use]
pub fn query_icon_cstr(&mut self, name: &CStr, size: u32) -> IconRequest {
let uid = unsafe {
ffi::icon_fetcher::query(name.as_ptr(), size.try_into().unwrap_or(c_int::MAX))
};
IconRequest { uid }
}
#[must_use]
pub fn query_icon_wh(&mut self, name: &str, width: u32, height: u32) -> IconRequest {
let name = CString::new(name).expect("name contained nul bytes");
self.query_icon_wh_cstr(&*name, width, height)
}
#[must_use]
pub fn query_icon_wh_cstr(&mut self, name: &CStr, width: u32, height: u32) -> IconRequest {
let uid = unsafe {
ffi::icon_fetcher::query_advanced(
name.as_ptr(),
width.try_into().unwrap_or(c_int::MAX),
height.try_into().unwrap_or(c_int::MAX),
)
};
IconRequest { uid }
}
#[must_use]
#[allow(clippy::missing_panics_doc, clippy::needless_pass_by_value)]
pub fn retrieve_icon(&mut self, request: IconRequest) -> Option<cairo::Surface> {
let ptr = unsafe { ffi::icon_fetcher::get(request.uid) };
if ptr.is_null() {
None
} else {
Some(unsafe { cairo::Surface::from_raw_full(ptr) }.unwrap())
}
}
}
#[derive(Debug)]
pub struct IconRequest {
uid: u32,
}
impl IconRequest {
#[must_use]
pub fn wait(self, api: &mut Api<'_>) -> Option<cairo::Surface> {
api.retrieve_icon(self)
}
}