Obtaining the UUID of a Named Clipboard (thru JXA or AppleScript)

For flexible use of (Keyboard Maestro Engine).doScript with named clipboards, you need the UUID (rather than just the name) of a named clipboard.

(See, for example:

There may be more canonical ways of obtaining the UUID in exchange for a Clipboard name, but here, in the meanwhile, are AppleScript and JavaScript for Automation routes to doing that:

AppleScript

use framework "Foundation"
use scripting additions

-- Returns an Applescript record which either has a UUID in its |Right| field
-- or a message in its |Left| field, 
-- (if no named clipboard was found by the name supplied).

-- kmNamedClipUUID :: String -> Either Message UUID
on kmNamedClipUUID(strName)
    set k to toLower(strName)
    
    set ca to current application
    set clips to (ca's NSArray's ¬
        arrayWithContentsOfFile:((ca's NSString's ¬
            stringWithString:("~/Library/Application Support/" & ¬
                "Keyboard Maestro/Keyboard Maestro" & ¬
                " Clipboards.plist"))'s ¬
            stringByStandardizingPath)) as list
    
    script nameMatch
        on |λ|(x)
            k = toLower(|name| of x)
        end |λ|
    end script
    
    script uidMay
        on |λ|(x)
            Just(UID of x)
        end |λ|
    end script
    
    set mbFound to bindMay(find(nameMatch, clips), uidMay)
    if Nothing of mbFound then
        |Left|("Clipboard not found by this name: " & strName)
    else
        |Right|(Just of mbFound)
    end if
end kmNamedClipUUID

-- TEST ------------------------------------------------------------------
on run
    kmNamedClipUUID("Gamma")
end run


-- GENERIC FUNCTIONS -----------------------------------------------------

-- Just :: a -> Just a
on Just(x)
    {type:"Maybe", Nothing:false, Just:x}
end Just

-- Nothing :: () -> Nothing
on Nothing()
    {type:"Maybe", Nothing:true}
end Nothing

-- Left :: a -> Either a b
on |Left|(x)
    {type:"Either", |Left|:x, |Right|:missing value}
end |Left|

-- Right :: b -> Either a b
on |Right|(x)
    {type:"Either", |Left|:missing value, |Right|:x}
end |Right|

-- bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
on bindMay(mb, mf)
    if Nothing of mb then
        mb
    else
        tell mReturn(mf) to |λ|(Just of mb)
    end if
end bindMay

-- find :: (a -> Bool) -> [a] -> Maybe a
on find(p, xs)
    tell mReturn(p)
        set lng to length of xs
        repeat with i from 1 to lng
            if |λ|(item i of xs) then return Just(item i of xs)
        end repeat
        Nothing()
    end tell
end find

-- Lift 2nd class handler function into 1st class script wrapper 
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    if class of f is script then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

-- toLower :: String -> String
on toLower(str)
    set ca to current application
    ((ca's NSString's stringWithString:(str))'s ¬
        lowercaseStringWithLocale:(ca's NSLocale's currentLocale())) as text
end toLower

JavaScript for Automation

(() => {
    'use strict';

    // Returns an JS dictionary which either has a UUID in its |Right| field
    // or a message in its |Left| field,
    // (if no named clipboard was found by the name supplied).

    // GENERIC FUNCTIONS --------------------------------------

    // Just :: a -> Just a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Nothing :: () -> Nothing
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
    const bindMay = (mb, mf) =>
        mb.Nothing ? mb : mf(mb.Just);

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (lf, rf, e) => {
        const ks = Object.keys(e);
        return elem('Left', ks) ? (
            lf(e.Left)
        ) : elem('Right', ks) ? (
            rf(e.Right)
        ) : undefined;
    };

    // elem :: Eq a => a -> [a] -> Bool
    const elem = (x, xs) => xs.includes(x);

    // find :: (a -> Bool) -> [a] -> Maybe a
    const find = (p, xs) => {
        for (var i = 0, lng = xs.length; i < lng; i++) {
            var x = xs[i];
            if (p(x)) return Just(x);
        }
        return Nothing();
    };

    // show :: Int -> a -> Indented String
    // show :: a -> String
    const show = (...x) =>
        JSON.stringify.apply(
            null, x.length > 1 ? [x[1], null, x[0]] : x
        );

    // MAIN ---------------------------------------------------

    // kmNamedClipUUID :: String -> Either Message UUID
    const kmNamedClipUUID = strName => {
        const
            k = strName.toLowerCase(),
            mbFound = bindMay(
                find(
                    x => k === x.Name.toLowerCase(),
                    ObjC.deepUnwrap(
                        $.NSArray.arrayWithContentsOfFile(
                            $(
                                "~/Library/Application\ Support/" +
                                "Keyboard\ Maestro/Keyboard\ Maestro" +
                                "\ Clipboards.plist"
                            )
                            .stringByStandardizingPath
                        )
                    )
                ),
                dct => Just(dct.UID)
            );
        return mbFound.Nothing ? (
            Left('Clipboard not found by this name: ' + strName)
        ) : Right(mbFound.Just);
    };

    // MAIN ---------------------------------------------------
    return show(
        kmNamedClipUUID('Gamma')
    );
})();

When using ComplexPoint's script to obtain the uuid of a named clipboard (https://forum.keyboardmaestro.com/t/get-text-from-named-clipboard-from-km-use-in-applescript/9145/5), I discovered that some (seven) new named clipboards created via AppleScript and xml -each containing an image- are not listed in /Users/tsukadjed/Library/Application Support/Keyboard Maestro/Keyboard Maestro Clipboards.plist Can you tell me what I am doing wrong so I can (1) update the Keyboard Maestro Clipboards.plist and (2) paste named clipboards? The newly created named clipboards are seen when using the KM editor but they are not in the Clipboard plist.

Descriptions never suffice, alas, for diagnosis.

You would need to show us a working example, posting here:

  1. A working test macro
  2. your expected output / result
  3. the observed output / result

Bear in mind, incidentally, that Keyboard Maestro variables are strings – and any images would need to be encoded, for KM persistence, in a stringified form like Base64.

2- Snagit change n images Ysize to 600 and set Clipboards Image N°n.kmmacros (54 KB)

As anticipated the named clipboards are created with the images.
prefClipboards

Yet, when I try to obtain the UUID of the three named Clipboards (Image_1, Image_2, Image_3) with your nice script Obtaining the UUID of a Named Clipboard I get the answer: "Clipboard not found by this name".

I have checked the ~/Library/Application Support/Keyboard Maestro/Keyboard Maestro Clipboards.plist and the new names are not there.

When I run the shell script at the end of the Macro I get a list of very old Clipboards, many of which do not appear when I paste from clipboards:

pasteAction

In the paste from Clipboards choices I have got the new named Clipboards but here is the list of Clipboards obtained with your shell script:

ShellScriptResults_ClipoardsPslist

As you can see the Clipboards are not in the plist, therefore I cannot obtain the UUID to paste from these Clipboards automatically.

Is it OK if I delete the clipboards plist? Apart from that, is there a way to delete named Clipboards programmatically?

Does Keyboard Maestro still use Clipboards.plist ?

I think this macro may have been built in an earlier epoch and for an earlier approach (under the hood) to preserving named clipboards between sessions.

On my system that file doesn't appear to have been touched / updated for a few years, and there's never any guarantee that scripting approaches which directly reads .plists (or anything else in the Application Support folder) will prove durable.

The deep and hidden architecture of KM is sometimes improved between builds, and I think a file with the extension .kmchunked, perhaps safely out of reach of scripting, has now taken its place.

A question for @peternlewis ?

1 Like

I assume you mean Keyboard Maestro Clipboards.plist?

Nope.

Correct. The file was too slow for handling large clipboards in plist format.

It'll probably change to an sqlite file at some point in the future, but currently it is monolithically read and written.

2 Likes

Thank you for explaining: I never had much chance with named Clipboards anyway! I will change the way (using write to file and inserting image from file) to solve my problem.

1 Like