Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,44 @@
use objc2::foundation::{NSArray, NSDictionary, NSObject};
use objc2::ns_string;
use objc2::rc::autoreleasepool;
fn main() {
// Create and compare NSObjects
let obj = NSObject::new();
#[allow(clippy::eq_op)]
{
println!("{:?} == {:?}? {:?}", obj, obj, obj == obj);
}
let obj2 = NSObject::new();
println!("{:?} == {:?}? {:?}", obj, obj2, obj == obj2);
// Create an NSArray from a Vec
let objs = vec![obj, obj2];
let array = NSArray::from_vec(objs);
for obj in array.iter() {
println!("{:?}", obj);
}
println!("{}", array.len());
// Turn the NSArray back into a Vec
let mut objs = NSArray::into_vec(array);
let obj = objs.pop().unwrap();
// Create a static NSString
let string = ns_string!("Hello, world!");
// Use an autoreleasepool to get the `str` contents of the NSString
autoreleasepool(|pool| {
println!("{}", string.as_str(pool));
});
// Or simply use the `Display` implementation
let _s = string.to_string(); // Using ToString
println!("{}", string); // Or Display directly
// Create a dictionary mapping strings to objects
let keys = &[string];
let vals = vec![obj];
let dict = NSDictionary::from_keys_and_objects(keys, vals);
println!("{:?}", dict.get(string));
println!("{}", dict.len());
}

View file

@ -0,0 +1,142 @@
//! A custom Objective-C class with a lifetime parameter.
//!
//! Note that we can't use the `declare_class!` macro for this, it doesn't
//! support such use-cases. Instead, we'll declare the class manually!
#![deny(unsafe_op_in_unsafe_fn)]
use std::marker::PhantomData;
use std::sync::Once;
use objc2::declare::{ClassBuilder, Ivar, IvarType};
use objc2::foundation::NSObject;
use objc2::rc::{Id, Owned};
use objc2::runtime::{Class, Object, Sel};
use objc2::{msg_send, msg_send_id, sel};
use objc2::{ClassType, Encoding, Message, RefEncode};
/// Helper type for the instance variable
struct NumberIvar<'a> {
// Doesn't actually matter what we put here, but we have to use the
// lifetime parameter somehow
p: PhantomData<&'a mut u8>,
}
unsafe impl<'a> IvarType for NumberIvar<'a> {
type Type = &'a mut u8;
const NAME: &'static str = "_number_ptr";
}
/// Struct that represents our custom object.
#[repr(C)]
pub struct MyObject<'a> {
// Required to give MyObject the proper layout
superclass: NSObject,
// SAFETY: The ivar is declared below, and is properly initialized in the
// designated initializer.
//
// Note! Attempting to acess the ivar before it has been initialized is
// undefined behaviour!
number: Ivar<NumberIvar<'a>>,
}
unsafe impl RefEncode for MyObject<'_> {
const ENCODING_REF: Encoding = Object::ENCODING_REF;
}
unsafe impl Message for MyObject<'_> {}
impl<'a> MyObject<'a> {
unsafe extern "C" fn init_with_ptr(
&mut self,
_cmd: Sel,
ptr: Option<&'a mut u8>,
) -> Option<&'a mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
// Properly initialize the number reference
Ivar::write(&mut this.number, ptr.expect("got NULL number ptr"));
this
})
}
pub fn new(number: &'a mut u8) -> Id<Self, Owned> {
// SAFETY: The lifetime of the reference is properly bound to the
// returned type
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithPtr: number]
}
}
pub fn get(&self) -> &u8 {
&self.number
}
pub fn set(&mut self, number: u8) {
**self.number = number;
}
}
unsafe impl<'a> ClassType for MyObject<'a> {
type Super = NSObject;
const NAME: &'static str = "MyObject";
fn class() -> &'static Class {
// TODO: Use std::lazy::LazyCell
static REGISTER_CLASS: Once = Once::new();
REGISTER_CLASS.call_once(|| {
let superclass = NSObject::class();
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();
builder.add_static_ivar::<NumberIvar<'a>>();
unsafe {
builder.add_method(
sel!(initWithPtr:),
Self::init_with_ptr as unsafe extern "C" fn(_, _, _) -> _,
);
}
let _cls = builder.register();
});
Class::get("MyObject").unwrap()
}
fn as_super(&self) -> &Self::Super {
&self.superclass
}
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.superclass
}
}
fn main() {
let mut number = 54;
let mut obj = MyObject::new(&mut number);
// It is not possible to convert to `Id<NSObject, Owned>` since that would
// loose the lifetime information that `MyObject` stores
// let obj = Id::into_super(obj);
println!("Number: {}", obj.get());
obj.set(7);
// Won't compile, since `obj` holds a mutable reference to number
// println!("Number: {}", number);
println!("Number: {}", obj.get());
let obj = Id::into_shared(obj);
let obj2 = obj.clone();
// We gave up ownership above, so can't edit the number any more!
// obj.set(7);
println!("Number: {}", obj.get());
println!("Number: {}", obj2.get());
drop(obj);
drop(obj2);
println!("Number: {}", number);
}

