/* 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 };