tags:

views:

553

answers:

4

We have the following snippet.

OSStatus createErr = PasteboardCreate(kPasteboardClipboard, &m_pboard);
if (createErr != noErr) {
 LOG((CLOG_DEBUG "failed to create clipboard reference: error %i" createErr));
}

This compiles fine, however, it fails to run when called from SSH. This is because there is no pasteboard available in the SSH terminal. However, the idea here is to share clipboards between computers.

When run from desktop terminal, this works just fine. But when run from SSH, PasteboardCreate returns -4960 (aka, coreFoundationUnknownErr). I assume that the only way around this issue is to run the application from within the same environment as the pasteboard, but is this possible?

+3  A: 

I tried doing it in AppleScript, and it worked (even when invoked via SSH). My script is as follows:

#!/usr/bin/osascript

on run
    tell application "Finder"
        display dialog (get the clipboard)
    end tell
end run

This definitely isn't an ideal solution, but perhaps if you worked out how AppleScript does it then it'd help you implement it yourself.

David
Ah, nice. Could be worth a look at.
nbolton
AppleScript uses Apple Events. They're messy and some of the documentation is poor.
outis
A: 
Pascal
They seem to have the same problem. Possibly anything that's not a descendant of WindowServer isn't considered part of a login session and thus doesn't have access to the pasteboard server, but that's a wild guess. Needs more research.
outis
Hmmm, it works when I use SSH to connect from my Linux box to my Mac, gives the same result as when I take a look at my Clipboard or use `pbpaste` locally. In which scenario did you find that it does not work?
Pascal
`pbpaste` doesn't work when I connect via SSH to my Mac from anywhere (BSD box, Windows, even `ssh localhost`). Running OS X 10.4.11 and OpenSSH 5.1p1. Hmmm.... should maybe look into upgrading.
outis
Upgrading OpenSSH, that is. I'd love to upgrade OS X, but can't right now.
outis
See the screenshots, this works pretty well with Snow Leopard and Ubuntu 9.04. :)
Pascal
Man, wish I had Snow Leopard.
outis
+3  A: 

Accessing the pasteboard directly looks to be a no-go. First, launchd won't register the processes1 with the pasteboard server's mach port. You'd first need find a way to get the pasteboard server's mach port (mach_port_names?). Also, direct communication between user sessions is prohibited2, and other communication is limited. I'm not sure if your program will have the rights to connect to the pasteboard server.

Here's a first shot at an illustrative example on using Apple events to get & set the clipboard as a string. Error handling is minimal to nonexistent (I'm not certain how I feel about require_noerr). If you're going to get/set clipboard data multiple times during a run, you can save the Apple events and, when copying to the clipboard, use AECreateDesc & AEPutParamDesc or (maybe) AEBuildParameters. AEVTBuilder might be of use.

NSString* paste() {
    NSString *content;

    AppleEvent paste, reply = { typeNull, 0L };
    AEBuildError buildError = { typeNull, 0L };
    AEDesc clipDesc = { typeNull, 0L };

    OSErr err;

    err = AEBuildAppleEvent(kAEJons, kAEGetClipboard, 
                            typeApplicationBundleID, "com.apple.finder", strlen("com.apple.finder"), 
                            kAutoGenerateReturnID, kAnyTransactionID,
                            &paste, &buildError,
                            ""
        );
    require_noerr(err, paste_end);
    err = AESendMessage(&paste, &reply, kAEWaitReply, kAEDefaultTimeout);
    err = AEGetParamDesc(&reply, keyDirectObject, typeUTF8Text, &clipDesc);
    require_noerr(err, pastErr_getReply);

    Size dataSize = AEGetDescDataSize(&clipDesc);
    char* clipData = malloc(dataSize);
    if (clipData) {
        err = AEGetDescData(&clipDesc, clipData, dataSize);
        if (noErr == err) {
            content = [NSString stringWithCString:clipData encoding:NSUTF8StringEncoding];
        } else {}
        free(clipData);
    }

    AEDisposeDesc(&clipDesc);
pastErr_getReply:
    AEDisposeDesc(&reply);
pasteErr_sending:
    AEDisposeDesc(&paste);
paste_end:
    return content;
}

