Notification when the system is idle or not on OS X - macos

I know that there is some way to get the system idle time with the IOKit framework on OS X, but I want to know if there's notifications available.
I can create a timer to check if the idle time is more than x, and that's fine. It doesn't matter if I detect the idle mode a few seconds later.
The problem is to detect when the Mac is not idle anymore. I want my app to show a notification as soon as possible, not a few seconds later.
Is there a way to have a notification for that? (iChat seems to have one)

This is from http://developer.apple.com/library/mac/#qa/qa1340/_index.html (from Bill the Lizard comment)
- (void) receiveSleepNote: (NSNotification*) note
{
NSLog(#"receiveSleepNote: %#", [note name]);
}
- (void) receiveWakeNote: (NSNotification*) note
{
NSLog(#"receiveWakeNote: %#", [note name]);
}
- (void) fileNotifications
{
//These notifications are filed on NSWorkspace's notification center, not the default
// notification center. You will not receive sleep/wake notifications if you file
//with the default notification center.
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
selector: #selector(receiveSleepNote:)
name: NSWorkspaceWillSleepNotification object: NULL];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
selector: #selector(receiveWakeNote:)
name: NSWorkspaceDidWakeNotification object: NULL];
}

NSTimeInterval GetIdleTimeInterval() {
io_iterator_t iter = 0;
int64_t nanoseconds = 0;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"), &iter) == KERN_SUCCESS) {
io_registry_entry_t entry = IOIteratorNext(iter);
if (entry) {
CFMutableDictionaryRef dict;
if (IORegistryEntryCreateCFProperties(entry, &dict, kCFAllocatorDefault, 0) == KERN_SUCCESS) {
CFNumberRef obj = CFDictionaryGetValue(dict, CFSTR("HIDIdleTime"));
if (obj)
CFNumberGetValue(obj, kCFNumberSInt64Type, &nanoseconds);
CFRelease(dict);
}
IOObjectRelease(entry);
}
IOObjectRelease(iter);
}
return (double)nanoseconds / 1000000000.0;
}

Related

CloudKit didReceiveRemoteNotification not called on the Mac

