/*- * redshift-ng - Automatically adjust display colour temperature according the Sun * * Copyright (c) 2009-2018 Jon Lund Steffensen * Copyright (c) 2014-2016, 2025 Mattias Andrée * * 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 . */ #include "common.h" #import #import /** * 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 @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 method 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 : %s", _("Unexpected message size")); } else if (!r || errno == EAGAIN) { break; } else if (errno != EINTR) { weprintf("read :"); 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);