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..d50933f3 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 -a "x$have_geoclue" = 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/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,-)
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..e36d54d8 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,53 @@ 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) \
+ $(GEOCLUE_CFLAGS)
+redshift_dbus_LDADD = \
+ $(GLIB_LIBS) $(GDBUS_LIBS) \
+ $(GEOCLUE_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 +92,7 @@ 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)
endif
diff --git a/src/redshift-dbus.c b/src/redshift-dbus.c
new file mode 100644
index 00000000..cab9ed5a
--- /dev/null
+++ b/src/redshift-dbus.c
@@ -0,0 +1,1156 @@
+/* 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
+
+#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"
+
+
+/* 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;
+
+/* Geoclue proxy objects */
+static GDBusProxy *geoclue_manager = NULL;
+static GDBusProxy *geoclue_client = NULL;
+
+
+/* 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_print("Location: %f, %f\n", lat, lon);
+ g_print("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_print("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_print("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);
+}
+
+
+/* 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,
+ 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_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,
+ 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_print("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_print("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_print("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_print("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_print("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 */
+ emit_position_changed(conn, forced_lat, forced_lon);
+
+ screen_update_restart(conn);
+
+ g_print("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_print("UnenforceLocation for `%s'.\n", program);
+
+ if (forced_location_cookie == cookie) {
+ forced_location_cookie = 0;
+ screen_update_restart(conn);
+
+ /* Signal change in location */
+ emit_position_changed(conn, latitude, longitude);
+ }
+
+ 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) {
+ 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) {
+ 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) {
+ 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;
+}
+
+
+/* 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_client_signal_cb(GDBusProxy *client,
+ gchar *sender_name,
+ gchar *signal_name,
+ GVariant *parameters,
+ gpointer *data)
+{
+ GDBusConnection *conn = G_DBUS_CONNECTION(data);
+
+ /* Only handle LocationUpdated signals */
+ if (g_strcmp0(signal_name, "LocationUpdated") != 0) {
+ return;
+ }
+
+ /* Obtain location path */
+ const gchar *location_path;
+ g_variant_get_child(parameters, 1, "&o", &location_path);
+
+ /* 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 client */
+static int
+init_geoclue_position(GDBusConnection *conn)
+{
+ /* Obtain Geoclue Manager */
+ GError *error = NULL;
+ 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;
+ }
+
+ /* 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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_variant_unref(ret_v);
+
+ return 0;
+}
+
+
+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_printerr("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);
+
+ /* Initialize Geoclue position provider */
+ int r = init_geoclue_position(conn);
+ if (r < 0) exit(EXIT_FAILURE);
+
+ /* Start screen update timer */
+ screen_update_restart(conn);
+}
+
+static void
+on_name_acquired(GDBusConnection *conn,
+ const gchar *name,
+ gpointer data)
+{
+ g_printerr("Name acquired: `%s'.\n", name);
+}
+
+static void
+on_name_lost(GDBusConnection *conn,
+ const gchar *name,
+ gpointer data)
+{
+ g_printerr("Name lost: `%s'.\n", name);
+ exit(EXIT_FAILURE);
+}
+
+
+/* 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[])
+{
+#if !GLIB_CHECK_VERSION(2, 35, 0)
+ 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);
+
+ /* 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_print("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);
+
+ /* 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_object_unref(geoclue_client);
+ g_object_unref(geoclue_manager);
+
+ g_print("Restoring gamma ramps.\n");
+
+ /* Restore gamma ramps */
+ if (current_method != NULL) {
+ current_method->restore(&gamma_state);
+ current_method->free(&gamma_state);
+ }
+
+ return 0;
+}
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()