167 lines
5.0 KiB
Rust
167 lines
5.0 KiB
Rust
use alloc::{string::String, vec::Vec};
|
|
use core::ffi::c_void;
|
|
|
|
type CFIndex = isize;
|
|
type Boolean = u8;
|
|
type CFStringEncoding = u32;
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
|
|
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy)]
|
|
struct CFRange {
|
|
pub location: CFIndex,
|
|
pub length: CFIndex,
|
|
}
|
|
|
|
type CFTypeRef = *const c_void;
|
|
|
|
#[repr(C)]
|
|
struct __CFArray(c_void);
|
|
type CFArrayRef = *const __CFArray;
|
|
|
|
#[repr(C)]
|
|
struct __CFString(c_void);
|
|
type CFStringRef = *const __CFString;
|
|
|
|
// Most of these definitions come from `core-foundation-sys`, but we want this crate
|
|
// to be `no_std` and `core-foundation-sys` isn't currently.
|
|
#[link(name = "CoreFoundation", kind = "framework")]
|
|
extern "C" {
|
|
fn CFArrayGetCount(theArray: CFArrayRef) -> CFIndex;
|
|
fn CFArrayGetValueAtIndex(theArray: CFArrayRef, idx: CFIndex) -> *const c_void;
|
|
|
|
fn CFStringGetLength(theString: CFStringRef) -> CFIndex;
|
|
fn CFStringGetBytes(
|
|
theString: CFStringRef,
|
|
range: CFRange,
|
|
encoding: CFStringEncoding,
|
|
lossByte: u8,
|
|
isExternalRepresentation: Boolean,
|
|
buffer: *mut u8,
|
|
maxBufLen: CFIndex,
|
|
usedBufLen: *mut CFIndex,
|
|
) -> CFIndex;
|
|
|
|
fn CFRelease(cf: CFTypeRef);
|
|
|
|
fn CFLocaleCopyPreferredLanguages() -> CFArrayRef;
|
|
}
|
|
|
|
pub(crate) fn get() -> impl Iterator<Item = String> {
|
|
let preferred_langs = get_languages();
|
|
let mut idx = 0;
|
|
|
|
#[allow(clippy::as_conversions)]
|
|
core::iter::from_fn(move || unsafe {
|
|
let (langs, num_langs) = preferred_langs.as_ref()?;
|
|
|
|
// 0 to N-1 inclusive
|
|
if idx >= *num_langs {
|
|
return None;
|
|
}
|
|
|
|
// SAFETY: The current index has been checked that its still within bounds of the array.
|
|
// XXX: We don't retain the strings because we know we have total ownership of the backing array.
|
|
let locale = CFArrayGetValueAtIndex(langs.0, idx) as CFStringRef;
|
|
idx += 1;
|
|
|
|
// SAFETY: `locale` is a valid CFString pointer because the array will always contain a value.
|
|
let str_len = CFStringGetLength(locale);
|
|
|
|
let range = CFRange {
|
|
location: 0,
|
|
length: str_len,
|
|
};
|
|
|
|
let mut capacity = 0;
|
|
// SAFETY:
|
|
// - `locale` is a valid CFString
|
|
// - The supplied range is within the length of the string.
|
|
// - `capacity` is writable.
|
|
// Passing NULL and `0` is correct for the buffer to get the
|
|
// encoded output length.
|
|
CFStringGetBytes(
|
|
locale,
|
|
range,
|
|
kCFStringEncodingUTF8,
|
|
0,
|
|
false as Boolean,
|
|
core::ptr::null_mut(),
|
|
0,
|
|
&mut capacity,
|
|
);
|
|
|
|
// Guard against a zero-sized allocation, if that were to somehow occur.
|
|
if capacity == 0 {
|
|
return None;
|
|
}
|
|
|
|
// Note: This is the number of bytes (u8) that will be written to
|
|
// the buffer, not the number of codepoints they would contain.
|
|
let mut buffer = Vec::with_capacity(capacity as usize);
|
|
|
|
// SAFETY:
|
|
// - `locale` is a valid CFString
|
|
// - The supplied range is within the length of the string.
|
|
// - `buffer` is writable and has sufficent capacity to receive the data.
|
|
// - `maxBufLen` is correctly based on `buffer`'s available capacity.
|
|
// - `out_len` is writable.
|
|
let mut out_len = 0;
|
|
CFStringGetBytes(
|
|
locale,
|
|
range,
|
|
kCFStringEncodingUTF8,
|
|
0,
|
|
false as Boolean,
|
|
buffer.as_mut_ptr(),
|
|
capacity as CFIndex,
|
|
&mut out_len,
|
|
);
|
|
|
|
// Sanity check that both calls to `CFStringGetBytes`
|
|
// were equivalent. If they weren't, the system is doing
|
|
// something very wrong...
|
|
assert!(out_len <= capacity);
|
|
|
|
// SAFETY: The system has written `out_len` elements, so they are
|
|
// initialized and inside the buffer's capacity bounds.
|
|
buffer.set_len(out_len as usize);
|
|
|
|
// This should always contain UTF-8 since we told the system to
|
|
// write UTF-8 into the buffer, but the value is small enough that
|
|
// using `from_utf8_unchecked` isn't worthwhile.
|
|
String::from_utf8(buffer).ok()
|
|
})
|
|
}
|
|
|
|
fn get_languages() -> Option<(CFArray, CFIndex)> {
|
|
unsafe {
|
|
// SAFETY: This function is safe to call and has no invariants. Any value inside the
|
|
// array will be owned by us.
|
|
let langs = CFLocaleCopyPreferredLanguages();
|
|
if !langs.is_null() {
|
|
let langs = CFArray(langs);
|
|
// SAFETY: The returned array is a valid CFArray object.
|
|
let count = CFArrayGetCount(langs.0);
|
|
if count != 0 {
|
|
Some((langs, count))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
struct CFArray(CFArrayRef);
|
|
|
|
impl Drop for CFArray {
|
|
fn drop(&mut self) {
|
|
// SAFETY: This wrapper contains a valid CFArray.
|
|
unsafe { CFRelease(self.0.cast()) }
|
|
}
|
|
}
|