Code Monkey home page Code Monkey logo

Comments (14)

Tatsh avatar Tatsh commented on May 24, 2024

This is an interesting issue. I really have doubts Apple has spent a lot of time with this.

In general with OS X/iOS, you are not supposed to work with any *Ref type directly. These are opaque for that reason. You only use the pointers with functions and methods that accept them. Reading the data directly for any reason is generally pointless and discouraged.

In this case, you get back a TISInputSourceRef and you should now be calling any functions/methods that accept that data type, like $.TISSelectInputSource(). There is no casting in JXA. In the code you are pointing to, I see little reason for all the casting I see there, except any casts with __bridge* for going to Objective-C.

All your code involving CoreFoundation data types has to be treated as if there is no such thing as Objective-C or casting. You have to manually convert CFArray to NSArray for example:

var objcArray = NSArray.alloc;
for (var i = 0, len = $.CFArrayGetCount(cfarr); i++) {
    objcArr.addObject(
        $.CFArrayGetValueAtIndex(cfarr, i) // still not converted to an Objective-C object or JS; probably useless for calls accepting NSArray
    );
}

Note that ObjC.unwrap() will generally do nothing with CF data types. I found that calling $.CFBridgingRetain(cf_something) can give some clues once you inspect the return value fully. The return value will be an Objective-C type, but it does not seem to convert to the respective type (i.e. CFArray to NSArray). Calling $.CFBridgingRelease() as you would do in Objective-C will cause a segfault.

You probably to have to call $.CFStringCreateExternalRepresentation(null, cfstringref, someEncoding, 0) (converting to CFData first) to convert from CFString to JavaScript String.

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

That's a really helpful set of insights - many thanks !

from jxa-cookbook.

Tatsh avatar Tatsh commented on May 24, 2024

Figured out how to go back and forth with a CFString:

var strToStore = 'My String';
var len = strToStore.length + 1; // add 1 for null byte
var cfs = $.CFStringCreateWithBytes(null, strToStore, len, 'UTF-8', false);

// Get back the string as a JS string (manual says char * is converted to JS string)
var cPtr = $.CFStringGetCStringPtr(cfs, 'UTF-8');
console.log(cPtr);

// Another way: call CFStringCreateExternalRepresentation() and CFDataGetBytePtr()
var data = $.CFStringCreateExternalRepresentation(null, cfs, 'UTF-8', 0); // CFDataRef
cPtr = $.CFDataGetBytePtr(data);
console.log(cPtr);

So you do not have to call CFStringCreateExternalRepresentation() for CFString. Once you have a JS string in any case, you should not need to re-wrap to make an Objective-C or C call that accepts char * or NSString *.

Internally, every time something returns char *, the interpreter is actually calling +[NSString stringWithUTF8String:] with that return value. You can see this if you get a NULL pointer back from a C function that returns char * (you will get an Error thrown).

Try hard to stay away from CF stuff :)

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

Ha ! wonderful ... thank you :-)

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

Ah ... my (updated) impression is that we may be looking at CFDictionary objects here,

( $.TISCopyCurrentKeyboardInputSource() )

before we get to the CFStringsRefs.

(function () {
    'use strict';

    ObjC.import('CoreServices')
    ObjC.import('Carbon');
    ObjC.import('CoreFoundation');

    // CFObjectRefce -> s
    function CFTypeName(objRef) {
        return $.CFDataGetBytePtr(
            $.CFStringCreateExternalRepresentation(
                null,
                $.CFCopyTypeIDDescription(
                    $.CFGetTypeID(
                        objRef
                    )
                ),
                'UTF-8',
                0
            )
        );
    }

    return CFTypeName(
        $.TISCopyCurrentKeyboardInputSource()
    );

    // --> "CFDictionary"

})();

Do you think that CFDictionary keys and values might conceivably be within reach from the JS side ?

from jxa-cookbook.

Tatsh avatar Tatsh commented on May 24, 2024

Try passing the CFDictionaryRef to NSDictionary.dictionaryWithDictionary() to convert it then you can unwrap it. Internal values will still be CF types but the keys will be accessible (for (k in obj)).

Still not sure what to do when it comes to structs and direct access.

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

Thanks – that certainly yields some progress – we do get a tractable dictionary.

There seems, though, at first sight, to be some collapse of the value to a key string of some kind rather than a reference to a CF value holding the input information.

(function () {
    'use strict';

    ObjC.import('CoreServices')
    ObjC.import('Carbon');
    ObjC.import('CoreFoundation');

    return $.NSDictionary.dictionaryWithDictionary(
        $.TISCopyCurrentKeyboardInputSource()
    )
})();

// --> $({"type":$("{__TISInputSource=}")})

from jxa-cookbook.

Tatsh avatar Tatsh commented on May 24, 2024

Yes I noticed that. I think you cannot convert this way after all, despite this working in 'real' Objective-C.

What's happening is sort of what would happen in Objective-C though, here. It is confusing though.

TISCopyCurrentKeyboardInputSource() gives a TISInputSourceRef opaque pointer return value, which points to a struct __TISInputSource (and the fields to this are not documented). I think what happens in JXA with structs is they get converted to CFDictionary, and this can work as long as you keep using the CFDictionary functions to get values, as long as you know the field names.

I think also this means passing a normal JS literal object to a C function that accepts a struct will work as long as it is a non-pointer? There is no way to do &someVar in JXA and I doubt this auto-translates. I am also curious how one would call int f(char a) in JXA. Pass a 0-255 integer?

Seems kind of lame all around (and very untested considering you can get a segfault or bus error even with documented functions (not something I expect from an interpreter)). You might want to consider using Apple's Python and their libraries and/or FFI in Python to fix up everything else for this part. You would be able to correctly call any C function. You can execute the Python code from JXA with $.NSTask.

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

That's a really helpful insight and suggestion – many thanks for taking time to look at this.

from jxa-cookbook.

Tatsh avatar Tatsh commented on May 24, 2024

By the way I think it isn't possible to call TISGetInputSourceProperty() and read the string value out. For void * return value, the conversion to a C string is not automatically made (as this cannot be done a safe manner), and CFString* functions will not accept a void * type (the interpreter stops the function call). There is no type casting in JXA so it seems there is no way around this. I think a bug report to Apple is in order.

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

I will do that – your analysis makes a lots of sense, and I think you may have saved me quite a lot of time :-) Thanks !

from jxa-cookbook.

bacongravy avatar bacongravy commented on May 24, 2024

You can coerce a CF type to an NS type by first re-binding the CFMakeCollectable function so that it takes 'void *' and returns 'id', and then using that function to perform the coercion:

ObjC.bindFunction('CFMakeCollectable', [ 'id', [ 'void *' ] ]);

var cfString = $.CFStringCreateWithCString(0, "foo", 0); // => [object Ref]
var nsString = $.CFMakeCollectable(cfString);            // => $("foo")

To make this easier to use in your code, you might define a .toNS() function on the Ref prototype:

Ref.prototype.toNS = function () { return $.CFMakeCollectable(this); }

Here is how you would use this new function with the TIS* functions:

ObjC.import('Carbon');

var current_source = $.TISCopyCurrentKeyboardInputSource();
var cfs = $.TISGetInputSourceProperty(current_source, $.kTISPropertyInputSourceID);

cfs.toNS() // => $("com.apple.keylayout.US")

from jxa-cookbook.

RobTrew avatar RobTrew commented on May 24, 2024

Thank you !

from jxa-cookbook.

Tatsh avatar Tatsh commented on May 24, 2024

Closing due to age.

from jxa-cookbook.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.