No one? really?... it's sad.
Post
Replies
Boosts
Views
Activity
I did not find a way to manage UNNotifications from a LaunchAgent. My work-around solution is:
I created a little "menu-bar app" with that tiny icon, that my launch-agent launches when it needs to communicate with user.
I also made my LaunchAgent a mach-xpc-service with an NSXPCListener and created a proprietary protocol between the two.
via XPC I convey information from the LaunchAgent to that app, which in turn creates the local notifications using UNNotificationCenter.
I could not use the older NSNotificationCenter, because I need many of the newer features of UNNotificationCenter (coalescing and grouping user notifications, customizing their UI, adding user-options etc.)
The important thing is, when you "click" a notification in the user-notification-center, MacOS launches the App that created and sent that notification (my menu-bar app) - and sends it a delegate call with lots of information, to continue the dialog with the User.
A LaunchAgent like mine, has very dull UI and most of the time it doesn't "remember" the user's state, so in my case it is cumbersome, but acceptable solution.
Thank you very much for this clear and useful answer.
Making it an "XPC service" - launchd daemon "Advertising" a public XPC service is probably the right way for me.
I'm trying to make it work this way (still doesn't work and clients don't seem to "find" me) ... still struggling.
However... for theoretical completeness, if I wanted to pack this monster as an XPC Service (capital S) inside an App's bundle, what then? Is there a way to overcome the inability to call xpc_main(my_event_handler) ? I'm asking purely for understanding the mechanism, and maybe... some day... I'll be able to persuade managers to make the whole product a real Mac App - and so gain a zillion and one benefits.
Although I never experienced such behavior and the same expression works perfect for me - one thing you write which (I think) is NOT TRUE - you say that NSUUID is "based on CFUUID" and it isn't. Not only NSUUID* is not "toll-free-bridged" to CFUUIDRef (try to cast and bridge then send a message - you'll crash immediately) - the only way to construct one from the other, is either via "exporting" one as a string and initializing the other from that string, or - extracting the "raw uuid 128bit value" and creating the other from it. Not even a special initializer to bridge the worlds.
Just for your info.
I see this as a shortcoming of Apple engineering, trying to patch the OS in order to let buggy old software that assumed MacOS will remain X (10) forever continue running. This is more MS-Windows than MacOS, and I wish versions were the only aspect of this.
What I'm concerned with, however, is the opposite - out software is deployed on both older and newer systems, and needs to report to server the MacOS version it runs on - and we're having Big Sur appear sometimes as 10.16 sometimes 11.0 --- calling the same API!
No, we can't align all our build-tools to MacOS SDK 11 - since on older systems we use APIs that are now deprecated on new OS versions, so we maintain older build machines for deployment on older Macs.
What I seek - is a way to CONSISTENTLY get 11.x from MacOS Big-Sur, for ALL our versions running on ALL Macs and MacOSs
With all that wonderful and so-elaborate Info.plist thing - why can't we have 10.16 .x for SDK version and 11.x for "Marketing version string" ???
Sad to say, these issues persist even now with Xcode 12.5.1 and MacOS 11.4
It sure looks like a bug, because alternative APIs to achieve more-or-less the same, are still supported and seem to work.
In my current code I use the combination of:
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
[[NSRunningApplication currentApplication] unhide];
[NSApp activateIgnoringOtherApps:YES];
As alternative to the always failing
if ( [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular] == NO)
os_log_error(preventionUILog, "Failed to change ActivationPolicy to Regular");
Hope this helps somehow...
Mine is an Obj-C MacOS X project with several targets, Running latest Xcode 12.5.1. Failing target is a normal Cocoa App.
It started failing with this exact error after I dragged an image file to the project (into Resources group/directory), added to the target and tried to build. I use this image resource programmatically. Failed again and again. Removing the image removed the problem.
I found the image file WAS NOT copied to the project repo, and the reference pointed to somewhere outside the repo. I deleted the reference from the project, copied the actual file into the project repository, re-added it to my project/group and target. Verified the reference was now inside the project repo (relative to group, with just the file name as a 'path').
Now it built OK.
I then dragged another image (this time I first copied it into my Resources group/directory) and sad to say - the problem resurfaced.
Reading other answers here, I saw the proposed build script: xattr -cr ~/Library/Developer/Xcode/DerivedData || echo Clear
So I started to suspect the actual image files had something about them that was not to Xcode's liking. so... a Quick Terminal shell:
mini-mac7:Resources $ ls -la@ total 392 drwxr-xr-x 5 mshneor staff 160 Jun 28 13:57 . drwxr-xr-x 12 mshneor staff 384 Jun 28 13:44 .. -rw-r--r--@ 1 mshneor staff 19688 Jun 28 13:44 MyPtShield.png com.apple.lastuseddate#PS 16 -rw-r--r--@ 1 mshneor staff 172435 Mar 16 2017 My-logo-reg-K.png com.apple.FinderInfo 32 com.apple.lastuseddate#PS 16 com.apple.quarantine 57 -rw-r--r--@ 1 mshneor staff 258 Jun 28 13:44 factoryDefaults.plist com.apple.lastuseddate#PS 16
And sure enough - both image files DO HAVE extra information and extended attributes.
So this time, I ran that script once, directly at my Resources directory, thus removing all Extended attributes from the resource files, like this
mini-mac7:Resources $ xattr -cr . || echo Clear mini-mac7:Resources $ ls -la@ total 392 drwxr-xr-x 5 mshneor staff 160 Jun 28 13:57 . drwxr-xr-x 12 mshneor staff 384 Jun 28 13:44 .. -rw-r--r-- 1 mshneor staff 19688 Jun 28 13:44 PfPtShield.png -rw-r--r-- 1 mshneor staff 172435 Mar 16 2017 Proofpoint-logo-reg-K.png -rw-r--r-- 1 mshneor staff 258 Jun 28 13:44 factoryDefaults.plist mini-mac7:Resources $
And sure enough - next build worked like a charm. I really hope git is smart enough to push the removal of extended attributes to the repo too.
I can add to @mdolan's answer, that the DiskArbitration framework can also be used to collect information for mount-points at mount-time (AUTH_MOUNT etc), in addition to IOKit
roughly...
(NSDictionary *)infoForMountPoint:(NSString *)mountPoint {
CFURLRef mountPointURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)mountPoint, kCFURLPOSIXPathStyle, YES);
DADiskRef = DADiskCreateFromVolumePath(kCFAllocatorDefault, _daSession, mountPointURL);
CFDictionaryRef diskDescription = DADiskCopyDescription(diskRef);
NSMutableDictionary *mountPointInfo = [NSMutableDictionary dictionaryWithDictionary: CFBridgingRelease( diskDescription )];
// Add IOKit info we need, if available...
I could extend the complaint to any kind of debugging / externally looking at / even "Sampling" a working EndpointSecurity client daemon/app.
In my experience, it doesn't matter which kind of events I'm registering for, it doesn't matter if I respond immediately (synchronously) or in deferred block,
Any attempt to "poke" into the process causes (at least here - MacOS 10.15.6) complete system hang (at least Mac UI is dead so I can't really know if anything lives at that time) for about 30-40 seconds, and then OS kills my ES client process (kill -9) and then OS is free and back to normal.
For that I completely gave up any debugger use, any Instruments use, or actually any debugging tool except os_log() messages.
I had that guess, more of a gut feeling, that "playing inside the authorisation scheme" OS doesn't like anyone to interfere, and is so phobic about it, that it prefers killing the thing. Again - just a feeling.
kind'a beats the purpose of sandboxing? I would guess there's some special "entitlement" you can apply to the sandboxed app to allow sending data to your App, however - you are of course aware that ANY observer can register for, and receive these notifications, together with the data you package there - so this can infringe user privacy. An XPC connection is preferable, becasues
The "XPC service" can verify incoming connection requests and deny based on their code-signing and other identity.
XPC calls can return a response, thus you can know that your notification passed through.
OK. I got it to basically work as described (reverse connection approach). Now for the "gotcha"'s. Agent (maintains anonymous listener) connects to Daemon (mach-service-named), calls the Daemon and registers its anonymous-listener's endpoint.
Daemon in turn creates the reverse-connection, and later uses it to convey information to the Agent using specific protocol and methods.
If my Agent goes away (killed, quits etc.) Daemon's connection for some reason doesn't know about it (neither interruption handler nor invalidation handler on the reverse-connection are being called). Is this also the source of the issue of stacking-up of pending requests in memory?
Since Daemon has the original connection (Agent to Daemon) it knows when agent connection goes away - its invalidated.
Can I make a practical assumption that if my original connection was invalidated - than the reverse-connection should be treated as dead too, and needs to be re-established?
Also, when using the reverse-connection, could I make use of the:
(id)synchronousRemoteObjectProxyWithErrorHandler:(void (^)(NSError *error))handler API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0));
To be sure I don't stack up requests? and if so... how to use it? I do not understand what's "synchronous" here --- this is just an accessor providing me a proxy.
Could you clarify how "Client/Agent", after creating an anonymous listener and getting its endpoint, "uses an XPC message to send that to the service." ???
To send something to the service/daemon, Client/Agent side needs to already have established a connection? and how do you send an endpoint in an XPC message? how you obtain it on the other (Service/Daemon) side? and is the new connection-from-endpoint created on the service side unique and maintained for that specific Client/Agent? Can I have more than one?
This post is wonderful, and I'm now trying to establish my "back connection" from daemon-XPC-service back to its user-bound agents. One question though. I AM SURE I read somewhere in The XPC documentation that once established - an XPC connection is bi-directional? doesn't that mean that on some lower-level both parties (the "Daemon-XPC-Service" publishing itself via machServiceName and its (multiple) clients) can initiate "requests" on the same connection? Or maybe I got this wrong and the "bi-directional" part is only the "response" passing back for a "request" ? I'd like a clarification.
In my case, NSProgress (oh, lovely NSProgress) isn't enough, and I must pass a dictionary describing some work Deamon-XPC-Service has done that is related to the user's agent (client). Because I may have more than one XPC-client (actually, one per logged-in user) The global notification isn't best choice (unless I could pack there all the data I need to pass, together with the user-ID, so that all agents would listen, and filter for their own user-id)
Last - both global-daemon and user-agent can of course be killed/restarted/fail-to-launch because they lack some TCC permissions and so on - how is best to manage such scenario of several clients, each with its own normal and "back" XPC connection to the Daemon?
Hi, have a similar scenario, and have been trying to do this for quite some time, to no avail (my "Agent" isn't a UI-session, but still runs as a daemon on the user's session). My global-daemon (runs in the system domain as root) receives connection from my "Agent",
"Agent" can send messages to daemon successfully, and receive results.
Now if you could be so kind as to space a few lines of code explaining what it means to "send the anonymous XPC listener endpoint to the daemon" and how can Daemon use this endpoint. I tried several things and they didn't work for me so far.
In my case, it's not a "one time" job. Agent connects to Daemon and Daemon needs to regularly report its actions related to the Agent's user (I can have an agent for each user logged in).
My current state of code (not working) is:
Agent side creating connection:
ObjectiveC
// lazy property getter (NSXPCConnection *) myXPCConnection {
// Create the XPC Connection on demand
if (_myXPCConnection == nil) {
_myXPCConnection = [[NSXPCConnection alloc] initWithMachServiceName:preventionServiceLabel options:NSXPCConnectionPrivileged];
_myXPCConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(myXPCProtocol)];
// Attempt to create a bi-directional connection, so to receive reports from the XPC service.
_myXPCConnection.exportedInterface = NSXPCInterface interfaceWithProtocol:@protocol(OITPreventionXPCProtocol)];
_myXPCConnection.exportedObject = self;
_myXPCConnection.invalidationHandler = ^{ // on invalidation we simply nullify connection reference, so it will be re-created once app tries to use it again.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
self.myXPCConnection.invalidationHandler = nil;
self.myXPCConnection = nil;
os_log(preventionUILog, "connection has been invalidated");
#pragma clang diagnostic pop
}];
};
_myXPCConnection.interruptionHandler = ^{ // on interruption we simply nullify connection reference, so it will be re-created once app tries to use it again.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
self.myXPCConnection.interruptionHandler = nil;
self.myXPCConnection = nil;
os_log(preventionUILog, "connection interrupted");
#pragma clang diagnostic pop
}];
};
[_myXPCConnection resume]; // New connections always crated and starts in a suspended state
}
return _myXPCConnection;
}
Daemon side:
ObjectiveC (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
assert(listener == _listener);
assert(newConnection != nil);
BOOL accept = NO;
do {
// Verify client identity, only accept if client is code-signed by our teamID and if its signingID is of our Agent.
if (/* not allowed connection.... then */)
break;
accept = YES; // we accept connections from processes signed by our development team (proofpoint)
// now see who's calling.
if (/* it is Agent */)
[self.monitor agentConnected:newConnection];
// Configure incoming connection. First, set the interface that the exported object implements.
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(OITPreventionXPCProtocol)];
// Next set self to be the object the connection exports. All messages sent on the connection to this service will be sent to self to handle. The connection retains the exported object.
newConnection.exportedObject = self;
newConnection.invalidationHandler = ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
[self.monitor agentDisconnectedForUser:newConnection.effectiveUserIdentifier];
#pragma clang diagnostic pop
}];
};
newConnection.interruptionHandler = ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
os_log_info(myLog, "Agent Connection interrupted.");
#pragma clang diagnostic pop
}];
};
os_log_info(myLog, "XPCListener accepted XPC connection from pid: %d", newConnection.processIdentifier);
[newConnection resume]; // New connections always start in a suspended state, start it
} while(false);
return accept; // Returning YES from this method tells the system that you have accepted this connection.
}
and last - when Connection is established, and after Daemon receives a message call and replies to Daemon - I try to use the connection from the Daemon side like this:
ObjectiveC(void)reportPrevention:(MYEventESInfo * _Nonnull)eventInfo {
uid_t user = eventInfo.processInfo.auid;
NSXPCConnection *agentConnection = [self.connectedAgents objectForKey:@(user)];
if (agentConnection != nil) {
[agentConnection.remoteObjectProxy eventHandled:[eventInfo dictionary]]; // HERE I CRASH WITH EXCEPTION
}
else {
os_log(monitoringLog, "No Agent for Event: %{public}@", eventInfo );
}
}
Objective-C exception : method 'eventHandled:' unknown to remoteObjectProxy.
Any idea what's wrong?
@jasosimm have you last succeeded in this? my situation is very similar, and bidirectional simply doesn't work for me. I wonder what I'm doing wrong. If you managed to make it work, maybe paste here the few relevant lines of code that demonstrate the flow?