/* location-corelocation.m -- CoreLocation (OSX) location provider source
This file is part of Redshift.
Redshift is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Redshift is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Redshift. If not, see .
Copyright (c) 2014-2017 Jon Lund Steffensen
*/
#include "common.h"
#import
#import
struct location_state {
NSThread *thread;
NSLock *lock;
int pipe_fd_read;
int pipe_fd_write;
int available;
int error;
struct location location;
};
@interface LocationDelegate : NSObject
@property (strong, nonatomic) CLLocationManager *locationManager;
@property (nonatomic) struct location_state *state;
@end
@implementation LocationDelegate;
- (void)start
{
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = 50000;
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
CLAuthorizationStatus authStatus =
[CLLocationManager authorizationStatus];
if (authStatus != kCLAuthorizationStatusNotDetermined &&
authStatus != kCLAuthorizationStatusAuthorized) {
fputs(_("Not authorized to obtain location"
" from CoreLocation.\n"), stderr);
[self markError];
} else {
[self.locationManager startUpdatingLocation];
}
}
- (void)markError
{
[self.state->lock lock];
self.state->error = 1;
[self.state->lock unlock];
pipeutils_signal(self.state->pipe_fd_write);
}
- (void)markUnavailable
{
[self.state->lock lock];
self.state->available = 0;
[self.state->lock unlock];
pipeutils_signal(self.state->pipe_fd_write);
}
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
{
CLLocation *newLocation = [locations firstObject];
[self.state->lock lock];
self.state->location.lat = newLocation.coordinate.latitude;
self.state->location.lon = newLocation.coordinate.longitude;
self.state->available = 1;
[self.state->lock unlock];
pipeutils_signal(self.state->pipe_fd_write);
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"),
[[error localizedDescription] UTF8String]);
if ([error code] == kCLErrorDenied) {
[self markError];
} else {
[self markUnavailable];
}
}
- (void)locationManager:(CLLocationManager *)manager
didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
if (status == kCLAuthorizationStatusNotDetermined) {
fputs(_("Waiting for authorization to obtain location...\n"), stderr);
} else if (status != kCLAuthorizationStatusAuthorized) {
fputs(_("Request for location was not authorized!\n"), stderr);
[self markError];
}
}
@end
// Callback when the pipe is closed.
//
// Stops the run loop causing the thread to end.
static void
pipe_close_callback(
CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info)
{
CFFileDescriptorInvalidate(fdref);
CFRelease(fdref);
CFRunLoopStop(CFRunLoopGetCurrent());
}
@interface LocationThread : NSThread
@property (nonatomic) struct location_state *state;
@end
@implementation LocationThread;
// Run loop for location provider thread.
- (void)main
{
@autoreleasepool {
LocationDelegate *locationDelegate = [[LocationDelegate alloc] init];
locationDelegate.state = self.state;
// Start the location delegate on the run loop in this thread.
[locationDelegate performSelector:@selector(start)
withObject:nil afterDelay:0];
// Create a callback that is triggered when the pipe is closed. This will
// trigger the main loop to quit and the thread to stop.
CFFileDescriptorRef fdref = CFFileDescriptorCreate(
kCFAllocatorDefault, self.state->pipe_fd_write, false,
pipe_close_callback, NULL);
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(
kCFAllocatorDefault, fdref, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// Run the loop
CFRunLoopRun();
close(self.state->pipe_fd_write);
}
}
@end
static int
location_corelocation_init(struct location_state **state)
{
*state = malloc(sizeof(struct location_state));
if (*state == NULL) return -1;
return 0;
}
static int
location_corelocation_start(struct location_state *state)
{
state->pipe_fd_read = -1;
state->pipe_fd_write = -1;
state->available = 0;
state->error = 0;
state->location.lat = 0;
state->location.lon = 0;
int pipefds[2];
int r = pipeutils_create_nonblocking(pipefds);
if (r < 0) {
fputs(_("Failed to start CoreLocation provider!\n"), stderr);
return -1;
}
state->pipe_fd_read = pipefds[0];
state->pipe_fd_write = pipefds[1];
pipeutils_signal(state->pipe_fd_write);
state->lock = [[NSLock alloc] init];
LocationThread *thread = [[LocationThread alloc] init];
thread.state = state;
[thread start];
state->thread = thread;
return 0;
}
static void
location_corelocation_free(struct location_state *state)
{
if (state->pipe_fd_read != -1) {
close(state->pipe_fd_read);
}
free(state);
}
static void
location_corelocation_print_help(FILE *f)
{
fputs(_("Use the location as discovered by the Corelocation provider.\n"), f);
fputs("\n", f);
}
static int
location_corelocation_set_option(
struct location_state *state, const char *key, const char *value)
{
fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
return -1;
}
static int
location_corelocation_get_fd(struct location_state *state)
{
return state->pipe_fd_read;
}
static int
location_corelocation_handle(
struct location_state *state,
location_t *location, int *available)
{
pipeutils_handle_signal(state->pipe_fd_read);
[state->lock lock];
int error = state->error;
*location->lat = state->location;
*available = state->available;
[state->lock unlock];
if (error) return -1;
return 0;
}
const location_provider_t corelocation_location_provider = {
"corelocation",
(location_provider_init_func *)location_corelocation_init,
(location_provider_start_func *)location_corelocation_start,
(location_provider_free_func *)location_corelocation_free,
(location_provider_print_help_func *)location_corelocation_print_help,
(location_provider_set_option_func *)location_corelocation_set_option,
(location_provider_get_fd_func *)location_corelocation_get_fd,
(location_provider_handle_func *)location_corelocation_handle
};