From ab43258e7e2aab8dad9b304600cd38daa2480b39 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Mon, 9 Dec 2013 11:11:59 -0500 Subject: [PATCH 01/10] Add DBus service Add a seperate executable redshift-dbus that functions like redshift but runs the main loop through Glib. In addition it registers a service on the session DBus such that it can be queried and controlled. --- Makefile.am | 19 +- configure.ac | 27 + .../dk.jonls.redshift.Redshift.service.in | 3 + src/Makefile.am | 49 +- src/redshift-dbus.c | 893 ++++++++++++++++++ 5 files changed, 980 insertions(+), 11 deletions(-) create mode 100644 data/dbus-1/services/dk.jonls.redshift.Redshift.service.in create mode 100644 src/redshift-dbus.c diff --git a/Makefile.am b/Makefile.am index 675fda82..8c1e19a6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,9 @@ SYSTEMD_USER_UNIT_IN_FILES = \ data/systemd/redshift.service.in \ data/systemd/redshift-gtk.service.in +DBUS_SERVICE_IN_FILES = \ + data/dbus-1/services/dk.jonls.redshift.Redshift.service.in + # Icons if ENABLE_GUI @@ -78,6 +81,16 @@ $(systemduserunit_DATA): $(SYSTEMD_USER_UNIT_IN_FILES) Makefile sed -e "s|\@bindir\@|$(bindir)|g" $< > $@ +# D-Bus service file +if ENABLE_DBUS +dbus_servicedir = @datadir@/dbus-1/services +dbus_service_DATA = $(DBUS_SERVICE_IN_FILES:.service.in=.service) +endif + +$(dbus_service_DATA): $(DBUS_SERVICE_IN_FILES) Makefile + $(AM_V_GEN)$(MKDIR_P) $(@D) && \ + sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ + EXTRA_DIST = \ $(EXTRA_ROOTDOC_FILES) \ @@ -85,9 +98,11 @@ EXTRA_DIST = \ $(_UBUNTU_MONO_DARK_FILES) \ $(_UBUNTU_MONO_LIGHT_FILES) \ $(_DESKTOP_FILES) \ - $(SYSTEMD_USER_UNIT_IN_FILES) + $(SYSTEMD_USER_UNIT_IN_FILES) \ + $(DBUS_SERVICE_IN_FILES) -CLEANFILES = $(systemduserunit_DATA) +CLEANFILES = $(systemduserunit_DATA) \ + $(dbus_service_DATA) # Update PO translations diff --git a/configure.ac b/configure.ac index deae0cfc..5f908a1f 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,7 @@ PKG_CHECK_MODULES([XCB_RANDR], [xcb-randr], [have_xcb_randr=yes], [have_xcb_randr=no]) PKG_CHECK_MODULES([GLIB], [glib-2.0 gobject-2.0], [have_glib=yes], [have_glib=no]) +PKG_CHECK_MODULES([GDBUS], [gio-2.0], [have_gio=yes], [have_gio=no]) PKG_CHECK_MODULES([GEOCLUE], [geoclue], [have_geoclue=yes], [have_geoclue=no]) AC_CHECK_HEADER([windows.h], [have_windows_h=yes], [have_windows_h=no]) @@ -153,6 +154,30 @@ AS_IF([test "x$enable_geoclue" != xno], [ ]) AM_CONDITIONAL([ENABLE_GEOCLUE], [test "x$enable_geoclue" = xyes]) +# Check DBus service +AC_MSG_CHECKING([whether to enable DBus service]) +AC_ARG_ENABLE([dbus], [AC_HELP_STRING([--enable-dbus], + [enable DBus service])], + [enable_dbus=$enableval],[enable_dbus=no]) +AS_IF([test "x$enable_dbus" != xno], [ + AS_IF([test "x$have_glib" = xyes -a "x$have_gio" = xyes], [ + AC_DEFINE([ENABLE_DBUS], 1, + [Define to 1 to enable DBus service]) + AC_MSG_RESULT([yes]) + enable_dbus=yes + ], [ + AC_MSG_RESULT([missing dependencies]) + AS_IF([test "x$enable_dbus" = xyes], [ + AC_MSG_ERROR([missing dependencies for DBus service]) + ]) + enable_dbus=no + ]) +], [ + AC_MSG_RESULT([no]) + enable_dbus=no +]) +AM_CONDITIONAL([ENABLE_DBUS], [test "x$enable_dbus" = xyes]) + # Check for GUI status icon AC_MSG_CHECKING([whether to enable GUI status icon]) @@ -245,6 +270,8 @@ echo " Location providers: Geoclue: ${enable_geoclue} + DBus service: ${enable_dbus} + GUI: ${enable_gui} Ubuntu icons: ${enable_ubuntu} systemd units: ${enable_systemd} ${systemduserunitdir} diff --git a/data/dbus-1/services/dk.jonls.redshift.Redshift.service.in b/data/dbus-1/services/dk.jonls.redshift.Redshift.service.in new file mode 100644 index 00000000..b4fcc37a --- /dev/null +++ b/data/dbus-1/services/dk.jonls.redshift.Redshift.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=dk.jonls.redshift.Redshift +Exec=@libexecdir@/redshift-dbus diff --git a/src/Makefile.am b/src/Makefile.am index 37a03080..0bef6666 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,6 +8,11 @@ AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" # redshift Program bin_PROGRAMS = redshift +# redshift-dbus Program +if ENABLE_DBUS +libexec_PROGRAMS = redshift-dbus +endif + redshift_SOURCES = \ redshift.c redshift.h \ colorramp.c colorramp.h \ @@ -17,6 +22,12 @@ redshift_SOURCES = \ systemtime.c systemtime.h \ gamma-dummy.c gamma-dummy.h +redshift_dbus_SOURCES = \ + redshift-dbus.c \ + colorramp.c colorramp.h \ + solar.c solar.h \ + gamma-dummy.c gamma-dummy.h + EXTRA_redshift_SOURCES = \ gamma-drm.c gamma-drm.h \ gamma-randr.c gamma-randr.h \ @@ -24,31 +35,49 @@ EXTRA_redshift_SOURCES = \ gamma-w32gdi.c gamma-w32gdi.h \ location-geoclue.c location-geoclue.h -AM_CFLAGS = -redshift_LDADD = @LIBINTL@ + EXTRA_DIST = + +redshift_CFLAGS = +redshift_LDADD = @LIBINTL@ +redshift_dbus_CFLAGS = $(GLIB_CFLAGS) $(GDBUS_CFLAGS) +redshift_dbus_LDADD = $(GLIB_LIBS) $(GDBUS_LIBS) + + if ENABLE_DRM redshift_SOURCES += gamma-drm.c gamma-drm.h -AM_CFLAGS += $(DRM_CFLAGS) +redshift_CFLAGS += $(DRM_CFLAGS) redshift_LDADD += \ $(DRM_LIBS) $(DRM_CFLAGS) endif if ENABLE_RANDR redshift_SOURCES += gamma-randr.c gamma-randr.h -AM_CFLAGS += $(XCB_CFLAGS) $(XCB_RANDR_CFLAGS) +redshift_CFLAGS += $(XCB_CFLAGS) $(XCB_RANDR_CFLAGS) redshift_LDADD += \ $(XCB_LIBS) $(XCB_CFLAGS) \ $(XCB_RANDR_LIBS) $(XCB_RANDR_CFLAGS) + +redshift_dbus_SOURCES += gamma-randr.c gamma-randr.h +redshift_dbus_CFLAGS += $(XCB_CFLAGS) $(XCB_RANDR_CFLAGS) +redshift_dbus_LDADD += \ + $(XCB_LIBS) $(XCB_CFLAGS) \ + $(XCB_RANDR_LIBS) $(XCB_RANDR_CFLAGS) endif if ENABLE_VIDMODE redshift_SOURCES += gamma-vidmode.c gamma-vidmode.h -AM_CFLAGS += $(X11_CFLAGS) $(XF86VM_CFLAGS) +redshift_CFLAGS += $(X11_CFLAGS) $(XF86VM_CFLAGS) redshift_LDADD += \ $(X11_LIBS) $(X11_CFLAGS) \ $(XF86VM_LIBS) $(XF86VM_CFLAGS) + +redshift_dbus_SOURCES += gamma-vidmode.c gamma-vidmode.h +redshift_dbus_CFLAGS += $(X11_CFLAGS) $(XF86VM_CFLAGS) +redshift_dbus_LDADD += \ + $(X11_LIBS) $(X11_CFLAGS) \ + $(XF86VM_LIBS) $(XF86VM_CFLAGS) endif if ENABLE_WINGDI @@ -59,10 +88,12 @@ endif if ENABLE_GEOCLUE redshift_SOURCES += location-geoclue.c location-geoclue.h -AM_CFLAGS += \ - $(GEOCLUE_CFLAGS) $(GEOCLUE_LIBS) \ - $(GLIB_CFLAGS) $(GLIB_LIBS) +redshift_CFLAGS += $(GEOCLUE_CFLAGS) redshift_LDADD += \ $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) - $(GLIB_LIBS) $(GLIB_CFLAGS) + +redshift_dbus_SOURCES += location-geoclue.c location-geoclue.h +redshift_dbus_CFLAGS += $(GEOCLUE_CFLAGS) +redshift_dbus_LDADD += \ + $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) endif diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c new file mode 100644 index 00000000..696afb0a --- /dev/null +++ b/src/redshift-dbus.c @@ -0,0 +1,893 @@ +/* redshift-dbus.c -- DBus server for Redshift + 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) 2013 Jon Lund Steffensen +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "redshift.h" +#include "solar.h" + + +#ifdef ENABLE_RANDR +# include "gamma-randr.h" +#endif + +#ifdef ENABLE_VIDMODE +# include "gamma-vidmode.h" +#endif + +#include "gamma-dummy.h" + +#ifdef ENABLE_GEOCLUE +# include "location-geoclue.h" +#endif + + +/* Union of state data for gamma adjustment methods */ +union { +#ifdef ENABLE_RANDR + randr_state_t randr; +#endif +#ifdef ENABLE_VIDMODE + vidmode_state_t vidmode; +#endif +} gamma_state; + + +/* Gamma adjustment method structs */ +static const gamma_method_t gamma_methods[] = { +#ifdef ENABLE_RANDR + { + "randr", 1, + (gamma_method_init_func *)randr_init, + (gamma_method_start_func *)randr_start, + (gamma_method_free_func *)randr_free, + (gamma_method_print_help_func *)randr_print_help, + (gamma_method_set_option_func *)randr_set_option, + (gamma_method_restore_func *)randr_restore, + (gamma_method_set_temperature_func *)randr_set_temperature + }, +#endif +#ifdef ENABLE_VIDMODE + { + "vidmode", 1, + (gamma_method_init_func *)vidmode_init, + (gamma_method_start_func *)vidmode_start, + (gamma_method_free_func *)vidmode_free, + (gamma_method_print_help_func *)vidmode_print_help, + (gamma_method_set_option_func *)vidmode_set_option, + (gamma_method_restore_func *)vidmode_restore, + (gamma_method_set_temperature_func *)vidmode_set_temperature + }, +#endif + { + "dummy", 0, + (gamma_method_init_func *)gamma_dummy_init, + (gamma_method_start_func *)gamma_dummy_start, + (gamma_method_free_func *)gamma_dummy_free, + (gamma_method_print_help_func *)gamma_dummy_print_help, + (gamma_method_set_option_func *)gamma_dummy_set_option, + (gamma_method_restore_func *)gamma_dummy_restore, + (gamma_method_set_temperature_func *)gamma_dummy_set_temperature + }, + { NULL } +}; + +static const gamma_method_t *current_method = NULL; + + +/* DBus names */ +#define REDSHIFT_BUS_NAME "dk.jonls.redshift.Redshift" +#define REDSHIFT_OBJECT_PATH "/dk/jonls/redshift/Redshift" +#define REDSHIFT_INTERFACE_NAME "dk.jonls.redshift.Redshift" + +/* Parameter bounds */ +#define LAT_MIN -90.0 +#define LAT_MAX 90.0 +#define LON_MIN -180.0 +#define LON_MAX 180.0 +#define TEMP_MIN 1000 +#define TEMP_MAX 25000 + +/* The color temperature when no adjustment is applied. */ +#define TEMP_NEUTRAL 6500 + +/* Default values for parameters. */ +#define DEFAULT_DAY_TEMP TEMP_NEUTRAL +#define DEFAULT_NIGHT_TEMP 3500 +#define DEFAULT_BRIGHTNESS 1.0 +#define DEFAULT_GAMMA 1.0 + +/* Angular elevation of the sun at which the color temperature + transition period starts and ends (in degress). + Transition during twilight, and while the sun is lower than + 3.0 degrees above the horizon. */ +#define TRANSITION_LOW SOLAR_CIVIL_TWILIGHT_ELEV +#define TRANSITION_HIGH 3.0 + +/* Periods of day */ +typedef enum { + PERIOD_NONE = 0, + PERIOD_DAY, + PERIOD_NIGHT, + PERIOD_TRANSITION +} period_t; + +static const gchar *period_names[] = { + "None", "Day", "Night", "Transition" +}; + + +static GDBusNodeInfo *introspection_data = NULL; + +/* Cookies for programs wanting to interact though + the DBus service. */ +static GHashTable *cookies = NULL; +static gint32 next_cookie = 1; + +/* Set of clients inhibiting the program. */ +static GHashTable *inhibitors = NULL; +static gboolean inhibited = FALSE; + +/* Solar elevation */ +static gdouble elevation = 0.0; +static period_t period = PERIOD_NONE; + +/* Location */ +static gdouble latitude = 0.0; +static gdouble longitude = 0.0; + +/* Temperature bounds */ +static guint temp_day = DEFAULT_DAY_TEMP; +static guint temp_night = DEFAULT_NIGHT_TEMP; + +/* Current temperature */ +static guint temperature = 0; +static guint temp_now = TEMP_NEUTRAL; + +/* Short transition parameters */ +static guint trans_timer = 0; +static guint trans_temp_start = 0; +static guint trans_length = 0; +static guint trans_time = 0; + +/* Forced temperature: 2-layered, so that an external program + can drive the temperature, and at the same time an external + program can demo temperatures as part of the user interface + (priority mode). */ +#define FORCED_TEMP_LAYERS 2 +static gint32 forced_temp_cookie[FORCED_TEMP_LAYERS] = {0}; +static guint forced_temp[FORCED_TEMP_LAYERS] = {0}; + +/* Forced location */ +static gint32 forced_location_cookie = 0; +static gdouble forced_lat = 0.0; +static gdouble forced_lon = 0.0; + +/* Screen update timer */ +static guint screen_update_timer = 0; + + +/* DBus service definition */ +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + + +/* Update elevation from location and time. */ +static void +update_elevation() +{ + gdouble time = g_get_real_time() / 1000000.0; + + /* Check for forced location */ + gdouble lat = latitude; + gdouble lon = longitude; + if (forced_location_cookie != 0) { + lat = forced_lat; + lon = forced_lon; + } + + elevation = solar_elevation(time, lat, lon); + + g_printf("Location: %f, %f\n", lat, lon); + g_printf("Elevation: %f\n", elevation); +} + +/* Update temperature from elevation */ +static void +update_temperature() +{ + /* Calculate temperature according to elevation */ + if (elevation < TRANSITION_LOW) { + temperature = temp_night; + } else if (elevation < TRANSITION_HIGH) { + float a = (TRANSITION_LOW - elevation) / + (TRANSITION_LOW - TRANSITION_HIGH); + temperature = (1.0-a)*temp_night + a*temp_day; + } else { + temperature = temp_day; + } +} + + +/* Timer callback to update short transitions */ +static gboolean +short_transition_update_cb(gpointer data) +{ + trans_time += 1; + gfloat a = trans_time/(gfloat)trans_length; + guint temp = (1.0-a)*trans_temp_start + a*temperature; + + const gfloat gamma[] = {1.0, 1.0, 1.0}; + if (current_method != NULL) { + current_method->set_temperature(&gamma_state, temp, + 1.0, gamma); + } + temp_now = temp; + + if (trans_time >= trans_length) { + trans_time = 0; + trans_length = 0; + return FALSE; + } + + return TRUE; +} + +/* Timer callback to update the screen */ +static gboolean +screen_update_cb(gpointer data) +{ + GDBusConnection *conn = G_DBUS_CONNECTION(data); + + /* Update elevation from location */ + update_elevation(); + + gboolean prev_inhibit = inhibited; + guint prev_temp = temperature; + period_t prev_period = period; + + /* Calculate period */ + if (elevation < TRANSITION_LOW) { + period = PERIOD_NIGHT; + } else if (elevation < TRANSITION_HIGH) { + period = PERIOD_TRANSITION; + } else { + period = PERIOD_DAY; + } + + /* Check for inhibition */ + inhibited = g_hash_table_size(inhibitors) > 0; + + if (inhibited) { + temperature = TEMP_NEUTRAL; + } else { + /* Check for forced temperature */ + int forced_index = -1; + for (int i = FORCED_TEMP_LAYERS-1; i >= 0; i--) { + if (forced_temp_cookie[i] != 0) { + forced_index = i; + break; + } + } + + if (forced_index < 0) { + update_temperature(); + } else { + temperature = forced_temp[forced_index]; + } + } + + g_printf("Temperature: %u\n", temperature); + + /* Signal if temperature has changed */ + if (prev_temp != temperature || + prev_inhibit != inhibited || + prev_period != period) { + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + if (prev_temp != temperature) { + g_variant_builder_add(builder, "{sv}", + "Temperature", g_variant_new_uint32(temperature)); + } + if (prev_inhibit != inhibited) { + g_variant_builder_add(builder, "{sv}", + "Inhibited", g_variant_new_boolean(inhibited)); + } + if (prev_period != period) { + const char *name = period_names[period]; + g_variant_builder_add(builder, "{sv}", + "Period", g_variant_new_string(name)); + } + + g_dbus_connection_emit_signal(conn, NULL, + REDSHIFT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + REDSHIFT_INTERFACE_NAME, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); + } + + /* If the temperature difference is large enough, + make a nice transition. */ + if (abs(temperature - temp_now) > 25) { + if (trans_timer != 0) { + g_source_remove(trans_timer); + } + + g_printf("Create short transition: %u -> %u\n", temp_now, temperature); + trans_temp_start = temp_now; + trans_length = 40 - trans_time; + trans_time = 0; + + trans_timer = g_timeout_add(100, short_transition_update_cb, NULL); + } else if (temperature != temp_now) { + const gfloat gamma[] = {1.0, 1.0, 1.0}; + if (current_method != NULL) { + current_method->set_temperature(&gamma_state, temperature, + 1.0, gamma); + } + temp_now = temperature; + } + + return TRUE; +} + +static void +screen_update_restart(GDBusConnection *conn) +{ + if (screen_update_timer != 0) { + g_source_remove(screen_update_timer); + } + screen_update_cb(conn); + screen_update_timer = g_timeout_add_seconds(5, screen_update_cb, conn); +} + + +/* DBus service functions */ +static void +handle_method_call(GDBusConnection *conn, + const gchar *sender, + const gchar *obj_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer data) +{ + if (g_strcmp0(method_name, "AcquireCookie") == 0) { + gint32 cookie = next_cookie++; + const gchar *program; + g_variant_get(parameters, "(&s)", &program); + + g_printf("AcquireCookie for `%s'.\n", program); + + g_hash_table_insert(cookies, GINT_TO_POINTER(cookie), g_strdup(program)); + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(i)", cookie)); + } else if (g_strcmp0(method_name, "ReleaseCookie") == 0) { + gint32 cookie; + g_variant_get(parameters, "(i)", &cookie); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + g_printf("ReleaseCookie for `%s'.\n", program); + + /* Remove all rules enforced by program */ + gboolean found = FALSE; + found = g_hash_table_remove(inhibitors, GINT_TO_POINTER(cookie)); + + for (int i = 0; i < FORCED_TEMP_LAYERS; i++) { + if (forced_temp_cookie[i] == cookie) { + forced_temp_cookie[i] = 0; + found = TRUE; + } + } + + if (forced_location_cookie == cookie) { + forced_location_cookie = 0; + found = TRUE; + } + + if (found) screen_update_restart(conn); + + /* Remove from list of cookies */ + g_hash_table_remove(cookies, GINT_TO_POINTER(cookie)); + g_free(program); + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "Inhibit") == 0) { + gint32 cookie; + g_variant_get(parameters, "(i)", &cookie); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + g_hash_table_add(inhibitors, GINT_TO_POINTER(cookie)); + + if (!inhibited) { + screen_update_restart(conn); + } + + g_printf("Inhibit for `%s'.\n", program); + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "Uninhibit") == 0) { + gint32 cookie; + g_variant_get(parameters, "(i)", &cookie); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + g_hash_table_remove(inhibitors, GINT_TO_POINTER(cookie)); + + if (inhibited && g_hash_table_size(inhibitors) == 0) { + screen_update_restart(conn); + } + + g_printf("Uninhibit for `%s'.\n", program); + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "EnforceTemperature") == 0) { + gint32 cookie; + guint32 temp; + gboolean priority; + g_variant_get(parameters, "(iub)", &cookie, &temp, &priority); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + int index = priority ? 1 : 0; + + if (forced_temp_cookie[index] != 0 && + forced_temp_cookie[index] != cookie) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.AlreadyEnforced", + "Another client is already enforcing temperature"); + return; + } + + /* Check parameter bounds */ + if (temp < TEMP_MIN || temp > TEMP_MAX) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.InvalidArgument", + "Temperature is invalid"); + return; + } + + /* Set forced location */ + forced_temp_cookie[index] = cookie; + forced_temp[index] = temp; + + screen_update_restart(conn); + + g_printf("EnforceTemperature for `%s'.\n", program); + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "UnenforceTemperature") == 0) { + gint32 cookie; + gboolean priority; + g_variant_get(parameters, "(ib)", &cookie, &priority); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + g_printf("UnenforceTemperature for `%s'.\n", program); + + int index = priority ? 1 : 0; + + if (forced_temp_cookie[index] == cookie) { + forced_temp_cookie[index] = 0; + screen_update_restart(conn); + } + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "EnforceLocation") == 0) { + gint32 cookie; + gdouble lat; + gdouble lon; + g_variant_get(parameters, "(idd)", &cookie, &lat, &lon); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + if (forced_location_cookie != 0 && + forced_location_cookie != cookie) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.AlreadyEnforced", + "Another client is already enforcing location"); + return; + } + + /* Check parameter bounds */ + if (lat < LAT_MIN || lat > LAT_MAX || + lon < LON_MIN || lon > LON_MAX) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.InvalidArgument", + "Location is invalid"); + return; + } + + /* Set forced location */ + forced_location_cookie = cookie; + forced_lat = lat; + forced_lon = lon; + + /* Signal change in location */ + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "CurrentLatitude", g_variant_new_double(forced_lat)); + g_variant_builder_add(builder, "{sv}", "CurrentLongitude", g_variant_new_double(forced_lon)); + + g_dbus_connection_emit_signal(conn, NULL, + obj_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + interface_name, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); + + screen_update_restart(conn); + + g_printf("EnforceLocation for `%s'.\n", program); + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "UnenforceLocation") == 0) { + gint32 cookie; + g_variant_get(parameters, "(i)", &cookie); + + gchar *program = g_hash_table_lookup(cookies, GINT_TO_POINTER(cookie)); + if (program == NULL) { + g_dbus_method_invocation_return_dbus_error(invocation, + "dk.jonls.redshift.Redshift.UnknownCookie", + "Unknown cookie value"); + return; + } + + g_printf("UnenforceLocation for `%s'.\n", program); + + if (forced_location_cookie == cookie) { + forced_location_cookie = 0; + screen_update_restart(conn); + + /* Signal change in location */ + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "CurrentLatitude", g_variant_new_double(latitude)); + g_variant_builder_add(builder, "{sv}", "CurrentLongitude", g_variant_new_double(longitude)); + + g_dbus_connection_emit_signal(conn, NULL, + obj_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + interface_name, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); + } + + g_dbus_method_invocation_return_value(invocation, NULL); + } else if (g_strcmp0(method_name, "GetElevation") == 0) { + g_dbus_method_invocation_return_value(invocation, + g_variant_new("(d)", elevation)); + } +} + +static GVariant * +handle_get_property(GDBusConnection *conn, + const gchar *sender, + const gchar *obj_path, + const gchar *interface_name, + const gchar *prop_name, + GError **error, + gpointer data) +{ + GVariant *ret = NULL; + + if (g_strcmp0(prop_name, "Inhibited") == 0) { + ret = g_variant_new_boolean(inhibited); + } else if (g_strcmp0(prop_name, "Period") == 0) { + ret = g_variant_new_string(period_names[period]); + } else if (g_strcmp0(prop_name, "Temperature") == 0) { + ret = g_variant_new_uint32(temperature); + } else if (g_strcmp0(prop_name, "CurrentLatitude") == 0) { + double lat = latitude; + if (forced_location_cookie != 0) lat = forced_lat; + ret = g_variant_new_double(lat); + } else if (g_strcmp0(prop_name, "CurrentLongitude") == 0) { + double lon = longitude; + if (forced_location_cookie != 0) lon = forced_lon; + ret = g_variant_new_double(lon); + } else if (g_strcmp0(prop_name, "TemperatureDay") == 0) { + ret = g_variant_new_uint32(temp_day); + } else if (g_strcmp0(prop_name, "TemperatureNight") == 0) { + ret = g_variant_new_uint32(temp_night); + } + + return ret; +} + +static gboolean +handle_set_property(GDBusConnection *conn, + const gchar *sender, + const gchar *obj_path, + const gchar *interface_name, + const gchar *prop_name, + GVariant *value, + GError **error, + gpointer data) +{ + if (g_strcmp0(prop_name, "TemperatureDay") == 0) { + guint32 temp = g_variant_get_uint32(value); + + if (temp < TEMP_MIN || temp > TEMP_MAX) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Temperature out of bounds"); + } else { + temp_day = temp; + + screen_update_restart(conn); + + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "TemperatureDay", + g_variant_new_uint32(temp_day)); + + g_dbus_connection_emit_signal(conn, NULL, + obj_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + interface_name, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); + } + } else if (g_strcmp0(prop_name, "TemperatureNight") == 0) { + guint32 temp = g_variant_get_uint32(value); + + if (temp < TEMP_MIN || temp > TEMP_MAX) { + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Temperature out of bounds"); + } else { + temp_night = temp; + + screen_update_restart(conn); + + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "TemperatureNight", + g_variant_new_uint32(temp_night)); + + g_dbus_connection_emit_signal(conn, NULL, + obj_path, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + interface_name, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); + } + } + + return *error == NULL; +} + +static const GDBusInterfaceVTable interface_vtable = { + handle_method_call, + handle_get_property, + handle_set_property +}; + + +static void +on_bus_acquired(GDBusConnection *conn, + const gchar *name, + gpointer data) +{ + g_fprintf(stderr, "Bus acquired: `%s'.\n", name); + + guint registration_id = g_dbus_connection_register_object(conn, + REDSHIFT_OBJECT_PATH, + introspection_data->interfaces[0], + &interface_vtable, + NULL, NULL, + NULL); + g_assert(registration_id > 0); + + /* Start screen update timer */ + screen_update_restart(conn); +} + +static void +on_name_acquired(GDBusConnection *conn, + const gchar *name, + gpointer data) +{ + g_fprintf(stderr, "Name acquired: `%s'.\n", name); +} + +static void +on_name_lost(GDBusConnection *conn, + const gchar *name, + gpointer data) +{ + g_fprintf(stderr, "Name lost: `%s'.\n", name); + exit(EXIT_FAILURE); +} + + +int +main(int argc, char *argv[]) +{ +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif + + /* Create hash table for cookies */ + cookies = g_hash_table_new(NULL, NULL); + + /* Create hash table for inhibitors (set) */ + inhibitors = g_hash_table_new(NULL, NULL); + + /* Setup gamma method */ + for (int i = 0; gamma_methods[i].name != NULL; i++) { + const gamma_method_t *m = &gamma_methods[i]; + + int r = m->init(&gamma_state); + if (r < 0) continue; + + r = m->start(&gamma_state); + if (r < 0) continue; + + current_method = m; + g_printf("Using method `%s'.\n", current_method->name); + break; + } + + /* Build node info from XML */ + introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, + NULL); + g_assert(introspection_data != NULL); + + /* Obtain DBus bus name */ + GBusNameOwnerFlags flags = + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE; + guint owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, + REDSHIFT_BUS_NAME, + flags, + on_bus_acquired, + on_name_acquired, + on_name_lost, + NULL, + NULL); + + /* Start main loop */ + GMainLoop *mainloop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(mainloop); + + /* Clean up */ + g_bus_unown_name(owner_id); + + if (current_method != NULL) { + current_method->restore(&gamma_state); + current_method->free(&gamma_state); + } + + return 0; +} From 4eed993363e46ee7d193e58db57e28ea1dbac6a3 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Fri, 13 Dec 2013 01:58:40 -0500 Subject: [PATCH 02/10] Update RPM spec to build dbus service --- contrib/redshift.spec.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/redshift.spec.in b/contrib/redshift.spec.in index 312d9ac1..154ee50d 100644 --- a/contrib/redshift.spec.in +++ b/contrib/redshift.spec.in @@ -12,6 +12,7 @@ BuildRequires: libXxf86vm-devel BuildRequires: libxcb-devel BuildRequires: geoclue-devel BuildRequires: systemd +BuildRequires: glib2 >= 2.26.0 %description Redshift adjusts the color temperature of your screen according to your @@ -44,7 +45,7 @@ temperature adjustment program. %setup -q %build -%configure --enable-gui --enable-geoclue --enable-randr --enable-vidmode --with-systemduserunitdir=%{_userunitdir} +%configure --libexecdir=%{_libexecdir}/%{name} --enable-gui --enable-geoclue --enable-randr --enable-vidmode --enable-dbus --with-systemduserunitdir=%{_userunitdir} make %{?_smp_mflags} V=1 %install @@ -69,8 +70,10 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %defattr(-,root,root,-) %doc COPYING NEWS README README-colorramp %{_bindir}/redshift +%{_libexecdir}/%{name}/redshift-dbus %{_mandir}/man1/* %{_userunitdir}/* +%{_datadir}/dbus-1/services/dk.jonls.redshift.Redshift.service %files -n %{name}-gtk %defattr(-,root,root,-) From 242176aa10fc558f3536ae2e4d93241fc3c49077 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Wed, 7 May 2014 18:57:16 -0400 Subject: [PATCH 03/10] redshit-dbus: Get location from Geoclue Try to obtain location on program start and register a signal so that the position will be updated with new information from Geoclue. --- configure.ac | 2 +- src/Makefile.am | 13 ++- src/redshift-dbus.c | 191 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 166 insertions(+), 40 deletions(-) diff --git a/configure.ac b/configure.ac index 5f908a1f..d50933f3 100644 --- a/configure.ac +++ b/configure.ac @@ -160,7 +160,7 @@ AC_ARG_ENABLE([dbus], [AC_HELP_STRING([--enable-dbus], [enable DBus service])], [enable_dbus=$enableval],[enable_dbus=no]) AS_IF([test "x$enable_dbus" != xno], [ - AS_IF([test "x$have_glib" = xyes -a "x$have_gio" = xyes], [ + AS_IF([test "x$have_glib" = xyes -a "x$have_gio" = xyes -a "x$have_geoclue" = xyes], [ AC_DEFINE([ENABLE_DBUS], 1, [Define to 1 to enable DBus service]) AC_MSG_RESULT([yes]) diff --git a/src/Makefile.am b/src/Makefile.am index 0bef6666..e36d54d8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,8 +41,12 @@ EXTRA_DIST = redshift_CFLAGS = redshift_LDADD = @LIBINTL@ -redshift_dbus_CFLAGS = $(GLIB_CFLAGS) $(GDBUS_CFLAGS) -redshift_dbus_LDADD = $(GLIB_LIBS) $(GDBUS_LIBS) +redshift_dbus_CFLAGS = \ + $(GLIB_CFLAGS) $(GDBUS_CFLAGS) \ + $(GEOCLUE_CFLAGS) +redshift_dbus_LDADD = \ + $(GLIB_LIBS) $(GDBUS_LIBS) \ + $(GEOCLUE_LIBS) if ENABLE_DRM @@ -91,9 +95,4 @@ redshift_SOURCES += location-geoclue.c location-geoclue.h redshift_CFLAGS += $(GEOCLUE_CFLAGS) redshift_LDADD += \ $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) - -redshift_dbus_SOURCES += location-geoclue.c location-geoclue.h -redshift_dbus_CFLAGS += $(GEOCLUE_CFLAGS) -redshift_dbus_LDADD += \ - $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS) endif diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index 696afb0a..a72fd41b 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -28,6 +28,9 @@ #include #include +#include +#include + #include "redshift.h" #include "solar.h" @@ -415,6 +418,31 @@ screen_update_restart(GDBusConnection *conn) } +/* Emit signal that current position changed */ +static void +emit_position_changed(GDBusConnection *conn, gdouble lat, gdouble lon) +{ + /* Signal change in location */ + GError *local_error = NULL; + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "CurrentLatitude", + g_variant_new_double(lat)); + g_variant_builder_add(builder, "{sv}", "CurrentLongitude", + g_variant_new_double(lon)); + + g_dbus_connection_emit_signal(conn, NULL, + REDSHIFT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new("(sa{sv}as)", + REDSHIFT_INTERFACE_NAME, + builder, + NULL), + &local_error); + g_assert_no_error(local_error); +} + + /* DBus service functions */ static void handle_method_call(GDBusConnection *conn, @@ -616,21 +644,7 @@ handle_method_call(GDBusConnection *conn, forced_lon = lon; /* Signal change in location */ - GError *local_error = NULL; - GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - g_variant_builder_add(builder, "{sv}", "CurrentLatitude", g_variant_new_double(forced_lat)); - g_variant_builder_add(builder, "{sv}", "CurrentLongitude", g_variant_new_double(forced_lon)); - - g_dbus_connection_emit_signal(conn, NULL, - obj_path, - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new("(sa{sv}as)", - interface_name, - builder, - NULL), - &local_error); - g_assert_no_error(local_error); + emit_position_changed(conn, forced_lat, forced_lon); screen_update_restart(conn); @@ -656,21 +670,7 @@ handle_method_call(GDBusConnection *conn, screen_update_restart(conn); /* Signal change in location */ - GError *local_error = NULL; - GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - g_variant_builder_add(builder, "{sv}", "CurrentLatitude", g_variant_new_double(latitude)); - g_variant_builder_add(builder, "{sv}", "CurrentLongitude", g_variant_new_double(longitude)); - - g_dbus_connection_emit_signal(conn, NULL, - obj_path, - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new("(sa{sv}as)", - interface_name, - builder, - NULL), - &local_error); - g_assert_no_error(local_error); + emit_position_changed(conn, latitude, longitude); } g_dbus_method_invocation_return_value(invocation, NULL); @@ -698,11 +698,11 @@ handle_get_property(GDBusConnection *conn, } else if (g_strcmp0(prop_name, "Temperature") == 0) { ret = g_variant_new_uint32(temperature); } else if (g_strcmp0(prop_name, "CurrentLatitude") == 0) { - double lat = latitude; + gdouble lat = latitude; if (forced_location_cookie != 0) lat = forced_lat; ret = g_variant_new_double(lat); } else if (g_strcmp0(prop_name, "CurrentLongitude") == 0) { - double lon = longitude; + gdouble lon = longitude; if (forced_location_cookie != 0) lon = forced_lon; ret = g_variant_new_double(lon); } else if (g_strcmp0(prop_name, "TemperatureDay") == 0) { @@ -787,6 +787,129 @@ handle_set_property(GDBusConnection *conn, return *error == NULL; } + +/* Handle position change callbacks */ +static void +geoclue_position_changed_cb(GeocluePosition *position, + GeocluePositionFields fields, + int timestamp, + gdouble lat, + gdouble lon, + gdouble altitude, + GeoclueAccuracy *accuracy, + gpointer data) +{ + GDBusConnection *conn = G_DBUS_CONNECTION(data); + + if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && + fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) { + g_print("Position changed: %f, %f\n", lat, lon); + + latitude = lat; + longitude = lon; + + if (forced_location_cookie == 0) { + emit_position_changed(conn, latitude, longitude); + } + } else { + g_print("Latitude and longitude not available.\n"); + } +} + +/* Initialize Geoclue position provider */ +static int +init_geoclue_position(GDBusConnection *conn) +{ + /* Obtain Geoclue Client */ + GError *error = NULL; + GeoclueMaster *master = geoclue_master_get_default(); + GeoclueMasterClient *client = + geoclue_master_create_client(master, NULL, &error); + g_object_unref(master); + + if (client == NULL) { + if (error != NULL) { + g_printerr("Unable to obtain master client: %s\n", + error->message); + g_error_free(error); + } else { + g_printerr("Unable to obtain master client\n"); + } + return -1; + } + + /* Set requirements for client */ + gboolean ret = + geoclue_master_client_set_requirements(client, + GEOCLUE_ACCURACY_LEVEL_REGION, + 0, FALSE, + GEOCLUE_RESOURCE_NETWORK, + &error); + if (!ret) { + if (error != NULL) { + g_printerr("Can't set requirements for master: %s\n", + error->message); + g_error_free(error); + } else { + g_printerr("Can't set requirements for master\n"); + } + g_object_unref(client); + + return -1; + } + + /* Obtain the actual position provider */ + GeocluePosition *position = + geoclue_master_client_create_position(client, NULL); + g_object_unref(client); + + gchar *provider_name = NULL; + + ret = geoclue_provider_get_provider_info(GEOCLUE_PROVIDER(position), + &provider_name, NULL, NULL); + if (ret) { + g_print("Started Geoclue provider: %s.\n", provider_name); + g_free(provider_name); + } else { + g_printerr("Could not find a usable Geoclue provider.\n"); + return -1; + } + + /* Obtain position */ + GeocluePositionFields fields; + gdouble lat, lon; + + fields = geoclue_position_get_position(position, NULL, + &lat, &lon, NULL, + NULL, &error); + if (error) { + g_printerr("Could not get location: %s.\n", error->message); + g_error_free(error); + return -1; + } + + if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && + fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) { + g_print("According to the geoclue provider" + " we're at: %.2f, %.2f\n", + lat, lon); + latitude = lat; + longitude = lon; + + if (forced_location_cookie == 0) { + emit_position_changed(conn, latitude, longitude); + } + } else { + g_warning("Provider does not have a valid location available."); + } + + g_signal_connect(G_OBJECT(position), "position-changed", + G_CALLBACK(geoclue_position_changed_cb), conn); + + return 0; +} + + static const GDBusInterfaceVTable interface_vtable = { handle_method_call, handle_get_property, @@ -809,6 +932,10 @@ on_bus_acquired(GDBusConnection *conn, NULL); g_assert(registration_id > 0); + /* Initialize Geoclue position provider */ + int r = init_geoclue_position(conn); + if (r < 0) exit(EXIT_FAILURE); + /* Start screen update timer */ screen_update_restart(conn); } From c37add18a2304ff501d4c791131577fc915aaea7 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Fri, 13 Dec 2013 02:00:46 -0500 Subject: [PATCH 04/10] redshift-gtk: Adapt to use DBus service --- src/redshift-gtk/statusicon.py | 267 +++++++++++++++++---------------- 1 file changed, 139 insertions(+), 128 deletions(-) diff --git a/src/redshift-gtk/statusicon.py b/src/redshift-gtk/statusicon.py index 2efbc074..7ca89b7a 100644 --- a/src/redshift-gtk/statusicon.py +++ b/src/redshift-gtk/statusicon.py @@ -24,11 +24,13 @@ ''' import sys, os -import signal, fcntl +import signal import re import gettext -from gi.repository import Gdk, Gtk, GLib +from gi.repository import Gdk, Gtk, GLib, GObject +from gi.repository import Gio + try: from gi.repository import AppIndicator3 as appindicator except ImportError: @@ -40,27 +42,93 @@ _ = gettext.gettext -def sigterm_handler(signal, frame): - sys.exit() +class RedshiftController(GObject.GObject): + __gsignals__ = { + 'inhibit-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), + 'temperature-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), + 'period-changed': (GObject.SIGNAL_RUN_FIRST, None, (str,)), + 'location-changed': (GObject.SIGNAL_RUN_FIRST, None, (float, float)) + } + + def __init__(self, name): + GObject.GObject.__init__(self) + + # Connect to Redshift DBus service + bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) + self._redshift = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, + 'dk.jonls.redshift.Redshift', + '/dk/jonls/redshift/Redshift', + 'dk.jonls.redshift.Redshift', None) + self._redshift_props = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, + 'dk.jonls.redshift.Redshift', + '/dk/jonls/redshift/Redshift', + 'org.freedesktop.DBus.Properties', None) + + self._name = name + self._cookie = None + + # Setup signals to property changes + self._redshift.connect('g-properties-changed', self._property_change_cb) + + def _property_change_cb(self, proxy, props, invalid, data=None): + # Cast to python dict as 'in' keyword is broken for GLib dict + props = dict(props) + if 'Inhibited' in props: + self.emit('inhibit-changed', props['Inhibited']) + if 'Temperature' in props: + self.emit('temperature-changed', props['Temperature']) + if 'Period' in props: + self.emit('period-changed', props['Period']) + if 'CurrentLatitude' in props and 'CurrentLongitude' in props: + self.emit('location-changed', props['CurrentLatitude'], props['CurrentLongitude']) + + @property + def cookie(self): + if self._cookie is None: + self._cookie = self._redshift.AcquireCookie('(s)', self._name) + return self._cookie + + def release_cookie(self): + if self._cookie is not None: + self._redshift.ReleaseCookie('(i)', self._cookie) + self._cookie = None + + def __del__(self): + self.release_cookie() + + @property + def elevation(self): + return self._redshift.GetElevation() + + @property + def inhibited(self): + return self._redshift_props.Get('(ss)', 'dk.jonls.redshift.Redshift', 'Inhibited') + + @property + def temperature(self): + return self._redshift_props.Get('(ss)', 'dk.jonls.redshift.Redshift', 'Temperature') + + @property + def period(self): + return self._redshift_props.Get('(ss)', 'dk.jonls.redshift.Redshift', 'Period') + + @property + def location(self): + lat = self._redshift_props.Get('(ss)', 'dk.jonls.redshift.Redshift', 'CurrentLatitude') + lon = self._redshift_props.Get('(ss)', 'dk.jonls.redshift.Redshift', 'CurrentLongitude') + return (lat, lon) + + def set_inhibit(self, inhibit): + if inhibit: + self._redshift.Inhibit('(i)', self.cookie) + else: + self._redshift.Uninhibit('(i)', self.cookie) class RedshiftStatusIcon(object): - def __init__(self, args=[]): - # Initialize state variables - self._status = False - self._temperature = 0 - self._period = 'Unknown' - self._location = (0.0, 0.0) - - # Install TERM signal handler - signal.signal(signal.SIGTERM, sigterm_handler) - - # Start redshift with arguments - args.insert(0, os.path.join(defs.BINDIR, 'redshift')) - if '-v' not in args: - args.append('-v') - - self.start_child_process(args) + def __init__(self): + # Initialize controller + self._controller = RedshiftController('redshift-gtk') if appindicator: # Create indicator @@ -149,6 +217,18 @@ def __init__(self, args=[]): self.info_dialog.connect('response', self.response_info_cb) + # Setup signals to property changes + self._controller.connect('inhibit-changed', self.inhibit_change_cb) + self._controller.connect('period-changed', self.period_change_cb) + self._controller.connect('temperature-changed', self.temperature_change_cb) + self._controller.connect('location-changed', self.location_change_cb) + + # Set info box text + self.change_inhibited(self._controller.inhibited) + self.change_period(self._controller.period) + self.change_temperature(self._controller.temperature) + self.change_location(self._controller.location) + if appindicator: self.status_menu.show_all() @@ -163,54 +243,24 @@ def __init__(self, args=[]): # Initialize suspend timer self.suspend_timer = None - # Handle child input - class InputBuffer(object): - lines = [] - buf = '' - self.input_buffer = InputBuffer() - self.error_buffer = InputBuffer() - - # Set non blocking - fcntl.fcntl(self.process[2], fcntl.F_SETFL, - fcntl.fcntl(self.process[2], fcntl.F_GETFL) | os.O_NONBLOCK) - - # Add watch on child process - GLib.child_watch_add(GLib.PRIORITY_DEFAULT, self.process[0], self.child_cb) - GLib.io_add_watch(self.process[2], GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self.child_data_cb, (True, self.input_buffer)) - GLib.io_add_watch(self.process[3], GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self.child_data_cb, (False, self.error_buffer)) - # Notify desktop that startup is complete Gdk.notify_startup_complete() - def start_child_process(self, args): - # Start child process with C locale so we can parse the output - env = os.environ.copy() - env['LANG'] = env['LANGUAGE'] = env['LC_ALL'] = env['LC_MESSAGES'] = 'C' - self.process = GLib.spawn_async(args, envp=['{}={}'.format(k,v) for k, v in env.items()], - flags=GLib.SPAWN_DO_NOT_REAP_CHILD, - standard_output=True, standard_error=True) - def remove_suspend_timer(self): if self.suspend_timer is not None: GLib.source_remove(self.suspend_timer) self.suspend_timer = None def suspend_cb(self, item, minutes): - if self.is_enabled(): - self.child_toggle_status() - - # If "suspend" is clicked while redshift is disabled, we reenable - # it after the last selected timespan is over. + # Inhibit + self._controller.set_inhibit(True) self.remove_suspend_timer() # If redshift was already disabled we reenable it nonetheless. self.suspend_timer = GLib.timeout_add_seconds(minutes * 60, self.reenable_cb) def reenable_cb(self): - if not self.is_enabled(): - self.child_toggle_status() + self._controller.set_inhibit(False) def popup_menu_cb(self, widget, button, time, data=None): self.status_menu.show_all() @@ -219,13 +269,14 @@ def popup_menu_cb(self, widget, button, time, data=None): def toggle_cb(self, widget, data=None): self.remove_suspend_timer() - self.child_toggle_status() + self._controller.set_inhibit(not self._controller.inhibited) def toggle_item_cb(self, widget, data=None): # Only toggle if a change from current state was requested - if self.is_enabled() != widget.get_active(): + active = not self._controller.inhibited + if active != widget.get_active(): self.remove_suspend_timer() - self.child_toggle_status() + self._controller.set_inhibit(not self._controller.inhibited) # Info dialog callbacks def show_info_cb(self, widget, data=None): @@ -237,41 +288,46 @@ def response_info_cb(self, widget, data=None): def update_status_icon(self): # Update status icon if appindicator: - if self.is_enabled(): + if not self._controller.inhibited: self.indicator.set_icon('redshift-status-on') else: self.indicator.set_icon('redshift-status-off') else: - if self.is_enabled(): + if not self._controller.inhibited: self.status_icon.set_from_icon_name('redshift-status-on') else: self.status_icon.set_from_icon_name('redshift-status-off') - # Status update functions. Called when child process indicates a - # change in state. - def change_status(self, status): - self._status = status + # State update functions + def inhibit_change_cb(self, controller, inhibit): + self.change_inhibited(inhibit) + + def period_change_cb(self, controller, period): + self.change_period(period) + + def temperature_change_cb(self, controller, temperature): + self.change_temperature(temperature) + + def location_change_cb(self, controller, lat, lon): + self.change_location((lat, lon)) + + + # Update interface + def change_inhibited(self, inhibited): self.update_status_icon() - self.toggle_item.set_active(self.is_enabled()) - self.status_label.set_markup('{}: {}'.format(_('Status'), - _('Enabled') if status else _('Disabled'))) + self.toggle_item.set_active(not inhibited) + self.status_label.set_markup(_('Status: {}').format(_('Disabled') if inhibited else _('Enabled'))) def change_temperature(self, temperature): - self._temperature = temperature - self.temperature_label.set_markup('{}: {}K'.format(_('Color temperature'), temperature)) + self.temperature_label.set_markup(_('Color temperature: {}K').format(temperature)) def change_period(self, period): - self._period = period - self.period_label.set_markup('{}: {}'.format(_('Period'), period)) + self.period_label.set_markup(_('Period: {}').format(period)) def change_location(self, location): - self._location = location - self.location_label.set_markup('{}: {}, {}'.format(_('Location'), *location)) - + self.location_label.set_markup(_('Location: {}, {}').format(*location)) - def is_enabled(self): - return self._status def autostart_cb(self, widget, data=None): utils.set_autostart(widget.get_active()) @@ -282,67 +338,22 @@ def destroy_cb(self, widget, data=None): Gtk.main_quit() return False - def child_toggle_status(self): - os.kill(self.process[0], signal.SIGUSR1) - - def child_cb(self, pid, cond, data=None): - sys.exit(-1) - - def child_key_change_cb(self, key, value): - if key == 'Status': - self.change_status(value != 'Disabled') - elif key == 'Color temperature': - self.change_temperature(int(value.rstrip('K'), 10)) - elif key == 'Period': - self.change_period(value) - elif key == 'Location': - self.change_location(tuple(float(x) for x in value.split(', '))) - - def child_stdout_line_cb(self, line): - if line: - m = re.match(r'([\w ]+): (.+)', line) - if m: - key = m.group(1) - value = m.group(2) - self.child_key_change_cb(key, value) - - def child_data_cb(self, f, cond, data): - stdout, ib = data - ib.buf += os.read(f, 256).decode('utf-8') - - # Split input at line break - while True: - first, sep, last = ib.buf.partition('\n') - if sep == '': - break - ib.buf = last - ib.lines.append(first) - if stdout: - self.child_stdout_line_cb(first) - - return True - - def termwait(self): - os.kill(self.process[0], signal.SIGINT) - os.waitpid(self.process[0], 0) +def sigterm_handler(signal, frame): + sys.exit(0) def run(): utils.setproctitle('redshift-gtk') + # Install TERM signal handler + signal.signal(signal.SIGTERM, sigterm_handler) + # Internationalisation gettext.bindtextdomain('redshift', defs.LOCALEDIR) gettext.textdomain('redshift') # Create status icon - s = RedshiftStatusIcon(sys.argv[1:]) - - try: - # Run main loop - Gtk.main() - except KeyboardInterrupt: - # Ignore user interruption - pass - finally: - # Always terminate redshift - s.termwait() + s = RedshiftStatusIcon() + + # Run main loop + Gtk.main() From da392d78a2b5fc4052c553d71eb6fe2e4fa804b0 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Thu, 8 May 2014 15:36:36 -0400 Subject: [PATCH 05/10] redshift-dbus: Use proper Glib message output functions --- src/redshift-dbus.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index a72fd41b..ffabcbe4 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -259,8 +258,8 @@ update_elevation() elevation = solar_elevation(time, lat, lon); - g_printf("Location: %f, %f\n", lat, lon); - g_printf("Elevation: %f\n", elevation); + g_print("Location: %f, %f\n", lat, lon); + g_print("Elevation: %f\n", elevation); } /* Update temperature from elevation */ @@ -348,7 +347,7 @@ screen_update_cb(gpointer data) } } - g_printf("Temperature: %u\n", temperature); + g_print("Temperature: %u\n", temperature); /* Signal if temperature has changed */ if (prev_temp != temperature || @@ -389,7 +388,7 @@ screen_update_cb(gpointer data) g_source_remove(trans_timer); } - g_printf("Create short transition: %u -> %u\n", temp_now, temperature); + g_print("Create short transition: %u -> %u\n", temp_now, temperature); trans_temp_start = temp_now; trans_length = 40 - trans_time; trans_time = 0; @@ -459,7 +458,7 @@ handle_method_call(GDBusConnection *conn, const gchar *program; g_variant_get(parameters, "(&s)", &program); - g_printf("AcquireCookie for `%s'.\n", program); + g_print("AcquireCookie for `%s'.\n", program); g_hash_table_insert(cookies, GINT_TO_POINTER(cookie), g_strdup(program)); g_dbus_method_invocation_return_value(invocation, @@ -476,7 +475,7 @@ handle_method_call(GDBusConnection *conn, return; } - g_printf("ReleaseCookie for `%s'.\n", program); + g_print("ReleaseCookie for `%s'.\n", program); /* Remove all rules enforced by program */ gboolean found = FALSE; @@ -519,7 +518,7 @@ handle_method_call(GDBusConnection *conn, screen_update_restart(conn); } - g_printf("Inhibit for `%s'.\n", program); + g_print("Inhibit for `%s'.\n", program); g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "Uninhibit") == 0) { @@ -540,7 +539,7 @@ handle_method_call(GDBusConnection *conn, screen_update_restart(conn); } - g_printf("Uninhibit for `%s'.\n", program); + g_print("Uninhibit for `%s'.\n", program); g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "EnforceTemperature") == 0) { @@ -581,7 +580,7 @@ handle_method_call(GDBusConnection *conn, screen_update_restart(conn); - g_printf("EnforceTemperature for `%s'.\n", program); + g_print("EnforceTemperature for `%s'.\n", program); g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "UnenforceTemperature") == 0) { @@ -597,7 +596,7 @@ handle_method_call(GDBusConnection *conn, return; } - g_printf("UnenforceTemperature for `%s'.\n", program); + g_print("UnenforceTemperature for `%s'.\n", program); int index = priority ? 1 : 0; @@ -648,7 +647,7 @@ handle_method_call(GDBusConnection *conn, screen_update_restart(conn); - g_printf("EnforceLocation for `%s'.\n", program); + g_print("EnforceLocation for `%s'.\n", program); g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "UnenforceLocation") == 0) { @@ -663,7 +662,7 @@ handle_method_call(GDBusConnection *conn, return; } - g_printf("UnenforceLocation for `%s'.\n", program); + g_print("UnenforceLocation for `%s'.\n", program); if (forced_location_cookie == cookie) { forced_location_cookie = 0; @@ -922,7 +921,7 @@ on_bus_acquired(GDBusConnection *conn, const gchar *name, gpointer data) { - g_fprintf(stderr, "Bus acquired: `%s'.\n", name); + g_printerr("Bus acquired: `%s'.\n", name); guint registration_id = g_dbus_connection_register_object(conn, REDSHIFT_OBJECT_PATH, @@ -945,7 +944,7 @@ on_name_acquired(GDBusConnection *conn, const gchar *name, gpointer data) { - g_fprintf(stderr, "Name acquired: `%s'.\n", name); + g_printerr("Name acquired: `%s'.\n", name); } static void @@ -953,7 +952,7 @@ on_name_lost(GDBusConnection *conn, const gchar *name, gpointer data) { - g_fprintf(stderr, "Name lost: `%s'.\n", name); + g_printerr("Name lost: `%s'.\n", name); exit(EXIT_FAILURE); } @@ -982,7 +981,7 @@ main(int argc, char *argv[]) if (r < 0) continue; current_method = m; - g_printf("Using method `%s'.\n", current_method->name); + g_print("Using method `%s'.\n", current_method->name); break; } From 708aab399b458145115ecef998d490f34c7e1b9a Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Thu, 8 May 2014 15:50:40 -0400 Subject: [PATCH 06/10] Fix: redshift-dbus failure when starting at boot --- src/redshift-dbus.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index ffabcbe4..2baceeb3 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -874,7 +874,11 @@ init_geoclue_position(GDBusConnection *conn) return -1; } - /* Obtain position */ + /* Try to obtain the position. At this point we have successfully + acquired a position provider so even if we cannot get a + position, we can wait and see if a position becomes avaiable later. + This is often the case if redshift-dbus is started right when the + user logs in. */ GeocluePositionFields fields; gdouble lat, lon; @@ -884,7 +888,6 @@ init_geoclue_position(GDBusConnection *conn) if (error) { g_printerr("Could not get location: %s.\n", error->message); g_error_free(error); - return -1; } if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && From 4b6e64742174ca8a8540936a1e9837dcb446f7e1 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Thu, 8 May 2014 18:25:12 -0400 Subject: [PATCH 07/10] redshift-dbus: Save and restore known position for next startup When a position is obtained from Geoclue it is saved to `$XDG_DATA_HOME/redshift/position`. This file is loaded on program start to find the initial position. If the file does not exist the default position of 0,0 is used until the correct position can be found. --- src/redshift-dbus.c | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index 2baceeb3..34c0b7f9 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -787,6 +788,75 @@ handle_set_property(GDBusConnection *conn, } +/* Save position state */ +static void +save_position_state() +{ + /* Probably should use STATE directory when that + is standardized in the XDG spec. */ + char *path = g_build_filename(g_get_user_data_dir(), + "redshift", "position", NULL); + FILE *state = g_fopen(path, "w"); + if (state == NULL) { + /* Try to create the directory */ + char *path_dir = g_path_get_dirname(path); + gint r = g_mkdir_with_parents(path_dir, S_IRWXU); + if (r == 0) { + state = g_fopen(path, "w"); + } + g_free(path_dir); + } + + g_free(path); + + if (state != NULL) { + g_fprintf(state, "%f\n%f\n", latitude, longitude); + fclose(state); + } +} + +/* Restore saved position state */ +static void +restore_position_state() +{ + /* Probably should use STATE directory when that + is standardized in the XDG spec. */ + char *path = g_build_filename(g_get_user_data_dir(), + "redshift", "position", NULL); + FILE *state = g_fopen(path, "r"); + + g_free(path); + + if (state != NULL) { + gdouble lat, lon; + char buffer[64]; + + /* Read latitude */ + char *r = fgets(buffer, sizeof(buffer), state); + if (r == NULL) { + fclose(state); + return; + } + + lat = g_ascii_strtod(buffer, NULL); + + /* Read longitude */ + r = fgets(buffer, sizeof(buffer), state); + if (r == NULL) { + fclose(state); + return; + } + + lon = g_ascii_strtod(buffer, NULL); + + g_print("Restored position %.2f, %.2f\n", lat, lon); + + latitude = lat; + longitude = lon; + } +} + + /* Handle position change callbacks */ static void geoclue_position_changed_cb(GeocluePosition *position, @@ -806,6 +876,7 @@ geoclue_position_changed_cb(GeocluePosition *position, latitude = lat; longitude = lon; + save_position_state(); if (forced_location_cookie == 0) { emit_position_changed(conn, latitude, longitude); @@ -897,6 +968,7 @@ init_geoclue_position(GDBusConnection *conn) lat, lon); latitude = lat; longitude = lon; + save_position_state(); if (forced_location_cookie == 0) { emit_position_changed(conn, latitude, longitude); @@ -967,6 +1039,9 @@ main(int argc, char *argv[]) g_type_init(); #endif + /* Restore position state from last run */ + restore_position_state(); + /* Create hash table for cookies */ cookies = g_hash_table_new(NULL, NULL); From 5a315565d67737a5dfd4f8b1a7144cb6e4eea718 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Fri, 9 May 2014 12:45:51 -0400 Subject: [PATCH 08/10] Fix: Remove include of location-geoclue.h in redshift-dbus --- src/redshift-dbus.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index 34c0b7f9..bf4f7bab 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -45,10 +45,6 @@ #include "gamma-dummy.h" -#ifdef ENABLE_GEOCLUE -# include "location-geoclue.h" -#endif - /* Union of state data for gamma adjustment methods */ union { From db28c5d016860408b260f8bc638423a5b3e0f663 Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Sat, 10 May 2014 19:53:07 -0400 Subject: [PATCH 09/10] redshift-dbus: Exit on SIGTERM, SIGINT --- src/redshift-dbus.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index bf4f7bab..83c21063 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -1028,6 +1029,16 @@ on_name_lost(GDBusConnection *conn, } +/* Handle termination signal */ +static gboolean +term_signal_cb(gpointer data) +{ + GMainLoop *loop = (GMainLoop *)data; + g_main_loop_quit(loop); + return TRUE; +} + + int main(int argc, char *argv[]) { @@ -1077,13 +1088,22 @@ main(int argc, char *argv[]) NULL, NULL); - /* Start main loop */ + /* Create main loop */ GMainLoop *mainloop = g_main_loop_new(NULL, FALSE); + + /* Attach signal handler for termination */ + g_unix_signal_add(SIGTERM, term_signal_cb, mainloop); + g_unix_signal_add(SIGINT, term_signal_cb, mainloop); + + /* Start main loop */ g_main_loop_run(mainloop); /* Clean up */ g_bus_unown_name(owner_id); + g_print("Restoring gamma ramps.\n"); + + /* Restore gamma ramps */ if (current_method != NULL) { current_method->restore(&gamma_state); current_method->free(&gamma_state); From f5a5ec96d1fbd498baf41438999f38b346067aca Mon Sep 17 00:00:00 2001 From: Jon Lund Steffensen Date: Sat, 10 May 2014 19:53:42 -0400 Subject: [PATCH 10/10] redshift-dbus: Port to Geoclue2 --- src/redshift-dbus.c | 237 ++++++++++++++++++++++++++------------------ 1 file changed, 140 insertions(+), 97 deletions(-) diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c index 83c21063..cab9ed5a 100644 --- a/src/redshift-dbus.c +++ b/src/redshift-dbus.c @@ -29,9 +29,6 @@ #include #include -#include -#include - #include "redshift.h" #include "solar.h" @@ -191,6 +188,10 @@ static gdouble forced_lon = 0.0; /* Screen update timer */ static guint screen_update_timer = 0; +/* Geoclue proxy objects */ +static GDBusProxy *geoclue_manager = NULL; +static GDBusProxy *geoclue_client = NULL; + /* DBus service definition */ static const gchar introspection_xml[] = @@ -856,126 +857,166 @@ restore_position_state() /* Handle position change callbacks */ static void -geoclue_position_changed_cb(GeocluePosition *position, - GeocluePositionFields fields, - int timestamp, - gdouble lat, - gdouble lon, - gdouble altitude, - GeoclueAccuracy *accuracy, - gpointer data) +geoclue_client_signal_cb(GDBusProxy *client, + gchar *sender_name, + gchar *signal_name, + GVariant *parameters, + gpointer *data) { GDBusConnection *conn = G_DBUS_CONNECTION(data); - if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && - fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) { - g_print("Position changed: %f, %f\n", lat, lon); + /* Only handle LocationUpdated signals */ + if (g_strcmp0(signal_name, "LocationUpdated") != 0) { + return; + } - latitude = lat; - longitude = lon; - save_position_state(); + /* Obtain location path */ + const gchar *location_path; + g_variant_get_child(parameters, 1, "&o", &location_path); - if (forced_location_cookie == 0) { - emit_position_changed(conn, latitude, longitude); - } - } else { - g_print("Latitude and longitude not available.\n"); + /* 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); + if (location == NULL) { + g_printerr("Unable to obtain location: %s.\n", + error->message); + g_error_free(error); + return; + } + + /* Read location properties */ + GVariant *lat_v = g_dbus_proxy_get_cached_property(location, + "Latitude"); + gdouble lat = g_variant_get_double(lat_v); + + GVariant *lon_v = g_dbus_proxy_get_cached_property(location, + "Longitude"); + gdouble lon = g_variant_get_double(lon_v); + + /* Save position */ + latitude = lat; + longitude = lon; + save_position_state(); + + g_print("Position from Geoclue: %.2f, %.2f\n", lat, lon); + + if (forced_location_cookie == 0) { + emit_position_changed(conn, latitude, longitude); } } -/* Initialize Geoclue position provider */ +/* Initialize Geoclue position client */ static int init_geoclue_position(GDBusConnection *conn) { - /* Obtain Geoclue Client */ + /* Obtain Geoclue Manager */ GError *error = NULL; - GeoclueMaster *master = geoclue_master_get_default(); - GeoclueMasterClient *client = - geoclue_master_create_client(master, NULL, &error); - g_object_unref(master); - - if (client == NULL) { - if (error != NULL) { - g_printerr("Unable to obtain master client: %s\n", - error->message); - g_error_free(error); - } else { - g_printerr("Unable to obtain master client\n"); - } + 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); return -1; } - /* Set requirements for client */ - gboolean ret = - geoclue_master_client_set_requirements(client, - GEOCLUE_ACCURACY_LEVEL_REGION, - 0, FALSE, - GEOCLUE_RESOURCE_NETWORK, - &error); - if (!ret) { - if (error != NULL) { - g_printerr("Can't set requirements for master: %s\n", - error->message); - g_error_free(error); - } else { - g_printerr("Can't set requirements for master\n"); - } - g_object_unref(client); - + /* Obtain Geoclue Client path */ + error = NULL; + GVariant *client_path_v = + g_dbus_proxy_call_sync(geoclue_manager, + "GetClient", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (client_path_v == NULL) { + g_printerr("Unable to obtain Geoclue client path: %s.\n", + error->message); + g_error_free(error); + g_object_unref(geoclue_manager); return -1; } - /* Obtain the actual position provider */ - GeocluePosition *position = - geoclue_master_client_create_position(client, NULL); - g_object_unref(client); - - gchar *provider_name = NULL; - - ret = geoclue_provider_get_provider_info(GEOCLUE_PROVIDER(position), - &provider_name, NULL, NULL); - if (ret) { - g_print("Started Geoclue provider: %s.\n", provider_name); - g_free(provider_name); - } else { - g_printerr("Could not find a usable Geoclue provider.\n"); + const gchar *client_path; + g_variant_get(client_path_v, "(&o)", &client_path); + + /* Obtain GeoClue client */ + error = NULL; + 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); return -1; } - /* Try to obtain the position. At this point we have successfully - acquired a position provider so even if we cannot get a - position, we can wait and see if a position becomes avaiable later. - This is often the case if redshift-dbus is started right when the - user logs in. */ - GeocluePositionFields fields; - gdouble lat, lon; - - fields = geoclue_position_get_position(position, NULL, - &lat, &lon, NULL, - NULL, &error); - if (error) { - g_printerr("Could not get location: %s.\n", error->message); + g_variant_unref(client_path_v); + + /* Set distance threshold */ + 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", + "DistanceThreshold", + g_variant_new("u", 10000)), + 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); + return -1; } - if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE && - fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) { - g_print("According to the geoclue provider" - " we're at: %.2f, %.2f\n", - lat, lon); - latitude = lat; - longitude = lon; - save_position_state(); - - if (forced_location_cookie == 0) { - emit_position_changed(conn, latitude, longitude); - } - } else { - g_warning("Provider does not have a valid location available."); + g_variant_unref(ret_v); + + /* Attach signal callback to client */ + g_signal_connect(geoclue_client, "g-signal", + G_CALLBACK(geoclue_client_signal_cb), + conn); + + /* Start Geoclue client */ + error = NULL; + ret_v = g_dbus_proxy_call_sync(geoclue_client, + "Start", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, &error); + if (ret_v == NULL) { + g_printerr("Unable to start Geoclue client: %s.\n", + error->message); + g_error_free(error); + g_object_unref(geoclue_client); + g_object_unref(geoclue_manager); + return -1; } - g_signal_connect(G_OBJECT(position), "position-changed", - G_CALLBACK(geoclue_position_changed_cb), conn); + g_variant_unref(ret_v); return 0; } @@ -1100,6 +1141,8 @@ main(int argc, char *argv[]) /* Clean up */ g_bus_unown_name(owner_id); + g_object_unref(geoclue_client); + g_object_unref(geoclue_manager); g_print("Restoring gamma ramps.\n");