JXA-Cookbook / JXA-Cookbook

Cookbook for JavaScript for Automation in Mac OS X Yosemite
2.88k stars 117 forks source link

Clipboard typeClasses ? #12

Closed RobTrew closed 4 years ago

RobTrew commented 9 years ago

In Applescript we can evaluate

the clipboard as record

and get an object with keys to any RTF, HTML utf8 etc data

The Javascript view of the StandardAdditions.sdef documentation suggests something similar, but doesn't (I think) define the set of accepted type class arguments.

theClipboard method : Return the contents of an application’s clipboard. Use in a ‘tell’ block after activating the application
theClipboard
[as: type class] : the type of data desired
→ any : the data

It might be good, if anyone can unlock JXA access to HTML and RTF clipboard contents, to have a clipboard section of this Wiki …

( .clipbboardInfo() returns an array of byte sizes for various types of clipboard data, but offers only undefined where one might expect to see the type class names. Perhaps ObjC access to NSPasteBoard is the route to take … )

RobTrew commented 9 years ago

For the record, this kind of thing may be easier:

ObjC.import('AppKit');

// Types: 'public.rtf', 'public.html' etc
function pboardUnpacked(strType) {
    return ObjC.unwrap(
        $.NSPasteboard.generalPasteboard.stringForType(
            strType
        )
    )
}

pboardUnpacked('public.rtf');
RobTrew commented 9 years ago

And for plist formats like the Safari clipboard web content:

ObjC.import('AppKit');

// Types: 'com.apple.webarchive' etc
function pboardPlist(strType) {
    return ObjC.deepUnwrap(
        $.NSPasteboard.generalPasteboard.propertyListForType(
            strType
        )
    )
}

pboardPlist('com.apple.webarchive');
JMichaelTX commented 8 years ago

( .clipbboardInfo() returns an array of byte sizes for various types of clipboard data, but offers only undefined where one might expect to see the type class names.

This appears to be another area where JXA drops the ball.

On top of that, it takes 3 statements in JXA for 1 statement in AppleScript

Output from AppleScript

set lstCBInfo to clipboard info

(*
{{«class HTML», 1961}, {«class weba», 51332}, {«class rtfd», 1418}, 
{«class RTF », 1327}, {«class ut16», 304}, {uniform styles, 1080}, 
{string, 151}, {scrap styles, 182}, {«class utf8», 150}, 
{Unicode text, 302}, {uniform styles, 1080}, {scrap styles, 182}}
*)

Output from JXA:

var app = Application.currentApplication()
app.includeStandardAdditions = true

app.clipboardInfo()

/*
[[undefined, 1961], [undefined, 51332], [undefined, 1418],
 [undefined, 1327], [undefined, 304], [undefined, 1080],
 ["string", 151], [undefined, 182], [undefined, 150], 
[undefined, 302], [undefined, 1080], [undefined, 182]]
*/
Tatsh commented 8 years ago

Maybe it will get improved in the future. It is hard to say how that from AppleScript was supposed to be converted to JS other than string types. I prefer using the Objective-C bridge regardless most of the time. This is where JXA trumps AppleScript.

RobTrew commented 8 years ago

To see this at work, for example:

ObjC.import('AppKit');

// Types: 'public.rtf', 'public.html' etc
function pboardUnpacked(strType) {
    return ObjC.unwrap(
        $.NSPasteboard.generalPasteboard.stringForType(
            strType
        )
    )
}

pboardUnpacked('public.rtf');

Copy some RTF, perhaps from an app like TextEdit, and then run the script in Script Editor:

The return value will be an upacked RTF string like:

"{\\rtf1\\ansi\\ansicpg1252\\cocoartf1404\\cocoasubrtf130\n{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica;}\n{\\colortbl;\\red255\\green255\\blue255;}\n\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural\\partightenfactor0\n\n\\f0\\fs24 \\cf0 It takes some courage to \n\\b shout\n\\b0  in the dark :-)}"

Not sure if the dramatic headline formatting was intentional :-) but all tools have their particular profiles, applicabilities, learning curves and levels of documentation.

JXA may be the tool you need at the moment, or may not be. It doesn't particularly strike me as 'dropping the ball' anywhere, and I find it very useful. JS certainly has a more flexible data model than Applescript, richer libraries, and a wider field of application, too.

JMichaelTX commented 8 years ago

@RobTrew, I was responding to your statement that the clipboard info returned by JXA is nearly useless, as compared with the full info provided by AppleScript:

.clipbboardInfo() returns an array of byte sizes for various types of clipboard data, but offers only undefined where one might expect to see the type class names.

So the bottom line is that with JXA we must resort to using ObjC to unpack the clipboard. Is there a reference that provides the clipboard class types like "public.rtf" that is needed for ObjC? I searched and couldn't find one.

RobTrew commented 8 years ago

I think Apple calls those "Uniform Type Identifiers"

JMichaelTX commented 8 years ago

Great. So how do we get a list of the class types available on the current clipboard? IOW, since the JXA app.clipboardInfo() doesn't work, what do we use instead? Is there on ObjC equivalent that works?

RobTrew commented 8 years ago

I'm afraid you'll just have to jump in and find your away around:

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSPasteboard_Class/

The JXA release notes are worth rereading on how you translate an NS object name to the format expected by the JavaScript bridge.

RobTrew commented 8 years ago

More simply, of course, you could loop/map through the UTI identifiers that interest you, testing whether pboardUnpacked() (above) yields any content for each of them.

JMichaelTX commented 8 years ago

