macOS: Detect all application launches including background apps? - objective-c

Newbie here. I'm trying to create a small listener for application launches, and I already have this:
// almon.m
#import <Cocoa/Cocoa.h>
#import <stdio.h>
#include <signal.h>
#interface almon: NSObject {}
-(id) init;
-(void) launchedApp: (NSNotification*) notification;
#end
#implementation almon
-(id) init {
NSNotificationCenter * notify
= [[NSWorkspace sharedWorkspace] notificationCenter];
[notify addObserver: self
selector: #selector(launchedApp:)
name: #"NSWorkspaceWillLaunchApplicationNotification"
object: nil
];
fprintf(stderr,"Listening...\n");
[[NSRunLoop currentRunLoop] run];
fprintf(stderr,"Stopping...\n");
return self;
}
-(void) launchedApp: (NSNotification*) notification {
NSDictionary *userInfo = [notification userInfo]; // read full application launch info
NSString* AppPID = [userInfo objectForKey:#"NSApplicationProcessIdentifier"]; // parse for AppPID
int killPID = [AppPID intValue]; // define integer from NSString
kill((killPID), SIGSTOP); // interrupt app launch
NSString* AppPath = [userInfo objectForKey:#"NSApplicationPath"]; // read application path
NSString* AppBundleID = [userInfo objectForKey:#"NSApplicationBundleIdentifier"]; // read BundleID
NSString* AppName = [userInfo objectForKey:#"NSApplicationName"]; // read AppName
NSLog(#":::%#:::%#:::%#:::%#", AppPID, AppPath, AppBundleID, AppName);
}
#end
int main( int argc, char ** argv) {
[[almon alloc] init];
return 0;
}
// build: gcc -Wall almon.m -o almon -lobjc -framework Cocoa
// run: ./almon
Note: when I build it, it will run fine, but if you do it with Xcode 10 on High Sierra, you will get ld warnings, which you can ignore, however.
My question: Is there a way to also detect a launch of a background application, e.g. a menu bar application like Viscosity etc.? Apple says that
the system does not post
[NSWorkspaceWillLaunchApplicationNotification] for background apps or
for apps that have the LSUIElement key in their Info.plist file.
If you want to know when all apps (including background apps) are
launched or terminated, use key-value observing to monitor the value
returned by the runningApplications method.
Here: https://developer.apple.com/documentation/appkit/nsworkspacewilllaunchapplicationnotification?language=objc
I would at least try to add support for background apps etc. to the listener, but I don't know how to go about it. Any ideas?

As the document suggests, you use Key-Value Observing to observe the runningApplications property of the shared workspace object:
static const void *kMyKVOContext = (void*)&kMyKVOContext;
[[NSWorkspace sharedWorkspace] addObserver:self
forKeyPath:#"runningApplications"
options:NSKeyValueObservingOptionNew // maybe | NSKeyValueObservingOptionInitial
context:kMyKVOContext];
Then, you would implement the observation method (using Xcode's ready-made snippet):
- (void) observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
if (context != kMyKVOContext)
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if ([keyPath isEqualToString:#"runningApplications"])
{
<#code to be executed when runningApplications has changed#>
}
}

Related

OSX: Quit app correctly upon logout

Every time I am trying to log out the current user (not Fast User Switchting!) I get a message from macOS (excuse me if its not exactly the message, I am getting it in german): "Unable to logout, because application "com.my.app" does not quit".
What do I need in order to avoid this? I currently have this in my AppDelegate:
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return NSTerminateNow;
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return true;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
selector: #selector(receiveLogOffOrShutdown:)
name: NSWorkspaceWillPowerOffNotification object: NULL];
}
-(void) receiveLogOffOrShutdown: (NSNotification*) note
{
[application stop:nil];
}
What I have observed for example is that receiveLogOffOrShutdown is never triggered. As well as applicationShouldTerminate never triggers a breakpoint.
Am I missing something here?
Use NSApp.terminate method
Objective C
-(void)relaunch {
int processIdentifier = NSProcessInfo.processInfo.processIdentifier;
NSString *path = [NSBundle mainBundle].executablePath;
NSArray *arg = [NSArray arrayWithObjects:[NSString stringWithFormat:#"%d",processIdentifier], nil];
[NSTask launchedTaskWithLaunchPath:path arguments:arg];
[NSApp terminate:self];
}
Swift
func relaunch() {
let processIdentifier: Int32 = NSProcessInfo.processInfo().processIdentifier
let path = NSBundle.mainBundle().executablePath! as NSString
// let myPath: String = "\(path.fileSystemRepresentation)"
NSTask.launchedTaskWithLaunchPath(path as String, arguments: ["\(processIdentifier)"])
NSApp.terminate(self)
}
func terminate(sender: AnyObject?)
When invoked, this method performs several steps to process the termination request
Hope this help you.

Handle any application closing in objective c

I want to execute my method when any application is closing. My code is:
#interface FO: NSObject
- (void)applicationKilled:(NSNotification*)notification;
- (void)appDidLaunch:(NSNotification*)notification;
#end
#implementation FO
- (void)applicationKilled:(NSNotification*)notification {
NSLog(#"success");
}
- (void)appDidLaunch:(NSNotification*)notification {
NSLog(#"app info: %#", [notification userInfo]);
}
#end
#implementation Main:NSObject
FO fo;
NSString * filePath = "...MyPath";
NSString * application = "..MyApplication";
int main(int argc, const char * argv[]) {
fo = [[FO alloc]init];
[Main MyMethod];
while(1==1) {
...some code;
}
return 0;
}
+(void) MyMethod {
center = [[NSWorkspace sharedWorkspace] notificationCenter];
[center addObserver:fo selector:#selector(appDidLaunch:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
[center addObserver:fo selector:#selector(applicationKilled:) name:NSWorkspaceDidTerminateApplicationNotification
object:nil];
[[NSWorkspace sharedWorkspace] openFile:filePath withApplication:application]; }
#end
However, appDidLaunch method is not firing, even if i'll open another application in finder. Also applicationKilled method is never firing.
When i'm executing following code
[center postNotificationName:NSWorkspaceDidLaunchApplicationNotification
object:self];
appDidLaunch method is firing OK. Where can be a problem? Should this methods be fired every time when some application is opened or closed?
CRD is on the right track. You absolutely must have a runloop to receive this notification. For example:
#implementation Main : NSObject
- (void)applicationDidFinishLaunching:(NSApplication *)app {
[Main MyMethod];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ... The rest of your program ...
});
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
MyDelegate *delegate = [Main new];
[[NSApplication sharedApplication] setDelegate:delegate];
[NSApp run];
}
return 0;
}
I've put "the rest of your program" into a dispatch_async because you must not block the main thread. The usual way that Mac apps work is not with a big while (YES) loop. The usual way is by registering for various events and then waiting from them to happen. That's what the run loop is for. But if you have to manage your own loop (you generally shouldn't, but if you must), then you need to move it off of the main queue.
Assuming you are using ARC and also guessing as the information you give seems to be incomplete:
In your updated question you show fo declared as a local variable of MyMethod. The method addObserver:selector:name:object: does not keep a strong reference to the observer. After MyMethod returns the local fo object will be reclaimed, you now have no observer to call methods on.
However, while the above would explain why your code doesn't work it wouldn't explain why your app does not crash - and you don't report that it crashes. Running the code you give above causes the app to crash. So it appears that you've missed some information out or at least not reported the crash.
Guess Two
You have no run loop.
Many parts of the framework rely on there being a run loop which dispatches incoming events to appropriate handlers - just type "run loop" into Xcode's help. If you create a standard application using Xcode's "Cocoa Application" template the run loop is created for you by the code in main.m.
Events produced by OS X when applications start and stop are dispatched by the run loop to framework handlers which produce the corresponding notifications. Without a run loop these system events will not be handled, so no notifications.
You have:
int main(int argc, const char * argv[])
{
fo = [[FO alloc]init];
[Main MyMethod];
while(1==1)
{
...some code;
}
return 0;
}
so unless "...some code" creates a run loop the system events will not be handled.
Write your project using the standard "Cocoa Application" template and, for example, put your call to setup the notification handlers in applicationDidFinishLaunching:.
HTH

Substitute keystroke

I am trying to intercept a keystroke, and substitute it with a different character. I have been able to intercept the key being pressed, as well as perform some extra operations. Now I need to hold the key being pressed if it matches one of the ones I am watching, and insert a different character. Here is the code I have right now:
#import "AppDelegate.h"
#import <AppKit/AppKit.h>
#import <CoreGraphics/CoreGraphics.h>
#include <ApplicationServices/ApplicationServices.h>
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//Keys that are being watched to be switched out
NSArray *keysToWatch = [[NSArray alloc] initWithObjects:#"c",#".", nil];
// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
//Get characters
NSString *chars = [[event characters] lowercaseString];
//Get the actual character being pressed
unichar character = [chars characterAtIndex:0];
//Transform it to a string
NSString *aString = [NSString stringWithCharacters:&character length:1];
//If it is in the list, start looking if Keynote is active
if ([keysToWatch containsObject:[NSString stringWithString:aString]]) {
//DEBUG: Print a message
NSLog(#"Key being watched has been pressed");
//Get a list of all running apps
for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications]) {
//Get current active app
if ([currApp isActive]) {
//Check if it is Keynote, if yes perform remap
if ([[currApp localizedName] isEqualToString:#"Keynote"]){
//DEBUG: Print a small message
NSLog(#"Current app is Keynote");
if (character=='.') {
NSLog(#"Pressed a dot");
//I want to post a different character here
PostKeyWithModifiers((CGKeyCode)11, FALSE);
}
else if ([aString isEqualToString:#"c"]) {
NSLog(#"Pressed c");
}
}
else if ([[currApp localizedName] isEqualToString:#"Microsoft PowerPoint"]){
}
}
}
}
}
];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)acceptsFirstResponder {
return YES;
}
void PostKeyWithModifiers(CGKeyCode key, CGEventFlags modifiers)
{
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
CGEventRef keyDown = CGEventCreateKeyboardEvent(source, key, TRUE);
CGEventSetFlags(keyDown, modifiers);
CGEventRef keyUp = CGEventCreateKeyboardEvent(source, key, FALSE);
CGEventPost(kCGAnnotatedSessionEventTap, keyDown);
CGEventPost(kCGAnnotatedSessionEventTap, keyUp);
CFRelease(keyUp);
CFRelease(keyDown);
CFRelease(source);
}
#end
My problem is that I am not able to stop the original keystroke. Please keep in mind that I am completely new at Obj C, so let me know if there is anything that I can do better. Thanks!
From the docs for +[NSEvent addGlobalMonitorForEventsMatchingMask:handler:]:
Events are delivered asynchronously to your app and you can only
observe the event; you cannot modify or otherwise prevent the event
from being delivered to its original target application.
You would have to use a Quartz Event Tap for that.
So, after a LOT of digging around, I found a way to do this using Quartz Event Taps here. Thanks to #Ken Thomases for pointing me in the correct direction. I then combined my code with the one explained in this article and voilĂ , it works.

NSNotifier on Mac OSX

I'm building a mac daemon, from scratch.
Here's a simplified version of the code.
#import <stdio.h>
#import <stdlib.h>
#import <Foundation/Foundation.h>
#include "notifier.h"
int new_notification();
notifier *not;
int main () {
#autoreleasepool {
not = [[[notifier alloc] init] autorelease];
pid_t pid;
pid = fork();
if(pid > 0) {
printf("my child id is %d\n", pid);
exit(0);
}
while(1) {
int n = new_notification();
if(n > 0) {
NSUserNotification *notification = [[NSUserNotification alloc] init];
notification.title = #"Hello, World!";
notification.informativeText = #"A notification";
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
printf("new notification : count = %d !!\n", n);
}
sleep(1);
}
}
return 0;
}
int new_notification() {
return [not get_notifications];
}
I don't see the notification on my window though, I think I have to make my application a "key" application, if so, how do it do that? I can see the output on my terminal though, and on checking if
(NSClassFromString(#"NSUserNotificationCenter")==nil)
I get FALSE
Extended comments that I hope answer your issue...
If your app is active, the notification is unlikely to be displayed. But you'd be able to find it in the notification center. (Notifications draw a user's attention to an app that they're not already looking at.)
I'd suggest trying [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory] to make it clear your app isn't in the foreground but I'm not totally certain whether [NSApplication sharedApplication] aka NSApp is something you'd use with a daemon.
Part of your issue may actually be that without invoking [NSApplication sharedApplication] and telling it to run you never become an app, so you can't use the notification center... or become an accessory. But I'm not certain of that either.

asiHttpRequest under iOS5

I am succesfuly using AsiHttpRequest library to make url connections in my apps. However, I upgrade to iOS5 and Reachability.m file is reporting some errors (4) on following functions:
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) {
#pragma unused (target, flags)
NSCAssert(info, #"info was NULL in ReachabilityCallback");
NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], #"info was the wrong class in ReachabilityCallback");
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: (Reachability *) info];
} // ReachabilityCallback()
- (BOOL) startNotifier {
SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) {
if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) {
return YES;
}
}
return NO;
} // startNotifier
1sr error: in NSCAssert line, "Cast of C pointer type 'void' to Objective-C pointer type NSObject requires a bridged cast". Why happens and how to solve it?
ANSWER: you can disable ARC for each file. Go to build settings of your project and set the -fno-objc-arc flag on all the ASIHTTPRequest files (double click to edit text). Then you must remove ASIAuthenticationDialog and any references to it that are still generating errors. It works for me.
EDIT: I remember now and yes, the problem is ARC. But you can exclude files from being complied under ARC by setting the following compiler flag in Build Phases >> Compile Sources: -fno-objc-arc. If you select all the ASIHTTPRequest files and double-click, you can set the flag for all of them in one fell swoop.
ORIGINAL POST:
I've been using ASIHTTPRequest for a couple of weeks now and I remember reading a post somewhere about problems with Reachability, I just can't remember what it was exactly.
Anyway, this is what those lines in my Reachability.m look like:
//Start listening for reachability notifications on the current run loop
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) {
#pragma unused (target, flags)
NSCAssert(info, #"info was NULL in ReachabilityCallback");
NSCAssert([(NSObject*) info isKindOfClass: [Reachability class]], #"info was the wrong class in ReachabilityCallback");
//We're on the main RunLoop, so an NSAutoreleasePool is not necessary, but is added defensively
// in case someone uses the Reachablity object in a different thread.
NSAutoreleasePool* pool = [NSAutoreleasePool new];
// Post a notification to notify the client that the network reachability changed.
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification
object: (Reachability *) info];
[pool release];
} // ReachabilityCallback()
- (BOOL) startNotifier {
SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
if(SCNetworkReachabilitySetCallback(reachabilityRef, ReachabilityCallback, &context)) {
if(SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) {
return YES;
}
}
return NO;
} // startNotifier