aboutsummaryrefslogtreecommitdiffstats
path: root/redshift/location.c
diff options
context:
space:
mode:
Diffstat (limited to 'redshift/location.c')
-rw-r--r--redshift/location.c249
1 files changed, 249 insertions, 0 deletions
diff --git a/redshift/location.c b/redshift/location.c
new file mode 100644
index 0000000..5979a2d
--- /dev/null
+++ b/redshift/location.c
@@ -0,0 +1,249 @@
+/*-
+ * 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"
+
+
+const struct location_provider *location_providers[] = {
+#ifdef ENABLE_GEOCLUE2
+ &geoclue2_location_provider,
+#endif
+#ifdef ENABLE_CORELOCATION
+ &corelocation_location_provider,
+#endif
+ &manual_location_provider,
+#ifndef WINDOWS
+ &geofile_location_provider,
+ &timezone_location_provider,
+#endif
+ NULL
+};
+
+
+/**
+ * Get the current monotonic time in milliseconds
+ *
+ * @return The number of milliseconds elapsed since some arbitrary fixed time
+ */
+static long long int
+get_monotonic_millis(void)
+{
+#if defined(WINDOWS)
+ return (long long int)GetTickCount64();
+#else
+ struct timespec now;
+ if (clock_gettime(CLOCK_MONOTONIC, &now))
+ eprintf("clock_gettime CLOCK_MONOTONIC:");
+ return (long long int)now.tv_sec * 1000LL + (long long int)now.tv_nsec / 1000000LL;
+#endif
+}
+
+
+/**
+ * Attempt to start a specific location provider
+ *
+ * @param provider The location provider
+ * @param state_out Output parameter for the location provider state
+ * @param config Loaded information file
+ * @param args `NULL` or option part of the command line argument for the location provider
+ * @return 0 on success, -1 on failure
+ */
+static int
+try_start(const struct location_provider *provider, LOCATION_STATE **state_out, struct config_ini_state *config, char *args)
+{
+ const char *manual_keys[] = {"lat", "lon"};
+ struct config_ini_section *section;
+ struct config_ini_setting *setting;
+ char *next_arg, *value;
+ const char *key;
+ int i;
+
+ if (provider->create(state_out) < 0) {
+ weprintf(_("Initialization of %s failed."), provider->name);
+ goto fail;
+ }
+
+ /* Set provider options from config file */
+ if ((section = config_ini_get_section(config, provider->name)))
+ for (setting = section->settings; setting; setting = setting->next)
+ if (provider->set_option(*state_out, setting->name, setting->value) < 0)
+ goto set_option_fail;
+
+ /* Set provider options from command line */
+ for (i = 0; args && *args; i++, args = next_arg) {
+ next_arg = &args[strcspn(args, ";:")];
+ if (*next_arg)
+ *next_arg++ = '\0';
+
+ key = args;
+ value = strchr(args, '=');
+ if (!value) {
+ /* The options for the "manual" method can be set
+ * without keys on the command line for convencience
+ * and for backwards compatability. We add the proper
+ * keys here before calling set_option(). */
+ if (!strcmp(provider->name, "manual") && i < (int)ELEMSOF(manual_keys)) {
+ key = manual_keys[i];
+ value = args;
+ } else {
+ weprintf(_("Failed to parse option `%s'."), args);
+ goto fail;
+ }
+ } else {
+ *value++ = '\0';
+ }
+
+ if (provider->set_option(*state_out, key, value) < 0)
+ goto set_option_fail;
+ }
+
+ /* Start provider */
+ if (provider->start(*state_out) < 0) {
+ weprintf(_("Failed to start provider %s."), provider->name);
+ goto fail;
+ }
+
+ return 0;
+
+set_option_fail:
+ weprintf(_("Failed to set %s option."), provider->name);
+ /* TRANSLATORS: `help' must not be translated. */
+ weprintf(_("Try `-l %s:help' for more information."), provider->name);
+fail:
+ if (*state_out) {
+ provider->free(*state_out);
+ *state_out = NULL;
+ }
+ return -1;
+}
+
+
+int
+get_location(const struct location_provider *provider, LOCATION_STATE *state, int timeout, struct location *location_out)
+{
+#ifdef WINDOWS /* we don't have poll on Windows, but neither do with have any dynamic location providers */
+ int available;
+ return provider->fetch(state, location_out, &available) < 0 ? -1 : available;
+
+#else
+ int r, available = 0;
+ struct pollfd pollfds[1];
+ long long int now = get_monotonic_millis();
+ long long int end = now + (long long int)timeout;
+
+ do {
+ pollfds[0].fd = provider->get_fd(state);
+ if (pollfds[0].fd >= 0) {
+ /* Poll on file descriptor until ready */
+ pollfds[0].events = POLLIN;
+ timeout = (int)MAX(end - now, 0);
+ r = poll(pollfds, 1, timeout);
+ if (r > 0) {
+ now = get_monotonic_millis();
+ } else if (r < 0) {
+#ifndef WINDOWS
+ if (errno == EINTR)
+ continue;
+#endif
+ weprintf("poll {{.fd=<location provider>, .events=EPOLLIN}} 1 %i:", timeout);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ if (provider->fetch(state, location_out, &available) < 0)
+ return -1;
+ } while (!available && !exiting);
+
+ if (exiting)
+ eprintf(_("Terminated by user."));
+
+ return 1;
+#endif
+}
+
+
+void
+acquire_location_provider(struct settings *settings, LOCATION_STATE **location_state_out)
+{
+ size_t i;
+
+ if (settings->provider) {
+ /* Use provider specified on command line */
+ if (try_start(settings->provider, location_state_out, &settings->config, settings->provider_args) < 0)
+ exit(1);
+ } else {
+ /* Try all providers, use the first that works */
+ for (i = 0; location_providers[i]; i++) {
+ weprintf(_("Trying location provider `%s'..."), location_providers[i]->name);
+ if (try_start(location_providers[i], location_state_out, &settings->config, NULL) < 0) {
+ weprintf(_("Trying next provider..."));
+ continue;
+ }
+
+ /* Found provider that works */
+ printf(_("Using provider `%s'.\n"), location_providers[i]->name);
+ settings->provider = location_providers[i];
+ break;
+ }
+
+ /* Failure if no providers were successful at this point */
+ if (!settings->provider)
+ eprintf(_("No more location providers to try."));
+ }
+}
+
+
+int
+location_is_valid(const struct location *location)
+{
+ if (!WITHIN(MIN_LATITUDE, location->latitude, MAX_LATITUDE)) {
+ /* TRANSLATORS: Append degree symbols if possible. */
+ weprintf(_("Latitude must be between %.1f and %.1f."), MIN_LATITUDE, MAX_LATITUDE);
+ return 0;
+ }
+ if (!WITHIN(MIN_LONGITUDE, location->longitude, MAX_LONGITUDE)) {
+ /* TRANSLATORS: Append degree symbols if possible. */
+ weprintf(_("Longitude must be between %.1f and %.1f."), MIN_LONGITUDE, MAX_LONGITUDE);
+ return 0;
+ }
+ return 1;
+}
+
+
+void
+print_location(const struct location *location)
+{
+ /* TRANSLATORS: Abbreviation for `north' */
+ const char *north = _("N");
+ /* TRANSLATORS: Abbreviation for `south' */
+ const char *south = _("S");
+ /* TRANSLATORS: Abbreviation for `east' */
+ const char *east = _("E");
+ /* TRANSLATORS: Abbreviation for `west' */
+ const char *west = _("W");
+
+ /* TRANSLATORS: Append degree symbols after %f if possible.
+ * The string following each number is an abreviation for
+ * north, source, east or west (N, S, E, W). */
+ printf(_("Location: %.2f %s, %.2f %s\n"),
+ fabs(location->latitude), signbit(location->latitude) ? south : north,
+ fabs(location->longitude), signbit(location->longitude) ? west : east);
+}