I am having trouble with getting an assistive-enabled application (XCode in the development case) to capture global keyDown events. I've seen lots of code examples like the below, but this doesn't work for me on 10.9.4.
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
NSDictionary* opts = #{(__bridge id)kAXTrustedCheckOptionPrompt: #YES};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
int main(int argc, const char * argv[])
{
#autoreleasepool {
if (checkAccessibility()) {
NSLog(#"Accessibility Enabled");
}
else {
NSLog(#"Accessibility Disabled");
}
NSLog(#"registering keydown mask");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"keydown: %#", event.characters);
}];
NSLog(#"entering run loop.");
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
The output received is:
2014-08-25 17:26:36.054 test[64725:303] Accessibility Enabled
2014-08-25 17:26:36.055 test[64725:303] registering keydown mask
2014-08-25 17:26:36.067 test[64725:303] entering run loop.
Once here, no other logging occurs, regardless of which keys I hit or what application has focus when I hit them.
FWIW, I'm trying to write an assistive application, not a key-logger or other evil thing. I've looked at the other instances of this question, but they seem to deal with either 1) the application not being assistive-enabled or 2) not receiving certain 'special' command keys that one would need CGEvents to receive. I am not seeing any keys, even simple ones (it's been running through my typing of this post and nothing was logged). TIA!
So, thanks to Ken Thomases' question above, I was able to work out how to do this. The key detail is that I am using a command line application template (I don't have any need for a UI, so I was trying to keep things minimal). News to me, but obvious in hindsight, just creating a run loop doesn't create an event loop. In order to replicate the creation of an event loop within a command line application, more of the guts of a typical cocoa application have to be brought into play. First, you have to have a class implementing the NSApplicationDelegate protocol, and that delegate will be where the application code lives, leaving the main method to simply do the following:
#import <Foundation/Foundation.h>
#include "AppDelegate.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init];
NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate];
[NSApp run];
}
}
This is a nib-less, menubar-less application, just like the usual command line application template, but it does have a true event loop, due to the [NSApp run] call. Then the application code that I used to have in my main method above moved into the app delegate:
#import "AppDelegate.h"
#implementation AppDelegate
// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
NSDictionary* opts = #{(__bridge id)kAXTrustedCheckOptionPrompt: #YES};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (checkAccessibility()) {
NSLog(#"Accessibility Enabled");
}
else {
NSLog(#"Accessibility Disabled");
}
NSLog(#"registering keydown mask");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event){
NSLog(#"keydown: %#", event.characters);
}];
}
#end
And just for completeness sake and future readers, the header file looks like this:
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
#end
Related
I am trying to run a program in a background process that will register every shutdown event in the system.
Doing so by registering to NSWorkspaceWillPowerOffNotification as show below:
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
NSNotificationCenter *notCenter;
notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
[[NSRunLoop currentRunLoop] run];
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return NSTerminateCancel;
}
#end
For some reason that I cannot understand, the NSWorkspaceWillPowerOffNotification handler is never called!
Also added the following to my .plist:
<key>NSSupportsSuddenTermination</key>
<false/>
but still no notification is register when shutting down my system.
Any idea as to why??
For future reference:
I was not able to register to the above event and see it fire.
Finally, I went with a different approach:
apple open-source power management
Here, you can see we have notifications names such as:
"com.apple.system.loginwindow.logoutNoReturn"
You can register to those, and that will do the trick.
Hope this will help someone one day :)
When you say you've ran your code as a background process, do you mean that it's based on launchd daemon handled according to plist file in /Library/LaunchDeamon ? In this case, the notification will not be sent. Try running it under Cocoa application
Seems like this notification doesn't work on LaunchDaemon linked against AppKit.
I'm still looking for a way to get these notifications on background deamon process.
i can make NSWorkspaceWillPowerOffNotification and applicationShouldTerminate: to work by adding NSApplicationActivationPolicyAccessory and replacing the run loop with [NSApp run]. it still won't be very useful if you want to continue to monitor all logouts (and/or power off attempts) since it only works if the process is launched while the user has already logged in, and it can only detect the current UI login session's power-off/logout notification and "app should terminate" delegate.
"apps" that belong to the current UI login session get terminated by the OS when the user logs out. non-app (no [NSApp run]) processes will continue to run even after the user logs out.
so, if you wan to continue to monitor the user logouts (or power off attempts), you would need a non-app (no [NSApp run]) process.
NSWorkspaceSessionDidBecomeActiveNotification and NSWorkspaceSessionDidResignActiveNotification work without [NSApp run] if you want to monitor if the user switched out or back in.
or you can try the System Configuration APIs if it works for your use case.
"Technical Q&A QA1133: Determining console user login status" https://developer.apple.com/library/archive/qa/qa1133/_index.html
sounds like com.apple.system.loginwindow.logoutNoReturn was the best option for whatever you wanted to do. good to know!
here is the updated code (although it's not really useful):
// cc powreoff-notification.m -o poweroff-notification -framework AppKit
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
#autoreleasepool {
NSLog(#"%s", __func__);
NSNotificationCenter* notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
// without NSApplicationActivationPolicyAccessory, the [NSApp run] process gets killed on
// poweroff/logout instead of getting the power off notification or applicationShouldTerminate:.
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// without [NSApp run], you won't get the power off notification or applicationShouldTerminate:.
//[[NSRunLoop currentRunLoop] run];
[NSApp run];
// this process needs to be launched while the user has already logged in. otherwise,
// you won't get the power off notification or applicationShouldTerminate:.
}
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSLog(#"%s", __func__);
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss\n"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSLog(#"%s", __func__);
return NSTerminateCancel;
}
#end
I have looked at other similar questions on this site, apple dev site and countless YouTube videos but I am still unable to figure this out. There seems to be very little info on getting a user's location in Xcode/iOS8.
I have a map view that works fine except when I click on a button to get my location, nothing happens and in the logs I see
Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.
I don't understand what I am doing wrong. I am very new to programming and all information I have seen doesn't make much sense to me.
I'm using storyboards in my app, and the code specific to this is as follows:
mapViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#interface mapViewController : UIViewController {
MKMapView *mapview;
}
#property (nonatomic, retain) IBOutlet MKMapView *mapview;
-(IBAction)setMap:(id)sender;
-(IBAction)getCurrentLocation:(id)sender;
#property (nonatomic, retain) IBOutlet CLLocationManager *locationManager;
#end
mapViewController.m
#import "mapViewController.h"
#interface mapViewController ()
#end
#implementation mapViewController {
CLLocationManager *locationManager;
}
#synthesize mapview;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.locationManager.delegate=self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.requestWhenInUseAuthorization;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)setMap:(id)sender {
switch (((UISegmentedControl *) sender).selectedSegmentIndex) {
case 0:
mapview.mapType = MKMapTypeStandard;
break;
case 1:
mapview.mapType = MKMapTypeSatellite;
break;
case 2:
mapview.mapType = MKMapTypeHybrid;
break;
default:
break;
}
}
-(IBAction)getCurrentLocation:(id)sender {
mapview.showsUserLocation = YES;
}
#end
I've been struggling with this for so long I'm tearing my hair out. Can someone point out where I've gone wrong?
The error message is quite self-explained.
First of all, you didn't initialized CLLocationManager in your viewDidLoad. You can initialize with (before the delegate line):
self.locationManager = [[CLLocationManager alloc] init];
Then, you have to request for location service, using [self.locationManager requestWhenInUseAuthorization] or [self.locationManager requestAlwaysAuthorization]. (Not to forget to add corresponding usage description key in Info.plist).
Last, you have to check the response of authorization from user. If user denied the authorization, you shouldn't start any Location Service-related actions, until you ask again.
Update if you support iOS 7 as well, the codes will be slightly different.
I can observe NSWorkspaceWillPowerOffNotification notifications in the application delegate of a GUI program, but not in a command line utility. Any ideas why the following does not work? Or is there a different approach I should consider?
Edit: Cold it be that NSWorkspace's notification center doesn't work for a GUI-less program? (I have verified that |notificationCenter| is not nil)
#interface Helper : NSObject
-(void)userLogout:(NSNotification*)notification;
#end
#implementation Helper
-(void)userLogout:(NSNotification*)notification
{
NSLog(#"Notification Received");
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Helper* helper = [[Helper alloc] init];
NSNotificationCenter* notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notificationCenter addObserver:helper
selector:#selector(userLogout:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
return 0;
}
My app can open the type of file it is supposed to work with when I double click the file, AND the app is already running. However, when the app is not running yet and i double click a file, the app starts, but it does not open the file. Why could that be?
The app delegate implements the methods:
-(void) application:(NSApplication *)sender openFiles:(NSArray *)filenames {
for (NSString *name in filenames) {
NSLog(#"Openning files");
[self.topController addFileAtPath:name];
}
}
-(BOOL) application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(#"Openning file_");
[self.topController addFileAtPath:filename];
return YES;
}
For those who might fall into the same trap:
Turns out, the methods above get called earlier than the "-applicationDidFinishLaunching:", in which I was doing all my app initialisation. I ended up creating an "alive" flag (to show if my app has been inited yet), and put all my initialization logic in a separate method. Then, in my "...finishedLaunching", "openFiles" and "openFile" i check whether that flag is on or off, and call the application initialization method accordingly:
#implementation DTVAppDelegate
BOOL alive = NO;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
if (!alive) {
[self startApp];
}
}
- (void) startApp {
// init logic
alive = YES;
}
-(void) application:(NSApplication *)sender openFiles:(NSArray *)filenames {
if (!alive) {
[self startApp];
}
for (NSString *name in filenames) {
NSLog(#"Openning files");
[self.topController addFileAtPath:name];
}
}
NSWindowDelegate protocol has a windowDidDeminiaturize callback, but no windowWillDeminiaturize callback. I need to catch the moment when the window is starting to deminiaturize and make changes to it before the user sees the changes applied.
I can't do the changes in windowDidMiniaturize because I need to show another window; if I do it in windowDidMiniaturize, this other window will appear as soon as the first one has miniaturized.
Any ideas?
Edit: I'm leaving this answer here, but it totally does not work reliably, see my comment below.
You could subclass NSWindow and override deminiaturize:.
#interface MyWindow : NSWindow
#end
#implementation MyWindow
- (void) deminiaturize:(id)sender
{
NSLog( #"window about to deminiaturize!" );
[super deminiaturize:sender];
}
#end
Probably you want the window delegate to take some action when this happens, not the window, so you could do something like this:
- (void) deminiaturize:(id)sender
{
id<NSWindowDelegate> delegate = [self delegate];
if( [delegate respondsToSelector:#selector(windowWillDeminiaturize)] ) {
[delegate performSelector:#selector(windowWillDeminiaturize)];
}
[super deminiaturize:sender];
}