OSStatus copy(NSString* clip) {
    AppleEvent copy, reply = { typeNull, 0L };
    AEBuildError buildError = { typeNull, 0L };

    OSErr err = AEBuildAppleEvent(kAEJons, kAESetClipboard, 
                                  typeApplicationBundleID, "com.apple.finder", strlen("com.apple.finder"), 
                                  kAutoGenerateReturnID, kAnyTransactionID,
                                  &copy, &buildError,
                                  "'----':utf8(@)",
                                  AEPARAMSTR([clip UTF8String])
                                  /*
                                    "'----':obj {form: enum(prop), want: type(@), seld: type(@), from: null()}"
                                    "data:utf8(@)",
                                    AEPARAM(typeUTF8Text),
                                    AEPARAM(pClipboard),
                                    AEPARAMSTR([clip UTF8String])
                                  */
        );
    if (aeBuildSyntaxNoErr != buildError.fError) {
        return err;
    }
    AESendMessage(&copy, &reply, kAENoReply, kAEDefaultTimeout);
    AEDisposeDesc(&reply);
    AEDisposeDesc(&copy);
    return noErr;
}

I'm leaving the Core Foundation approach above, but you'll probably want to use NSAppleEventDescriptor to extract the clipboard contents from the Apple Event reply.

    err = AESendMessage(&paste, &reply, kAEWaitReply, kAEDefaultTimeout);
require_noerr(err, pasteErr_sending);
    // nsReply takes ownership of reply
    NSAppleEventDescriptor *nsReply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&reply];
    content = [[nsReply descriptorAtIndex:1] stringValue];
    [nsReply release];

pasteErr_sending:
    AEDisposeDesc(&paste);
paste_end:
    return content;
}

An NSAppleEventDescriptor is also easier to examine in a debugger than an AEDesc. To examine replies, you can also to set the AEDebugReceives environment variable when using osascript or Script Editor.app:

AEDebugReceives=1 osascript -e 'tell application "Finder" to get the clipboard'

References:

  1. "Configuring User Sessions"
  2. "Communicating Across Login Sessions"
  3. Mach Kernel Interface, especially:
  4. CFMessagePort Reference (mach port wrapper):
  5. Apple Events Programming Guide
  6. Apple Event Manager Reference
  7. AEBuild*, AEPrint* and Friends
  8. AEBuildAppleEvent on CocoaDev
  9. Mac OS X Debugging Magic (for AEDebugSends and other AEDebug* environment variables)
outis
Haven't tried this yet, but it looks like the most correct answer - I'd rather the bounty went to this answer rather than auto selection.
nbolton
Note that this approach should support types other than text. You can use `AEPrintDescToHandle` after getting something from the clipboard to examine its structure. `NSAppleEventDescriptor` may make parsing results easier.
outis
A: 

You can access the pasteboard with PasteboardCreate via SSH on SnowLeopard but not on Leopard or Tiger.

You probably don't want to use pbcopy and pbpaste for a full pasteboard sync since those only deal with plain text, RTF, and EPS. If, for example, you copy an image and then try to write it out with pbpaste, you'll get no output.

Assuming you have an app running in the user's session on both computers, you could serialize the pasteboard data out to a file, transfer it over SSH, read it from your app on the remote side, and then put the pasteboard data on the remote pasteboard. However, getting the pasteboard serialization right may be tricky and I'm not sure how portable pasteboard data is between OSes and architectures.

Doug Richardson
Serializing the pasteboard sounds like the safest solution to me. `pbpaste` is okay for text, if you only need to sync the text, this might be enough.
Pascal