Search code examples
objective-crustptp

Apple PTP - withCompleteContentCatalog not firing (Rust + Obj-C)


From the imagecapturecore-rs crate here: https://github.com/brandonhamilton/image-capture-core-rs/issues/7

Only didOpenSessionWithError fires when connecting a PTP (picture transfer protocol) device with a None for the error value and an NSArray with a count of 0.

            decl.add_method(
                sel!(device:didOpenSessionWithError:),
                device_did_open_session_with_error as extern "C" fn(&Object, Sel, id, id),
            );
            println!("  πŸ“Έ add_method didCloseSessionWithError");
            decl.add_method(
                sel!(device:didCloseSessionWithError:),
                device_did_close_session_with_error as extern "C" fn(&Object, Sel, id, id),
            );
            println!("  πŸ“Έ add_method didRemoveDevice");
            decl.add_method(
                sel!(didRemoveDevice:),
                device_did_remove_device as extern "C" fn(&Object, Sel, id),
            );
            println!("  πŸ“Έ add_method withCompleteContentCatalog");
            decl.add_method(
                sel!(withCompleteContentCatalog:),
                device_did_become_ready as extern "C" fn(&Object, Sel, id),
            );

Do I need to be using the fancier cameraDevice.requestOpenSession() with the callback function from here? https://developer.apple.com/documentation/imagecapturecore/icdevice/3142916-requestopensession

Full Code:

extern crate cocoa;
extern crate dispatch;
extern crate image_capture_core;
extern crate libc;


pub mod image_capture_core_mod {
    use cocoa::appkit::{
        NSApp, NSApplication, NSApplicationActivateIgnoringOtherApps,
        NSApplicationActivationPolicyRegular, NSRunningApplication,
    };
    use cocoa::base::{id, nil, BOOL};
    use cocoa::foundation::{NSAutoreleasePool, NSString, NSArray};
    use dispatch::{Queue};
    use image_capture_core::device::{
        ICDevice, ICDeviceLocationTypeMask, 
        ICDeviceTypeMask,
    };
    use image_capture_core::camera_device::ICCameraDevice;
    use image_capture_core::camera_item::ICCameraItem;
    use image_capture_core::device_browser::ICDeviceBrowser;
    use objc::declare::ClassDecl;
    use objc::runtime::{Class, Object, Sel};
    use std::ffi::{CStr, CString};

    
    /// Convert an NSString object into a Rust String
    pub fn nsstring_decode(str: id) -> String {
        unsafe {
            let cstr: *const libc::c_char = msg_send![str, UTF8String];
            let rstr = CStr::from_ptr(cstr).to_string_lossy().into_owned();
            rstr
        }
    }
    pub fn nsstring_encode(str_enc: String) -> *mut libc::c_char {
        CString::new(str_enc).unwrap().into_raw()
    }

    // deviceDidBecomeReady
    // withCompleteContentCatalog
    // https://developer.apple.com/documentation/imagecapturecore/iccameradevicedelegate/1508008-devicedidbecomeready
    extern "C" fn device_did_become_ready(
        _: &Object,
        _: Sel,
        _: id,
    ) {
        println!("  πŸ€©πŸ“· device_did_become_ready");
    }
    extern "C" fn device_did_open_session_with_error(
        obj_obj: &Object,
        sel_sel: Sel,
        device: id,
        error: id,
    ) {
        // let camera_items: Vec<ICCameraItem> = vec![];
        println!("  πŸ“ΈπŸ“Έ device_did_open_session_with_error obj: {:?}", obj_obj);
        println!("  πŸ“ΈπŸ“Έ device_did_open_session_with_error sel: {:?}", sel_sel);
        println!("  πŸ“ΈπŸ“Έ device_did_open_session_with_error Error: {:?}", unsafe { error.as_ref() });
        println!("  πŸ“ΈπŸ“Έ device_did_open_session_with_error device.delegate: {:?}", unsafe { ICDevice::delegate(device).as_ref().unwrap() });

        unsafe {
            let cam = device.mediaFiles().as_ref().unwrap();
            let cam_class = cam.class();
            let super_cls = cam_class.superclass().unwrap();

            for method in obj_obj.class().adopted_protocols().iter() {
                println!("  β„οΈπŸ“Έ obj_obj protocol: {}", method.name());
            }
            for method in cam_class.adopted_protocols().iter() {
                println!("  β„οΈπŸ“Έ class protocol: {}", method.name());
            }

            for ivar in cam_class.instance_variables().iter() {
                println!("  πŸŒžπŸ“Έ class ivar: {}", ivar.name());
            }
            for ivar in cam_class.instance_methods().iter() {
                println!("  πŸ“›πŸ“· class method: {:?}", ivar.name());
            }
        }
        println!("  πŸ„πŸ“· device.open: {:?}", unsafe { device.hasOpenSession() });
    }
    extern "C" fn device_did_close_session_with_error(
        _: &Object,
        _: Sel,
        device: id,
        _: id,
    ) {
        println!("  πŸ“ΈπŸ“Έ device_did_close_session_with_error");
    }
    extern "C" fn device_did_remove_device(
        _: &Object,
        _: Sel,
        _: id,
    ) {
        println!("  πŸ“ΈπŸ“Έ device_did_remove");
    }