View file

@ -0,0 +1,118 @@
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{NSCopying, NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{declare_class, extern_class, msg_send, msg_send_id, ns_string, ClassType};
#[cfg(all(feature = "apple", target_os = "macos"))]
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
#[cfg(all(feature = "apple", target_os = "macos"))]
extern_class!(
#[derive(Debug)]
struct NSResponder;
unsafe impl ClassType for NSResponder {
type Super = NSObject;
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
declare_class!(
#[derive(Debug)]
struct CustomAppDelegate {
pub ivar: u8,
another_ivar: bool,
box_ivar: IvarDrop<Box<i32>>,
maybe_box_ivar: IvarDrop<Option<Box<i32>>>,
id_ivar: IvarDrop<Id<NSString, Shared>>,
maybe_id_ivar: IvarDrop<Option<Id<NSString, Shared>>>,
}
unsafe impl ClassType for CustomAppDelegate {
#[inherits(NSObject)]
type Super = NSResponder;
const NAME: &'static str = "MyCustomAppDelegate";
}
unsafe impl CustomAppDelegate {
#[sel(initWith:another:)]
fn init_with(self: &mut Self, ivar: u8, another_ivar: bool) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
// TODO: `ns_string` can't be used inside closures; investigate!
let s = ns_string!("def");
this.map(|this| {
Ivar::write(&mut this.ivar, ivar);
*this.another_ivar = another_ivar;
*this.maybe_box_ivar = None;
*this.maybe_id_ivar = Some(s.copy());
Ivar::write(&mut this.box_ivar, Box::new(2));
Ivar::write(&mut this.id_ivar, NSString::from_str("abc"));
this
})
}
#[sel(myClassMethod)]
fn my_class_method() {
println!("A class method!");
}
}
// For some reason, `NSApplicationDelegate` is not a "real" protocol we
// can retrieve using `objc_getProtocol` - it seems it is created by
// `clang` only when used in Objective-C...
//
// TODO: Investigate this!
unsafe impl CustomAppDelegate {
/// This is `unsafe` because it expects `sender` to be valid
#[sel(applicationDidFinishLaunching:)]
unsafe fn did_finish_launching(&self, sender: *mut Object) {
println!("Did finish launching!");
// Do something with `sender`
dbg!(sender);
}
/// Some comment before `sel`.
#[sel(applicationWillTerminate:)]
/// Some comment after `sel`.
fn will_terminate(&self, _: *mut Object) {
println!("Will terminate!");
}
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
impl CustomAppDelegate {
pub fn new(ivar: u8, another_ivar: bool) -> Id<Self, Shared> {
let cls = Self::class();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWith: ivar,
another: another_ivar,
]
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
fn main() {
let delegate = CustomAppDelegate::new(42, true);
println!("{:?}", delegate);
println!("{:?}", delegate.ivar);
println!("{:?}", delegate.another_ivar);
println!("{:?}", delegate.box_ivar);
println!("{:?}", delegate.maybe_box_ivar);
println!("{:?}", delegate.id_ivar);
println!("{:?}", delegate.maybe_id_ivar);
}
#[cfg(not(all(feature = "apple", target_os = "macos")))]
fn main() {
panic!("This example uses AppKit, which is only present on macOS");
}

View file

@ -0,0 +1,58 @@
use objc2::foundation::NSObject;
use objc2::runtime::Class;
use objc2::{sel, ClassType, Encode};
fn main() {
// Get the class representing `NSObject`
let cls = NSObject::class();
// Inspect various properties of the class
println!("NSObject superclass: {:?}", cls.superclass());
println!("NSObject size: {}", cls.instance_size());
println!(
"-[NSObject alloc] would work: {}",
cls.responds_to(sel!(alloc))
);
println!(
"+[NSObject alloc] would work: {}",
cls.metaclass().responds_to(sel!(alloc))
);
// Inspect an instance variable on the class
//
// Note: You should not rely on the `isa` ivar being available,
// this is only for demonstration.
let ivar = cls
.instance_variable("isa")
.expect("No ivar with name 'isa' found on NSObject");
println!(
"Instance variable {} has type encoding {:?}",
ivar.name(),
ivar.type_encoding()
);
assert!(<*const Class>::ENCODING.equivalent_to_str(ivar.type_encoding()));
// Inspect a method of the class
let method = cls.instance_method(sel!(hash)).unwrap();
println!(
"-[NSObject hash] takes {} parameters",
method.arguments_count()
);
#[cfg(feature = "malloc")]
{
let hash_return = method.return_type();
println!("-[NSObject hash] return type: {:?}", hash_return);
assert!(usize::ENCODING.equivalent_to_str(&hash_return));
}
// Create an instance
let obj = NSObject::new();
println!("NSObject address: {:p}", obj);
// Access an ivar of the object
//
// As before, you should not rely on the `isa` ivar being available!
let isa = unsafe { *obj.ivar::<*const Class>("isa") };
println!("NSObject isa: {:?}", isa);
}

View file

@ -0,0 +1,144 @@
//! Read from the global pasteboard, and write a new string into it.
//!
//! Works on macOS 10.7+
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(not(all(feature = "apple", target_os = "macos")), allow(unused))]
use std::mem::ManuallyDrop;
use objc2::foundation::{NSArray, NSDictionary, NSInteger, NSObject, NSString};
use objc2::rc::{Id, Shared};
use objc2::runtime::{Class, Object};
use objc2::{extern_class, msg_send, msg_send_id, ClassType};
type NSPasteboardType = NSString;
type NSPasteboardReadingOptionKey = NSString;
#[cfg(all(feature = "apple", target_os = "macos"))]
#[link(name = "AppKit", kind = "framework")]
extern "C" {
/// <https://developer.apple.com/documentation/appkit/nspasteboardtypestring?language=objc>
static NSPasteboardTypeString: Option<&'static NSPasteboardType>;
}
#[cfg(all(feature = "apple", target_os = "macos"))]
extern_class!(
/// <https://developer.apple.com/documentation/appkit/nspasteboard?language=objc>
pub struct NSPasteboard;
// SAFETY: NSPasteboard actually inherits from NSObject.
unsafe impl ClassType for NSPasteboard {
type Super = NSObject;
}
);
#[cfg(all(feature = "apple", target_os = "macos"))]
impl NSPasteboard {
/// We return a `Shared` `Id` because `general` can easily be called
/// again, and it would return the same object, resulting in two aliasing
/// mutable references if we returned an `Owned` Id.
///
/// Besides, even if we could prevent this, there might be Objective-C
/// code somewhere else that accesses this instance.
///
/// TODO: Is this safe to call outside the main thread?
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1530091-generalpasteboard?language=objc>
pub fn general() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), generalPasteboard] }
}
/// Simple, straightforward implementation
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1533566-stringfortype?language=objc>
pub fn text_impl_1(&self) -> Id<NSString, Shared> {
let s = unsafe { NSPasteboardTypeString }.unwrap();
unsafe { msg_send_id![self, stringForType: s] }
}
/// More complex implementation using `readObjectsForClasses:options:`,
/// intended to show some how some patterns might require more knowledge
/// of nitty-gritty details.
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1524454-readobjectsforclasses?language=objc>
pub fn text_impl_2(&self) -> Id<NSString, Shared> {
// The NSPasteboard API is a bit weird, it requires you to pass
// classes as objects, which `objc2::foundation::NSArray` was not
// really made for - so we convert the class to an `Object` type
// instead. Also, we wrap it in `ManuallyDrop` because I'm not sure
// how classes handle `release` calls?
//
// TODO: Investigate and find a better way to express this in objc2.
let string_classes: ManuallyDrop<[Id<Object, Shared>; 1]> = {
let cls: *const Class = NSString::class();
let cls = cls as *mut Object;
unsafe { ManuallyDrop::new([Id::new(cls).unwrap()]) }
};
// Temporary, see https://github.com/rust-lang/rust-clippy/issues/9101
#[allow(unknown_lints, clippy::explicit_auto_deref)]
let class_array = NSArray::from_slice(&*string_classes);
let options = NSDictionary::new();
let objects = unsafe { self.read_objects_for_classes(&class_array, &options) };
// TODO: Should perhaps return Id<Object, Shared>?
let ptr: *const Object = objects.first().unwrap();
// And this part is weird as well, since we now have to convert the
// object into an NSString, which we know it to be since that's what
// we told `readObjectsForClasses:options:`.
let ptr = ptr as *mut NSString;
unsafe { Id::retain(ptr) }.unwrap()
}
/// Defined here to make it easier to declare which types are expected.
/// This is a common pattern that I can wholeheartedly recommend!
///
/// SAFETY: `class_array` must contain classes!
unsafe fn read_objects_for_classes(
&self,
class_array: &NSArray<Object, Shared>,
options: &NSDictionary<NSPasteboardReadingOptionKey, Object>,
) -> Id<NSArray<Object, Shared>, Shared> {
unsafe { msg_send_id![self, readObjectsForClasses: class_array, options: options] }
}
/// This takes `&self` even though `writeObjects:` would seem to mutate
/// the pasteboard. "What is going on?", you might rightfully ask!
///
/// We do this because we can't soundly get a mutable reference to the
/// global `NSPasteboard` instance, see [`NSPasteboard::general`].
///
/// This is sound because `NSPasteboard` contains `NSObject`, which in
/// turn contains `UnsafeCell`, allowing interior mutability.
///
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1533599-clearcontents?language=objc>
/// <https://developer.apple.com/documentation/appkit/nspasteboard/1525945-writeobjects?language=objc>
pub fn set_text(&self, text: Id<NSString, Shared>) {
let _: NSInteger = unsafe { msg_send![self, clearContents] };
let string_array = NSArray::from_slice(&[text]);
let res: bool = unsafe { msg_send![self, writeObjects: &*string_array] };
if !res {
panic!("Failed writing to pasteboard");
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
fn main() {
let pasteboard = NSPasteboard::general();
let impl_1 = pasteboard.text_impl_1();
let impl_2 = pasteboard.text_impl_2();
println!("Pasteboard text from implementation 1 was: {:?}", impl_1);
println!("Pasteboard text from implementation 2 was: {:?}", impl_2);
assert_eq!(impl_1, impl_2);
let s = NSString::from_str("Hello, world!");
pasteboard.set_text(s.clone());
println!("Now the pasteboard text should be: {:?}", s);
assert_eq!(s, pasteboard.text_impl_1());
}
#[cfg(not(all(feature = "apple", target_os = "macos")))]
fn main() {
panic!("this example only works on macOS");
}

View file

@ -0,0 +1,178 @@
//! Speak synthethized text.
//!
//! This uses `NSSpeechSynthesizer` on macOS, and `AVSpeechSynthesizer` on
//! other Apple platforms. Note that `AVSpeechSynthesizer` _is_ available on
//! macOS, but only since 10.15!
//!
//! TODO: Unsure about when to use `&mut` here?
//!
//! Works on macOS >= 10.7 and iOS > 7.0.
#![deny(unsafe_op_in_unsafe_fn)]
#![cfg_attr(feature = "gnustep-1-7", allow(unused))]
use std::thread;
use std::time::Duration;
use objc2::foundation::{NSObject, NSString};
use objc2::rc::{Id, Owned};
use objc2::{extern_class, msg_send, msg_send_id, ns_string, ClassType};
#[cfg(all(feature = "apple", target_os = "macos"))]
mod appkit {
use objc2::{foundation::NSCopying, rc::Shared};
use super::*;
#[link(name = "AppKit", kind = "framework")]
extern "C" {}
extern_class!(
/// <https://developer.apple.com/documentation/appkit/nsspeechsynthesizer?language=objc>
pub struct NSSpeechSynthesizer;
unsafe impl ClassType for NSSpeechSynthesizer {
type Super = NSObject;
}
);
impl NSSpeechSynthesizer {
// Uses default voice
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
fn set_rate(&mut self, rate: f32) {
unsafe { msg_send![self, setRate: rate] }
}
fn set_volume(&mut self, volume: f32) {
unsafe { msg_send![self, setVolume: volume] }
}
fn start_speaking(&mut self, s: &NSString) {
unsafe { msg_send![self, startSpeakingString: s] }
}
pub fn speak(&mut self, utterance: &Utterance) {
// Convert to the range 90-720 that `NSSpeechSynthesizer` seems to
// support
//
// Note that you'd probably want a nonlinear conversion here to
// make it match `AVSpeechSynthesizer`.
self.set_rate(90.0 + (utterance.rate * (360.0 - 90.0)));
self.set_volume(utterance.volume);
self.start_speaking(&utterance.string);
}
pub fn is_speaking(&self) -> bool {
unsafe { msg_send![self, isSpeaking] }
}
}
// Shim to make NSSpeechSynthesizer work similar to AVSpeechSynthesizer
pub struct Utterance {
rate: f32,
volume: f32,
string: Id<NSString, Shared>,
}
impl Utterance {
pub fn new(string: &NSString) -> Self {
Self {
rate: 0.5,
volume: 1.0,
string: string.copy(),
}
}
pub fn set_rate(&mut self, rate: f32) {
self.rate = rate;
}
pub fn set_volume(&mut self, volume: f32) {
self.volume = volume;
}
}
}
#[cfg(all(feature = "apple", not(target_os = "macos")))]
mod avfaudio {
use super::*;
#[link(name = "AVFoundation", kind = "framework")]
extern "C" {}
extern_class!(
/// <https://developer.apple.com/documentation/avfaudio/avspeechsynthesizer?language=objc>
#[derive(Debug)]
pub struct AVSpeechSynthesizer;
unsafe impl ClassType for AVSpeechSynthesizer {
type Super = NSObject;
}
);
impl AVSpeechSynthesizer {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn speak(&mut self, utterance: &AVSpeechUtterance) {
unsafe { msg_send![self, speakUtterance: utterance] }
}
pub fn is_speaking(&self) -> bool {
unsafe { msg_send![self, isSpeaking] }
}
}
extern_class!(
/// <https://developer.apple.com/documentation/avfaudio/avspeechutterance?language=objc>
#[derive(Debug)]
pub struct AVSpeechUtterance;
unsafe impl ClassType for AVSpeechUtterance {
type Super = NSObject;
}
);
impl AVSpeechUtterance {
pub fn new(string: &NSString) -> Id<Self, Owned> {
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithString: string] }
}
pub fn set_rate(&mut self, rate: f32) {
unsafe { msg_send![self, setRate: rate] }
}
pub fn set_volume(&mut self, volume: f32) {
unsafe { msg_send![self, setVolume: volume] }
}
}
}
#[cfg(all(feature = "apple", target_os = "macos"))]
use appkit::{NSSpeechSynthesizer as Synthesizer, Utterance};
#[cfg(all(feature = "apple", not(target_os = "macos")))]
use avfaudio::{AVSpeechSynthesizer as Synthesizer, AVSpeechUtterance as Utterance};
#[cfg(feature = "apple")]
fn main() {
let mut synthesizer = Synthesizer::new();
let mut utterance = Utterance::new(ns_string!("Hello from Rust!"));
utterance.set_rate(0.5);
utterance.set_volume(0.5);
synthesizer.speak(&utterance);
// Wait until speech has properly started up
thread::sleep(Duration::from_millis(1000));
// Wait until finished speaking
while synthesizer.is_speaking() {
thread::sleep(Duration::from_millis(100));
}
}
#[cfg(feature = "gnustep-1-7")]
fn main() {
panic!("this example is only available on Apple targets");
}