diff options
Diffstat (limited to 'redshift/location-corelocation.m')
-rw-r--r-- | redshift/location-corelocation.m | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/redshift/location-corelocation.m b/redshift/location-corelocation.m new file mode 100644 index 0000000..1b94137 --- /dev/null +++ b/redshift/location-corelocation.m @@ -0,0 +1,311 @@ +/*- + * redshift-ng - Automatically adjust display colour temperature according the Sun + * + * Copyright (c) 2009-2018 Jon Lund Steffensen <jonlst@gmail.com> + * Copyright (c) 2014-2016, 2025 Mattias Andrée <m@maandree.se> + * + * redshift-ng 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-ng 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-ng. If not, see <http://www.gnu.org/licenses/>. + */ +#include "common.h" + +#import <Foundation/Foundation.h> +#import <CoreLocation/CoreLocation.h> + + +/** + * Location data + */ +struct location_data { + /** + * The user's geographical location + */ + struct location location; + + /** + * Whether the location provider is available + */ + int available; + + /** + * Whether an unrecoverable error has occurred + */ + int error; +}; + + +struct location_state { + /** + * Slave thread, used to receive location updates + */ + NSThread *thread; + + /** + * Read-end of piped used to send location data + * from the slave thread to the master thread + */ + int pipe_fd_read; + + /** + * Write-end of piped used to send location data + * from the slave thread to the master thread + */ + int pipe_fd_write; + + /** + * Location data available from the slave thread + */ + struct location_data data; + + /** + * Location data sent to the master thread + */ + struct location_data saved_data; +}; + + +@interface LocationDelegate : NSObject <CLLocationManagerDelegate> + @property (strong, nonatomic) CLLocationManager *locationManager; + @property (nonatomic) struct location_state *state; +@end + + +static void +send_data(struct location_state *state) +{ + while (write(state->pipe_fd_write, &state->data, sizeof(state->data)) == -1 && errno == EINTR); +} + + +@implementation LocationDelegate; + +- (void)start +{ + CLAuthorizationStatus authStatus; + + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; + self.locationManager.distanceFilter = 50000; + self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer; + + authStatus = [CLLocationManager authorizationStatus]; + + if (authStatus != kCLAuthorizationStatusNotDetermined && + authStatus != kCLAuthorizationStatusAuthorized) { + weprintf(_("Not authorized to obtain location from CoreLocation.")); + [self markError]; + } else { + [self.locationManager startUpdatingLocation]; + } +} + +- (void)markError +{ + self.state->data.error = 1; + send_data(self.state); +} + +- (void)markUnavailable +{ + self.state->data.available = 0; + send_data(self.state); +} + +- (void)locationManager:(CLLocationManager *)manager + didUpdateLocations:(NSArray *)locations +{ + CLLocation *newLocation = [locations firstObject]; + + self.state->data.location.lat = newLocation.coordinate.latitude; + self.state->data.location.lon = newLocation.coordinate.longitude; + self.state->data.available = 1; + send_data(self.state); +} + +- (void)locationManager:(CLLocationManager *)manager + didFailWithError:(NSError *)error +{ + weprintf(_("Error obtaining location from CoreLocation: %s"), [[error localizedDescription] UTF8String]); + if ([error code] == kCLErrorDenied) + [self markError]; + else + [self markUnavailable]; +} + +- (void)locationManager:(CLLocationManager *)manager + didChangeAuthorizationStatus:(CLAuthorizationStatus)status +{ + if (status == kCLAuthorizationStatusNotDetermined) { + weprintf(_("Waiting for authorization to obtain location...")); + } else if (status != kCLAuthorizationStatusAuthorized) { + weprintf(_("Request for location was not authorized!")); + [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; + CFFileDescriptorRef fdref; + CFRunLoopSourceRef source; + + 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. + fdref = CFFileDescriptorCreate(kCFAllocatorDefault, self.state->pipe_fd_write, + false, pipe_close_callback, NULL); + CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); + source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + + // Run the loop + CFRunLoopRun(); + + close(self.state->pipe_fd_write); + } +} + +@end + + +static int +corelocation_create(struct location_state **state_out) +{ + *state_out = emalloc(sizeof(**state_out)); + return 0; +} + + +static int +corelocation_start(struct location_state *state) +{ + LocationThread *thread; + int pipefds[2]; + + state->pipe_fd_read = -1; + state->pipe_fd_write = -1; + + state->data.available = 0; + state->data.error = 0; + state->data.location.lat = 0; + state->data.location.lon = 0; + state->saved_data = state->data; + + pipe_rdnonblock(pipefds); + state->pipe_fd_read = pipefds[0]; + state->pipe_fd_write = pipefds[1]; + + send_data(state); /* TODO why? */ + + thread = [[LocationThread alloc] init]; + thread.state = state; + [thread start]; + state->thread = thread; + + return 0; +} + + +static void +corelocation_free(struct location_state *state) +{ + if (state->pipe_fd_read >= 0) + close(state->pipe_fd_read); + free(state); +} + + +static void +corelocation_print_help(void) +{ + printf(_("Use the location as discovered by the Corelocation provider.\n")); + printf("\n"); +} + + +static int +corelocation_set_option(struct location_state *state, const char *key, const char *value) +{ + (void) state; + (void) value; + weprintf(_("Unknown provider parameter: `%s'."), key); + return -1; +} + + +static int +corelocation_get_fd(struct location_state *state) +{ + return state->pipe_fd_read; +} + + +static int +corelocation_fetch(struct location_state *state, struct location *location_out, int *available_out) +{ + struct location_data data; + ssize_t r; + + for (;;) { + r = read(state->pipe_fd_read, &data, sizeof(data)); + if (r == (ssize_t)sizeof(data)) { + state->saved_data = data; + } else if (r > 0) { + /* writes of 512 bytes or less are always atomic on pipes */ + weprintf("read <pipe>: %s", _("Unexpected message size")); + } else if (!r || errno == EAGAIN) { + break; + } else if (errno != EINTR) { + weprintf("read <pipe>:"); + state->saved_data.error = 1; + break; + } + } + + *location_out = state->saved_data.location; + *available_out = state->saved_data.available; + return state->saved_data.error ? -1 : 0; +} + + +const location_provider_t corelocation_location_provider = LOCATION_PROVIDER_INIT("corelocation", corelocation); |