    fn get_device_delegate() -> ClassDecl {
        unsafe {
            let mut decl = ClassDecl::new(
                "CameraDeviceDelegate", 
                objc::runtime::Class::get("BrowserDelegate").unwrap()
            ).unwrap();

            println!("  πŸ“Έ add_method didOpenSessionWithError");
            decl.add_method(
                sel!(device:didOpenSessionWithError:),
                device_did_open_session_with_error as extern "C" fn(&Object, Sel, id, id),
            );
            println!("  πŸ“Έ add_method didCloseSessionWithError");
            decl.add_method(
                sel!(device:didCloseSessionWithError:),
                device_did_close_session_with_error as extern "C" fn(&Object, Sel, id, id),
            );
            println!("  πŸ“Έ add_method didRemoveDevice");
            decl.add_method(
                sel!(didRemoveDevice:),
                device_did_remove_device as extern "C" fn(&Object, Sel, id),
            );
            println!("  πŸ“Έ add_method withCompleteContentCatalog");
            decl.add_method(
                sel!(withCompleteContentCatalog:),
                device_did_become_ready as extern "C" fn(&Object, Sel, id),
            );
            println!("  πŸ“Έ add_method end");
            decl
        }
    }

    pub fn init(queue: Queue) {
        println!("  πŸ™‚ image_capture_core_mod init!");
        unsafe {
            queue.clone().barrier_async(move || {
                println!("  πŸ™‚ image_capture_core_mod exec_async");
                let main = Queue::main();
                
                main.barrier_async(|| {
                    println!("  πŸ™‚ image_capture_core_mod exec_sync");
                    let _pool = NSAutoreleasePool::new(nil);                   

                    // Create the device browser delegate
                    let superclass = class!(NSObject);
                    let mut decl = ClassDecl::new("BrowserDelegate", superclass).unwrap();

                    extern "C" fn device_browser_did_add_device(
                        _: &Object,
                        _: Sel,
                        cam_id: id,
                        device_self: id,
                        _more_coming: BOOL,
                    ) {
                        let device_name = unsafe { ICDevice::name(device_self) };
                        let device_type = unsafe { ICDevice::type_(device_self) };
                        let name = nsstring_decode( device_name );

                        unsafe {
                            let device_delegate = get_device_delegate();
                            let delegate_class = device_delegate.register();
                            let delegate_object = msg_send![delegate_class, new]
                            let camera_device: Box<ICCameraDevice> = Box::new(device_self.clone());

                            ICDevice::setDelegate(device_self, delegate_object);
                            // ICCameraDevice::setDelegate(camera_device, delegate_object);                                

                            println!("  πŸ“ΈπŸ“· class_copyMethodList ICDeviceDelegate: {:?}", device_self.as_ref().unwrap());
                            if device_self.hasOpenSession() == 0 {
                                device_self.requestOpenSession();
                                ICDevice::requestOpenSession(device_self);
                                println!("  πŸ“ΈπŸ“Έ image_capture_core_mod requestOpen");
                            }
                            println!("  πŸ“ΈπŸ“Έ image_capture_core_mod hasOpenSession: {:?}", device_self.hasOpenSession());
                        }

                        println!("  πŸ“ΈπŸ“Έ image_capture_core_mod _more_coming: {}", _more_coming);
                        println!("  πŸ“ΈπŸ“Έ image_capture_core_mod device_self name: {}", name);
                        println!("  πŸ“ΈπŸ“Έ image_capture_core_mod device_self type: {:?}", device_type);
                        
                    }

                    extern "C" fn device_browser_did_remove_device(
                        _: &Object,
                        _: Sel,
                        _: id,
                        device: id,
                        _more_going: BOOL,
                    ) {
                        let name = nsstring_decode(unsafe { ICDevice::name(device) });
                        println!("  πŸ“ΈπŸ“Έ image_capture_core_mod Device removed: '{}'", name);
                    }

                    decl.add_method(
                        sel!(deviceBrowser:didAddDevice:moreComing:),
                        device_browser_did_add_device as extern "C" fn(&Object, Sel, id, id, BOOL),
                    );
                    decl.add_method(
                        sel!(deviceBrowser:didRemoveDevice:moreGoing:),
                        device_browser_did_remove_device as extern "C" fn(&Object, Sel, id, id, BOOL),
                    );

                    let delegate_class = decl.register();
                    let delegate_object = msg_send![delegate_class, new];

                    // Create the device browser
                    let browser = ICDeviceBrowser::new(nil).autorelease();
                    ICDeviceBrowser::setDelegate(browser, delegate_object);
                    println!("  πŸ₯²πŸ“· ICDeviceBrowser.delegate: {:?}", ICDeviceBrowser::delegate(browser));
                    let types_mask 
                        ICDeviceTypeMask::ICDeviceTypeMaskCamera;
                    let locations_mask = ICDeviceLocationTypeMask::ICDeviceLocationTypeMaskLocal
                        | ICDeviceLocationTypeMask::ICDeviceLocationTypeMaskRemote;
                    browser.setBrowsedDeviceTypeMask(types_mask.bits() | locations_mask.bits());
                    browser.start();

                    // Run the application
                    let app = NSApp();
                    println!("  πŸ“± app id: {:?}", app);
                    println!("  πŸ“± app run browser.isBrowsing: {:?}", browser.isBrowsing());
                    let app = NSApp();
                    app.activateIgnoringOtherApps_(1);
                    app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
                    let current_app = NSRunningApplication::currentApplication(nil);
                    app.run();
                    app.finishLaunching();
                    browser.stop();
                    println!("  πŸ“± app stop browser.isBrowsing: {:?}", browser.isBrowsing());
                    app.stop_(app);
                });
                drop(main);
            });
        }
    }
}

Solution

  • Protocol ICCameraDeviceDelegate declares

    - deviceDidBecomeReadyWithCompleteContentCatalog:
    

    instead of

    - withCompleteContentCatalog:
    

    See ICDeviceDelegate and ICCameraDeviceDelegate.