From 153dec0e21530d52b8ee82f6ec588620ec0857d2 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Wed, 16 Aug 2017 21:20:06 -0700 Subject: Change location providers to allow updates Change location provider implementations so it is possible for location providers to dynamically update the location. This commit adds the interfaces and infrastructure in redshift.c but none of the location provides are changed to become dynamic. --- src/location-corelocation.h | 7 +- src/location-corelocation.m | 124 +++++++++--------- src/location-geoclue2.c | 97 +++++++------- src/location-geoclue2.h | 7 +- src/location-manual.c | 15 ++- src/location-manual.h | 7 +- src/redshift.c | 298 ++++++++++++++++++++++++++++++++++---------- src/redshift.h | 9 +- 8 files changed, 382 insertions(+), 182 deletions(-) (limited to 'src') diff --git a/src/location-corelocation.h b/src/location-corelocation.h index 4b74382..9c276e9 100644 --- a/src/location-corelocation.h +++ b/src/location-corelocation.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2014 Jon Lund Steffense + Copyright (c) 2014-2017 Jon Lund Steffense */ #ifndef REDSHIFT_LOCATION_CORELOCATION_H @@ -33,8 +33,9 @@ void location_corelocation_print_help(FILE *f); int location_corelocation_set_option(void *state, const char *key, const char *value); -int location_corelocation_get_location(void *state, - location_t *location); +int location_corelocation_get_fd(void *state); +int location_corelocation_handle( + void *state, location_t *location, int *available); #endif /* ! REDSHIFT_LOCATION_CORELOCATION_H */ diff --git a/src/location-corelocation.m b/src/location-corelocation.m index 2f1768d..e33c853 100644 --- a/src/location-corelocation.m +++ b/src/location-corelocation.m @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2014 Jon Lund Steffensen + Copyright (c) 2014-2017 Jon Lund Steffensen */ #ifdef HAVE_CONFIG_H @@ -40,6 +40,7 @@ @interface Delegate : NSObject @property (strong, nonatomic) CLLocationManager *locationManager; @property (nonatomic) BOOL success; +@property (nonatomic) BOOL error; @property (nonatomic) float latitude; @property (nonatomic) float longitude; @end @@ -48,58 +49,61 @@ - (void)start { - self.locationManager = [[CLLocationManager alloc] init]; - self.locationManager.delegate = self; + self.locationManager = [[CLLocationManager alloc] init]; + self.locationManager.delegate = self; - CLAuthorizationStatus authStatus = - [CLLocationManager authorizationStatus]; + CLAuthorizationStatus authStatus = + [CLLocationManager authorizationStatus]; - if (authStatus != kCLAuthorizationStatusNotDetermined && - authStatus != kCLAuthorizationStatusAuthorized) { - fputs(_("Not authorized to obtain location" - " from CoreLocation.\n"), stderr); - CFRunLoopStop(CFRunLoopGetCurrent()); - } + if (authStatus != kCLAuthorizationStatusNotDetermined && + authStatus != kCLAuthorizationStatusAuthorized) { + fputs(_("Not authorized to obtain location" + " from CoreLocation.\n"), stderr); + self.error = YES; + CFRunLoopStop(CFRunLoopGetCurrent()); + } - [self.locationManager startUpdatingLocation]; + [self.locationManager startUpdatingLocation]; } - (void)stop { - [self.locationManager stopUpdatingLocation]; - CFRunLoopStop(CFRunLoopGetCurrent()); + [self.locationManager stopUpdatingLocation]; + CFRunLoopStop(CFRunLoopGetCurrent()); } - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { - CLLocation *newLocation = [locations firstObject]; - self.latitude = newLocation.coordinate.latitude; - self.longitude = newLocation.coordinate.longitude; - self.success = YES; + CLLocation *newLocation = [locations firstObject]; + self.latitude = newLocation.coordinate.latitude; + self.longitude = newLocation.coordinate.longitude; + self.success = YES; - [self stop]; + [self stop]; } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { - fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"), - [[error localizedDescription] UTF8String]); - [self stop]; + fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"), + [[error localizedDescription] UTF8String]); + self.error = YES; + [self stop]; } - (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 stop]; - } + 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.error = YES; + [self stop]; + } } @end @@ -108,13 +112,13 @@ int location_corelocation_init(void *state) { - return 0; + return 0; } int location_corelocation_start(void *state) { - return 0; + return 0; } void @@ -125,40 +129,46 @@ location_corelocation_free(void *state) void location_corelocation_print_help(FILE *f) { - fputs(_("Use the location as discovered by the Corelocation provider.\n"), f); - fputs("\n", f); + fputs(_("Use the location as discovered by the Corelocation provider.\n"), f); + fputs("\n", f); - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "CoreLocation"); - fputs("\n", f); + fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" + "which means it has to be restarted to take notice after travel.\n"), + "CoreLocation"); + fputs("\n", f); } int location_corelocation_set_option(void *state, - const char *key, const char *value) + const char *key, const char *value) { - fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); - return -1; + fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); + return -1; } int -location_corelocation_get_location(void *state, - location_t *location) +location_corelocation_get_fd(void *state) { - int result = -1; - - @autoreleasepool { - Delegate *delegate = [[Delegate alloc] init]; - [delegate performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; - CFRunLoopRun(); - - if (delegate.success) { - location->lat = delegate.latitude; - location->lon = delegate.longitude; - result = 0; - } - } + return -1; +} - return result; +int +location_corelocation_handle(void *state, location_t *location, int *available) +{ + @autoreleasepool { + Delegate *delegate = [[Delegate alloc] init]; + [delegate performSelectorOnMainThread:@selector(start) + withObject:nil waitUntilDone:NO]; + CFRunLoopRun(); + + if (delegate.error) return -1; + + if (delegate.success) { + location->lat = delegate.latitude; + location->lon = delegate.longitude; + *available = 1; + } + } + + return 0; } diff --git a/src/location-geoclue2.c b/src/location-geoclue2.c index abccbd3..ab57535 100644 --- a/src/location-geoclue2.c +++ b/src/location-geoclue2.c @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2014 Jon Lund Steffensen + Copyright (c) 2014-2017 Jon Lund Steffensen */ #include @@ -39,50 +39,11 @@ typedef struct { GMainLoop *loop; int available; + int error; location_t location; } get_location_data_t; -int -location_geoclue2_init(void *state) -{ -#if !GLIB_CHECK_VERSION(2, 35, 0) - g_type_init(); -#endif - return 0; -} - -int -location_geoclue2_start(void *state) -{ - return 0; -} - -void -location_geoclue2_free(void *state) -{ -} - -void -location_geoclue2_print_help(FILE *f) -{ - fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), f); - fputs("\n", f); - - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "GeoClue2"); - fputs("\n", f); -} - -int -location_geoclue2_set_option(void *state, - const char *key, const char *value) -{ - fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); - return -1; -} - /* Handle position change callbacks */ static void geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, @@ -267,16 +228,63 @@ on_name_vanished(GDBusConnection *connection, const gchar *name, get_location_data_t *data = (get_location_data_t *)user_data; g_fprintf(stderr, _("Unable to connect to GeoClue.\n")); + data->error = 1; g_main_loop_quit(data->loop); } int -location_geoclue2_get_location(void *state, - location_t *location) +location_geoclue2_init(void *state) +{ +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif + return 0; +} + +int +location_geoclue2_start(void *state) +{ + return 0; +} + +void +location_geoclue2_free(void *state) +{ +} + +void +location_geoclue2_print_help(FILE *f) +{ + fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), f); + fputs("\n", f); + + fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" + "which means it has to be restarted to take notice after travel.\n"), + "GeoClue2"); + fputs("\n", f); +} + +int +location_geoclue2_set_option(void *state, + const char *key, const char *value) +{ + fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); + return -1; +} + +int +location_geoclue2_get_fd(void *state) +{ + return -1; +} + +int +location_geoclue2_handle(void *state, location_t *location, int *available) { get_location_data_t data; data.available = 0; + data.error = 0; guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, "org.freedesktop.GeoClue2", @@ -289,8 +297,9 @@ location_geoclue2_get_location(void *state, g_bus_unwatch_name(watcher_id); - if (!data.available) return -1; + if (data.error) return -1; + *available = data.available; *location = data.location; return 0; diff --git a/src/location-geoclue2.h b/src/location-geoclue2.h index c3c377b..095d86f 100644 --- a/src/location-geoclue2.h +++ b/src/location-geoclue2.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2014 Jon Lund Steffensen + Copyright (c) 2014-2017 Jon Lund Steffensen */ #ifndef REDSHIFT_LOCATION_GEOCLUE2_H @@ -33,8 +33,9 @@ void location_geoclue2_print_help(FILE *f); int location_geoclue2_set_option(void *state, const char *key, const char *value); -int location_geoclue2_get_location(void *state, - location_t *loc); +int location_geoclue2_get_fd(void *state); +int location_geoclue2_handle(void *state, + location_t *location, int *available); #endif /* ! REDSHIFT_LOCATION_GEOCLUE2_H */ diff --git a/src/location-manual.c b/src/location-manual.c index c5da074..8ce324c 100644 --- a/src/location-manual.c +++ b/src/location-manual.c @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2010-2014 Jon Lund Steffensen + Copyright (c) 2010-2017 Jon Lund Steffensen */ #include @@ -101,10 +101,17 @@ location_manual_set_option(location_manual_state_t *state, const char *key, } int -location_manual_get_location(location_manual_state_t *state, - location_t *loc) +location_manual_get_fd(location_manual_state_t *state) { - *loc = state->loc; + return -1; +} + +int +location_manual_handle( + location_manual_state_t *state, location_t *location, int *available) +{ + *location = state->loc; + *available = 1; return 0; } diff --git a/src/location-manual.h b/src/location-manual.h index e70d9cf..7094e9a 100644 --- a/src/location-manual.h +++ b/src/location-manual.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2010-2014 Jon Lund Steffensen + Copyright (c) 2010-2017 Jon Lund Steffensen */ #ifndef REDSHIFT_LOCATION_MANUAL_H @@ -38,8 +38,9 @@ void location_manual_print_help(FILE *f); int location_manual_set_option(location_manual_state_t *state, const char *key, const char *value); -int location_manual_get_location(location_manual_state_t *state, - location_t *loc); +int location_manual_get_fd(location_manual_state_t *state); +int location_manual_handle( + location_manual_state_t *state, location_t *location, int *available); #endif /* ! REDSHIFT_LOCATION_MANUAL_H */ diff --git a/src/redshift.c b/src/redshift.c index ead3a84..bf0ab86 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with Redshift. If not, see . - Copyright (c) 2009-2015 Jon Lund Steffensen + Copyright (c) 2009-2017 Jon Lund Steffensen */ #ifdef HAVE_CONFIG_H @@ -29,6 +29,21 @@ #include #include +/* poll.h is not available on Windows but there is no Windows location provider + using polling. On Windows, we just define some stubs to make things compile. + */ +#ifndef _WIN32 +# include +#else +#define POLLIN 0 +struct pollfd { + int fd; + short events; + short revents; +}; +int poll(struct pollfd *fds, int nfds, int timeout) { abort(); return -1; } +#endif + #if defined(HAVE_SIGNAL_H) && !defined(__WIN32__) # include #endif @@ -206,8 +221,8 @@ static const location_provider_t location_providers[] = { location_geoclue2_print_help, (location_provider_set_option_func *) location_geoclue2_set_option, - (location_provider_get_location_func *) - location_geoclue2_get_location + (location_provider_get_fd_func *)location_geoclue2_get_fd, + (location_provider_handle_func *)location_geoclue2_handle }, #endif #ifdef ENABLE_CORELOCATION @@ -220,8 +235,8 @@ static const location_provider_t location_providers[] = { location_corelocation_print_help, (location_provider_set_option_func *) location_corelocation_set_option, - (location_provider_get_location_func *) - location_corelocation_get_location + (location_provider_get_fd_func *)location_corelocation_get_fd, + (location_provider_handle_func *)location_corelocation_handle }, #endif { @@ -233,8 +248,8 @@ static const location_provider_t location_providers[] = { location_manual_print_help, (location_provider_set_option_func *) location_manual_set_option, - (location_provider_get_location_func *) - location_manual_get_location + (location_provider_get_fd_func *)location_manual_get_fd, + (location_provider_handle_func *)location_manual_handle }, { NULL } }; @@ -717,6 +732,33 @@ gamma_is_valid(const float gamma[3]) } +/* Check whether location is valid. + Prints error message on stderr and returns 0 if invalid, otherwise + returns 1. */ +static int +location_is_valid(const location_t *location) +{ + /* Latitude */ + if (location->lat < MIN_LAT || location->lat > MAX_LAT) { + /* TRANSLATORS: Append degree symbols if possible. */ + fprintf(stderr, + _("Latitude must be between %.1f and %.1f.\n"), + MIN_LAT, MAX_LAT); + return 0; + } + + /* Longitude */ + if (location->lon < MIN_LON || location->lon > MAX_LON) { + /* TRANSLATORS: Append degree symbols if possible. */ + fprintf(stderr, + _("Longitude must be between" + " %.1f and %.1f.\n"), MIN_LON, MAX_LON); + return 0; + } + + return 1; +} + static const gamma_method_t * find_gamma_method(const char *name) { @@ -747,13 +789,71 @@ find_location_provider(const char *name) return provider; } +/* Wait for location to become available from provider. + Waits until timeout (milliseconds) has elapsed or forever if timeout + is -1. Writes location to loc. Returns -1 on error, + 0 if timeout was reached, 1 if location became available. */ +static int +provider_get_location( + const location_provider_t *provider, location_state_t *state, + int timeout, location_t *loc) +{ + int available = 0; + struct pollfd pollfds[1]; + while (!available) { + int loc_fd = provider->get_fd(state); + if (loc_fd >= 0) { + /* Provider is dynamic. */ + double now; + int r = systemtime_get_time(&now); + if (r < 0) { + fputs(_("Unable to read system time.\n"), + stderr); + return -1; + } + + /* Poll on file descriptor until ready. */ + pollfds[0].fd = loc_fd; + pollfds[0].events = POLLIN; + r = poll(pollfds, 1, timeout); + if (r < 0) { + perror("poll"); + return -1; + } else if (r == 0) { + return 0; + } + + double later; + r = systemtime_get_time(&later); + if (r < 0) { + fputs(_("Unable to read system time.\n"), + stderr); + return -1; + } + + /* Adjust timeout by elapsed time */ + if (timeout >= 0) { + timeout -= (later - now) * 1000; + timeout = timeout < 0 ? 0 : timeout; + } + } + + + int r = provider->handle(state, loc, &available); + if (r < 0) return -1; + } + + return 1; +} + /* Run continual mode loop This is the main loop of the continual mode which keeps track of the current time and continuously updates the screen to the appropriate color temperature. */ static int -run_continual_mode(const location_t *loc, +run_continual_mode(const location_provider_t *provider, + location_state_t *location_state, const transition_scheme_t *scheme, const gamma_method_t *method, gamma_state_t *state, @@ -786,9 +886,29 @@ run_continual_mode(const location_t *loc, color_setting_t prev_interp = { -1, { NAN, NAN, NAN }, NAN }; + fputs(_("Waiting for initial location" + " to become available...\n"), stderr); + + /* Get initial location from provider */ + location_t loc = { NAN, NAN }; + r = provider_get_location(provider, location_state, -1, &loc); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + return -1; + } + + if (!location_is_valid(&loc)) { + fputs(_("Invalid location returned from provider.\n"), stderr); + return -1; + } + + print_location(&loc); + /* Continuously adjust color temperature */ int done = 0; int disabled = 0; + int location_available = 1; while (1) { /* Check to see if disable signal was caught */ if (disable) { @@ -849,8 +969,7 @@ run_continual_mode(const location_t *loc, } /* Current angular elevation of the sun */ - double elevation = solar_elevation(now, loc->lat, - loc->lon); + double elevation = solar_elevation(now, loc.lat, loc.lon); /* Use elevation of sun to set color temperature */ color_setting_t interp; @@ -918,8 +1037,8 @@ run_continual_mode(const location_t *loc, if (!disabled || short_trans_delta || set_adjustments) { r = method->set_temperature(state, &interp); if (r < 0) { - fputs(_("Temperature adjustment" - " failed.\n"), stderr); + fputs(_("Temperature adjustment failed.\n"), + stderr); return -1; } } @@ -930,10 +1049,64 @@ run_continual_mode(const location_t *loc, sizeof(color_setting_t)); /* Sleep for 5 seconds or 0.1 second. */ + int delay = SLEEP_DURATION; if (short_trans_delta) { - systemtime_msleep(SLEEP_DURATION_SHORT); + delay = SLEEP_DURATION_SHORT; + } + + int loc_fd = provider->get_fd(location_state); + if (loc_fd >= 0) { + /* Provider is dynamic. */ + struct pollfd pollfds[1]; + pollfds[0].fd = loc_fd; + pollfds[0].events = POLLIN; + int r = poll(pollfds, 1, delay); + if (r < 0) { + if (errno == EINTR) continue; + perror("poll"); + fputs(_("Unable to get location" + " from provider.\n"), stderr); + return -1; + } else if (r == 0) { + continue; + } + + /* Get new location and availability information. */ + location_t new_loc; + int new_available; + provider->handle( + location_state, &new_loc, + &new_available); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + return -1; + } + + if (!new_available && + new_available != location_available) { + fputs(_("Location is temporarily unavailable;" + " Using previous location until it" + " becomes available...\n"), stderr); + } + + if (new_available && + (new_loc.lat != loc.lat || + new_loc.lon != loc.lon || + new_available != location_available)) { + loc = new_loc; + print_location(&loc); + } + + location_available = new_available; + + if (!location_is_valid(&loc)) { + fputs(_("Invalid location returned" + " from provider.\n"), stderr); + return -1; + } } else { - systemtime_msleep(SLEEP_DURATION); + systemtime_msleep(delay); } } @@ -1306,8 +1479,6 @@ main(int argc, char *argv[]) if (transition < 0) transition = 1; - location_t loc = { NAN, NAN }; - /* Initialize location provider. If provider is NULL try all providers until one that works is found. */ location_state_t location_state; @@ -1352,19 +1523,7 @@ main(int argc, char *argv[]) } } - /* Get current location. */ - r = provider->get_location(&location_state, &loc); - if (r < 0) { - fputs(_("Unable to get location from provider.\n"), - stderr); - exit(EXIT_FAILURE); - } - - provider->free(&location_state); - if (verbose) { - print_location(&loc); - printf(_("Temperatures: %dK at day, %dK at night\n"), scheme.day.temperature, scheme.night.temperature); @@ -1374,24 +1533,6 @@ main(int argc, char *argv[]) scheme.high, scheme.low); } - /* Latitude */ - if (loc.lat < MIN_LAT || loc.lat > MAX_LAT) { - /* TRANSLATORS: Append degree symbols if possible. */ - fprintf(stderr, - _("Latitude must be between %.1f and %.1f.\n"), - MIN_LAT, MAX_LAT); - exit(EXIT_FAILURE); - } - - /* Longitude */ - if (loc.lon < MIN_LON || loc.lon > MAX_LON) { - /* TRANSLATORS: Append degree symbols if possible. */ - fprintf(stderr, - _("Longitude must be between" - " %.1f and %.1f.\n"), MIN_LON, MAX_LON); - exit(EXIT_FAILURE); - } - /* Color temperature */ if (scheme.day.temperature < MIN_TEMP || scheme.day.temperature > MAX_TEMP || @@ -1501,6 +1642,25 @@ main(int argc, char *argv[]) case PROGRAM_MODE_ONE_SHOT: case PROGRAM_MODE_PRINT: { + fputs(_("Waiting for current location" + " to become available...\n"), stderr); + + /* Wait for location provider. */ + location_t loc = { NAN, NAN }; + int r = provider_get_location( + provider, &location_state, -1, &loc); + if (r < 0) { + fputs(_("Unable to get location" + " from provider.\n"), stderr); + exit(EXIT_FAILURE); + } + + if (!location_is_valid(&loc)) { + exit(EXIT_FAILURE); + } + + print_location(&loc); + /* Current angular elevation of the sun */ double now; r = systemtime_get_time(&now); @@ -1534,24 +1694,24 @@ main(int argc, char *argv[]) interp.brightness); } - if (mode == PROGRAM_MODE_PRINT) { - exit(EXIT_SUCCESS); - } - - /* Adjust temperature */ - r = method->set_temperature(&state, &interp); - if (r < 0) { - fputs(_("Temperature adjustment failed.\n"), stderr); - method->free(&state); - exit(EXIT_FAILURE); - } + if (mode != PROGRAM_MODE_PRINT) { + /* Adjust temperature */ + r = method->set_temperature(&state, &interp); + if (r < 0) { + fputs(_("Temperature adjustment failed.\n"), + stderr); + method->free(&state); + exit(EXIT_FAILURE); + } - /* In Quartz (OSX) the gamma adjustments will automatically - revert when the process exits. Therefore, we have to loop - until CTRL-C is received. */ - if (strcmp(method->name, "quartz") == 0) { - fputs(_("Press ctrl-c to stop...\n"), stderr); - pause(); + /* In Quartz (macOS) the gamma adjustments will + automatically revert when the process exits. + Therefore, we have to loop until CTRL-C is received. + */ + if (strcmp(method->name, "quartz") == 0) { + fputs(_("Press ctrl-c to stop...\n"), stderr); + pause(); + } } } break; @@ -1601,7 +1761,7 @@ main(int argc, char *argv[]) break; case PROGRAM_MODE_CONTINUAL: { - r = run_continual_mode(&loc, &scheme, + r = run_continual_mode(provider, &location_state, &scheme, method, &state, transition, verbose); if (r < 0) exit(EXIT_FAILURE); @@ -1610,7 +1770,15 @@ main(int argc, char *argv[]) } /* Clean up gamma adjustment state */ - method->free(&state); + if (mode != PROGRAM_MODE_PRINT) { + method->free(&state); + } + + /* Clean up location provider state */ + if (mode != PROGRAM_MODE_RESET && + mode != PROGRAM_MODE_MANUAL) { + provider->free(&location_state); + } return EXIT_SUCCESS; } diff --git a/src/redshift.h b/src/redshift.h index bac8e34..c659502 100644 --- a/src/redshift.h +++ b/src/redshift.h @@ -89,7 +89,9 @@ typedef void location_provider_free_func(void *state); typedef void location_provider_print_help_func(FILE *f); typedef int location_provider_set_option_func(void *state, const char *key, const char *value); -typedef int location_provider_get_location_func(void *state, location_t *loc); +typedef int location_provider_get_fd_func(void *state); +typedef int location_provider_handle_func( + void *state, location_t *location, int *available); typedef struct { char *name; @@ -106,8 +108,9 @@ typedef struct { /* Set an option key, value-pair. */ location_provider_set_option_func *set_option; - /* Get current location. */ - location_provider_get_location_func *get_location; + /* Listen and handle location updates. */ + location_provider_get_fd_func *get_fd; + location_provider_handle_func *handle; } location_provider_t; -- cgit v1.2.3-70-g09d2 From ce037545dd28cd658f3037e8494089b9771177b1 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Wed, 16 Aug 2017 21:06:41 -0700 Subject: pipeutils: Add utils for pipe signals Add pipeutils.c with utility functions for working with pipes as signals across threads. Using pipes for signals makes it easy for the main thread to wait on (multiple) file descriptors with or without a timeout. --- src/Makefile.am | 1 + src/pipeutils.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pipeutils.h | 28 +++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 src/pipeutils.c create mode 100644 src/pipeutils.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 73ead4b..99c8a2e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ redshift_SOURCES = \ gamma-dummy.c gamma-dummy.h \ hooks.c hooks.h \ location-manual.c location-manual.h \ + pipeutils.c pipeutils.h \ redshift.c redshift.h \ signals.c signals.h \ solar.c solar.h \ diff --git a/src/pipeutils.c b/src/pipeutils.c new file mode 100644 index 0000000..75302cb --- /dev/null +++ b/src/pipeutils.c @@ -0,0 +1,98 @@ +/* pipeutils.c -- Utilities for using pipes as signals + 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) 2017 Jon Lund Steffensen +*/ + +#include +#include +#include + + +#ifndef _WIN32 + +/* Create non-blocking set of pipe fds. */ +int +pipeutils_create_nonblocking(int pipefds[2]) +{ + int r = pipe(pipefds); + if (r == -1) { + perror("pipe"); + return -1; + } + + int flags = fcntl(pipefds[0], F_GETFL); + if (flags == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + r = fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK); + if (r == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + flags = fcntl(pipefds[1], F_GETFL); + if (flags == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + r = fcntl(pipefds[1], F_SETFL, flags | O_NONBLOCK); + if (r == -1) { + perror("fcntl"); + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + + return 0; +} + +#else /* _WIN32 */ + +/* Create non-blocking set of pipe fds. + + Not supported on Windows! Always fails. */ +int +pipeutils_create_nonblocking(int pipefds[2]) +{ + return -1; +} + +#endif + +/* Signal on write-end of pipe. */ +void +pipeutils_signal(int write_fd) +{ + write(write_fd, "", 1); +} + +/* Mark signal as handled on read-end of pipe. */ +void +pipeutils_handle_signal(int read_fd) +{ + char data; + read(read_fd, &data, 1); +} diff --git a/src/pipeutils.h b/src/pipeutils.h new file mode 100644 index 0000000..69c3350 --- /dev/null +++ b/src/pipeutils.h @@ -0,0 +1,28 @@ +/* pipeutils.h -- Utilities for using pipes as signals + 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) 2017 Jon Lund Steffensen +*/ + +#ifndef REDSHIFT_PIPEUTILS_H +#define REDSHIFT_PIPEUTILS_H + +int pipeutils_create_nonblocking(int pipefds[2]); + +void pipeutils_signal(int write_fd); +void pipeutils_handle_signal(int read_fd); + +#endif /* ! REDSHIFT_PIPEUTILS_H */ -- cgit v1.2.3-70-g09d2 From df10508e30ad8a8eab43ef1e3666efa3af1f73ef Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Wed, 16 Aug 2017 22:28:38 -0700 Subject: corelocation: Update continuously --- configure.ac | 2 +- src/location-corelocation.h | 29 +++++-- src/location-corelocation.m | 201 ++++++++++++++++++++++++++++++++------------ src/redshift.c | 3 + 4 files changed, 173 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/configure.ac b/configure.ac index 40b4316..14663e2 100644 --- a/configure.ac +++ b/configure.ac @@ -257,7 +257,7 @@ AC_ARG_ENABLE([corelocation], [AC_HELP_STRING([--enable-corelocation], AS_IF([test "x$enable_corelocation" != xno], [ AS_IF([test "x$have_corelocation_h" = xyes], [ CORELOCATION_CFLAGS="" - CORELOCATION_LIBS="-framework Foundation -framework CoreLocation" + CORELOCATION_LIBS="-framework Foundation -framework Cocoa -framework CoreLocation" AC_DEFINE([ENABLE_CORELOCATION], 1, [Define to 1 to enable CoreLocation provider]) AC_MSG_RESULT([yes]) diff --git a/src/location-corelocation.h b/src/location-corelocation.h index 9c276e9..ae1feeb 100644 --- a/src/location-corelocation.h +++ b/src/location-corelocation.h @@ -24,18 +24,33 @@ #include "redshift.h" +typedef struct location_corelocation_private location_corelocation_private_t; -int location_corelocation_init(void *state); -int location_corelocation_start(void *state); -void location_corelocation_free(void *state); +typedef struct { + location_corelocation_private_t *private; + int pipe_fd_read; + int pipe_fd_write; + int available; + int error; + float latitude; + float longitude; +} location_corelocation_state_t; + + +int location_corelocation_init(location_corelocation_state_t *state); +int location_corelocation_start(location_corelocation_state_t *state); +void location_corelocation_free(location_corelocation_state_t *state); void location_corelocation_print_help(FILE *f); -int location_corelocation_set_option(void *state, - const char *key, const char *value); +int location_corelocation_set_option( + location_corelocation_state_t *state, + const char *key, const char *value); -int location_corelocation_get_fd(void *state); +int location_corelocation_get_fd( + location_corelocation_state_t *state); int location_corelocation_handle( - void *state, location_t *location, int *available); + location_corelocation_state_t *state, + location_t *location, int *available); #endif /* ! REDSHIFT_LOCATION_CORELOCATION_H */ diff --git a/src/location-corelocation.m b/src/location-corelocation.m index e33c853..5150839 100644 --- a/src/location-corelocation.m +++ b/src/location-corelocation.m @@ -25,9 +25,11 @@ #import #include "location-corelocation.h" +#include "pipeutils.h" #include "redshift.h" #include +#include #ifdef ENABLE_NLS # include @@ -37,20 +39,25 @@ #endif -@interface Delegate : NSObject +struct location_corelocation_private { + NSThread *thread; + NSLock *lock; +}; + + +@interface LocationDelegate : NSObject @property (strong, nonatomic) CLLocationManager *locationManager; -@property (nonatomic) BOOL success; -@property (nonatomic) BOOL error; -@property (nonatomic) float latitude; -@property (nonatomic) float longitude; +@property (nonatomic) location_corelocation_state_t *state; @end -@implementation Delegate; +@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]; @@ -59,50 +66,106 @@ authStatus != kCLAuthorizationStatusAuthorized) { fputs(_("Not authorized to obtain location" " from CoreLocation.\n"), stderr); - self.error = YES; - CFRunLoopStop(CFRunLoopGetCurrent()); + [self markError]; + } else { + [self.locationManager startUpdatingLocation]; } - - [self.locationManager startUpdatingLocation]; } -- (void)stop +- (void)markError { - [self.locationManager stopUpdatingLocation]; - CFRunLoopStop(CFRunLoopGetCurrent()); + [self.state->private->lock lock]; + + self.state->error = 1; + + [self.state->private->lock unlock]; + + pipeutils_signal(self.state->pipe_fd_write); } - (void)locationManager:(CLLocationManager *)manager - didUpdateLocations:(NSArray *)locations + didUpdateLocations:(NSArray *)locations { CLLocation *newLocation = [locations firstObject]; - self.latitude = newLocation.coordinate.latitude; - self.longitude = newLocation.coordinate.longitude; - self.success = YES; - [self stop]; + [self.state->private->lock lock]; + + self.state->latitude = newLocation.coordinate.latitude; + self.state->longitude = newLocation.coordinate.longitude; + self.state->available = 1; + + [self.state->private->lock unlock]; + + pipeutils_signal(self.state->pipe_fd_write); } - (void)locationManager:(CLLocationManager *)manager - didFailWithError:(NSError *)error + didFailWithError:(NSError *)error { fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"), [[error localizedDescription] UTF8String]); - self.error = YES; - [self stop]; + [self markError]; } - (void)locationManager:(CLLocationManager *)manager - didChangeAuthorizationStatus:(CLAuthorizationStatus)status + didChangeAuthorizationStatus:(CLAuthorizationStatus)status { if (status == kCLAuthorizationStatusNotDetermined) { - fputs(_("Waiting for authorization to obtain location...\n"), - stderr); + fputs(_("Waiting for authorization to obtain location...\n"), stderr); } else if (status != kCLAuthorizationStatusAuthorized) { - fputs(_("Request for location was not authorized!\n"), - stderr); - self.error = YES; - [self stop]; + 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) location_corelocation_state_t *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); } } @@ -110,20 +173,56 @@ int -location_corelocation_init(void *state) +location_corelocation_init(location_corelocation_state_t *state) { return 0; } int -location_corelocation_start(void *state) +location_corelocation_start(location_corelocation_state_t *state) { + state->pipe_fd_read = -1; + state->pipe_fd_write = -1; + + state->available = 0; + state->error = 0; + state->latitude = 0; + state->longitude = 0; + + state->private = malloc(sizeof(location_corelocation_private_t)); + if (state->private == NULL) return -1; + + int pipefds[2]; + int r = pipeutils_create_nonblocking(pipefds); + if (r < 0) { + fputs(_("Failed to start CoreLocation provider!\n"), stderr); + free(state->private); + return -1; + } + + state->pipe_fd_read = pipefds[0]; + state->pipe_fd_write = pipefds[1]; + + pipeutils_signal(state->pipe_fd_write); + + state->private->lock = [[NSLock alloc] init]; + + LocationThread *thread = [[LocationThread alloc] init]; + thread.state = state; + [thread start]; + state->private->thread = thread; + return 0; } void -location_corelocation_free(void *state) +location_corelocation_free(location_corelocation_state_t *state) { + if (state->pipe_fd_read != -1) { + close(state->pipe_fd_read); + } + + free(state->private); } void @@ -131,44 +230,38 @@ location_corelocation_print_help(FILE *f) { fputs(_("Use the location as discovered by the Corelocation provider.\n"), f); fputs("\n", f); - - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "CoreLocation"); - fputs("\n", f); } int -location_corelocation_set_option(void *state, - const char *key, const char *value) +location_corelocation_set_option( + location_corelocation_state_t *state, const char *key, const char *value) { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; } int -location_corelocation_get_fd(void *state) +location_corelocation_get_fd(location_corelocation_state_t *state) { - return -1; + return state->pipe_fd_read; } -int -location_corelocation_handle(void *state, location_t *location, int *available) +int location_corelocation_handle( + location_corelocation_state_t *state, + location_t *location, int *available) { - @autoreleasepool { - Delegate *delegate = [[Delegate alloc] init]; - [delegate performSelectorOnMainThread:@selector(start) - withObject:nil waitUntilDone:NO]; - CFRunLoopRun(); + pipeutils_handle_signal(state->pipe_fd_read); - if (delegate.error) return -1; + [state->private->lock lock]; - if (delegate.success) { - location->lat = delegate.latitude; - location->lon = delegate.longitude; - *available = 1; - } - } + int error = state->error; + location->lat = state->latitude; + location->lon = state->longitude; + *available = state->available; + + [state->private->lock unlock]; + + if (error) return -1; return 0; } diff --git a/src/redshift.c b/src/redshift.c index bf0ab86..e1b9593 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -206,6 +206,9 @@ static const gamma_method_t gamma_methods[] = { /* Union of state data for location providers */ typedef union { location_manual_state_t manual; +#ifdef ENABLE_CORELOCATION + location_corelocation_state_t corelocation; +#endif } location_state_t; -- cgit v1.2.3-70-g09d2 From 00e2d538e8774ab9e3cf1ec220ac1c76ecdb585e Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Wed, 16 Aug 2017 22:28:53 -0700 Subject: geoclue2: Update continuously --- src/location-geoclue2.c | 280 ++++++++++++++++++++++++++++++++---------------- src/location-geoclue2.h | 26 +++-- src/redshift.c | 3 + 3 files changed, 209 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/location-geoclue2.c b/src/location-geoclue2.c index ab57535..9641507 100644 --- a/src/location-geoclue2.c +++ b/src/location-geoclue2.c @@ -26,6 +26,7 @@ #include "location-geoclue2.h" #include "redshift.h" +#include "pipeutils.h" #ifdef ENABLE_NLS # include @@ -35,22 +36,26 @@ #endif -typedef struct { - GMainLoop *loop; +/* Indicate an unrecoverable error during GeoClue2 communication. */ +static void +mark_error(location_geoclue2_state_t *state) +{ + g_mutex_lock(&state->lock); - int available; - int error; - location_t location; -} get_location_data_t; + state->error = 1; + g_mutex_unlock(&state->lock); + + pipeutils_signal(state->pipe_fd_write); +} /* Handle position change callbacks */ static void geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, gchar *signal_name, GVariant *parameters, - gpointer *user_data) + gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; /* Only handle LocationUpdated signals */ if (g_strcmp0(signal_name, "LocationUpdated") != 0) { @@ -63,34 +68,38 @@ geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name, /* Obtain location */ GError *error = NULL; - GDBusProxy *location = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - location_path, - "org.freedesktop.GeoClue2.Location", - NULL, &error); + GDBusProxy *location = g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + location_path, + "org.freedesktop.GeoClue2.Location", + NULL, &error); if (location == NULL) { g_printerr(_("Unable to obtain location: %s.\n"), error->message); g_error_free(error); + mark_error(state); return; } + g_mutex_lock(&state->lock); + /* Read location properties */ - GVariant *lat_v = g_dbus_proxy_get_cached_property(location, - "Latitude"); - data->location.lat = g_variant_get_double(lat_v); + GVariant *lat_v = g_dbus_proxy_get_cached_property( + location, "Latitude"); + state->latitude = g_variant_get_double(lat_v); - GVariant *lon_v = g_dbus_proxy_get_cached_property(location, - "Longitude"); - data->location.lon = g_variant_get_double(lon_v); + GVariant *lon_v = g_dbus_proxy_get_cached_property( + location, "Longitude"); + state->longitude = g_variant_get_double(lon_v); - data->available = 1; + state->available = 1; - /* Return from main loop */ - g_main_loop_quit(data->loop); + g_mutex_unlock(&state->lock); + + pipeutils_signal(state->pipe_fd_write); } /* Callback when GeoClue name appears on the bus */ @@ -98,22 +107,23 @@ static void on_name_appeared(GDBusConnection *conn, const gchar *name, const gchar *name_owner, gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; /* Obtain GeoClue Manager */ GError *error = NULL; - GDBusProxy *geoclue_manager = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - "/org/freedesktop/GeoClue2/Manager", - "org.freedesktop.GeoClue2.Manager", - NULL, &error); + GDBusProxy *geoclue_manager = g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + "/org/freedesktop/GeoClue2/Manager", + "org.freedesktop.GeoClue2.Manager", + NULL, &error); if (geoclue_manager == NULL) { g_printerr(_("Unable to obtain GeoClue Manager: %s.\n"), error->message); g_error_free(error); + mark_error(state); return; } @@ -130,6 +140,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, error->message); g_error_free(error); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -138,20 +149,21 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Obtain GeoClue client */ error = NULL; - GDBusProxy *geoclue_client = - g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.freedesktop.GeoClue2", - client_path, - "org.freedesktop.GeoClue2.Client", - NULL, &error); + GDBusProxy *geoclue_client = g_dbus_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.GeoClue2", + client_path, + "org.freedesktop.GeoClue2.Client", + NULL, &error); if (geoclue_client == NULL) { g_printerr(_("Unable to obtain GeoClue Client: %s.\n"), error->message); g_error_free(error); g_variant_unref(client_path_v); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -159,15 +171,15 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Set desktop id (basename of the .desktop file) */ error = NULL; - GVariant *ret_v = - g_dbus_proxy_call_sync(geoclue_client, - "org.freedesktop.DBus.Properties.Set", - g_variant_new("(ssv)", - "org.freedesktop.GeoClue2.Client", - "DesktopId", - g_variant_new("s", "redshift")), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, &error); + GVariant *ret_v = g_dbus_proxy_call_sync( + geoclue_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new("(ssv)", + "org.freedesktop.GeoClue2.Client", + "DesktopId", + g_variant_new("s", "redshift")), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); if (ret_v == NULL) { /* Ignore this error for now. The property is not available in early versions of GeoClue2. */ @@ -177,20 +189,22 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Set distance threshold */ error = NULL; - ret_v = g_dbus_proxy_call_sync(geoclue_client, - "org.freedesktop.DBus.Properties.Set", - g_variant_new("(ssv)", - "org.freedesktop.GeoClue2.Client", - "DistanceThreshold", - g_variant_new("u", 50000)), - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, &error); + ret_v = g_dbus_proxy_call_sync( + geoclue_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new("(ssv)", + "org.freedesktop.GeoClue2.Client", + "DistanceThreshold", + g_variant_new("u", 50000)), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); if (ret_v == NULL) { g_printerr(_("Unable to set distance threshold: %s.\n"), error->message); g_error_free(error); g_object_unref(geoclue_client); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -199,7 +213,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, /* Attach signal callback to client */ g_signal_connect(geoclue_client, "g-signal", G_CALLBACK(geoclue_client_signal_cb), - data); + user_data); /* Start GeoClue client */ error = NULL; @@ -214,6 +228,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name, g_error_free(error); g_object_unref(geoclue_client); g_object_unref(geoclue_manager); + mark_error(state); return; } @@ -225,16 +240,70 @@ static void on_name_vanished(GDBusConnection *connection, const gchar *name, gpointer user_data) { - get_location_data_t *data = (get_location_data_t *)user_data; + location_geoclue2_state_t *state = user_data; + + g_mutex_lock(&state->lock); + + state->available = 0; + + g_mutex_unlock(&state->lock); + + pipeutils_signal(state->pipe_fd_write); +} + +/* Callback when the pipe to the main thread is closed. */ +static gboolean +on_pipe_closed(GIOChannel *channel, GIOCondition condition, gpointer user_data) +{ + location_geoclue2_state_t *state = user_data; + g_main_loop_quit(state->loop); + + return FALSE; +} + + +/* Run loop for location provider thread. */ +void * +run_geoclue2_loop(void *state_) +{ + location_geoclue2_state_t *state = state_; + + GMainContext *context = g_main_context_new(); + g_main_context_push_thread_default(context); + state->loop = g_main_loop_new(context, FALSE); + + guint watcher_id = g_bus_watch_name( + G_BUS_TYPE_SYSTEM, + "org.freedesktop.GeoClue2", + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + on_name_appeared, + on_name_vanished, + state, NULL); + + /* Listen for closure of pipe */ + GIOChannel *pipe_channel = g_io_channel_unix_new(state->pipe_fd_write); + GSource *pipe_source = g_io_create_watch( + pipe_channel, G_IO_IN | G_IO_HUP | G_IO_ERR); + g_source_set_callback( + pipe_source, (GSourceFunc)on_pipe_closed, state, NULL); + g_source_attach(pipe_source, context); + + g_main_loop_run(state->loop); + + g_source_unref(pipe_source); + g_io_channel_unref(pipe_channel); + close(state->pipe_fd_write); + + g_bus_unwatch_name(watcher_id); - g_fprintf(stderr, _("Unable to connect to GeoClue.\n")); - data->error = 1; + g_main_loop_unref(state->loop); + g_main_context_unref(context); - g_main_loop_quit(data->loop); + return NULL; } int -location_geoclue2_init(void *state) +location_geoclue2_init(location_geoclue2_state_t *state) { #if !GLIB_CHECK_VERSION(2, 35, 0) g_type_init(); @@ -243,64 +312,87 @@ location_geoclue2_init(void *state) } int -location_geoclue2_start(void *state) +location_geoclue2_start(location_geoclue2_state_t *state) { + state->pipe_fd_read = -1; + state->pipe_fd_write = -1; + + state->available = 0; + state->error = 0; + state->latitude = 0; + state->longitude = 0; + + int pipefds[2]; + int r = pipeutils_create_nonblocking(pipefds); + if (r < 0) { + fputs(_("Failed to start GeoClue2 provider!\n"), stderr); + return -1; + } + + state->pipe_fd_read = pipefds[0]; + state->pipe_fd_write = pipefds[1]; + + pipeutils_signal(state->pipe_fd_write); + + g_mutex_init(&state->lock); + state->thread = g_thread_new("geoclue2", run_geoclue2_loop, state); + return 0; } void -location_geoclue2_free(void *state) +location_geoclue2_free(location_geoclue2_state_t *state) { + if (state->pipe_fd_read != -1) { + close(state->pipe_fd_read); + } + + /* Closing the pipe should cause the thread to exit. */ + g_thread_join(state->thread); + state->thread = NULL; + + g_mutex_clear(&state->lock); } void location_geoclue2_print_help(FILE *f) { - fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), f); - fputs("\n", f); - - fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n" - "which means it has to be restarted to take notice after travel.\n"), - "GeoClue2"); + fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), + f); fputs("\n", f); } int -location_geoclue2_set_option(void *state, - const char *key, const char *value) +location_geoclue2_set_option(location_geoclue2_state_t *state, + const char *key, const char *value) { fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key); return -1; } int -location_geoclue2_get_fd(void *state) +location_geoclue2_get_fd(location_geoclue2_state_t *state) { - return -1; + return state->pipe_fd_read; } int -location_geoclue2_handle(void *state, location_t *location, int *available) +location_geoclue2_handle( + location_geoclue2_state_t *state, + location_t *location, int *available) { - get_location_data_t data; - data.available = 0; - data.error = 0; - - guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, - "org.freedesktop.GeoClue2", - G_BUS_NAME_WATCHER_FLAGS_AUTO_START, - on_name_appeared, - on_name_vanished, - &data, NULL); - data.loop = g_main_loop_new(NULL, FALSE); - g_main_loop_run(data.loop); + pipeutils_handle_signal(state->pipe_fd_read); - g_bus_unwatch_name(watcher_id); + g_mutex_lock(&state->lock); + + int error = state->error; + location->lat = state->latitude; + location->lon = state->longitude; + *available = state->available; - if (data.error) return -1; + g_mutex_unlock(&state->lock); - *available = data.available; - *location = data.location; + if (error) return -1; return 0; } diff --git a/src/location-geoclue2.h b/src/location-geoclue2.h index 095d86f..2f04eea 100644 --- a/src/location-geoclue2.h +++ b/src/location-geoclue2.h @@ -22,19 +22,33 @@ #include +#include + #include "redshift.h" +typedef struct { + GMainLoop *loop; + GThread *thread; + GMutex lock; + int pipe_fd_read; + int pipe_fd_write; + int available; + int error; + float latitude; + float longitude; +} location_geoclue2_state_t; + -int location_geoclue2_init(void *state); -int location_geoclue2_start(void *state); -void location_geoclue2_free(void *state); +int location_geoclue2_init(location_geoclue2_state_t *state); +int location_geoclue2_start(location_geoclue2_state_t *state); +void location_geoclue2_free(location_geoclue2_state_t *state); void location_geoclue2_print_help(FILE *f); -int location_geoclue2_set_option(void *state, +int location_geoclue2_set_option(location_geoclue2_state_t *state, const char *key, const char *value); -int location_geoclue2_get_fd(void *state); -int location_geoclue2_handle(void *state, +int location_geoclue2_get_fd(location_geoclue2_state_t *state); +int location_geoclue2_handle(location_geoclue2_state_t *state, location_t *location, int *available); diff --git a/src/redshift.c b/src/redshift.c index e1b9593..32def0b 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -206,6 +206,9 @@ static const gamma_method_t gamma_methods[] = { /* Union of state data for location providers */ typedef union { location_manual_state_t manual; +#ifdef ENABLE_GEOCLUE2 + location_geoclue2_state_t geoclue2; +#endif #ifdef ENABLE_CORELOCATION location_corelocation_state_t corelocation; #endif -- cgit v1.2.3-70-g09d2