you could loop/map through the UTI identifiers that interest you

The problem, of course, is learning the actual name of the UTI identifiers. You can't search for something you don't know the name of. :wink: Even though there is a big list in the UTI document, as you have posted elsewhere, all apps don't use them the same.

Surely there is a way to loop through each class type on the clipboard using ObjC, listing the class name and size (what the app.clipboardinfo() was supposed to do. Any clues on how to do this?

RobTrew commented 8 years ago

A solution – thanks to mklement0

(function() {

  ObjC.import('AppKit');

  return ObjC.deepUnwrap(
    $.NSPasteboard.generalPasteboard.pasteboardItems.js[0].types
  );
})();

--> (for example)

["public.rtf", "public.utf8-plain-text", "public.utf16-external-plain-text", 
"dyn.ah62d4rv4gk81n65yru", "com.apple.traditional-mac-plain-text",
 "dyn.ah62d4rv4gk81g7d3ru"]
JMichaelTX commented 8 years ago

My thanks to Rob for following through on this quest, and providing us with the basic code to get the clipboard/pasteboard types in JavaScript.

I took Rob's code and dressed it up a bit, and put into a function you can put into your script library. I've also included a log() function that I find helpful in easily generating output to the console.

getClipboardTypes()

// --- Example Usage of getClipboardTypes() Function ---

var arrCBTypes = getClipboardTypes()
log("arrCBTypes")

//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
function getClipboardTypes() {      // Get array of Pasteboard Types for JS
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    /*
    PURPOSE:
        • Return an array of Clipboard (AKA Pasteboard) types
            available on current Clipboard

    Based directly on code from Rob Trew (@ComplexPoint)
    JXA-Cookbook:   https://github.com/dtinth/JXA-Cookbook/issues/12#issuecomment-162172909

    FOR MORE DISCUSSION, SEE:

        • [Stackoverflow:  What clipboard type class strings does OS X JavaScript for Applications recognise?]
            (http://stackoverflow.com/questions/31833291/what-clipboard-type-class-strings-does-os-x-javascript-for-applications-recognis)

        • [JXA-Cookbook:  Clipboard typeClasses]
            (https://github.com/dtinth/JXA-Cookbook/issues/12)
    */

  'use strict';
  ObjC.import('AppKit');
    return ObjC.deepUnwrap($.NSPasteboard.generalPasteboard.pasteboardItems.js[0].types);

} // ===================== END function getClipboardTypes() =====================

//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
function log(psVarName) {       // Log variable info & contents to Console for debug
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
/*  Parameters:
            • psVarName       string  Name of the Variable to log

        Purpose:
            • Provide one function to make it easy to log all types of variables, name and
                value, to the Console in a readable format.
            • Array elements are logged in a list

        Ver: 2.0        Fri, Dec 4, 2015

        Author:
            • JMichaelTX (in GitHub, and MacScripters and Keyboard Maestro forums)
            • PM me in either if you find issues or have suggestions for improvement

        Programmer's Note:
            • Normally I avoid using the eval() function
            • But this is a simple exception, where there is no other way to
                call the function with one parameter, and log both the name and
                the value of the variable.
            • IMO, the risk of using eval() in this case is very low.
--------------------------------------------------------------------------------
*/
    // --- GET THE VARIABLE TYPE ---

    var strType = eval("typeof(" + psVarName + ")")

    // --- CHECK FOR UNDEFINED VARIABLE ---

    if (strType === "undefined") {
        console.log("*** ERROR ***    Variable is UNDEFINED: " + psVarName)

        //-------------
        return "ERROR"
        //-------------
    }   // *** END if undefined ***

    if (strType === "object") {
        if ( eval("Array.isArray(" + psVarName + ")") ) {
            strType = "array"

        } else {
                strType = "other"
        }
    }

    strType = strType.toUpperCase()

    // --- SET OUPUT BASED ON VARIABLE TYPE ---

    switch (strType) {

        case "ARRAY":
            var strLog = "console.log('ARRAY: " + psVarName + "   Len: ' + " + psVarName + ".length)"
            break;

        case "STRING":
            var strLog = "console.log('" + strType + ": " + psVarName + "  Len: ' + " + psVarName + ".length + '  Value: \"' + " + psVarName +  " + '\"')"
            break;

        default:                // All other types, including "number"
            var strLog = "console.log('" + strType + ": " + psVarName + "  Value: ' + " + psVarName + ")"
            break;

    }   // *** END switch (strType) ***

    //--- OUTPUT TO CONSOLE ---
    eval(strLog)

    // --- IF ARRAY, OUTPUT ARRAY ELEMENTS ---

    if (strType === "ARRAY") {
        var lenArr = eval(psVarName + ".length")
        for (i=0; i<lenArr; i++)    { 
            strLog = "console.log('[' + i + ']:  ' + " + psVarName + "[i])" 
            eval(strLog)
        }
    }   // *** END if ARRAY ***

    return "DONE"

}   // ========================== END function log() ==============================

Example Output

/* ARRAY: arrCBTypes   Len: 7 */
/* [0]:  public.rtf */
/* [1]:  public.utf8-plain-text */
/* [2]:  public.utf16-external-plain-text */
/* [3]:  dyn.ah62d4rv4gk81n65yru */
/* [4]:  com.apple.traditional-mac-plain-text */
/* [5]:  dyn.ah62d4rv4gk81g7d3ru */
/* [6]:  org.nspasteboard.TransientType */
Tatsh commented 4 years ago

Closing due to age.