I am using the following CKNotification Info and this seems to work fine:
CKNotificationInfo *note = [[CKNotificationInfo alloc] init];
note.alertBody = #"Something Happened";
note.shouldBadge = NO;
note.shouldSendContentAvailable = NO;
When something changes on an iOS device, my Mac app receives a Push notification based on a subscription with this notification. However, didReceiveRemoteNotification is never called so I can't process the event. I need to be able to refresh and fetch new changes. How do I do that?
Calling registerForRemoteNotificationTypes: and implementing didRegisterForRemoteNotificationsWithDeviceToken:
should be enough code, and the App ID should include the Push Notifications service.
I'm using CloudKit in a cross-platform (iOS/OS X) app to synchronize favorites between devices like so:
// OS X specific code
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp registerForRemoteNotificationTypes:NSRemoteNotificationTypeNone];// silent push notification!
}
- (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self.favCon handleCloudKitNotificationWithUserInfo:userInfo];
}
Note the usage of NSRemoteNotificationTypeNone which means silent push notification! This is how I set up CloudKit in the FavController class:
- (void)getOrCreateFavZoneWithCompletionHandler:(successCompletionHandler)handler {
// check if FavZone exists op
__block int createZone = 0;
CKFetchRecordZonesOperation *fetchRecZonesOp = [[CKFetchRecordZonesOperation alloc] initWithRecordZoneIDs:#[[FavController favRecordZoneID]]];
CKModifyRecordZonesOperation *saveRecZoneOp = [[CKModifyRecordZonesOperation alloc] initWithRecordZonesToSave:nil recordZoneIDsToDelete:nil];
fetchRecZonesOp.fetchRecordZonesCompletionBlock = ^(NSDictionary *recordZonesByZoneID, NSError *operationError) {
if (recordZonesByZoneID.count == 0) {// zone doesn't exist
createZone = 1;
CKRecordZone *favZone = [[CKRecordZone alloc] initWithZoneName:UTXAFavZoneName];
saveRecZoneOp.recordZonesToSave = #[favZone];
NSLog(#"Creating new Zone %#", favZone.zoneID.zoneName);
} else {
NSLog(#"Zone %# already exists.", [FavController favRecordZoneID].zoneName);
}
};
// create FavZone op
saveRecZoneOp.modifyRecordZonesCompletionBlock = ^(NSArray *savedRecordZones, NSArray *deletedRecordZoneIDs, NSError *operationError) {
[self successCompletionHandler:(savedRecordZones.count == createZone) error:operationError informDelegate:YES handler:handler];
};
[saveRecZoneOp addDependency:fetchRecZonesOp];
[[FavController favDatabase] addOperation:fetchRecZonesOp];
[[FavController favDatabase] addOperation:saveRecZoneOp];
}
- (void)subscribeToFavChanges:(successCompletionHandler)handler {
// get current subscription
[[FavController favDatabase] fetchSubscriptionWithID:UTXAFavConCKSubscriptionID completionHandler:^(CKSubscription *subscription, NSError *error) {
if (subscription) {
NSLog(#"using existing subscription: %#", subscription);
[self successCompletionHandler:YES error:nil informDelegate:NO handler:handler];
} else {
CKSubscription *sub = [[CKSubscription alloc] initWithZoneID:[FavController favRecordZoneID]
subscriptionID:UTXAFavConCKSubscriptionID
options:0];// "You must specify 0 for this parameter. Zone subscriptions currently do not support any options."
[[FavController favDatabase] saveSubscription:sub completionHandler:^(CKSubscription *subscription, NSError *error) {
NSLog(#"created new subscription: %# %#", subscription, error);
[self successCompletionHandler:(error == nil) error:error informDelegate:YES handler:handler];
}];
}
}];
}
As soon as I add or remove a record on one device, I'll get a notification on all other device, which I handle like so in the FavController class:
/// #abstract Handle push notifications sent by iCloud.
/// #discussion App delegates call this method when they receive a push notification through didReceiveRemoteNotification.
/// Currently, only airport favorites produce a PN, it is of type CKNotificationTypeRecordZone.
/// #param userInfo The userInfo dict tied to each push notification.
- (void)handleCloudKitNotificationWithUserInfo:(NSDictionary *)userInfo {
[self recursivelyCheckForPreviousCloudKitNotifications];
}
- (void)recursivelyCheckForPreviousCloudKitNotifications {
CKFetchNotificationChangesOperation *fetchOp = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:_defCon.notificationChangeToken];
__weak CKFetchNotificationChangesOperation *weakOp = fetchOp;
fetchOp.notificationChangedBlock = ^(CKNotification *notification) {
[self handleNotification:notification];
};
fetchOp.fetchNotificationChangesCompletionBlock = ^( CKServerChangeToken *serverChangeToken, NSError *operationError) {
NSLog(#"new notification change token: %#", serverChangeToken);
_defCon.notificationChangeToken = serverChangeToken;
if (weakOp.moreComing) {
NSLog(#"more coming!!");
[self recursivelyCheckForPreviousCloudKitNotifications];
} else {
NSLog(#"done handling notification changes.");
}
};
[[FavController favContainer] addOperation:fetchOp];
}
- (void)handleNotification:(CKNotification *)notification {// withCompletionHandler:(successCompletionHandler)handler {
if (notification.notificationType == CKNotificationTypeRecordZone) {// make sure we handle only zone changes
CKRecordZoneNotification *noti = (CKRecordZoneNotification *)notification;
if ([noti.recordZoneID.zoneName isEqualToString:[FavController favRecordZoneID].zoneName]) {
// received an update for the fav zone
[self queuedFavUpdateFromCloud];
} else {
// received an update for an unknown zone
NSLog(#"WARNING: received an update for an unknown zone: %#", noti.recordZoneID.zoneName);
}
} else {
NSLog(#"WARNING: received unknown notification: %#", notification);
}
}
Okay I've finally figured it out. If you use a CKNotificationInfo for your alerts, didReceiveRemoteNotification will NOT be called on the Mac until and unless you set CKNotificationInfo.soundName to an empty string! This looks like a bug only in OS X (10.10 & 10.11 so far) but can be worked around by this simple change.

Why is CoreLocation (Mac OS X) not remembering my choice to allow its use?

I'm probably being very stupid, but I can't work out why the following is happening:
I have an app on Mac OS X which uses CoreLocation. Below is the the relevant code:
It asks for permission to use my location every time it launches and never remembers that I have already granted use of location data.
The app never appears in the 'Privacy' Preference pane.
Am I missing something?
Thanks.
#pragma mark - General Instance Methods
- (void)determineLocation
{
if (self.locationManager)
{
DDLogWarn(#"determinLocation called, but we already have a locationManager instance variable...");
DDLogWarn(#"This is a bug.");
}
self.currentlocationName = #"Location unknown";
if (![CLLocationManager locationServicesEnabled])
{
DDLogWarn(#"Location Services not enabled.");
[self fallBackToHardcodedLocation];
return;
}
// Location services are available.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Coarse-grained location accuracy required.
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
self.locationManager.distanceFilter = 1000; // meters - 1km
[self.locationManager startUpdatingLocation];
}
#pragma mark - Location Manager Delegate Methods
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
DDLogVerbose(#"Our authorisation to use the location manager has changed.");
switch (status) {
case kCLAuthorizationStatusNotDetermined:
DDLogError(#"LocationManager Authorisation status not determined. (User hasn't chosen yet)");
break;
case kCLAuthorizationStatusAuthorized:
DDLogVerbose(#"We are now authorised for locationServices.");
[self.locationManager startUpdatingLocation];
break;
case kCLAuthorizationStatusDenied:
DDLogWarn(#"LocationManager: We are now explicitly denied!");
[self.locationManager stopUpdatingLocation];
break;
case kCLAuthorizationStatusRestricted:
DDLogWarn(#"LocationManager We are now restricted! (not authorised - perhaps due to parental controls...)");
[self.locationManager stopUpdatingLocation];
break;
default:
break;
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// Main callback for location discovery..ยง
CLLocation* location = [locations lastObject];
NSDate* eventDate = location.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 15.0)
{
// Event is recent. Do something with it.
if (location.horizontalAccuracy > 1000)
{
DDLogWarn(#"Location Accuracy is worse than 1km, discarding...");
} else {
[self updateLocation:location];
}
return;
}
DDLogWarn(#"Stale location data...");
[self updateLocation:location];
}
It always asks permission: (kCLAuthorizationStatusNotDetermined appears to always be the case.)
2015-02-07 01:03:36:127 Central Heating[2260:30b] LocationManager Authorisation status not determined. (User hasn't chosen yet)
(Dialog text: "Central Heating" would like to use your current location. Your location is needed for weather forecasting. [Don't allow] [OK])
As Gordon Mentioned, this issue is resolved by signing the application.

Is there some straightforward documentation on implementing the Game Center?

I'm trying to implement Game Center into my ios7 game (Xcode 5), but the material in the apple docs and the stuff I've seen online doesn't seem to work very well.
These are the two main methods I'm using wish produce no errors but I don't get any data either:
- (void) retrieveTopTenScores
{
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeGlobal;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeToday;
leaderboardRequest.identifier = kLeaderboardID;
leaderboardRequest.range = NSMakeRange(1,10);
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
// Process the score information.
} else {
NSLog(#"scores retrieved successfully but no scores in the leaderboard");
}
}];
}
}
-(void)submitMyScore
{
//This is the same category id you set in your itunes connect GameCenter LeaderBoard
GKScore *myScoreValue = [[GKScore alloc] initWithLeaderboardIdentifier:kLeaderboardID];
myScoreValue.value = 5123123;
[myScoreValue reportScoreWithCompletionHandler:^(NSError *error){
if(error != nil){
NSLog(#"Score Submission Failed");
} else {
NSLog(#"Score Submitted");
}
}];
}
So I'm looking for some simple example code to do this successfully...
thanks
rich
I see nothing wrong with your code. Is the player authenticated when you run it?, what error are you getting? If you look for sample GameKit code there is some at iOS 6 Advanced Cookbook from Erica Sadun, but nothing you shouldn't be able to figure out reading the API.
the answer is this for submitting scores in iOS7 to the game centre
Game Center Helper/Manager/Control (Object).h
+ (gamecenterhelper/manager/control *)sharedInstance;
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier;
Game Center Helper/Manager/Control (Object).m
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: identifier];
scoreReporter.value = score;
scoreReporter.context = 0;
NSArray *scores = #[scoreReporter];
[GKScore reportScores:scores withCompletionHandler:^(NSError *error) {
}];
}
viewcontroller.h
#import "gamecenterhelper/manager/control"
viewcontroller.m
[[gamecenterhelper/manager/control sharedInstance] reportScore:(int64_t) forLeaderboardID:(NSString*)];
//in place of int64_t place your integer you want uploaded, and instead on NNString* add your leaderboard identifier

Every time on scanning bluetooth devices gives CBCentralManagerStateUnknown?

First time I am using coreBluetooth.
Here is my implementation is there any thing wrong in it.
#synthesize CM,activePeripheral;
- (id)init
{
if ((self = [super init]))
{
CM = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (int) scanForPeripherals
{
if (self.CM.state != CBCentralManagerStatePoweredOn)
{
NSLog(#"CoreBluetooth is %s",[self centralManagerStateToString:self.CM.state]);
return -1;
}
[self.CM scanForPeripheralsWithServices:[NSArray arrayWithObject:[CBUUID UUIDWithString:#"180D"]] options:#{CBCentralManagerScanOptionAllowDuplicatesKey: #YES}];
return 0;
}
- (const char *) centralManagerStateToString: (int)state
{
switch(state)
{
case CBCentralManagerStateUnknown:
return "State unknown (CBCentralManagerStateUnknown)";
case CBCentralManagerStateResetting:
return "State resetting (CBCentralManagerStateUnknown)";
case CBCentralManagerStateUnsupported:
return "State BLE unsupported (CBCentralManagerStateResetting)";
case CBCentralManagerStateUnauthorized:
return "State unauthorized (CBCentralManagerStateUnauthorized)";
case CBCentralManagerStatePoweredOff:
return "State BLE powered off (CBCentralManagerStatePoweredOff)";
case CBCentralManagerStatePoweredOn:
return "State powered up and ready (CBCentralManagerStatePoweredOn)";
default:
return "State unknown";
}
return "Unknown state";
}
- (void) connectPeripheral:(CBPeripheral *)peripheral {
printf("Connecting to peripheral with UUID : %s\r\n",[self UUIDToString:peripheral.UUID]);
self.activePeripheral = peripheral;
self.activePeripheral.delegate = self;
[self.CM connectPeripheral:self.activePeripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}
-(const char *) UUIDToString:(CFUUIDRef)UUID
{
if (!UUID)
return "NULL";
CFStringRef s = CFUUIDCreateString(NULL, UUID);
return CFStringGetCStringPtr(s, 0);
}
#pragma mark -Central manager delegate method
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSLog(#"hits it");
if (central.state != CBCentralManagerStatePoweredOn) {
return;
}
isOn=YES;
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(#"Received periferal :%#",peripheral);
NSLog(#"Ad data :%#",advertisementData);
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(#"Connected peripheral %#",peripheral);
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(#"Error occured :%#",[error localizedDescription]);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)scanDevices:(id)sender {
if (isOn) {
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self scanForPeripherals];
}
}
Every time it gives log
CoreBluetooth is State unknown (CBCentralManagerStateUnknown)
update to question
After suggestion given by #Michael Dautermann , I come to know that my centralManagerDidUpdateState delegate method is not hitting.I dunno why this is happening.If anybody finds the reason please notify me.
Thanks you.
CoreBluetooth is simply in the process of starting up.
You need this delegate method to hit first:
- (void) centralManagerDidUpdateState: (CBCentralManager *) central;
And once it does, then you can start scanning for peripherals.
After few days, I checked out the initialization of CentralManager and I come to know that init method on firstViewController never called.I checked out by printing log in init method.
- (id)init
{
NSLog(#"hello init");
if ((self = [super init]))
{
CM = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
}
return self;
}
- (void)viewDidLoad
{ CM = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
isOn=NO;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
So , I initialize the central manager in viewDidLoad and now it is initialized and centralManagerDidUpdateState finally called.Now printing CBCentralManagerStateUnsupported and that's not the issue because iPhone 4 doesn't support it.Thanks to michael help me a lot.
Where do you call scanDevices? I think you're calling it too early. You should call it inside centralManagerDidUpdateState.
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSLog(#"hits it");
if (central.state != CBCentralManagerStatePoweredOn) {
[self scanDevices]:
}
isOn=YES;
}

How to check that current space is Dashboard?

Sample code is
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];
Then
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
// code to check if current workspace is dashboard?
}
I want to check whether the current space is dashboard or not? Any idea?
The first think i have tried is get to the current space id according to this answer: Detecting when a space changes in Spaces in Mac OS X . The problem here is that the key kCGWindowWorkspace is deprecated in OSX 10.8. So there is no direct way to get this information.
In my solution now i check for different windows or owners which are only one the dashboard space or on all other spaces:
The user is on the dashboard if there is one window which kCGWindowName ends with .wdgt/
The user is not on the dashboard if there is one window with kCGWindowName == System Status Item Clone, kCGWindowOwnerName == SystemUIServer | Finder
So why i'm not just using the .wdgt/ check? -- Because if there is now widget on the dashboard this not working
So why i'm using more than one window check? -. Because i'm not jet sure which window is always on all spaces. At least System Status Item Clone and Finder are not always there.
Here my implementation is add this function as category to NSWorkspace
- (BOOL) userIsOnDashboardSpace {
NSArray* windowsInSpace = (__bridge NSArray *) CGWindowListCopyWindowInfo(kCGWindowListOptionAll | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger indexOfWidget = [windowsInSpace indexOfObjectPassingTest:^BOOL(NSDictionary* obj, NSUInteger idx, BOOL *stop) {
if ([obj objectForKey:(id)kCGWindowName]) {
NSString *name = (NSString *)[obj objectForKey:(id)kCGWindowName];
if ([name isEqualToString:#"System Status Item Clone"]) {
*stop = true;
return false;
}
if ([name hasSuffix:#".wdgt/"]) {
*stop = true;
return true;
}
}
if ([obj objectForKey:(id)kCGWindowOwnerName]) {
NSString *name = (NSString *)[obj objectForKey:(id)kCGWindowOwnerName];
if ([name isEqualToString:#"SystemUIServer"]) {
*stop = true;
return false;
}
if ([name isEqualToString:#"Finder"]) {
*stop = true;
return false;
}
}
return false;
}];
return indexOfWidget != NSNotFound;
}

Resources