aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml71
-rw-r--r--CONTRIBUTING.md (renamed from HACKING.md)6
-rw-r--r--DESIGN4
l---------HACKING1
-rw-r--r--Makefile.am22
-rw-r--r--README.md55
-rw-r--r--appveyor.yml52
-rw-r--r--configure.ac87
-rw-r--r--data/apparmor/usr.bin.redshift.in42
-rw-r--r--po/POTFILES.in1
-rw-r--r--redshift.121
-rw-r--r--redshift.conf.sample34
-rw-r--r--src/Makefile.am37
-rw-r--r--src/gamma-quartz.c9
-rw-r--r--src/gamma-randr.c2
-rw-r--r--src/gamma-vidmode.c2
-rw-r--r--src/gamma-w32gdi.c22
-rw-r--r--src/location-corelocation.h34
-rw-r--r--src/location-corelocation.m245
-rw-r--r--src/location-geoclue.c218
-rw-r--r--src/location-geoclue.h46
-rw-r--r--src/location-geoclue2.c347
-rw-r--r--src/location-geoclue2.h29
-rw-r--r--src/location-manual.c15
-rw-r--r--src/location-manual.h7
-rw-r--r--src/pipeutils.c98
-rw-r--r--src/pipeutils.h28
-rw-r--r--src/redshift-gtk/Makefile.am5
-rw-r--r--src/redshift-gtk/controller.py227
-rw-r--r--src/redshift-gtk/statusicon.py359
-rw-r--r--src/redshift-gtk/utils.py34
-rw-r--r--src/redshift.c889
-rw-r--r--src/redshift.h9
-rw-r--r--src/windows/appicon.rc1
-rw-r--r--src/windows/redshift.icobin0 -> 87891 bytes
-rw-r--r--src/windows/versioninfo.rc20
37 files changed, 1977 insertions, 1103 deletions
diff --git a/.gitignore b/.gitignore
index 76659d4..077bca2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,7 @@ src/redshift-gtk/__pycache__/
/data/appdata/redshift-gtk.appdata.xml
/data/applications/redshift.desktop
/data/applications/redshift-gtk.desktop
+/data/apparmor/usr.bin.redshift
*.su
*.gch
diff --git a/.travis.yml b/.travis.yml
index eef7b6c..4f4ab0e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,62 @@
+
language: c
-before_install:
- - sed -i -e 's|AC_PREREQ(\[2.69\])|AC_PREREQ([2.68])|' configure.ac
- - sudo apt-get update -qq
+
+matrix:
+ include:
+ - os: linux
+ compiler: gcc
+ dist: trusty
+ sudo: false
+ - os: osx
+ compiler: clang
+
+addons:
+ apt:
+ packages:
+ - autopoint
+ - intltool
+ # DRM
+ - libdrm-dev
+ # RANDR
+ - libxcb1-dev
+ - libxcb-randr0-dev
+ # VidMode
+ - libx11-dev
+ - libxxf86vm-dev
+ # GeoClue2
+ - libglib2.0-dev
+ # GUI
+ - python3
+
+before_install: |
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ brew update
+ brew install gettext
+ brew link --force gettext
+ brew install intltool
+ brew install python3
+ fi
+
install:
- - sudo apt-get install -qq autopoint intltool
- - sudo apt-get install -qq libdrm-dev
- - sudo apt-get install -qq libxcb1-dev libxcb-randr0-dev
- - sudo apt-get install -qq libx11-dev libxxf86vm-dev
- - sudo apt-get install -qq libgeoclue-dev
- - sudo apt-get install -qq libglib2.0-dev
- - sudo apt-get install -qq python3
-script: ./bootstrap && ./configure --enable-drm --enable-vidmode --enable-randr --enable-geoclue --enable-geoclue2 --enable-gui && make -j2 distcheck
+ - ./bootstrap
+ - mkdir "$TRAVIS_BUILD_DIR/root"
+ - |
+ if [ "$TRAVIS_OS_NAME" == "linux" ]; then
+ ./configure --prefix="$TRAVIS_BUILD_DIR/root" --enable-drm --enable-vidmode --enable-randr --enable-geoclue2 --enable-gui --enable-apparmor
+ elif [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ ./configure --prefix="$TRAVIS_BUILD_DIR/root" --enable-corelocation --enable-quartz --enable-gui
+ fi
+ - make -j2 install
+ - make -j2 distcheck
+
+script:
+ - |
+ "$TRAVIS_BUILD_DIR"/root/bin/redshift -l 12:-34 -pv
+ - |
+ "$TRAVIS_BUILD_DIR"/root/bin/redshift -l 12:-34 -m dummy -vo
+ - |
+ echo -e "[redshift]\ndawn-time=6:30\ndusk-time=18:00-19:30" > time.config
+ - |
+ "$TRAVIS_BUILD_DIR"/root/bin/redshift -c time.config -pv
+ - |
+ "$TRAVIS_BUILD_DIR"/root/bin/redshift -c time.config -m dummy -vo
diff --git a/HACKING.md b/CONTRIBUTING.md
index c41b0e3..9ad02f8 100644
--- a/HACKING.md
+++ b/CONTRIBUTING.md
@@ -52,7 +52,7 @@ Dependencies
* libdrm (Optional, for DRM support)
* libxcb, libxcb-randr (Optional, for RandR support)
* libX11, libXxf86vm (Optional, for VidMode support)
-* geoclue (Optional, for geoclue support)
+* Glib 2 (Optional, for GeoClue2 support)
* python3, pygobject, pyxdg (Optional, for GUI support)
* appindicator (Optional, for Ubuntu-style GUI status icon)
@@ -158,8 +158,8 @@ Install MinGW and run `configure` using the following command line. Use
``` shell
$ ./configure --disable-drm --disable-randr --disable-vidmode --enable-wingdi \
- --disable-geoclue --disable-gui --disable-ubuntu \
- --host=x86_64-w64-mingw32
+ --disable-quartz --disable-geoclue2 --disable-corelocation --disable-gui \
+ --disable-ubuntu --host=x86_64-w64-mingw32
```
diff --git a/DESIGN b/DESIGN
index 7b70879..db9731c 100644
--- a/DESIGN
+++ b/DESIGN
@@ -64,8 +64,8 @@ of gamma ramps, which is what Redshift uses to change the screen color
temperature. There's also "wingdi" which is for the Windows version,
and "drm" which allows manipulation of gamma ramps in a TTY in Linux.
-Then there are location providers: "manual", "gnome-clock" and
-"geoclue". Some time ago there was only one way to specify the
+Then there are location providers: "manual", "geoclue2" and "corelocation".
+Some time ago there was only one way to specify the
location which had to be done manually with the argument "-l LAT:LON".
Then later, automatic "location providers" were added and the syntax
had to be changed to "-l PROVIDER:OPTIONS" where OPTIONS are arguments
diff --git a/HACKING b/HACKING
deleted file mode 120000
index 36b0b3a..0000000
--- a/HACKING
+++ /dev/null
@@ -1 +0,0 @@
-HACKING.md \ No newline at end of file
diff --git a/Makefile.am b/Makefile.am
index 1e2f1c8..7e58f08 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,7 +9,7 @@ DISTCHECK_CONFIGURE_FLAGS = \
UPDATE_ICON_CACHE = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor || :
EXTRA_ROOTDOC_FILES = \
- HACKING \
+ CONTRIBUTING.md \
DESIGN \
README \
README-colorramp \
@@ -39,6 +39,9 @@ SYSTEMD_USER_UNIT_IN_FILES = \
APPDATA_IN_FILES = \
data/appdata/redshift-gtk.appdata.xml.in
+APPARMOR_IN_FILES = \
+ data/apparmor/usr.bin.redshift.in
+
# Icons
if ENABLE_GUI
@@ -103,6 +106,17 @@ appdata_DATA = $(APPDATA_IN_FILES:.xml.in=.xml)
endif
+# AppArmor profile
+if ENABLE_APPARMOR
+apparmordir = @sysconfdir@/apparmor.d
+apparmor_DATA = $(APPARMOR_IN_FILES:.in=)
+
+$(apparmor_DATA): $(APPARMOR_IN_FILES) Makefile
+ $(AM_V_GEN)$(MKDIR_P) $(@D) && \
+ sed -e "s|\@bindir\@|$(bindir)|g" "$(srcdir)/$(@:=.in)" > $@
+endif
+
+
EXTRA_DIST = \
$(EXTRA_ROOTDOC_FILES) \
@@ -111,12 +125,14 @@ EXTRA_DIST = \
$(_UBUNTU_MONO_LIGHT_FILES) \
$(DESKTOP_IN_FILES) \
$(SYSTEMD_USER_UNIT_IN_FILES) \
- $(APPDATA_IN_FILES)
+ $(APPDATA_IN_FILES) \
+ $(APPARMOR_IN_FILES)
CLEANFILES = \
$(desktop_DATA) \
$(systemduserunit_DATA) \
- $(appdata_DATA)
+ $(appdata_DATA) \
+ $(apparmor_DATA)
# Update PO translations
diff --git a/README.md b/README.md
index 244a3c2..65899e3 100644
--- a/README.md
+++ b/README.md
@@ -18,15 +18,31 @@ Build status
------------
[![Build Status](https://travis-ci.org/jonls/redshift.svg?branch=master)](https://travis-ci.org/jonls/redshift)
-
-Building from source
---------------------
-
-See the file [HACKING](HACKING.md) for more details on building from source.
+[![Build Status](https://ci.appveyor.com/api/projects/status/github/jonls/redshift?branch=master&svg=true)](https://ci.appveyor.com/project/jonls/redshift)
FAQ
---
+**How do I install Redshift?**
+
+Use the packages provided by your distribution, e.g. for Ubuntu:
+`apt-get install redshift` or `apt-get install redshift-gtk`. For developers,
+please see _Building from source_ and _Latest builds from master branch_ below.
+
+**How do I setup a configuration file?**
+
+A configuration file is not required but is useful for saving custom
+configurations and manually defining the location in case of issues with the
+automatic location provider. An example configuration can be found in
+[redshift.conf.sample](redshift.conf.sample).
+
+The configuration file should be saved in the following location depending on
+the platform:
+
+- Linux/macOS: `~/.config/redshift.conf`.
+- Windows: Put `redshift.conf` in `%USERPROFILE%\AppData\Local\`
+ (aka `%localappdata%`).
+
**Where can I find my coordinates to put in the configuration file?**
There are multiple web sites that provide coordinates for map locations, for
@@ -34,6 +50,18 @@ example clicking anywhere on Google Maps will bring up a box with the
coordinates. Remember that longitudes in the western hemisphere (e.g. the
Americas) must be provided to Redshift as negative numbers.
+**Why does GeoClue fail with access denied error?**
+
+It is possible that the location services have been disabled completely. The
+check for this case varies by desktop environment. For example, in GNOME the
+location services can be toggled in Settings > Privacy > Location Services.
+
+If this is not the case, it is possible that Redshift has been improperly
+installed or not been given the required permissions to obtain location
+updates from a system administrator. See
+https://github.com/jonls/redshift/issues/318 for further discussion on this
+issue.
+
**Why doesn't Redshift work on my Chromebook/Raspberry Pi?**
Certain video drivers do not support adjustable gamma ramps. In some cases
@@ -45,13 +73,13 @@ adjustments to the gamma ramp.
Redshift has a brightness adjustment setting but it does not work the way most
people might expect. In fact it is a fake brightness adjustment obtained by
manipulating the gamma ramps which means that it does not reduce the backlight
-of the screen. Preferable only use it if your normal backlight adjustment is
+of the screen. Preferably only use it if your normal backlight adjustment is
too coarse-grained.
**Why doesn't Redshift work on Wayland (e.g. Fedora 25)?**
The Wayland protocol does not support Redshift. There is currently no way for
-Redshift adjust the color temperature in Wayland.
+Redshift to adjust the color temperature in Wayland.
**Why doesn't Redshift work on Ubuntu with Mir enabled?**
@@ -91,7 +119,14 @@ Please go to [the issue tracker](https://github.com/jonls/redshift/issues) and
check if your issue has already been reported. If not, please open a new issue
describing you problem.
-Donations
----------
+Latest builds from master branch
+--------------------------------
+
+- [Ubuntu PPA](https://launchpad.net/~dobey/+archive/ubuntu/redshift-daily/+packages) (`sudo add-apt-repository ppa:dobey/redshift-daily`)
+- [Windows x86_64](https://ci.appveyor.com/api/projects/jonls/redshift/artifacts/redshift-windows-x86_64.zip?branch=master&job=Environment%3A+arch%3Dx86_64&pr=false)
+- [Windows x86](https://ci.appveyor.com/api/projects/jonls/redshift/artifacts/redshift-windows-i686.zip?branch=master&job=Environment%3A+arch%3Di686&pr=false)
+
+Building from source
+--------------------
-[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/57936/Redshift)
+See the file [CONTRIBUTING](CONTRIBUTING.md) for more details on building from source.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..0ac3983
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,52 @@
+image:
+- Visual Studio 2015
+
+environment:
+ matrix:
+ - arch: x86_64
+ - arch: i686
+
+build:
+ verbosity: detailed
+
+build_script:
+- ps: |
+ If ($env:arch -Match "x86_64") {
+ $env:MSYSTEM = "MINGW64"
+ } Else {
+ $env:MSYSTEM = "MINGW32"
+ }
+
+ $env:CONFIGURE_FLAGS = "--disable-drm --disable-randr --disable-vidmode --enable-wingdi --disable-quartz --disable-geoclue2 --disable-corelocation --disable-gui --disable-ubuntu --disable-nls --host=$env:arch-w64-mingw32"
+
+- ps: md (Join-Path $env:APPVEYOR_BUILD_FOLDER root)
+- C:\msys64\usr\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER && ./bootstrap"
+- C:\msys64\usr\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER && ./configure --prefix=\"$APPVEYOR_BUILD_FOLDER/root\" $CONFIGURE_FLAGS"
+- C:\msys64\usr\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER && make distcheck DISTCHECK_CONFIGURE_FLAGS=\"$CONFIGURE_FLAGS\""
+- C:\msys64\usr\bin\bash -lc "cd $APPVEYOR_BUILD_FOLDER && make install"
+
+test_script:
+- |
+ %APPVEYOR_BUILD_FOLDER%\root\bin\redshift.exe -l 12:-34 -pv
+- |
+ %APPVEYOR_BUILD_FOLDER%\root\bin\redshift.exe -l 12:-34 -m dummy -vo
+- ps: Set-Content -Value "[redshift]`ndawn-time=6:30`ndusk-time=18:00-19:30`n" -Path time.config
+- |
+ %APPVEYOR_BUILD_FOLDER%\root\bin\redshift.exe -c time.config -pv
+- |
+ %APPVEYOR_BUILD_FOLDER%\root\bin\redshift.exe -c time.config -m dummy -vo
+
+after_build:
+- ps: |
+ $ZIP_NAME = "redshift-windows-$env:arch"
+ $ZIP_FILE = "redshift-windows-$env:arch.zip"
+
+ md $ZIP_NAME
+ Copy-Item -Path $env:APPVEYOR_BUILD_FOLDER\root\bin\redshift.exe -Destination $ZIP_NAME
+ Copy-Item -Path README.md -Destination $ZIP_NAME/README.txt
+ Copy-Item -Path NEWS.md -Destination $ZIP_NAME/NEWS.txt
+ Copy-Item -Path COPYING -Destination $ZIP_NAME/COPYING.txt
+ Copy-Item -Path redshift.conf.sample -Destination $ZIP_NAME
+ 7z a $ZIP_FILE $ZIP_NAME/
+
+- ps: Push-AppveyorArtifact $ZIP_FILE
diff --git a/configure.ac b/configure.ac
index 77cdf3f..be0b51a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,11 +13,32 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
# Checks for programs.
AC_PROG_CC_C99
AC_PROG_LIBTOOL
-AC_PROG_OBJC # For OSX support modules
+AC_PROG_OBJC # For macOS support modules
AC_LANG([C])
AC_PROG_INTLTOOL([0.50])
+AC_CANONICAL_HOST
+
+# Test host platform
+build_windows=no
+case "${host_os}" in
+ mingw*)
+ build_windows=yes
+ ;;
+esac
+
+# Test whether to compile Windows resources
+AC_CHECK_TOOL([WINDRES], [windres], [])
+AS_IF([test "x$build_windows" = "xyes" -a -n "x$WINDRES"], [
+ enable_windows_resource=yes
+], [
+ enable_windows_resource=no
+])
+AM_CONDITIONAL([ENABLE_WINDOWS_RESOURCE],
+ [test "x$enable_windows_resource" = xyes])
+
+
# Test whether Objective C compiler works
AC_MSG_CHECKING([whether Objective C compiler works])
AC_LANG_PUSH([Objective C])
@@ -48,10 +69,9 @@ 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([GEOCLUE], [geoclue], [have_geoclue=yes], [have_geoclue=no])
PKG_CHECK_MODULES([GEOCLUE2], [glib-2.0 gio-2.0 >= 2.26], [have_geoclue2=yes], [have_geoclue2=no])
-# OSX headers
+# macOS headers
AC_CHECK_HEADER([ApplicationServices/ApplicationServices.h], [have_appserv_h=yes], [have_appserv_h=no])
# CoreLocation.h is an Objective C header. Only test if
@@ -128,7 +148,7 @@ AS_IF([test "x$enable_randr" != xno], [
])
AM_CONDITIONAL([ENABLE_RANDR], [test "x$enable_randr" = xyes])
-# Check VidMode method
+# Check VidMode method
AC_MSG_CHECKING([whether to enable VidMode method])
AC_ARG_ENABLE([vidmode], [AC_HELP_STRING([--enable-vidmode],
[enable VidMode method])],
@@ -152,10 +172,10 @@ AS_IF([test "x$enable_vidmode" != xno], [
])
AM_CONDITIONAL([ENABLE_VIDMODE], [test "x$enable_vidmode" = xyes])
-# Check Quartz (OSX) method
+# Check Quartz (macOS) method
AC_MSG_CHECKING([whether to enable Quartz method])
AC_ARG_ENABLE([quartz], [AC_HELP_STRING([--enable-quartz],
- [enable Quartz (OSX) method])],
+ [enable Quartz (macOS) method])],
[enable_quartz=$enableval],[enable_quartz=maybe])
AS_IF([test "x$enable_quartz" != xno], [
AS_IF([test $have_appserv_h = yes], [
@@ -180,7 +200,7 @@ AM_CONDITIONAL([ENABLE_QUARTZ], [test "x$enable_quartz" = xyes])
AC_SUBST([QUARTZ_CFLAGS])
AC_SUBST([QUARTZ_LIBS])
-# Check Windows GDI method
+# Check Windows GDI method
AC_MSG_CHECKING([whether to enable WinGDI method])
AC_ARG_ENABLE([wingdi], [AC_HELP_STRING([--enable-wingdi],
[enable WinGDI method])],
@@ -205,30 +225,6 @@ AS_IF([test "x$enable_wingdi" != xno], [
AM_CONDITIONAL([ENABLE_WINGDI], [test "x$enable_wingdi" = xyes])
-# Check Geoclue location provider
-AC_MSG_CHECKING([whether to enable Geoclue location provider])
-AC_ARG_ENABLE([geoclue], [AC_HELP_STRING([--enable-geoclue],
- [enable Geoclue location provider])],
- [enable_geoclue=$enableval],[enable_geoclue=maybe])
-AS_IF([test "x$enable_geoclue" != xno], [
- AS_IF([test "x$have_geoclue" = xyes -a "x$have_glib" = xyes], [
- AC_DEFINE([ENABLE_GEOCLUE], 1,
- [Define to 1 to enable Geoclue location provider])
- AC_MSG_RESULT([yes])
- enable_geoclue=yes
- ], [
- AC_MSG_RESULT([missing dependencies])
- AS_IF([test "x$enable_geoclue" = xyes], [
- AC_MSG_ERROR([missing dependencies for Geoclue location provider])
- ])
- enable_geoclue=no
- ])
-], [
- AC_MSG_RESULT([no])
- enable_geoclue=no
-])
-AM_CONDITIONAL([ENABLE_GEOCLUE], [test "x$enable_geoclue" = xyes])
-
# Check Geoclue2 location provider
AC_MSG_CHECKING([whether to enable Geoclue2 location provider])
AC_ARG_ENABLE([geoclue2], [AC_HELP_STRING([--enable-geoclue2],
@@ -253,15 +249,15 @@ AS_IF([test "x$enable_geoclue2" != xno], [
])
AM_CONDITIONAL([ENABLE_GEOCLUE2], [test "x$enable_geoclue2" = xyes])
-# Check CoreLocation (OSX) provider
+# Check CoreLocation (macOS) provider
AC_MSG_CHECKING([whether to enable CoreLocation method])
AC_ARG_ENABLE([corelocation], [AC_HELP_STRING([--enable-corelocation],
- [enable CoreLocation (OSX) provider])],
+ [enable CoreLocation (macOS) provider])],
[enable_corelocation=$enableval],[enable_corelocation=maybe])
AS_IF([test "x$enable_corelocation" != xno], [
AS_IF([test "x$have_corelocation_h" = xyes], [
CORELOCATION_CFLAGS=""
- CORELOCATION_LIBS="-framework Foundation -framework CoreLocation"
+ CORELOCATION_LIBS="-framework Foundation -framework Cocoa -framework CoreLocation"
AC_DEFINE([ENABLE_CORELOCATION], 1,
[Define to 1 to enable CoreLocation provider])
AC_MSG_RESULT([yes])
@@ -335,6 +331,21 @@ AS_IF([test -n "$with_systemduserunitdir" -a "x$with_systemduserunitdir" != xno]
AM_CONDITIONAL([ENABLE_SYSTEMD], [test "x$enable_systemd" != xno])
+# Check for AppArmor
+AC_MSG_CHECKING([whether to enable AppArmor profile])
+AC_ARG_ENABLE([apparmor], [AC_HELP_STRING([--enable-apparmor],
+ [enable AppArmor profile])],
+ [enable_apparmor=$enableval],[enable_apparmor=no])
+AS_IF([test "x$enable_apparmor" != xno], [
+ AC_MSG_RESULT([yes])
+ enable_apparmor=yes
+], [
+ AC_MSG_RESULT([no])
+ enable_apparmor=no
+])
+AM_CONDITIONAL([ENABLE_APPARMOR], [test "x$enable_apparmor" != xno])
+
+
# Checks for header files.
AC_CHECK_HEADERS([locale.h stdint.h stdlib.h string.h unistd.h signal.h])
@@ -367,15 +378,15 @@ echo "
DRM: ${enable_drm}
RANDR: ${enable_randr}
VidMode: ${enable_vidmode}
- Quartz (OSX): ${enable_quartz}
+ Quartz (macOS): ${enable_quartz}
WinGDI (Windows): ${enable_wingdi}
Location providers:
- Geoclue: ${enable_geoclue}
- Geoclue2: ${enable_geoclue2}
- CoreLocation (OSX) ${enable_corelocation}
+ Geoclue2: ${enable_geoclue2}
+ CoreLocation (macOS): ${enable_corelocation}
GUI: ${enable_gui}
Ubuntu icons: ${enable_ubuntu}
systemd units: ${enable_systemd} ${systemduserunitdir}
+ AppArmor profile: ${enable_apparmor}
"
diff --git a/data/apparmor/usr.bin.redshift.in b/data/apparmor/usr.bin.redshift.in
new file mode 100644
index 0000000..d6696db
--- /dev/null
+++ b/data/apparmor/usr.bin.redshift.in
@@ -0,0 +1,42 @@
+# ------------------------------------------------------------------
+#
+# Copyright (C) 2015 Cameron Norman <camerontnorman@gmail.com>
+#
+# This program 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.
+#
+# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# ------------------------------------------------------------------
+
+#include <tunables/global>
+@bindir@/redshift {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+ #include <abstractions/dbus-strict>
+ #include <abstractions/X>
+
+ dbus send
+ bus=system
+ path=/org/freedesktop/GeoClue2/Client/[0-9]*,
+
+ dbus receive
+ bus=system
+ path=/org/freedesktop/GeoClue2/Manager,
+
+ # Allow but log any other dbus activity
+ audit dbus bus=system,
+
+ owner @{HOME}/.config/redshift.conf r,
+
+ # Site-specific additions and overrides. See local/README for details.
+ #include <local/usr.bin.redshift>
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4241670..e6b9c4d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -15,7 +15,6 @@ src/gamma-quartz.c
src/gamma-w32gdi.c
src/gamma-dummy.c
-src/location-geoclue.c
src/location-geoclue2.c
src/location-corelocation.m
src/location-manual.c
diff --git a/redshift.1 b/redshift.1
index 8064362..3555e15 100644
--- a/redshift.1
+++ b/redshift.1
@@ -67,7 +67,7 @@ Print mode (only print parameters and exit)
Reset mode (remove adjustment from screen)
.TP
\fB\-r\fR
-Disable temperature transitions
+Disable fading between color temperatures
.TP
\fB\-t\fR DAY:NIGHT
Color temperature to set at daytime/night
@@ -94,8 +94,9 @@ Daytime temperature
\fBtemp\-night\fR = integer
Night temperature
.TP
-\fBtransition\fR = 0 or 1
-Disable or enable transitions
+\fBfade\fR = 0 or 1
+Disable or enable fading between color temperatures when Redshift starts or
+stops
.TP
\fBbrightness\-day\fR = 0.1\-1.0
Screen brightness at daytime
@@ -104,10 +105,20 @@ Screen brightness at daytime
Screen brightness at night
.TP
\fBelevation-high\fR = decimal
-The solar elevation for the transition to daytime
+The solar elevation in degrees for the transition to daytime
.TP
\fBelevation-low\fR = decimal
-The solar elevation for the transition to night
+The solar elevation in degrees for the transition to night
+.TP
+\fBdawn-time\fR = HH:MM[-HH:MM]
+The custom time interval for the transition from night to day in the morning.
+When specified, the solar elevation will not be used to determine the current
+daytime/night period. If this option is set, dusk-time must also be specified.
+.TP
+\fBdusk-time\fR = HH:MM[-HH:MM]
+The custom time interval for the transition from day to night in the evening.
+When specified, the solar elevation will not be used to determine the current
+daytime/night period. If this option is set, dawn-time must also be specified.
.TP
\fBgamma\fR = R:G:B
Gamma adjustment to apply (day and night)
diff --git a/redshift.conf.sample b/redshift.conf.sample
index 633d0b3..882865b 100644
--- a/redshift.conf.sample
+++ b/redshift.conf.sample
@@ -4,10 +4,25 @@
temp-day=5700
temp-night=3500
-; Enable/Disable a smooth transition between day and night
-; 0 will cause a direct change from day to night screen temperature.
-; 1 will gradually increase or decrease the screen temperature.
-transition=1
+; Disable the smooth fade between temperatures when Redshift starts and stops.
+; 0 will cause an immediate change between screen temperatures.
+; 1 will gradually apply the new screen temperature over a couple of seconds.
+fade=1
+
+; Solar elevation thresholds.
+; By default, Redshift will use the current elevation of the sun to determine
+; whether it is daytime, night or in transition (dawn/dusk). When the sun is
+; above the degrees specified with elevation-high it is considered daytime and
+; below elevation-low it is considered night.
+;elevation-high=3
+;elevation-low=-6
+
+; Custom dawn/dusk intervals.
+; Instead of using the solar elevation, the time intervals of dawn and dusk
+; can be specified manually. The times must be specified as HH:MM in 24-hour
+; format.
+;dawn-time=6:00-7:45
+;dusk-time=18:35-20:15
; Set the screen brightness. Default is 1.0.
;brightness=0.9
@@ -24,7 +39,7 @@ gamma=0.8
;gamma-day=0.8:0.7:0.8
;gamma-night=0.6
-; Set the location-provider: 'geoclue', 'geoclue2', 'manual'
+; Set the location-provider: 'geoclue2', 'manual'
; type 'redshift -l list' to see possible values.
; The location provider settings are in a different section.
location-provider=manual
@@ -48,9 +63,8 @@ lon=11.6
; Configuration of the adjustment-method
; type 'redshift -m METHOD:help' to see the settings.
; ex: 'redshift -m randr:help'
-; In this example, randr is configured to adjust screen 1.
-; Note that the numbering starts from 0, so this is actually the
-; second screen. If this option is not specified, Redshift will try
-; to adjust _all_ screens.
+; In this example, randr is configured to adjust only screen 0.
+; Note that the numbering starts from 0, so this is actually the first screen.
+; If this option is not specified, Redshift will try to adjust _all_ screens.
[randr]
-screen=1
+screen=0
diff --git a/src/Makefile.am b/src/Makefile.am
index 318fc2c..99c8a2e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,15 +9,16 @@ AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\"
bin_PROGRAMS = redshift
redshift_SOURCES = \
- redshift.c redshift.h \
- signals.c signals.h \
colorramp.c colorramp.h \
config-ini.c config-ini.h \
+ gamma-dummy.c gamma-dummy.h \
+ hooks.c hooks.h \
location-manual.c location-manual.h \
+ pipeutils.c pipeutils.h \
+ redshift.c redshift.h \
+ signals.c signals.h \
solar.c solar.h \
- systemtime.c systemtime.h \
- hooks.c hooks.h \
- gamma-dummy.c gamma-dummy.h
+ systemtime.c systemtime.h
EXTRA_redshift_SOURCES = \
gamma-drm.c gamma-drm.h \
@@ -25,11 +26,14 @@ EXTRA_redshift_SOURCES = \
gamma-vidmode.c gamma-vidmode.h \
gamma-quartz.c gamma-quartz.h \
gamma-w32gdi.c gamma-w32gdi.h \
- location-geoclue.c location-geoclue.h
+ location-geoclue2.c location-geoclue2.h \
+ location-corelocation.m location-corelocation.h \
+ windows/appicon.rc \
+ windows/versioninfo.rc
AM_CFLAGS =
redshift_LDADD = @LIBINTL@
-EXTRA_DIST =
+EXTRA_DIST = windows/redshift.ico
if ENABLE_DRM
redshift_SOURCES += gamma-drm.c gamma-drm.h
@@ -67,16 +71,6 @@ redshift_LDADD += -lgdi32
endif
-if ENABLE_GEOCLUE
-redshift_SOURCES += location-geoclue.c location-geoclue.h
-AM_CFLAGS += \
- $(GEOCLUE_CFLAGS) $(GEOCLUE_LIBS) \
- $(GLIB_CFLAGS) $(GLIB_LIBS)
-redshift_LDADD += \
- $(GEOCLUE_LIBS) $(GEOCLUE_CFLAGS)
- $(GLIB_LIBS) $(GLIB_CFLAGS)
-endif
-
if ENABLE_GEOCLUE2
redshift_SOURCES += location-geoclue2.c location-geoclue2.h
AM_CFLAGS += \
@@ -99,3 +93,12 @@ liblocation_corelocation_la_LIBADD = \
$(CORELOCATION_CFLAGS) $(CORELOCATION_LIBS)
redshift_LDADD += liblocation-corelocation.la
endif
+
+
+# Windows resources
+if ENABLE_WINDOWS_RESOURCE
+redshift_SOURCES += windows/appicon.rc windows/versioninfo.rc
+endif
+
+.rc.o:
+ $(AM_V_GEN)$(WINDRES) -I$(top_builddir) -i $< -o $@
diff --git a/src/gamma-quartz.c b/src/gamma-quartz.c
index 6691c91..879da21 100644
--- a/src/gamma-quartz.c
+++ b/src/gamma-quartz.c
@@ -40,7 +40,7 @@
int
quartz_init(quartz_state_t *state)
{
- state->preserve = 0;
+ state->preserve = 1;
state->displays = NULL;
return 0;
@@ -177,10 +177,11 @@ quartz_set_option(quartz_state_t *state, const char *key, const char *value)
}
static void
-quartz_set_temperature_for_display(quartz_state_t *state, int display,
+quartz_set_temperature_for_display(quartz_state_t *state, int display_index,
const color_setting_t *setting)
{
- uint32_t ramp_size = state->displays[display].ramp_size;
+ CGDirectDisplayID display = state->displays[display_index].display;
+ uint32_t ramp_size = state->displays[display_index].ramp_size;
/* Create new gamma ramps */
float *gamma_ramps = malloc(3*ramp_size*sizeof(float));
@@ -195,7 +196,7 @@ quartz_set_temperature_for_display(quartz_state_t *state, int display,
if (state->preserve) {
/* Initialize gamma ramps from saved state */
- memcpy(gamma_ramps, state->displays[display].saved_ramps,
+ memcpy(gamma_ramps, state->displays[display_index].saved_ramps,
3*ramp_size*sizeof(float));
} else {
/* Initialize gamma ramps to pure state */
diff --git a/src/gamma-randr.c b/src/gamma-randr.c
index 901c0db..6e0fd00 100644
--- a/src/gamma-randr.c
+++ b/src/gamma-randr.c
@@ -53,7 +53,7 @@ randr_init(randr_state_t *state)
state->crtc_count = 0;
state->crtcs = NULL;
- state->preserve = 0;
+ state->preserve = 1;
xcb_generic_error_t *error;
diff --git a/src/gamma-vidmode.c b/src/gamma-vidmode.c
index c9682d2..e664f80 100644
--- a/src/gamma-vidmode.c
+++ b/src/gamma-vidmode.c
@@ -43,7 +43,7 @@ vidmode_init(vidmode_state_t *state)
state->screen_num = -1;
state->saved_ramps = NULL;
- state->preserve = 0;
+ state->preserve = 1;
/* Open display */
state->display = XOpenDisplay(NULL);
diff --git a/src/gamma-w32gdi.c b/src/gamma-w32gdi.c
index c518fe6..3d67331 100644
--- a/src/gamma-w32gdi.c
+++ b/src/gamma-w32gdi.c
@@ -37,13 +37,14 @@
#include "colorramp.h"
#define GAMMA_RAMP_SIZE 256
+#define MAX_ATTEMPTS 10
int
w32gdi_init(w32gdi_state_t *state)
{
state->saved_ramps = NULL;
- state->preserve = 0;
+ state->preserve = 1;
return 0;
}
@@ -136,7 +137,13 @@ w32gdi_restore(w32gdi_state_t *state)
}
/* Restore gamma ramps */
- BOOL r = SetDeviceGammaRamp(hDC, state->saved_ramps);
+ BOOL r = FALSE;
+ for (int i = 0; i < MAX_ATTEMPTS && !r; i++) {
+ /* We retry a few times before giving up because some
+ buggy drivers fail on the first invocation of
+ SetDeviceGammaRamp just to succeed on the second. */
+ r = SetDeviceGammaRamp(hDC, state->saved_ramps);
+ }
if (!r) fputs(_("Unable to restore gamma ramps.\n"), stderr);
/* Release device context */
@@ -187,11 +194,14 @@ w32gdi_set_temperature(w32gdi_state_t *state,
setting);
/* Set new gamma ramps */
- r = SetDeviceGammaRamp(hDC, gamma_ramps);
+ r = FALSE;
+ for (int i = 0; i < MAX_ATTEMPTS && !r; i++) {
+ /* We retry a few times before giving up because some
+ buggy drivers fail on the first invocation of
+ SetDeviceGammaRamp just to succeed on the second. */
+ r = SetDeviceGammaRamp(hDC, gamma_ramps);
+ }
if (!r) {
- /* TODO it happens that SetDeviceGammaRamp returns FALSE on
- occasions where the adjustment seems to be successful.
- Does this only happen with multiple monitors connected? */
fputs(_("Unable to set gamma ramps.\n"), stderr);
free(gamma_ramps);
ReleaseDC(NULL, hDC);
diff --git a/src/location-corelocation.h b/src/location-corelocation.h
index 4b74382..ae1feeb 100644
--- a/src/location-corelocation.h
+++ b/src/location-corelocation.h
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2014 Jon Lund Steffense <jonlst@gmail.com>
+ Copyright (c) 2014-2017 Jon Lund Steffense <jonlst@gmail.com>
*/
#ifndef REDSHIFT_LOCATION_CORELOCATION_H
@@ -24,17 +24,33 @@
#include "redshift.h"
+typedef struct location_corelocation_private location_corelocation_private_t;
-int location_corelocation_init(void *state);
-int location_corelocation_start(void *state);
-void location_corelocation_free(void *state);
+typedef struct {
+ location_corelocation_private_t *private;
+ int pipe_fd_read;
+ int pipe_fd_write;
+ int available;
+ int error;
+ float latitude;
+ float longitude;
+} location_corelocation_state_t;
-void location_corelocation_print_help(FILE *f);
-int location_corelocation_set_option(void *state,
- const char *key, const char *value);
-int location_corelocation_get_location(void *state,
- location_t *location);
+int location_corelocation_init(location_corelocation_state_t *state);
+int location_corelocation_start(location_corelocation_state_t *state);
+void location_corelocation_free(location_corelocation_state_t *state);
+
+void location_corelocation_print_help(FILE *f);
+int location_corelocation_set_option(
+ location_corelocation_state_t *state,
+ const char *key, const char *value);
+
+int location_corelocation_get_fd(
+ location_corelocation_state_t *state);
+int location_corelocation_handle(
+ location_corelocation_state_t *state,
+ location_t *location, int *available);
#endif /* ! REDSHIFT_LOCATION_CORELOCATION_H */
diff --git a/src/location-corelocation.m b/src/location-corelocation.m
index 2f1768d..5150839 100644
--- a/src/location-corelocation.m
+++ b/src/location-corelocation.m
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#ifdef HAVE_CONFIG_H
@@ -25,9 +25,11 @@
#import <CoreLocation/CoreLocation.h>
#include "location-corelocation.h"
+#include "pipeutils.h"
#include "redshift.h"
#include <stdio.h>
+#include <unistd.h>
#ifdef ENABLE_NLS
# include <libintl.h>
@@ -37,128 +39,229 @@
#endif
-@interface Delegate : NSObject <CLLocationManagerDelegate>
+struct location_corelocation_private {
+ NSThread *thread;
+ NSLock *lock;
+};
+
+
+@interface LocationDelegate : NSObject <CLLocationManagerDelegate>
@property (strong, nonatomic) CLLocationManager *locationManager;
-@property (nonatomic) BOOL success;
-@property (nonatomic) float latitude;
-@property (nonatomic) float longitude;
+@property (nonatomic) location_corelocation_state_t *state;
@end
-@implementation Delegate;
+@implementation LocationDelegate;
- (void)start
{
- self.locationManager = [[CLLocationManager alloc] init];
- self.locationManager.delegate = self;
+ self.locationManager = [[CLLocationManager alloc] init];
+ self.locationManager.delegate = self;
+ self.locationManager.distanceFilter = 50000;
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
+
+ CLAuthorizationStatus authStatus =
+ [CLLocationManager authorizationStatus];
+
+ if (authStatus != kCLAuthorizationStatusNotDetermined &&
+ authStatus != kCLAuthorizationStatusAuthorized) {
+ fputs(_("Not authorized to obtain location"
+ " from CoreLocation.\n"), stderr);
+ [self markError];
+ } else {
+ [self.locationManager startUpdatingLocation];
+ }
+}
- CLAuthorizationStatus authStatus =
- [CLLocationManager authorizationStatus];
+- (void)markError
+{
+ [self.state->private->lock lock];
- if (authStatus != kCLAuthorizationStatusNotDetermined &&
- authStatus != kCLAuthorizationStatusAuthorized) {
- fputs(_("Not authorized to obtain location"
- " from CoreLocation.\n"), stderr);
- CFRunLoopStop(CFRunLoopGetCurrent());
- }
+ self.state->error = 1;
- [self.locationManager startUpdatingLocation];
-}
+ [self.state->private->lock unlock];
-- (void)stop
-{
- [self.locationManager stopUpdatingLocation];
- CFRunLoopStop(CFRunLoopGetCurrent());
+ pipeutils_signal(self.state->pipe_fd_write);
}
- (void)locationManager:(CLLocationManager *)manager
- didUpdateLocations:(NSArray *)locations
+ didUpdateLocations:(NSArray *)locations
{
- CLLocation *newLocation = [locations firstObject];
- self.latitude = newLocation.coordinate.latitude;
- self.longitude = newLocation.coordinate.longitude;
- self.success = YES;
+ CLLocation *newLocation = [locations firstObject];
+
+ [self.state->private->lock lock];
+
+ self.state->latitude = newLocation.coordinate.latitude;
+ self.state->longitude = newLocation.coordinate.longitude;
+ self.state->available = 1;
- [self stop];
+ [self.state->private->lock unlock];
+
+ pipeutils_signal(self.state->pipe_fd_write);
}
- (void)locationManager:(CLLocationManager *)manager
- didFailWithError:(NSError *)error
+ didFailWithError:(NSError *)error
{
- fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"),
- [[error localizedDescription] UTF8String]);
- [self stop];
+ fprintf(stderr, _("Error obtaining location from CoreLocation: %s\n"),
+ [[error localizedDescription] UTF8String]);
+ [self markError];
}
- (void)locationManager:(CLLocationManager *)manager
- didChangeAuthorizationStatus:(CLAuthorizationStatus)status
+ didChangeAuthorizationStatus:(CLAuthorizationStatus)status
+{
+ if (status == kCLAuthorizationStatusNotDetermined) {
+ fputs(_("Waiting for authorization to obtain location...\n"), stderr);
+ } else if (status != kCLAuthorizationStatusAuthorized) {
+ fputs(_("Request for location was not authorized!\n"), stderr);
+ [self markError];
+ }
+}
+
+@end
+
+
+// Callback when the pipe is closed.
+//
+// Stops the run loop causing the thread to end.
+static void
+pipe_close_callback(
+ CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info)
+{
+ CFFileDescriptorInvalidate(fdref);
+ CFRelease(fdref);
+
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+
+@interface LocationThread : NSThread
+@property (nonatomic) location_corelocation_state_t *state;
+@end
+
+@implementation LocationThread;
+
+// Run loop for location provider thread.
+- (void)main
{
- if (status == kCLAuthorizationStatusNotDetermined) {
- fputs(_("Waiting for authorization to obtain location...\n"),
- stderr);
- } else if (status != kCLAuthorizationStatusAuthorized) {
- fputs(_("Request for location was not authorized!\n"),
- stderr);
- [self stop];
- }
+ @autoreleasepool {
+ LocationDelegate *locationDelegate = [[LocationDelegate alloc] init];
+ locationDelegate.state = self.state;
+
+ // Start the location delegate on the run loop in this thread.
+ [locationDelegate performSelector:@selector(start)
+ withObject:nil afterDelay:0];
+
+ // Create a callback that is triggered when the pipe is closed. This will
+ // trigger the main loop to quit and the thread to stop.
+ CFFileDescriptorRef fdref = CFFileDescriptorCreate(
+ kCFAllocatorDefault, self.state->pipe_fd_write, false,
+ pipe_close_callback, NULL);
+ CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
+ CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(
+ kCFAllocatorDefault, fdref, 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
+
+ // Run the loop
+ CFRunLoopRun();
+
+ close(self.state->pipe_fd_write);
+ }
}
@end
int
-location_corelocation_init(void *state)
+location_corelocation_init(location_corelocation_state_t *state)
{
- return 0;
+ return 0;
}
int
-location_corelocation_start(void *state)
+location_corelocation_start(location_corelocation_state_t *state)
{
- return 0;
+ state->pipe_fd_read = -1;
+ state->pipe_fd_write = -1;
+
+ state->available = 0;
+ state->error = 0;
+ state->latitude = 0;
+ state->longitude = 0;
+
+ state->private = malloc(sizeof(location_corelocation_private_t));
+ if (state->private == NULL) return -1;
+
+ int pipefds[2];
+ int r = pipeutils_create_nonblocking(pipefds);
+ if (r < 0) {
+ fputs(_("Failed to start CoreLocation provider!\n"), stderr);
+ free(state->private);
+ return -1;
+ }
+
+ state->pipe_fd_read = pipefds[0];
+ state->pipe_fd_write = pipefds[1];
+
+ pipeutils_signal(state->pipe_fd_write);
+
+ state->private->lock = [[NSLock alloc] init];
+
+ LocationThread *thread = [[LocationThread alloc] init];
+ thread.state = state;
+ [thread start];
+ state->private->thread = thread;
+
+ return 0;
}
void
-location_corelocation_free(void *state)
+location_corelocation_free(location_corelocation_state_t *state)
{
+ if (state->pipe_fd_read != -1) {
+ close(state->pipe_fd_read);
+ }
+
+ free(state->private);
}
void
location_corelocation_print_help(FILE *f)
{
- fputs(_("Use the location as discovered by the Corelocation provider.\n"), f);
- fputs("\n", f);
-
- fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n"
- "which means it has to be restarted to take notice after travel.\n"),
- "CoreLocation");
- fputs("\n", f);
+ fputs(_("Use the location as discovered by the Corelocation provider.\n"), f);
+ fputs("\n", f);
}
int
-location_corelocation_set_option(void *state,
- const char *key, const char *value)
+location_corelocation_set_option(
+ location_corelocation_state_t *state, const char *key, const char *value)
{
- fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
- return -1;
+ fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
+ return -1;
}
int
-location_corelocation_get_location(void *state,
- location_t *location)
+location_corelocation_get_fd(location_corelocation_state_t *state)
{
- int result = -1;
+ return state->pipe_fd_read;
+}
+
+int location_corelocation_handle(
+ location_corelocation_state_t *state,
+ location_t *location, int *available)
+{
+ pipeutils_handle_signal(state->pipe_fd_read);
+
+ [state->private->lock lock];
+
+ int error = state->error;
+ location->lat = state->latitude;
+ location->lon = state->longitude;
+ *available = state->available;
- @autoreleasepool {
- Delegate *delegate = [[Delegate alloc] init];
- [delegate performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
- CFRunLoopRun();
+ [state->private->lock unlock];
- if (delegate.success) {
- location->lat = delegate.latitude;
- location->lon = delegate.longitude;
- result = 0;
- }
- }
+ if (error) return -1;
- return result;
+ return 0;
}
diff --git a/src/location-geoclue.c b/src/location-geoclue.c
deleted file mode 100644
index e24c2d2..0000000
--- a/src/location-geoclue.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/* location-geoclue.c -- Geoclue location provider source
- 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 <http://www.gnu.org/licenses/>.
-
- Copyright (c) 2010 Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com>
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <geoclue/geoclue-master.h>
-#include <geoclue/geoclue-position.h>
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include "location-geoclue.h"
-#include "redshift.h"
-
-#ifdef ENABLE_NLS
-# include <libintl.h>
-# define _(s) gettext(s)
-#else
-# define _(s) s
-#endif
-
-#define DEFAULT_PROVIDER "org.freedesktop.Geoclue.Providers.UbuntuGeoIP"
-#define DEFAULT_PROVIDER_PATH "/org/freedesktop/Geoclue/Providers/UbuntuGeoIP"
-
-int
-location_geoclue_init(location_geoclue_state_t *state)
-{
-#if !GLIB_CHECK_VERSION(2, 35, 0)
- g_type_init();
-#endif
-
- state->position = NULL;
- state->provider = NULL;
- state->provider_path = NULL;
-
- return 0;
-}
-
-int
-location_geoclue_start(location_geoclue_state_t *state)
-{
- if (state->provider && state->provider_path) {
- state->position = geoclue_position_new(state->provider,
- state->provider_path);
- } else {
- if (getenv("DISPLAY") == NULL || *getenv("DISPLAY") == '\0') {
- /* TODO This (hack) should be removed when GeoClue has been patched. */
- putenv("DISPLAY=:0");
- }
- 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;
- }
-
- if (!geoclue_master_client_set_requirements(client,
- GEOCLUE_ACCURACY_LEVEL_REGION,
- 0, FALSE,
- GEOCLUE_RESOURCE_NETWORK,
- &error)) {
- 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;
- }
-
- state->position = geoclue_master_client_create_position(client, NULL);
-
- g_object_unref(client);
- }
-
- gchar *name = NULL;
-
- if (geoclue_provider_get_provider_info(GEOCLUE_PROVIDER(state->position),
- &name, NULL, NULL)) {
- fprintf(stdout, _("Started Geoclue provider `%s'.\n"), name);
- g_free(name);
- } else {
- fputs(_("Could not find a usable Geoclue provider.\n"), stderr);
- fputs(_("Try setting name and path to specify which to use.\n"), stderr);
- return -1;
- }
-
- return 0;
-}
-
-void
-location_geoclue_free(location_geoclue_state_t *state)
-{
- if (state->position != NULL) g_object_unref(state->position);
- free(state->provider);
- free(state->provider_path);
-}
-
-void
-location_geoclue_print_help(FILE *f)
-{
- fputs(_("Use the location as discovered by a Geoclue provider.\n"), f);
- fputs("\n", f);
-
- /* TRANSLATORS: Geoclue help output
- left column must not be translated */
- fputs(_(" name=N\tName of Geoclue provider (or `default')\n"
- " path=N\tPath of Geoclue provider (or `default')\n"), f);
- fputs("\n", f);
- fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n"
- "which means it has to be restarted to take notice after travel.\n"),
- "GeoClue");
- fputs("\n", f);
-}
-
-int
-location_geoclue_set_option(location_geoclue_state_t *state,
- const char *key, const char *value)
-{
- const char *provider = NULL;
- const char *path = NULL;
-
- /* Parse string value */
- if (strcasecmp(key, "name") == 0) {
- if (strcasecmp(value, "default") == 0) {
- provider = DEFAULT_PROVIDER;
- } else {
- provider = value;
- }
-
- state->provider = strdup(provider);
- if (state->provider == NULL) {
- perror("strdup");
- return -1;
- }
- } else if (strcasecmp(key, "path") == 0) {
- if (value != NULL && strcasecmp(value, "default") == 0) {
- path = DEFAULT_PROVIDER_PATH;
- } else {
- path = value;
- }
-
- state->provider_path = strdup(path);
- if (state->provider_path == NULL) {
- perror("strdup");
- return -1;
- }
- } else {
- fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
- return -1;
- }
-
- return 0;
-}
-
-int
-location_geoclue_get_location(location_geoclue_state_t *state,
- location_t *location)
-{
- GeocluePositionFields fields;
- GError *error = NULL;
- double latitude = 0, longitude = 0;
-
- fields = geoclue_position_get_position(state->position, NULL,
- &latitude, &longitude, 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) {
- fprintf(stdout, _("According to the geoclue provider"
- " we're at: %.2f, %.2f\n"),
- latitude, longitude);
- } else {
- g_warning(_("Provider does not have a valid location available."));
- return -1;
- }
-
- location->lat = latitude;
- location->lon = longitude;
-
- return 0;
-}
diff --git a/src/location-geoclue.h b/src/location-geoclue.h
deleted file mode 100644
index 3847ee2..0000000
--- a/src/location-geoclue.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* location-geoclue.h -- Geoclue location provider header
- 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 <http://www.gnu.org/licenses/>.
-
- Copyright (c) 2010 Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com>
-*/
-
-#ifndef REDSHIFT_LOCATION_GEOCLUE_H
-#define REDSHIFT_LOCATION_GEOCLUE_H
-
-#include <stdio.h>
-#include <geoclue/geoclue-position.h>
-
-#include "redshift.h"
-
-typedef struct {
- GeocluePosition *position; /* main geoclue object */
- char *provider; /* name of a geoclue provider */
- char *provider_path; /* path of the geoclue provider */
-} location_geoclue_state_t;
-
-int location_geoclue_init(location_geoclue_state_t *state);
-int location_geoclue_start(location_geoclue_state_t *state);
-void location_geoclue_free(location_geoclue_state_t *state);
-
-void location_geoclue_print_help(FILE *f);
-int location_geoclue_set_option(location_geoclue_state_t *state,
- const char *key, const char *value);
-
-int location_geoclue_get_location(location_geoclue_state_t *state,
- location_t *loc);
-
-
-#endif /* ! REDSHIFT_LOCATION_GEOCLUE_H */
diff --git a/src/location-geoclue2.c b/src/location-geoclue2.c
index abccbd3..6ebedb4 100644
--- a/src/location-geoclue2.c
+++ b/src/location-geoclue2.c
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#include <stdio.h>
@@ -26,6 +26,7 @@
#include "location-geoclue2.h"
#include "redshift.h"
+#include "pipeutils.h"
#ifdef ENABLE_NLS
# include <libintl.h>
@@ -34,62 +35,41 @@
# define _(s) s
#endif
+#define DBUS_ACCESS_ERROR "org.freedesktop.DBus.Error.AccessDenied"
-typedef struct {
- GMainLoop *loop;
- int available;
- location_t location;
-} get_location_data_t;
-
-
-int
-location_geoclue2_init(void *state)
+/* Print the message explaining denial from GeoClue. */
+static void
+print_denial_message()
{
-#if !GLIB_CHECK_VERSION(2, 35, 0)
- g_type_init();
-#endif
- return 0;
+ g_printerr(_(
+ "Access to the current location was denied by GeoClue!\n"
+ "Make sure that location services are enabled and that"
+ " Redshift is permitted\nto use location services."
+ " See https://github.com/jonls/redshift#faq for more\n"
+ "information.\n"));
}
-int
-location_geoclue2_start(void *state)
+/* Indicate an unrecoverable error during GeoClue2 communication. */
+static void
+mark_error(location_geoclue2_state_t *state)
{
- return 0;
-}
+ g_mutex_lock(&state->lock);
-void
-location_geoclue2_free(void *state)
-{
-}
+ state->error = 1;
-void
-location_geoclue2_print_help(FILE *f)
-{
- fputs(_("Use the location as discovered by a GeoClue2 provider.\n"), f);
- fputs("\n", f);
+ g_mutex_unlock(&state->lock);
- fprintf(f, _("NOTE: currently Redshift doesn't recheck %s once started,\n"
- "which means it has to be restarted to take notice after travel.\n"),
- "GeoClue2");
- fputs("\n", f);
-}
-
-int
-location_geoclue2_set_option(void *state,
- const char *key, const char *value)
-{
- fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
- return -1;
+ pipeutils_signal(state->pipe_fd_write);
}
/* Handle position change callbacks */
static void
geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name,
gchar *signal_name, GVariant *parameters,
- gpointer *user_data)
+ gpointer user_data)
{
- get_location_data_t *data = (get_location_data_t *)user_data;
+ location_geoclue2_state_t *state = user_data;
/* Only handle LocationUpdated signals */
if (g_strcmp0(signal_name, "LocationUpdated") != 0) {
@@ -102,34 +82,38 @@ geoclue_client_signal_cb(GDBusProxy *client, gchar *sender_name,
/* Obtain location */
GError *error = NULL;
- GDBusProxy *location =
- g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
- G_DBUS_PROXY_FLAGS_NONE,
- NULL,
- "org.freedesktop.GeoClue2",
- location_path,
- "org.freedesktop.GeoClue2.Location",
- NULL, &error);
+ GDBusProxy *location = g_dbus_proxy_new_sync(
+ g_dbus_proxy_get_connection(client),
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.GeoClue2",
+ location_path,
+ "org.freedesktop.GeoClue2.Location",
+ NULL, &error);
if (location == NULL) {
g_printerr(_("Unable to obtain location: %s.\n"),
error->message);
g_error_free(error);
+ mark_error(state);
return;
}
+ g_mutex_lock(&state->lock);
+
/* Read location properties */
- GVariant *lat_v = g_dbus_proxy_get_cached_property(location,
- "Latitude");
- data->location.lat = g_variant_get_double(lat_v);
+ GVariant *lat_v = g_dbus_proxy_get_cached_property(
+ location, "Latitude");
+ state->latitude = g_variant_get_double(lat_v);
+
+ GVariant *lon_v = g_dbus_proxy_get_cached_property(
+ location, "Longitude");
+ state->longitude = g_variant_get_double(lon_v);
- GVariant *lon_v = g_dbus_proxy_get_cached_property(location,
- "Longitude");
- data->location.lon = g_variant_get_double(lon_v);
+ state->available = 1;
- data->available = 1;
+ g_mutex_unlock(&state->lock);
- /* Return from main loop */
- g_main_loop_quit(data->loop);
+ pipeutils_signal(state->pipe_fd_write);
}
/* Callback when GeoClue name appears on the bus */
@@ -137,22 +121,23 @@ static void
on_name_appeared(GDBusConnection *conn, const gchar *name,
const gchar *name_owner, gpointer user_data)
{
- get_location_data_t *data = (get_location_data_t *)user_data;
+ location_geoclue2_state_t *state = user_data;
/* Obtain GeoClue Manager */
GError *error = NULL;
- GDBusProxy *geoclue_manager =
- g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
- G_DBUS_PROXY_FLAGS_NONE,
- NULL,
- "org.freedesktop.GeoClue2",
- "/org/freedesktop/GeoClue2/Manager",
- "org.freedesktop.GeoClue2.Manager",
- NULL, &error);
+ GDBusProxy *geoclue_manager = g_dbus_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.GeoClue2",
+ "/org/freedesktop/GeoClue2/Manager",
+ "org.freedesktop.GeoClue2.Manager",
+ NULL, &error);
if (geoclue_manager == NULL) {
g_printerr(_("Unable to obtain GeoClue Manager: %s.\n"),
error->message);
g_error_free(error);
+ mark_error(state);
return;
}
@@ -169,6 +154,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
error->message);
g_error_free(error);
g_object_unref(geoclue_manager);
+ mark_error(state);
return;
}
@@ -177,20 +163,21 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
/* Obtain GeoClue client */
error = NULL;
- GDBusProxy *geoclue_client =
- g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM,
- G_DBUS_PROXY_FLAGS_NONE,
- NULL,
- "org.freedesktop.GeoClue2",
- client_path,
- "org.freedesktop.GeoClue2.Client",
- NULL, &error);
+ GDBusProxy *geoclue_client = g_dbus_proxy_new_sync(
+ conn,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.GeoClue2",
+ client_path,
+ "org.freedesktop.GeoClue2.Client",
+ NULL, &error);
if (geoclue_client == NULL) {
g_printerr(_("Unable to obtain GeoClue Client: %s.\n"),
error->message);
g_error_free(error);
g_variant_unref(client_path_v);
g_object_unref(geoclue_manager);
+ mark_error(state);
return;
}
@@ -198,15 +185,15 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
/* Set desktop id (basename of the .desktop file) */
error = NULL;
- GVariant *ret_v =
- g_dbus_proxy_call_sync(geoclue_client,
- "org.freedesktop.DBus.Properties.Set",
- g_variant_new("(ssv)",
- "org.freedesktop.GeoClue2.Client",
- "DesktopId",
- g_variant_new("s", "redshift")),
- G_DBUS_CALL_FLAGS_NONE,
- -1, NULL, &error);
+ GVariant *ret_v = g_dbus_proxy_call_sync(
+ geoclue_client,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new("(ssv)",
+ "org.freedesktop.GeoClue2.Client",
+ "DesktopId",
+ g_variant_new("s", "redshift")),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
if (ret_v == NULL) {
/* Ignore this error for now. The property is not available
in early versions of GeoClue2. */
@@ -216,20 +203,22 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
/* Set distance threshold */
error = NULL;
- ret_v = g_dbus_proxy_call_sync(geoclue_client,
- "org.freedesktop.DBus.Properties.Set",
- g_variant_new("(ssv)",
- "org.freedesktop.GeoClue2.Client",
- "DistanceThreshold",
- g_variant_new("u", 50000)),
- G_DBUS_CALL_FLAGS_NONE,
- -1, NULL, &error);
+ ret_v = g_dbus_proxy_call_sync(
+ geoclue_client,
+ "org.freedesktop.DBus.Properties.Set",
+ g_variant_new("(ssv)",
+ "org.freedesktop.GeoClue2.Client",
+ "DistanceThreshold",
+ g_variant_new("u", 50000)),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1, NULL, &error);
if (ret_v == NULL) {
g_printerr(_("Unable to set distance threshold: %s.\n"),
error->message);
g_error_free(error);
g_object_unref(geoclue_client);
g_object_unref(geoclue_manager);
+ mark_error(state);
return;
}
@@ -238,7 +227,7 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
/* Attach signal callback to client */
g_signal_connect(geoclue_client, "g-signal",
G_CALLBACK(geoclue_client_signal_cb),
- data);
+ user_data);
/* Start GeoClue client */
error = NULL;
@@ -250,9 +239,18 @@ on_name_appeared(GDBusConnection *conn, const gchar *name,
if (ret_v == NULL) {
g_printerr(_("Unable to start GeoClue client: %s.\n"),
error->message);
+ if (g_dbus_error_is_remote_error(error)) {
+ gchar *dbus_error = g_dbus_error_get_remote_error(
+ error);
+ if (g_strcmp0(dbus_error, DBUS_ACCESS_ERROR) == 0) {
+ print_denial_message();
+ }
+ g_free(dbus_error);
+ }
g_error_free(error);
g_object_unref(geoclue_client);
g_object_unref(geoclue_manager);
+ mark_error(state);
return;
}
@@ -264,34 +262,159 @@ static void
on_name_vanished(GDBusConnection *connection, const gchar *name,
gpointer user_data)
{
- get_location_data_t *data = (get_location_data_t *)user_data;
+ location_geoclue2_state_t *state = user_data;
+
+ g_mutex_lock(&state->lock);
- g_fprintf(stderr, _("Unable to connect to GeoClue.\n"));
+ state->available = 0;
- g_main_loop_quit(data->loop);
+ g_mutex_unlock(&state->lock);
+
+ pipeutils_signal(state->pipe_fd_write);
}
-int
-location_geoclue2_get_location(void *state,
- location_t *location)
+/* Callback when the pipe to the main thread is closed. */
+static gboolean
+on_pipe_closed(GIOChannel *channel, GIOCondition condition, gpointer user_data)
{
- get_location_data_t data;
- data.available = 0;
-
- guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM,
- "org.freedesktop.GeoClue2",
- G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
- on_name_appeared,
- on_name_vanished,
- &data, NULL);
- data.loop = g_main_loop_new(NULL, FALSE);
- g_main_loop_run(data.loop);
+ location_geoclue2_state_t *state = user_data;
+ g_main_loop_quit(state->loop);
+
+ return FALSE;
+}
+
+
+/* Run loop for location provider thread. */
+void *
+run_geoclue2_loop(void *state_)
+{
+ location_geoclue2_state_t *state = state_;
+
+ GMainContext *context = g_main_context_new();
+ g_main_context_push_thread_default(context);
+ state->loop = g_main_loop_new(context, FALSE);
+
+ guint watcher_id = g_bus_watch_name(
+ G_BUS_TYPE_SYSTEM,
+ "org.freedesktop.GeoClue2",
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ on_name_appeared,
+ on_name_vanished,
+ state, NULL);
+
+ /* Listen for closure of pipe */
+ GIOChannel *pipe_channel = g_io_channel_unix_new(state->pipe_fd_write);
+ GSource *pipe_source = g_io_create_watch(
+ pipe_channel, G_IO_IN | G_IO_HUP | G_IO_ERR);
+ g_source_set_callback(
+ pipe_source, (GSourceFunc)on_pipe_closed, state, NULL);
+ g_source_attach(pipe_source, context);
+
+ g_main_loop_run(state->loop);
+
+ g_source_unref(pipe_source);
+ g_io_channel_unref(pipe_channel);
+ close(state->pipe_fd_write);
g_bus_unwatch_name(watcher_id);
- if (!data.available) return -1;
+ g_main_loop_unref(state->loop);
+ g_main_context_unref(context);
+
+ return NULL;
+}
+
+int
+location_geoclue2_init(location_geoclue2_state_t *state)
+{
+#if !GLIB_CHECK_VERSION(2, 35, 0)
+ g_type_init();
+#endif
+ return 0;
+}
+
+int
+location_geoclue2_start(location_geoclue2_state_t *state)
+{
+ state->pipe_fd_read = -1;
+ state->pipe_fd_write = -1;
+
+ state->available = 0;
+ state->error = 0;
+ state->latitude = 0;
+ state->longitude = 0;
+
+ int pipefds[2];
+ int r = pipeutils_create_nonblocking(pipefds);
+ if (r < 0) {
+ fputs(_("Failed to start GeoClue2 provider!\n"), stderr);
+ return -1;
+ }
+
+ state->pipe_fd_read = pipefds[0];
+ state->pipe_fd_write = pipefds[1];
+
+ pipeutils_signal(state->pipe_fd_write);
+
+ g_mutex_init(&state->lock);
+ state->thread = g_thread_new("geoclue2", run_geoclue2_loop, state);
+
+ return 0;
+}
+
+void
+location_geoclue2_free(location_geoclue2_state_t *state)
+{
+ if (state->pipe_fd_read != -1) {
+ close(state->pipe_fd_read);
+ }
+
+ /* Closing the pipe should cause the thread to exit. */
+ g_thread_join(state->thread);
+ state->thread = NULL;
+
+ g_mutex_clear(&state->lock);
+}
+
+void
+location_geoclue2_print_help(FILE *f)
+{
+ fputs(_("Use the location as discovered by a GeoClue2 provider.\n"),
+ f);
+ fputs("\n", f);
+}
+
+int
+location_geoclue2_set_option(location_geoclue2_state_t *state,
+ const char *key, const char *value)
+{
+ fprintf(stderr, _("Unknown method parameter: `%s'.\n"), key);
+ return -1;
+}
+
+int
+location_geoclue2_get_fd(location_geoclue2_state_t *state)
+{
+ return state->pipe_fd_read;
+}
+
+int
+location_geoclue2_handle(
+ location_geoclue2_state_t *state,
+ location_t *location, int *available)
+{
+ pipeutils_handle_signal(state->pipe_fd_read);
+
+ g_mutex_lock(&state->lock);
+
+ int error = state->error;
+ location->lat = state->latitude;
+ location->lon = state->longitude;
+ *available = state->available;
+
+ g_mutex_unlock(&state->lock);
- *location = data.location;
+ if (error) return -1;
return 0;
}
diff --git a/src/location-geoclue2.h b/src/location-geoclue2.h
index c3c377b..2f04eea 100644
--- a/src/location-geoclue2.h
+++ b/src/location-geoclue2.h
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2014 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2014-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#ifndef REDSHIFT_LOCATION_GEOCLUE2_H
@@ -22,19 +22,34 @@
#include <stdio.h>
+#include <glib.h>
+
#include "redshift.h"
+typedef struct {
+ GMainLoop *loop;
+ GThread *thread;
+ GMutex lock;
+ int pipe_fd_read;
+ int pipe_fd_write;
+ int available;
+ int error;
+ float latitude;
+ float longitude;
+} location_geoclue2_state_t;
+
-int location_geoclue2_init(void *state);
-int location_geoclue2_start(void *state);
-void location_geoclue2_free(void *state);
+int location_geoclue2_init(location_geoclue2_state_t *state);
+int location_geoclue2_start(location_geoclue2_state_t *state);
+void location_geoclue2_free(location_geoclue2_state_t *state);
void location_geoclue2_print_help(FILE *f);
-int location_geoclue2_set_option(void *state,
+int location_geoclue2_set_option(location_geoclue2_state_t *state,
const char *key, const char *value);
-int location_geoclue2_get_location(void *state,
- location_t *loc);
+int location_geoclue2_get_fd(location_geoclue2_state_t *state);
+int location_geoclue2_handle(location_geoclue2_state_t *state,
+ location_t *location, int *available);
#endif /* ! REDSHIFT_LOCATION_GEOCLUE2_H */
diff --git a/src/location-manual.c b/src/location-manual.c
index c5da074..8ce324c 100644
--- a/src/location-manual.c
+++ b/src/location-manual.c
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#include <stdio.h>
@@ -101,10 +101,17 @@ location_manual_set_option(location_manual_state_t *state, const char *key,
}
int
-location_manual_get_location(location_manual_state_t *state,
- location_t *loc)
+location_manual_get_fd(location_manual_state_t *state)
{
- *loc = state->loc;
+ return -1;
+}
+
+int
+location_manual_handle(
+ location_manual_state_t *state, location_t *location, int *available)
+{
+ *location = state->loc;
+ *available = 1;
return 0;
}
diff --git a/src/location-manual.h b/src/location-manual.h
index e70d9cf..7094e9a 100644
--- a/src/location-manual.h
+++ b/src/location-manual.h
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2010-2014 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2010-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#ifndef REDSHIFT_LOCATION_MANUAL_H
@@ -38,8 +38,9 @@ void location_manual_print_help(FILE *f);
int location_manual_set_option(location_manual_state_t *state,
const char *key, const char *value);
-int location_manual_get_location(location_manual_state_t *state,
- location_t *loc);
+int location_manual_get_fd(location_manual_state_t *state);
+int location_manual_handle(
+ location_manual_state_t *state, location_t *location, int *available);
#endif /* ! REDSHIFT_LOCATION_MANUAL_H */
diff --git a/src/pipeutils.c b/src/pipeutils.c
new file mode 100644
index 0000000..75302cb
--- /dev/null
+++ b/src/pipeutils.c
@@ -0,0 +1,98 @@
+/* pipeutils.c -- Utilities for using pipes as signals
+ This file is part of Redshift.
+
+ Redshift is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Redshift is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Redshift. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com>
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+
+#ifndef _WIN32
+
+/* Create non-blocking set of pipe fds. */
+int
+pipeutils_create_nonblocking(int pipefds[2])
+{
+ int r = pipe(pipefds);
+ if (r == -1) {
+ perror("pipe");
+ return -1;
+ }
+
+ int flags = fcntl(pipefds[0], F_GETFL);
+ if (flags == -1) {
+ perror("fcntl");
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+ }
+
+ r = fcntl(pipefds[0], F_SETFL, flags | O_NONBLOCK);
+ if (r == -1) {
+ perror("fcntl");
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+ }
+
+ flags = fcntl(pipefds[1], F_GETFL);
+ if (flags == -1) {
+ perror("fcntl");
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+ }
+
+ r = fcntl(pipefds[1], F_SETFL, flags | O_NONBLOCK);
+ if (r == -1) {
+ perror("fcntl");
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return -1;
+ }
+
+ return 0;
+}
+
+#else /* _WIN32 */
+
+/* Create non-blocking set of pipe fds.
+
+ Not supported on Windows! Always fails. */
+int
+pipeutils_create_nonblocking(int pipefds[2])
+{
+ return -1;
+}
+
+#endif
+
+/* Signal on write-end of pipe. */
+void
+pipeutils_signal(int write_fd)
+{
+ write(write_fd, "", 1);
+}
+
+/* Mark signal as handled on read-end of pipe. */
+void
+pipeutils_handle_signal(int read_fd)
+{
+ char data;
+ read(read_fd, &data, 1);
+}
diff --git a/src/pipeutils.h b/src/pipeutils.h
new file mode 100644
index 0000000..69c3350
--- /dev/null
+++ b/src/pipeutils.h
@@ -0,0 +1,28 @@
+/* pipeutils.h -- Utilities for using pipes as signals
+ This file is part of Redshift.
+
+ Redshift is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Redshift is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Redshift. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright (c) 2017 Jon Lund Steffensen <jonlst@gmail.com>
+*/
+
+#ifndef REDSHIFT_PIPEUTILS_H
+#define REDSHIFT_PIPEUTILS_H
+
+int pipeutils_create_nonblocking(int pipefds[2]);
+
+void pipeutils_signal(int write_fd);
+void pipeutils_handle_signal(int read_fd);
+
+#endif /* ! REDSHIFT_PIPEUTILS_H */
diff --git a/src/redshift-gtk/Makefile.am b/src/redshift-gtk/Makefile.am
index c4ab24f..b7303a2 100644
--- a/src/redshift-gtk/Makefile.am
+++ b/src/redshift-gtk/Makefile.am
@@ -2,8 +2,9 @@
if ENABLE_GUI
redshift_gtk_PYTHON = \
__init__.py \
- utils.py \
- statusicon.py
+ controller.py \
+ statusicon.py \
+ utils.py
nodist_redshift_gtk_PYTHON = \
defs.py
redshift_gtkdir = $(pythondir)/redshift_gtk
diff --git a/src/redshift-gtk/controller.py b/src/redshift-gtk/controller.py
new file mode 100644
index 0000000..24c58ae
--- /dev/null
+++ b/src/redshift-gtk/controller.py
@@ -0,0 +1,227 @@
+# controller.py -- Redshift child process controller
+# 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 <http://www.gnu.org/licenses/>.
+
+# Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com>
+
+import os
+import re
+import fcntl
+import signal
+
+import gi
+gi.require_version('GLib', '2.0')
+
+from gi.repository import GLib, GObject
+
+from . import defs
+
+
+class RedshiftController(GObject.GObject):
+ """GObject wrapper around the Redshift child process."""
+
+ __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)),
+ 'error-occured': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
+ 'stopped': (GObject.SIGNAL_RUN_FIRST, None, ()),
+ }
+
+ def __init__(self, args):
+ """Initialize controller and start child process.
+
+ The parameter args is a list of command line arguments to pass on to
+ the child process. The "-v" argument is automatically added.
+ """
+ GObject.GObject.__init__(self)
+
+ # Initialize state variables
+ self._inhibited = False
+ self._temperature = 0
+ self._period = 'Unknown'
+ self._location = (0.0, 0.0)
+
+ # Start redshift with arguments
+ args.insert(0, os.path.join(defs.BINDIR, 'redshift'))
+ if '-v' not in args:
+ args.insert(1, '-v')
+
+ # Start child process with C locale so we can parse the output
+ env = os.environ.copy()
+ for key in ('LANG', 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES'):
+ env[key] = '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)
+
+ # Wrap remaining contructor in try..except to avoid that the child
+ # process is not closed properly.
+ try:
+ # Handle child input
+ # The buffer is encapsulated in a class so we
+ # can pass an instance to the child callback.
+ class InputBuffer(object):
+ buf = ''
+
+ self._input_buffer = InputBuffer()
+ self._error_buffer = InputBuffer()
+ self._errors = ''
+
+ # 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))
+
+ # Signal handler to relay USR1 signal to redshift process
+ def relay_signal_handler(signal):
+ os.kill(self._process[0], signal)
+ return True
+
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGUSR1,
+ relay_signal_handler, signal.SIGUSR1)
+ except:
+ self.termwait()
+ raise
+
+ @property
+ def inhibited(self):
+ """Current inhibition state."""
+ return self._inhibited
+
+ @property
+ def temperature(self):
+ """Current screen temperature."""
+ return self._temperature
+
+ @property
+ def period(self):
+ """Current period of day."""
+ return self._period
+
+ @property
+ def location(self):
+ """Current location."""
+ return self._location
+
+ def set_inhibit(self, inhibit):
+ """Set inhibition state."""
+ if inhibit != self._inhibited:
+ self._child_toggle_inhibit()
+
+ def _child_signal(self, sg):
+ """Send signal to child process."""
+ os.kill(self._process[0], sg)
+
+ def _child_toggle_inhibit(self):
+ """Sends a request to the child process to toggle state."""
+ self._child_signal(signal.SIGUSR1)
+
+ def _child_cb(self, pid, status, data=None):
+ """Called when the child process exists."""
+
+ # Empty stdout and stderr
+ for f in (self._process[2], self._process[3]):
+ while True:
+ buf = os.read(f, 256).decode('utf-8')
+ if buf == '':
+ break
+ if f == self._process[3]: # stderr
+ self._errors += buf
+
+ # Check exit status of child
+ try:
+ GLib.spawn_check_exit_status(status)
+ except GLib.GError:
+ self.emit('error-occured', self._errors)
+
+ GLib.spawn_close_pid(self._process[0])
+ self.emit('stopped')
+
+ def _child_key_change_cb(self, key, value):
+ """Called when the child process reports a change of internal state."""
+
+ def parse_coord(s):
+ """Parse coordinate like `42.0 N` or `91.5 W`."""
+ v, d = s.split(' ')
+ return float(v) * (1 if d in 'NE' else -1)
+
+ if key == 'Status':
+ new_inhibited = value != 'Enabled'
+ if new_inhibited != self._inhibited:
+ self._inhibited = new_inhibited
+ self.emit('inhibit-changed', new_inhibited)
+ elif key == 'Color temperature':
+ new_temperature = int(value.rstrip('K'), 10)
+ if new_temperature != self._temperature:
+ self._temperature = new_temperature
+ self.emit('temperature-changed', new_temperature)
+ elif key == 'Period':
+ new_period = value
+ if new_period != self._period:
+ self._period = new_period
+ self.emit('period-changed', new_period)
+ elif key == 'Location':
+ new_location = tuple(parse_coord(x) for x in value.split(', '))
+ if new_location != self._location:
+ self._location = new_location
+ self.emit('location-changed', *new_location)
+
+ def _child_stdout_line_cb(self, line):
+ """Called when the child process outputs a line to stdout."""
+ 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):
+ """Called when the child process has new data on stdout/stderr."""
+ 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
+ if stdout:
+ self._child_stdout_line_cb(first)
+ else:
+ self._errors += first + '\n'
+
+ return True
+
+ def terminate_child(self):
+ """Send SIGINT to child process."""
+ self._child_signal(signal.SIGINT)
+
+ def kill_child(self):
+ """Send SIGKILL to child process."""
+ self._child_signal(signal.SIGKILL)
diff --git a/src/redshift-gtk/statusicon.py b/src/redshift-gtk/statusicon.py
index 9d9835d..7fdf9af 100644
--- a/src/redshift-gtk/statusicon.py
+++ b/src/redshift-gtk/statusicon.py
@@ -14,25 +14,23 @@
# You should have received a copy of the GNU General Public License
# along with Redshift. If not, see <http://www.gnu.org/licenses/>.
-# Copyright (c) 2013-2014 Jon Lund Steffensen <jonlst@gmail.com>
+# Copyright (c) 2013-2017 Jon Lund Steffensen <jonlst@gmail.com>
-'''GUI status icon for Redshift.
+"""GUI status icon for Redshift.
The run method will try to start an appindicator for Redshift. If the
appindicator module isn't present it will fall back to a GTK status icon.
-'''
+"""
-import sys, os
-import fcntl
+import sys
import signal
-import re
import gettext
import gi
gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk, GLib, GObject
+from gi.repository import Gtk, GLib
try:
gi.require_version('AppIndicator3', '0.1')
@@ -40,218 +38,27 @@ try:
except (ImportError, ValueError):
appindicator = None
+from .controller import RedshiftController
from . import defs
from . import utils
_ = gettext.gettext
-class RedshiftController(GObject.GObject):
- '''A GObject wrapper around the child process'''
-
- __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)),
- 'error-occured': (GObject.SIGNAL_RUN_FIRST, None, (str,))
- }
-
- def __init__(self, args):
- '''Initialize controller and start child process
-
- The parameter args is a list of command line arguments to pass on to
- the child process. The "-v" argument is automatically added.'''
-
- GObject.GObject.__init__(self)
-
- # Initialize state variables
- self._inhibited = False
- self._temperature = 0
- self._period = 'Unknown'
- self._location = (0.0, 0.0)
-
- # Start redshift with arguments
- args.insert(0, os.path.join(defs.BINDIR, 'redshift'))
- if '-v' not in args:
- args.insert(1, '-v')
-
- # 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)
-
- # Wrap remaining contructor in try..except to avoid that the child
- # process is not closed properly.
- try:
- # Handle child input
- # The buffer is encapsulated in a class so we
- # can pass an instance to the child callback.
- class InputBuffer(object):
- buf = ''
-
- self._input_buffer = InputBuffer()
- self._error_buffer = InputBuffer()
- self._errors = ''
-
- # 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))
-
- # Signal handler to relay USR1 signal to redshift process
- def relay_signal_handler(signal):
- os.kill(self._process[0], signal)
- return True
-
- GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGUSR1,
- relay_signal_handler, signal.SIGUSR1)
- except:
- self.termwait()
- raise
-
- @property
- def inhibited(self):
- '''Current inhibition state'''
- return self._inhibited
-
- @property
- def temperature(self):
- '''Current screen temperature'''
- return self._temperature
-
- @property
- def period(self):
- '''Current period of day'''
- return self._period
-
- @property
- def location(self):
- '''Current location'''
- return self._location
-
- def set_inhibit(self, inhibit):
- '''Set inhibition state'''
- if inhibit != self._inhibited:
- self._child_toggle_inhibit()
-
- def _child_signal(self, sg):
- """Send signal to child process."""
- os.kill(self._process[0], sg)
-
- def _child_toggle_inhibit(self):
- '''Sends a request to the child process to toggle state'''
- self._child_signal(signal.SIGUSR1)
-
- def _child_cb(self, pid, status, data=None):
- '''Called when the child process exists'''
-
- # Empty stdout and stderr
- for f in (self._process[2], self._process[3]):
- while True:
- buf = os.read(f, 256).decode('utf-8')
- if buf == '':
- break
- if f == self._process[3]: # stderr
- self._errors += buf
-
- # Check exit status of child
- report_errors = False
- try:
- GLib.spawn_check_exit_status(status)
- except GLib.GError:
- self.emit('error-occured', self._errors)
-
- GLib.spawn_close_pid(self._process[0])
- Gtk.main_quit()
-
- def _child_key_change_cb(self, key, value):
- '''Called when the child process reports a change of internal state'''
-
- def parse_coord(s):
- '''Parse coordinate like `42.0 N` or `91.5 W`'''
- v, d = s.split(' ')
- return float(v) * (1 if d in 'NE' else -1)
-
- if key == 'Status':
- new_inhibited = value != 'Enabled'
- if new_inhibited != self._inhibited:
- self._inhibited = new_inhibited
- self.emit('inhibit-changed', new_inhibited)
- elif key == 'Color temperature':
- new_temperature = int(value.rstrip('K'), 10)
- if new_temperature != self._temperature:
- self._temperature = new_temperature
- self.emit('temperature-changed', new_temperature)
- elif key == 'Period':
- new_period = value
- if new_period != self._period:
- self._period = new_period
- self.emit('period-changed', new_period)
- elif key == 'Location':
- new_location = tuple(parse_coord(x) for x in value.split(', '))
- if new_location != self._location:
- self._location = new_location
- self.emit('location-changed', *new_location)
-
- def _child_stdout_line_cb(self, line):
- '''Called when the child process outputs a line to stdout'''
- 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):
- '''Called when the child process has new data on stdout/stderr'''
-
- 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
- if stdout:
- self._child_stdout_line_cb(first)
- else:
- self._errors += first + '\n'
-
- return True
-
- def terminate_child(self):
- """Send SIGINT to child process."""
- self._child_signal(signal.SIGINT)
-
- def kill_child(self):
- """Send SIGKILL to child process."""
- self._child_signal(signal.SIGKILL)
-
-
class RedshiftStatusIcon(object):
- '''The status icon tracking the RedshiftController'''
+ """The status icon tracking the RedshiftController."""
def __init__(self, controller):
- '''Creates a new instance of the status icon'''
+ """Creates a new instance of the status icon."""
self._controller = controller
if appindicator:
# Create indicator
- self.indicator = appindicator.Indicator.new('redshift',
- 'redshift-status-on',
- appindicator.IndicatorCategory.APPLICATION_STATUS)
+ self.indicator = appindicator.Indicator.new(
+ 'redshift',
+ 'redshift-status-on',
+ appindicator.IndicatorCategory.APPLICATION_STATUS)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
else:
# Create status icon
@@ -280,16 +87,17 @@ class RedshiftStatusIcon(object):
self.status_menu.append(suspend_menu_item)
# Add autostart option
- autostart_item = Gtk.CheckMenuItem.new_with_label(_('Autostart'))
- try:
- autostart_item.set_active(utils.get_autostart())
- except IOError as strerror:
- print(strerror)
- autostart_item.set_property('sensitive', False)
- else:
- autostart_item.connect('toggled', self.autostart_cb)
- finally:
- self.status_menu.append(autostart_item)
+ if utils.supports_autostart():
+ autostart_item = Gtk.CheckMenuItem.new_with_label(_('Autostart'))
+ try:
+ autostart_item.set_active(utils.get_autostart())
+ except IOError as strerror:
+ print(strerror)
+ autostart_item.set_property('sensitive', False)
+ else:
+ autostart_item.connect('toggled', self.autostart_cb)
+ finally:
+ self.status_menu.append(autostart_item)
# Add info action
info_item = Gtk.MenuItem.new_with_label(_('Info'))
@@ -302,44 +110,52 @@ class RedshiftStatusIcon(object):
self.status_menu.append(quit_item)
# Create info dialog
- self.info_dialog = Gtk.Dialog()
- self.info_dialog.set_title(_('Info'))
- self.info_dialog.add_button(_('Close'), Gtk.ButtonsType.CLOSE)
+ self.info_dialog = Gtk.Window(title=_('Info'))
self.info_dialog.set_resizable(False)
self.info_dialog.set_property('border-width', 6)
+ self.info_dialog.connect('delete-event', self.close_info_dialog_cb)
+
+ content_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
+ self.info_dialog.add(content_area)
+ content_area.show()
self.status_label = Gtk.Label()
self.status_label.set_alignment(0.0, 0.5)
self.status_label.set_padding(6, 6)
- self.info_dialog.get_content_area().pack_start(self.status_label, True, True, 0)
+ content_area.pack_start(self.status_label, True, True, 0)
self.status_label.show()
self.location_label = Gtk.Label()
self.location_label.set_alignment(0.0, 0.5)
self.location_label.set_padding(6, 6)
- self.info_dialog.get_content_area().pack_start(self.location_label, True, True, 0)
+ content_area.pack_start(self.location_label, True, True, 0)
self.location_label.show()
self.temperature_label = Gtk.Label()
self.temperature_label.set_alignment(0.0, 0.5)
self.temperature_label.set_padding(6, 6)
- self.info_dialog.get_content_area().pack_start(self.temperature_label, True, True, 0)
+ content_area.pack_start(self.temperature_label, True, True, 0)
self.temperature_label.show()
self.period_label = Gtk.Label()
self.period_label.set_alignment(0.0, 0.5)
self.period_label.set_padding(6, 6)
- self.info_dialog.get_content_area().pack_start(self.period_label, True, True, 0)
+ content_area.pack_start(self.period_label, True, True, 0)
self.period_label.show()
- self.info_dialog.connect('response', self.response_info_cb)
+ self.close_button = Gtk.Button(label=_('Close'))
+ content_area.pack_start(self.close_button, True, True, 0)
+ self.close_button.connect('clicked', self.close_info_dialog_cb)
+ self.close_button.show()
# 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(
+ 'temperature-changed', self.temperature_change_cb)
self._controller.connect('location-changed', self.location_change_cb)
self._controller.connect('error-occured', self.error_occured_cb)
+ self._controller.connect('stopped', self.controller_stopped_cb)
# Set info box text
self.change_inhibited(self._controller.inhibited)
@@ -362,18 +178,18 @@ class RedshiftStatusIcon(object):
self.suspend_timer = None
def remove_suspend_timer(self):
- '''Disable any previously set suspend timer'''
+ """Disable any previously set suspend timer."""
if self.suspend_timer is not None:
GLib.source_remove(self.suspend_timer)
self.suspend_timer = None
def suspend_cb(self, item, minutes):
- '''Callback that handles activation of a suspend timer
-
- The minutes parameter is the number of minutes to suspend. Even if redshift
- is not disabled when called, it will still set a suspend timer and
- reactive redshift when the timer is up.'''
+ """Callback that handles activation of a suspend timer.
+ The minutes parameter is the number of minutes to suspend. Even if
+ redshift is not disabled when called, it will still set a suspend timer
+ and reactive redshift when the timer is up.
+ """
# Inhibit
self._controller.set_inhibit(True)
@@ -382,29 +198,30 @@ class RedshiftStatusIcon(object):
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)
+ self.suspend_timer = GLib.timeout_add_seconds(
+ minutes * 60, self.reenable_cb)
def reenable_cb(self):
- '''Callback to reenable redshift when a suspend timer expires'''
+ """Callback to reenable redshift when a suspend timer expires."""
self._controller.set_inhibit(False)
def popup_menu_cb(self, widget, button, time, data=None):
- '''Callback when the popup menu on the status icon has to open'''
+ """Callback when the popup menu on the status icon has to open."""
self.status_menu.show_all()
self.status_menu.popup(None, None, Gtk.StatusIcon.position_menu,
self.status_icon, button, time)
def toggle_cb(self, widget, data=None):
- '''Callback when a request to toggle redshift was made'''
+ """Callback when a request to toggle redshift was made."""
self.remove_suspend_timer()
self._controller.set_inhibit(not self._controller.inhibited)
def toggle_item_cb(self, widget, data=None):
- '''Callback then a request to toggle redshift was made from a toggle item
+ """Callback when a request to toggle redshift was made.
This ensures that the state of redshift is synchronised with
- the toggle state of the widget (e.g. Gtk.CheckMenuItem).'''
-
+ the toggle state of the widget (e.g. Gtk.CheckMenuItem).
+ """
active = not self._controller.inhibited
if active != widget.get_active():
self.remove_suspend_timer()
@@ -412,20 +229,24 @@ class RedshiftStatusIcon(object):
# Info dialog callbacks
def show_info_cb(self, widget, data=None):
- '''Callback when the info dialog should be presented'''
+ """Callback when the info dialog should be presented."""
self.info_dialog.show()
def response_info_cb(self, widget, data=None):
- '''Callback when a button in the info dialog was activated'''
+ """Callback when a button in the info dialog was activated."""
+ self.info_dialog.hide()
+
+ def close_info_dialog_cb(self, widget, data=None):
+ """Callback when the info dialog is closed."""
self.info_dialog.hide()
+ return True
def update_status_icon(self):
- '''Update the status icon according to the internally recorded state
+ """Update the status icon according to the internally recorded state.
This should be called whenever the internally recorded state
- might have changed.'''
-
- # Update status icon
+ might have changed.
+ """
if appindicator:
if not self._controller.inhibited:
self.indicator.set_icon('redshift-status-on')
@@ -439,67 +260,79 @@ class RedshiftStatusIcon(object):
# State update functions
def inhibit_change_cb(self, controller, inhibit):
- '''Callback when controller changes inhibition status'''
+ """Callback when controller changes inhibition status."""
self.change_inhibited(inhibit)
def period_change_cb(self, controller, period):
- '''Callback when controller changes period'''
+ """Callback when controller changes period."""
self.change_period(period)
def temperature_change_cb(self, controller, temperature):
- '''Callback when controller changes temperature'''
+ """Callback when controller changes temperature."""
self.change_temperature(temperature)
def location_change_cb(self, controller, lat, lon):
- '''Callback when controlled changes location'''
+ """Callback when controlled changes location."""
self.change_location((lat, lon))
def error_occured_cb(self, controller, error):
- '''Callback when an error occurs in the controller'''
- error_dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR,
- Gtk.ButtonsType.CLOSE, '')
- error_dialog.set_markup('<b>Failed to run Redshift</b>\n<i>' + error + '</i>')
+ """Callback when an error occurs in the controller."""
+ error_dialog = Gtk.MessageDialog(
+ None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR,
+ Gtk.ButtonsType.CLOSE, '')
+ error_dialog.set_markup(
+ '<b>Failed to run Redshift</b>\n<i>' + error + '</i>')
error_dialog.run()
# Quit when the model dialog is closed
sys.exit(-1)
+ def controller_stopped_cb(self, controller):
+ """Callback when controlled is stopped successfully."""
+ Gtk.main_quit()
+
# Update interface
def change_inhibited(self, inhibited):
- '''Change interface to new inhibition status'''
+ """Change interface to new inhibition status."""
self.update_status_icon()
self.toggle_item.set_active(not inhibited)
- self.status_label.set_markup(_('<b>Status:</b> {}').format(_('Disabled') if inhibited else _('Enabled')))
+ self.status_label.set_markup(
+ _('<b>Status:</b> {}').format(
+ _('Disabled') if inhibited else _('Enabled')))
def change_temperature(self, temperature):
- '''Change interface to new temperature'''
- self.temperature_label.set_markup('<b>{}:</b> {}K'.format(_('Color temperature'), temperature))
+ """Change interface to new temperature."""
+ self.temperature_label.set_markup(
+ '<b>{}:</b> {}K'.format(_('Color temperature'), temperature))
self.update_tooltip_text()
def change_period(self, period):
- '''Change interface to new period'''
- self.period_label.set_markup('<b>{}:</b> {}'.format(_('Period'), period))
+ """Change interface to new period."""
+ self.period_label.set_markup(
+ '<b>{}:</b> {}'.format(_('Period'), period))
self.update_tooltip_text()
def change_location(self, location):
- '''Change interface to new location'''
- self.location_label.set_markup('<b>{}:</b> {}, {}'.format(_('Location'), *location))
+ """Change interface to new location."""
+ self.location_label.set_markup(
+ '<b>{}:</b> {}, {}'.format(_('Location'), *location))
def update_tooltip_text(self):
- '''Update text of tooltip status icon '''
+ """Update text of tooltip status icon."""
if not appindicator:
self.status_icon.set_tooltip_text('{}: {}K, {}: {}'.format(
_('Color temperature'), self._controller.temperature,
_('Period'), self._controller.period))
def autostart_cb(self, widget, data=None):
- '''Callback when a request to toggle autostart is made'''
+ """Callback when a request to toggle autostart is made."""
utils.set_autostart(widget.get_active())
def destroy_cb(self, widget, data=None):
- '''Callback when a request to quit the application is made'''
+ """Callback when a request to quit the application is made."""
if not appindicator:
self.status_icon.set_visible(False)
+ self.info_dialog.destroy()
self._controller.terminate_child()
return False
@@ -526,7 +359,7 @@ def run():
try:
# Create status icon
- s = RedshiftStatusIcon(c)
+ RedshiftStatusIcon(c)
# Run main loop
Gtk.main()
diff --git a/src/redshift-gtk/utils.py b/src/redshift-gtk/utils.py
index 73c95a9..9365356 100644
--- a/src/redshift-gtk/utils.py
+++ b/src/redshift-gtk/utils.py
@@ -20,8 +20,14 @@
import ctypes
import os
import sys
-from xdg import BaseDirectory as base
-from xdg import DesktopEntry as desktop
+
+try:
+ from xdg import BaseDirectory
+ from xdg import DesktopEntry
+ has_xdg = True
+except ImportError:
+ has_xdg = False
+
REDSHIFT_DESKTOP = 'redshift-gtk.desktop'
@@ -32,12 +38,13 @@ AUTOSTART_KEYS = (('Hidden', ('true', 'false')),
def open_autostart_file():
- autostart_dir = base.save_config_path("autostart")
+ autostart_dir = BaseDirectory.save_config_path("autostart")
autostart_file = os.path.join(autostart_dir, REDSHIFT_DESKTOP)
if not os.path.exists(autostart_file):
- desktop_files = list(base.load_data_paths("applications",
- REDSHIFT_DESKTOP))
+ desktop_files = list(
+ BaseDirectory.load_data_paths(
+ "applications", REDSHIFT_DESKTOP))
if not desktop_files:
raise IOError("Installed redshift desktop file not found!")
@@ -45,21 +52,33 @@ def open_autostart_file():
desktop_file_path = desktop_files[0]
# Read installed file
- dfile = desktop.DesktopEntry(desktop_file_path)
+ dfile = DesktopEntry.DesktopEntry(desktop_file_path)
for key, values in AUTOSTART_KEYS:
dfile.set(key, values[False])
dfile.write(filename=autostart_file)
else:
- dfile = desktop.DesktopEntry(autostart_file)
+ dfile = DesktopEntry.DesktopEntry(autostart_file)
return dfile, autostart_file
+
+def supports_autostart():
+ return has_xdg
+
+
def get_autostart():
+ if not has_xdg:
+ return False
+
dfile, path = open_autostart_file()
check_key, check_values = AUTOSTART_KEYS[0]
return dfile.get(check_key) == check_values[True]
+
def set_autostart(active):
+ if not has_xdg:
+ return
+
dfile, path = open_autostart_file()
for key, values in AUTOSTART_KEYS:
dfile.set(key, values[active])
@@ -67,6 +86,7 @@ def set_autostart(active):
def setproctitle(title):
+ """Set process title."""
title_bytes = title.encode(sys.getdefaultencoding(), 'replace')
buf = ctypes.create_string_buffer(title_bytes)
if 'linux' in sys.platform:
diff --git a/src/redshift.c b/src/redshift.c
index 6eefd7d..f46853b 100644
--- a/src/redshift.c
+++ b/src/redshift.c
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with Redshift. If not, see <http://www.gnu.org/licenses/>.
- Copyright (c) 2009-2015 Jon Lund Steffensen <jonlst@gmail.com>
+ Copyright (c) 2009-2017 Jon Lund Steffensen <jonlst@gmail.com>
*/
#ifdef HAVE_CONFIG_H
@@ -29,6 +29,21 @@
#include <locale.h>
#include <errno.h>
+/* poll.h is not available on Windows but there is no Windows location provider
+ using polling. On Windows, we just define some stubs to make things compile.
+ */
+#ifndef _WIN32
+# include <poll.h>
+#else
+#define POLLIN 0
+struct pollfd {
+ int fd;
+ short events;
+ short revents;
+};
+int poll(struct pollfd *fds, int nfds, int timeout) { abort(); return -1; }
+#endif
+
#if defined(HAVE_SIGNAL_H) && !defined(__WIN32__)
# include <signal.h>
#endif
@@ -81,10 +96,6 @@
#include "location-manual.h"
-#ifdef ENABLE_GEOCLUE
-# include "location-geoclue.h"
-#endif
-
#ifdef ENABLE_GEOCLUE2
# include "location-geoclue2.h"
#endif
@@ -195,28 +206,17 @@ static const gamma_method_t gamma_methods[] = {
/* Union of state data for location providers */
typedef union {
location_manual_state_t manual;
-#ifdef ENABLE_GEOCLUE
- location_geoclue_state_t geoclue;
+#ifdef ENABLE_GEOCLUE2
+ location_geoclue2_state_t geoclue2;
+#endif
+#ifdef ENABLE_CORELOCATION
+ location_corelocation_state_t corelocation;
#endif
} location_state_t;
/* Location provider method structs */
static const location_provider_t location_providers[] = {
-#ifdef ENABLE_GEOCLUE
- {
- "geoclue",
- (location_provider_init_func *)location_geoclue_init,
- (location_provider_start_func *)location_geoclue_start,
- (location_provider_free_func *)location_geoclue_free,
- (location_provider_print_help_func *)
- location_geoclue_print_help,
- (location_provider_set_option_func *)
- location_geoclue_set_option,
- (location_provider_get_location_func *)
- location_geoclue_get_location
- },
-#endif
#ifdef ENABLE_GEOCLUE2
{
"geoclue2",
@@ -227,8 +227,8 @@ static const location_provider_t location_providers[] = {
location_geoclue2_print_help,
(location_provider_set_option_func *)
location_geoclue2_set_option,
- (location_provider_get_location_func *)
- location_geoclue2_get_location
+ (location_provider_get_fd_func *)location_geoclue2_get_fd,
+ (location_provider_handle_func *)location_geoclue2_handle
},
#endif
#ifdef ENABLE_CORELOCATION
@@ -241,8 +241,8 @@ static const location_provider_t location_providers[] = {
location_corelocation_print_help,
(location_provider_set_option_func *)
location_corelocation_set_option,
- (location_provider_get_location_func *)
- location_corelocation_get_location
+ (location_provider_get_fd_func *)location_corelocation_get_fd,
+ (location_provider_handle_func *)location_corelocation_handle
},
#endif
{
@@ -254,8 +254,8 @@ static const location_provider_t location_providers[] = {
location_manual_print_help,
(location_provider_set_option_func *)
location_manual_set_option,
- (location_provider_get_location_func *)
- location_manual_get_location
+ (location_provider_get_fd_func *)location_manual_get_fd,
+ (location_provider_handle_func *)location_manual_handle
},
{ NULL }
};
@@ -292,6 +292,9 @@ static const location_provider_t location_providers[] = {
#define SLEEP_DURATION 5000
#define SLEEP_DURATION_SHORT 100
+/* Length of fade in numbers of short sleep durations. */
+#define FADE_LENGTH 40
+
/* Program modes. */
typedef enum {
PROGRAM_MODE_CONTINUAL,
@@ -301,12 +304,22 @@ typedef enum {
PROGRAM_MODE_MANUAL
} program_mode_t;
+/* Time range.
+ Fields are offsets from midnight in seconds. */
+typedef struct {
+ int start;
+ int end;
+} time_range_t;
+
/* Transition scheme.
The solar elevations at which the transition begins/ends,
and the association color settings. */
typedef struct {
double high;
double low;
+ int use_time; /* When enabled, ignore elevation and use time ranges. */
+ time_range_t dawn;
+ time_range_t dusk;
color_setting_t day;
color_setting_t night;
} transition_scheme_t;
@@ -321,10 +334,25 @@ static const char *period_names[] = {
};
-/* Determine which period we are currently in. */
+/* Determine which period we are currently in based on time offset. */
static period_t
-get_period(const transition_scheme_t *transition,
- double elevation)
+get_period_from_time(const transition_scheme_t *transition, int time_offset)
+{
+ if (time_offset < transition->dawn.start ||
+ time_offset >= transition->dusk.end) {
+ return PERIOD_NIGHT;
+ } else if (time_offset >= transition->dawn.end &&
+ time_offset < transition->dusk.start) {
+ return PERIOD_DAYTIME;
+ } else {
+ return PERIOD_TRANSITION;
+ }
+}
+
+/* Determine which period we are currently in based on solar elevation. */
+static period_t
+get_period_from_elevation(
+ const transition_scheme_t *transition, double elevation)
{
if (elevation < transition->low) {
return PERIOD_NIGHT;
@@ -335,10 +363,31 @@ get_period(const transition_scheme_t *transition,
}
}
-/* Determine how far through the transition we are. */
+/* Determine how far through the transition we are based on time offset. */
+static double
+get_transition_progress_from_time(
+ const transition_scheme_t *transition, int time_offset)
+{
+ if (time_offset < transition->dawn.start ||
+ time_offset >= transition->dusk.end) {
+ return 0.0;
+ } else if (time_offset < transition->dawn.end) {
+ return (transition->dawn.start - time_offset) /
+ (double)(transition->dawn.start -
+ transition->dawn.end);
+ } else if (time_offset > transition->dusk.start) {
+ return (transition->dusk.end - time_offset) /
+ (double)(transition->dusk.end -
+ transition->dusk.start);
+ } else {
+ return 1.0;
+ }
+}
+
+/* Determine how far through the transition we are based on elevation. */
static double
-get_transition_progress(const transition_scheme_t *transition,
- double elevation)
+get_transition_progress_from_elevation(
+ const transition_scheme_t *transition, double elevation)
{
if (elevation < transition->low) {
return 0.0;
@@ -350,6 +399,16 @@ get_transition_progress(const transition_scheme_t *transition,
}
}
+/* Return number of seconds since midnight from timestamp. */
+static int
+get_seconds_since_midnight(double timestamp)
+{
+ time_t t = (time_t)timestamp;
+ struct tm tm;
+ localtime_r(&t, &tm);
+ return tm.tm_sec + tm.tm_min * 60 + tm.tm_hour * 3600;
+}
+
/* Print verbose description of the given period. */
static void
print_period(period_t period, double transition)
@@ -389,27 +448,52 @@ print_location(const location_t *location)
fabs(location->lon), location->lon >= 0.f ? east : west);
}
-/* Interpolate color setting structs based on solar elevation */
+/* Interpolate color setting structs given alpha. */
static void
-interpolate_color_settings(const transition_scheme_t *transition,
- double elevation,
- color_setting_t *result)
+interpolate_color_settings(
+ const color_setting_t *first,
+ const color_setting_t *second,
+ double alpha,
+ color_setting_t *result)
+{
+ alpha = CLAMP(0.0, alpha, 1.0);
+
+ result->temperature = (1.0-alpha)*first->temperature +
+ alpha*second->temperature;
+ result->brightness = (1.0-alpha)*first->brightness +
+ alpha*second->brightness;
+ for (int i = 0; i < 3; i++) {
+ result->gamma[i] = (1.0-alpha)*first->gamma[i] +
+ alpha*second->gamma[i];
+ }
+}
+
+/* Interpolate color setting structs transition scheme. */
+static void
+interpolate_transition_scheme(
+ const transition_scheme_t *transition,
+ double alpha,
+ color_setting_t *result)
{
const color_setting_t *day = &transition->day;
const color_setting_t *night = &transition->night;
- double alpha = (transition->low - elevation) /
- (transition->low - transition->high);
alpha = CLAMP(0.0, alpha, 1.0);
+ interpolate_color_settings(night, day, alpha, result);
+}
- result->temperature = (1.0-alpha)*night->temperature +
- alpha*day->temperature;
- result->brightness = (1.0-alpha)*night->brightness +
- alpha*day->brightness;
- for (int i = 0; i < 3; i++) {
- result->gamma[i] = (1.0-alpha)*night->gamma[i] +
- alpha*day->gamma[i];
- }
+/* Return 1 if color settings have major differences, otherwise 0.
+ Used to determine if a fade should be applied in continual mode. */
+static int
+color_setting_diff_is_major(
+ const color_setting_t *first,
+ const color_setting_t *second)
+{
+ return (abs(first->temperature - second->temperature) > 25 ||
+ fabsf(first->brightness - second->brightness) > 0.1 ||
+ fabsf(first->gamma[0] - second->gamma[0]) > 0.1 ||
+ fabsf(first->gamma[1] - second->gamma[1]) > 0.1 ||
+ fabsf(first->gamma[2] - second->gamma[2]) > 0.1);
}
@@ -435,14 +519,14 @@ print_help(const char *program_name)
no-wrap */
fputs(_(" -h\t\tDisplay this help message\n"
" -v\t\tVerbose output\n"
- " -V\t\tShow program version\n"), stdout);
+ " -V\t\tShow program version\n"), stdout);
fputs("\n", stdout);
/* TRANSLATORS: help output 4
`list' must not be translated
no-wrap */
fputs(_(" -b DAY:NIGHT\tScreen brightness to apply (between 0.1 and 1.0)\n"
- " -c FILE\tLoad settings from specified configuration file\n"
+ " -c FILE\tLoad settings from specified configuration file\n"
" -g R:G:B\tAdditional gamma correction to apply\n"
" -l LAT:LON\tYour current location\n"
" -l PROVIDER\tSelect provider for automatic"
@@ -455,17 +539,16 @@ print_help(const char *program_name)
" -O TEMP\tOne shot manual mode (set color temperature)\n"
" -p\t\tPrint mode (only print parameters and exit)\n"
" -x\t\tReset mode (remove adjustment from screen)\n"
- " -r\t\tDisable temperature transitions\n"
+ " -r\t\tDisable fading between color temperatures\n"
" -t DAY:NIGHT\tColor temperature to set at daytime/night\n"),
stdout);
fputs("\n", stdout);
/* TRANSLATORS: help output 5 */
- printf(_("The neutral temperature is %uK. Using this value will not\n"
- "change the color temperature of the display. Setting the\n"
- "color temperature to a value higher than this results in\n"
- "more blue light, and setting a lower value will result in\n"
- "more red light.\n"),
+ printf(_("The neutral temperature is %uK. Using this value will not change "
+ "the color\ntemperature of the display. Setting the color temperature "
+ "to a value higher\nthan this results in more blue light, and setting "
+ "a lower value will result in\nmore red light.\n"),
NEUTRAL_TEMP);
fputs("\n", stdout);
@@ -726,6 +809,57 @@ parse_brightness_string(const char *str, float *bright_day, float *bright_night)
}
}
+/* Parse transition time string e.g. "04:50". Returns negative on failure,
+ otherwise the parsed time is returned as seconds since midnight. */
+static int
+parse_transition_time(const char *str, const char **end)
+{
+ const char *min = NULL;
+ errno = 0;
+ long hours = strtol(str, (char **)&min, 10);
+ if (errno != 0 || min == str || min[0] != ':' ||
+ hours < 0 || hours >= 24) {
+ return -1;
+ }
+
+ min += 1;
+ errno = 0;
+ long minutes = strtol(min, (char **)end, 10);
+ if (errno != 0 || *end == min || minutes < 0 || minutes >= 60) {
+ return -1;
+ }
+
+ return minutes * 60 + hours * 3600;
+}
+
+/* Parse transition range string e.g. "04:50-6:20". Returns negative on
+ failure, otherwise zero. Parsed start and end times are returned as seconds
+ since midnight. */
+static int
+parse_transition_range(const char *str, time_range_t *range)
+{
+ const char *next = NULL;
+ int start_time = parse_transition_time(str, &next);
+ if (start_time < 0) return -1;
+
+ int end_time;
+ if (next[0] == '\0') {
+ end_time = start_time;
+ } else if (next[0] == '-') {
+ next += 1;
+ const char *end = NULL;
+ end_time = parse_transition_time(next, &end);
+ if (end_time < 0 || end[0] != '\0') return -1;
+ } else {
+ return -1;
+ }
+
+ range->start = start_time;
+ range->end = end_time;
+
+ return 0;
+}
+
/* Check whether gamma is within allowed levels. */
static int
gamma_is_valid(const float gamma[3])
@@ -736,7 +870,33 @@ gamma_is_valid(const float gamma[3])
gamma[1] > MAX_GAMMA ||
gamma[2] < MIN_GAMMA ||
gamma[2] > MAX_GAMMA);
+}
+/* Check whether location is valid.
+ Prints error message on stderr and returns 0 if invalid, otherwise
+ returns 1. */
+static int
+location_is_valid(const location_t *location)
+{
+ /* Latitude */
+ if (location->lat < MIN_LAT || location->lat > MAX_LAT) {
+ /* TRANSLATORS: Append degree symbols if possible. */
+ fprintf(stderr,
+ _("Latitude must be between %.1f and %.1f.\n"),
+ MIN_LAT, MAX_LAT);
+ return 0;
+ }
+
+ /* Longitude */
+ if (location->lon < MIN_LON || location->lon > MAX_LON) {
+ /* TRANSLATORS: Append degree symbols if possible. */
+ fprintf(stderr,
+ _("Longitude must be between"
+ " %.1f and %.1f.\n"), MIN_LON, MAX_LON);
+ return 0;
+ }
+
+ return 1;
}
static const gamma_method_t *
@@ -769,88 +929,171 @@ find_location_provider(const char *name)
return provider;
}
+/* Wait for location to become available from provider.
+ Waits until timeout (milliseconds) has elapsed or forever if timeout
+ is -1. Writes location to loc. Returns -1 on error,
+ 0 if timeout was reached, 1 if location became available. */
+static int
+provider_get_location(
+ const location_provider_t *provider, location_state_t *state,
+ int timeout, location_t *loc)
+{
+ int available = 0;
+ struct pollfd pollfds[1];
+ while (!available) {
+ int loc_fd = provider->get_fd(state);
+ if (loc_fd >= 0) {
+ /* Provider is dynamic. */
+ /* TODO: This should use a monotonic time source. */
+ double now;
+ int r = systemtime_get_time(&now);
+ if (r < 0) {
+ fputs(_("Unable to read system time.\n"),
+ stderr);
+ return -1;
+ }
+
+ /* Poll on file descriptor until ready. */
+ pollfds[0].fd = loc_fd;
+ pollfds[0].events = POLLIN;
+ r = poll(pollfds, 1, timeout);
+ if (r < 0) {
+ perror("poll");
+ return -1;
+ } else if (r == 0) {
+ return 0;
+ }
+
+ double later;
+ r = systemtime_get_time(&later);
+ if (r < 0) {
+ fputs(_("Unable to read system time.\n"),
+ stderr);
+ return -1;
+ }
+
+ /* Adjust timeout by elapsed time */
+ if (timeout >= 0) {
+ timeout -= (later - now) * 1000;
+ timeout = timeout < 0 ? 0 : timeout;
+ }
+ }
+
+
+ int r = provider->handle(state, loc, &available);
+ if (r < 0) return -1;
+ }
+
+ return 1;
+}
+
+/* Easing function for fade.
+ See https://github.com/mietek/ease-tween */
+static double
+ease_fade(double t)
+{
+ if (t <= 0) return 0;
+ if (t >= 1) return 1;
+ return 1.0042954579734844 * exp(
+ -6.4041738958415664 * exp(-7.2908241330981340 * t));
+}
+
/* Run continual mode loop
This is the main loop of the continual mode which keeps track of the
current time and continuously updates the screen to the appropriate
color temperature. */
static int
-run_continual_mode(const location_t *loc,
+run_continual_mode(const location_provider_t *provider,
+ location_state_t *location_state,
const transition_scheme_t *scheme,
const gamma_method_t *method,
gamma_state_t *state,
- int transition, int verbose)
+ int use_fade, int verbose)
{
int r;
- /* Make an initial transition from 6500K */
- int short_trans_delta = -1;
- int short_trans_len = 10;
-
- /* Amount of adjustment to apply. At zero the color
- temperature will be exactly as calculated, and at one it
- will be exactly 6500K. */
- double adjustment_alpha = 1.0;
+ /* Short fade parameters */
+ int fade_length = 0;
+ int fade_time = 0;
+ color_setting_t fade_start_interp;
r = signals_install_handlers();
if (r < 0) {
return r;
}
- if (verbose) {
- printf(_("Status: %s\n"), _("Enabled"));
+ /* Save previous parameters so we can avoid printing status updates if
+ the values did not change. */
+ period_t prev_period = PERIOD_NONE;
+
+ /* Previous target color setting and current actual color setting.
+ Actual color setting takes into account the current color fade. */
+ color_setting_t prev_target_interp =
+ { NEUTRAL_TEMP, { 1.0, 1.0, 1.0 }, 1.0 };
+ color_setting_t interp =
+ { NEUTRAL_TEMP, { 1.0, 1.0, 1.0 }, 1.0 };
+
+ location_t loc = { NAN, NAN };
+ int need_location = !scheme->use_time;
+ if (need_location) {
+ fputs(_("Waiting for initial location"
+ " to become available...\n"), stderr);
+
+ /* Get initial location from provider */
+ r = provider_get_location(provider, location_state, -1, &loc);
+ if (r < 0) {
+ fputs(_("Unable to get location"
+ " from provider.\n"), stderr);
+ return -1;
+ }
+
+ if (!location_is_valid(&loc)) {
+ fputs(_("Invalid location returned from provider.\n"),
+ stderr);
+ return -1;
+ }
+
+ print_location(&loc);
}
- /* Save previous colors so we can avoid
- printing status updates if the values
- did not change. */
- period_t prev_period = PERIOD_NONE;
- color_setting_t prev_interp =
- { -1, { NAN, NAN, NAN }, NAN };
+ if (verbose) {
+ printf(_("Color temperature: %uK\n"), interp.temperature);
+ printf(_("Brightness: %.2f\n"), interp.brightness);
+ }
/* Continuously adjust color temperature */
int done = 0;
+ int prev_disabled = 1;
int disabled = 0;
+ int location_available = 1;
while (1) {
/* Check to see if disable signal was caught */
- if (disable) {
- short_trans_len = 2;
- if (!disabled) {
- /* Transition to disabled state */
- short_trans_delta = 1;
- } else {
- /* Transition back to enabled */
- short_trans_delta = -1;
- }
+ if (disable && !done) {
disabled = !disabled;
disable = 0;
-
- if (verbose) {
- printf(_("Status: %s\n"), disabled ?
- _("Disabled") : _("Enabled"));
- }
}
/* Check to see if exit signal was caught */
if (exiting) {
if (done) {
- /* On second signal stop the ongoing
- transition */
- short_trans_delta = 0;
- adjustment_alpha = 0.0;
+ /* On second signal stop the ongoing fade. */
+ break;
} else {
- if (!disabled) {
- /* Make a short transition
- back to 6500K */
- short_trans_delta = 1;
- short_trans_len = 2;
- }
-
done = 1;
+ disabled = 1;
}
exiting = 0;
}
+ /* Print status change */
+ if (verbose && disabled != prev_disabled) {
+ printf(_("Status: %s\n"), disabled ?
+ _("Disabled") : _("Enabled"));
+ }
+
+ prev_disabled = disabled;
+
/* Read timestamp */
double now;
r = systemtime_get_time(&now);
@@ -859,36 +1102,51 @@ run_continual_mode(const location_t *loc,
return -1;
}
- /* Skip over transition if transitions are disabled */
- int set_adjustments = 0;
- if (!transition) {
- if (short_trans_delta) {
- adjustment_alpha = short_trans_delta < 0 ?
- 0.0 : 1.0;
- short_trans_delta = 0;
- set_adjustments = 1;
- }
+ period_t period;
+ double transition_prog;
+ if (scheme->use_time) {
+ int time_offset = get_seconds_since_midnight(now);
+
+ period = get_period_from_time(scheme, time_offset);
+ transition_prog = get_transition_progress_from_time(
+ scheme, time_offset);
+ } else {
+ /* Current angular elevation of the sun */
+ double elevation = solar_elevation(
+ now, loc.lat, loc.lon);
+
+ period = get_period_from_elevation(scheme, elevation);
+ transition_prog =
+ get_transition_progress_from_elevation(
+ scheme, elevation);
}
- /* Current angular elevation of the sun */
- double elevation = solar_elevation(now, loc->lat,
- loc->lon);
+ /* Use transition progress to get target color
+ temperature. */
+ color_setting_t target_interp;
+ interpolate_transition_scheme(
+ scheme, transition_prog, &target_interp);
+
+ if (disabled) {
+ /* Reset to neutral */
+ target_interp.temperature = NEUTRAL_TEMP;
+ target_interp.brightness = 1.0;
+ target_interp.gamma[0] = 1.0;
+ target_interp.gamma[1] = 1.0;
+ target_interp.gamma[2] = 1.0;
+ }
- /* Use elevation of sun to set color temperature */
- color_setting_t interp;
- interpolate_color_settings(scheme, elevation, &interp);
+ if (done) {
+ period = PERIOD_NONE;
+ }
/* Print period if it changed during this update,
- or if we are in transition. In transition we
+ or if we are in the transition period. In transition we
print the progress, so we always print it in
that case. */
- period_t period = get_period(scheme, elevation);
if (verbose && (period != prev_period ||
period == PERIOD_TRANSITION)) {
- double transition =
- get_transition_progress(scheme,
- elevation);
- print_period(period, transition);
+ print_period(period, transition_prog);
}
/* Activate hooks if period changed */
@@ -896,66 +1154,135 @@ run_continual_mode(const location_t *loc,
hooks_signal_period_change(prev_period, period);
}
- /* Ongoing short transition */
- if (short_trans_delta) {
- /* Calculate alpha */
- adjustment_alpha += short_trans_delta * 0.1 /
- (float)short_trans_len;
-
- /* Stop transition when done */
- if (adjustment_alpha <= 0.0 ||
- adjustment_alpha >= 1.0) {
- short_trans_delta = 0;
+ /* Start fade if the parameter differences are too big to apply
+ instantly. */
+ if (use_fade) {
+ if ((fade_length == 0 &&
+ color_setting_diff_is_major(
+ &interp,
+ &target_interp)) ||
+ (fade_length != 0 &&
+ color_setting_diff_is_major(
+ &target_interp,
+ &prev_target_interp))) {
+ fade_length = FADE_LENGTH;
+ fade_time = 0;
+ fade_start_interp = interp;
}
-
- /* Clamp alpha value */
- adjustment_alpha = CLAMP(0.0, adjustment_alpha, 1.0);
}
- /* Interpolate between 6500K and calculated
- temperature */
- interp.temperature = adjustment_alpha*6500 +
- (1.0-adjustment_alpha)*interp.temperature;
+ /* Handle ongoing fade */
+ if (fade_length != 0) {
+ fade_time += 1;
+ double frac = fade_time / (double)fade_length;
+ double alpha = CLAMP(0.0, ease_fade(frac), 1.0);
+
+ interpolate_color_settings(
+ &fade_start_interp, &target_interp, alpha,
+ &interp);
- interp.brightness = adjustment_alpha*1.0 +
- (1.0-adjustment_alpha)*interp.brightness;
+ if (fade_time > fade_length) {
+ fade_time = 0;
+ fade_length = 0;
+ }
+ } else {
+ interp = target_interp;
+ }
- /* Quit loop when done */
- if (done && !short_trans_delta) break;
+ /* Break loop when done and final fade is over */
+ if (done && fade_length == 0) break;
if (verbose) {
- if (interp.temperature !=
- prev_interp.temperature) {
+ if (prev_target_interp.temperature !=
+ target_interp.temperature) {
printf(_("Color temperature: %uK\n"),
- interp.temperature);
+ target_interp.temperature);
}
- if (interp.brightness !=
- prev_interp.brightness) {
+ if (prev_target_interp.brightness !=
+ target_interp.brightness) {
printf(_("Brightness: %.2f\n"),
- interp.brightness);
+ target_interp.brightness);
}
}
/* Adjust temperature */
- if (!disabled || short_trans_delta || set_adjustments) {
- r = method->set_temperature(state, &interp);
+ r = method->set_temperature(state, &interp);
+ if (r < 0) {
+ fputs(_("Temperature adjustment failed.\n"),
+ stderr);
+ return -1;
+ }
+
+ /* Save period and target color setting as previous */
+ prev_period = period;
+ prev_target_interp = target_interp;
+
+ /* Sleep length depends on whether a fade is ongoing. */
+ int delay = SLEEP_DURATION;
+ if (fade_length != 0) {
+ delay = SLEEP_DURATION_SHORT;
+ }
+
+ /* Update location. */
+ int loc_fd = -1;
+ if (need_location) {
+ loc_fd = provider->get_fd(location_state);
+ }
+
+ if (loc_fd >= 0) {
+ /* Provider is dynamic. */
+ struct pollfd pollfds[1];
+ pollfds[0].fd = loc_fd;
+ pollfds[0].events = POLLIN;
+ int r = poll(pollfds, 1, delay);
if (r < 0) {
- fputs(_("Temperature adjustment"
- " failed.\n"), stderr);
+ if (errno == EINTR) continue;
+ perror("poll");
+ fputs(_("Unable to get location"
+ " from provider.\n"), stderr);
return -1;
+ } else if (r == 0) {
+ continue;
}
- }
- /* Save temperature as previous */
- prev_period = period;
- memcpy(&prev_interp, &interp,
- sizeof(color_setting_t));
+ /* Get new location and availability
+ information. */
+ location_t new_loc;
+ int new_available;
+ r = provider->handle(
+ location_state, &new_loc,
+ &new_available);
+ if (r < 0) {
+ fputs(_("Unable to get location"
+ " from provider.\n"), stderr);
+ return -1;
+ }
+
+ if (!new_available &&
+ new_available != location_available) {
+ fputs(_("Location is temporarily"
+ " unavailable; Using previous"
+ " location until it becomes"
+ " available...\n"), stderr);
+ }
- /* Sleep for 5 seconds or 0.1 second. */
- if (short_trans_delta) {
- systemtime_msleep(SLEEP_DURATION_SHORT);
+ if (new_available &&
+ (new_loc.lat != loc.lat ||
+ new_loc.lon != loc.lon ||
+ new_available != location_available)) {
+ loc = new_loc;
+ print_location(&loc);
+ }
+
+ location_available = new_available;
+
+ if (!location_is_valid(&loc)) {
+ fputs(_("Invalid location returned"
+ " from provider.\n"), stderr);
+ return -1;
+ }
} else {
- systemtime_msleep(SLEEP_DURATION);
+ systemtime_msleep(delay);
}
}
@@ -965,6 +1292,7 @@ run_continual_mode(const location_t *loc,
return 0;
}
+
int
main(int argc, char *argv[])
{
@@ -983,11 +1311,17 @@ main(int argc, char *argv[])
/* Initialize settings to NULL values. */
char *config_filepath = NULL;
- /* Settings for day, night and transition.
+ /* Settings for day, night and transition period.
Initialized to indicate that the values are not set yet. */
transition_scheme_t scheme =
{ TRANSITION_HIGH, TRANSITION_LOW };
+ scheme.use_time = 0;
+ scheme.dawn.start = -1;
+ scheme.dawn.end = -1;
+ scheme.dusk.start = -1;
+ scheme.dusk.end = -1;
+
scheme.day.temperature = -1;
scheme.day.gamma[0] = NAN;
scheme.day.brightness = NAN;
@@ -1005,7 +1339,7 @@ main(int argc, char *argv[])
const location_provider_t *provider = NULL;
char *provider_args = NULL;
- int transition = -1;
+ int use_fade = -1;
program_mode_t mode = PROGRAM_MODE_CONTINUAL;
int verbose = 0;
char *s;
@@ -1135,7 +1469,7 @@ main(int argc, char *argv[])
mode = PROGRAM_MODE_PRINT;
break;
case 'r':
- transition = 0;
+ use_fade = 0;
break;
case 't':
s = strchr(optarg, ':');
@@ -1194,10 +1528,13 @@ main(int argc, char *argv[])
scheme.night.temperature =
atoi(setting->value);
}
- } else if (strcasecmp(setting->name,
- "transition") == 0) {
- if (transition < 0) {
- transition = !!atoi(setting->value);
+ } else if (strcasecmp(
+ setting->name, "transition") == 0 ||
+ strcasecmp(setting->name, "fade") == 0) {
+ /* "fade" is preferred, "transition" is
+ deprecated as the setting key. */
+ if (use_fade < 0) {
+ use_fade = !!atoi(setting->value);
}
} else if (strcasecmp(setting->name,
"brightness") == 0) {
@@ -1290,6 +1627,34 @@ main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
}
+ } else if (strcasecmp(setting->name,
+ "dawn-time") == 0) {
+ if (scheme.dawn.start < 0) {
+ int r = parse_transition_range(
+ setting->value, &scheme.dawn);
+ if (r < 0) {
+ fprintf(stderr, _("Malformed"
+ " dawn-time"
+ " setting"
+ " `%s'.\n"),
+ setting->value);
+ exit(EXIT_FAILURE);
+ }
+ }
+ } else if (strcasecmp(setting->name,
+ "dusk-time") == 0) {
+ if (scheme.dusk.start < 0) {
+ int r = parse_transition_range(
+ setting->value, &scheme.dusk);
+ if (r < 0) {
+ fprintf(stderr, _("Malformed"
+ " dusk-time"
+ " setting"
+ " `%s'.\n"),
+ setting->value);
+ exit(EXIT_FAILURE);
+ }
+ }
} else {
fprintf(stderr, _("Unknown configuration"
" setting `%s'.\n"),
@@ -1326,17 +1691,38 @@ main(int argc, char *argv[])
scheme.night.gamma[2] = DEFAULT_GAMMA;
}
- if (transition < 0) transition = 1;
+ if (use_fade < 0) use_fade = 1;
- location_t loc = { NAN, NAN };
+ if (scheme.dawn.start >= 0 || scheme.dawn.end >= 0 ||
+ scheme.dusk.start >= 0 || scheme.dusk.end >= 0) {
+ if (scheme.dawn.start < 0 || scheme.dawn.end < 0 ||
+ scheme.dusk.start < 0 || scheme.dusk.end < 0) {
+ fputs(_("Partitial time-configuration not"
+ " supported!\n"), stderr);
+ exit(EXIT_FAILURE);
+ }
- /* Initialize location provider. If provider is NULL
+ if (scheme.dawn.start > scheme.dawn.end ||
+ scheme.dawn.end > scheme.dusk.start ||
+ scheme.dusk.start > scheme.dusk.end) {
+ fputs(_("Invalid dawn/dusk time configuration!\n"),
+ stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ scheme.use_time = 1;
+ }
+
+ /* Initialize location provider if needed. If provider is NULL
try all providers until one that works is found. */
location_state_t location_state;
/* Location is not needed for reset mode and manual mode. */
- if (mode != PROGRAM_MODE_RESET &&
- mode != PROGRAM_MODE_MANUAL) {
+ int need_location =
+ mode != PROGRAM_MODE_RESET &&
+ mode != PROGRAM_MODE_MANUAL &&
+ !scheme.use_time;
+ if (need_location) {
if (provider != NULL) {
/* Use provider specified on command line. */
r = provider_try_start(provider, &location_state,
@@ -1374,44 +1760,27 @@ main(int argc, char *argv[])
}
}
- /* Get current location. */
- r = provider->get_location(&location_state, &loc);
- if (r < 0) {
- fputs(_("Unable to get location from provider.\n"),
- stderr);
- exit(EXIT_FAILURE);
+ /* Solar elevations */
+ if (scheme.high < scheme.low) {
+ fprintf(stderr,
+ _("High transition elevation cannot be lower than"
+ " the low transition elevation.\n"));
+ exit(EXIT_FAILURE);
}
- provider->free(&location_state);
-
if (verbose) {
- print_location(&loc);
-
- printf(_("Temperatures: %dK at day, %dK at night\n"),
- scheme.day.temperature,
- scheme.night.temperature);
-
- /* TRANSLATORS: Append degree symbols if possible. */
+ /* TRANSLATORS: Append degree symbols if possible. */
printf(_("Solar elevations: day above %.1f, night below %.1f\n"),
scheme.high, scheme.low);
}
+ }
- /* Latitude */
- if (loc.lat < MIN_LAT || loc.lat > MAX_LAT) {
- /* TRANSLATORS: Append degree symbols if possible. */
- fprintf(stderr,
- _("Latitude must be between %.1f and %.1f.\n"),
- MIN_LAT, MAX_LAT);
- exit(EXIT_FAILURE);
- }
-
- /* Longitude */
- if (loc.lon < MIN_LON || loc.lon > MAX_LON) {
- /* TRANSLATORS: Append degree symbols if possible. */
- fprintf(stderr,
- _("Longitude must be between"
- " %.1f and %.1f.\n"), MIN_LON, MAX_LON);
- exit(EXIT_FAILURE);
+ if (mode != PROGRAM_MODE_RESET &&
+ mode != PROGRAM_MODE_MANUAL) {
+ if (verbose) {
+ printf(_("Temperatures: %dK at day, %dK at night\n"),
+ scheme.day.temperature,
+ scheme.night.temperature);
}
/* Color temperature */
@@ -1424,14 +1793,6 @@ main(int argc, char *argv[])
MIN_TEMP, MAX_TEMP);
exit(EXIT_FAILURE);
}
-
- /* Solar elevations */
- if (scheme.high < scheme.low) {
- fprintf(stderr,
- _("High transition elevation cannot be lower than"
- " the low transition elevation.\n"));
- exit(EXIT_FAILURE);
- }
}
if (mode == PROGRAM_MODE_MANUAL) {
@@ -1523,7 +1884,27 @@ main(int argc, char *argv[])
case PROGRAM_MODE_ONE_SHOT:
case PROGRAM_MODE_PRINT:
{
- /* Current angular elevation of the sun */
+ location_t loc = { NAN, NAN };
+ if (need_location) {
+ fputs(_("Waiting for current location"
+ " to become available...\n"), stderr);
+
+ /* Wait for location provider. */
+ int r = provider_get_location(
+ provider, &location_state, -1, &loc);
+ if (r < 0) {
+ fputs(_("Unable to get location"
+ " from provider.\n"), stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!location_is_valid(&loc)) {
+ exit(EXIT_FAILURE);
+ }
+
+ print_location(&loc);
+ }
+
double now;
r = systemtime_get_time(&now);
if (r < 0) {
@@ -1532,48 +1913,60 @@ main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
- double elevation = solar_elevation(now, loc.lat, loc.lon);
+ period_t period;
+ double transition_prog;
+ if (scheme.use_time) {
+ int time_offset = get_seconds_since_midnight(now);
+ period = get_period_from_time(&scheme, time_offset);
+ transition_prog = get_transition_progress_from_time(
+ &scheme, time_offset);
+ } else {
+ /* Current angular elevation of the sun */
+ double elevation = solar_elevation(
+ now, loc.lat, loc.lon);
+ if (verbose) {
+ /* TRANSLATORS: Append degree symbol if
+ possible. */
+ printf(_("Solar elevation: %f\n"), elevation);
+ }
- if (verbose) {
- /* TRANSLATORS: Append degree symbol if possible. */
- printf(_("Solar elevation: %f\n"), elevation);
+ period = get_period_from_elevation(&scheme, elevation);
+ transition_prog =
+ get_transition_progress_from_elevation(
+ &scheme, elevation);
}
- /* Use elevation of sun to set color temperature */
+ /* Use transition progress to set color temperature */
color_setting_t interp;
- interpolate_color_settings(&scheme, elevation, &interp);
+ interpolate_transition_scheme(
+ &scheme, transition_prog, &interp);
if (verbose || mode == PROGRAM_MODE_PRINT) {
- period_t period = get_period(&scheme,
- elevation);
- double transition =
- get_transition_progress(&scheme,
- elevation);
- print_period(period, transition);
+ print_period(period, transition_prog);
printf(_("Color temperature: %uK\n"),
interp.temperature);
printf(_("Brightness: %.2f\n"),
interp.brightness);
}
- if (mode == PROGRAM_MODE_PRINT) {
- exit(EXIT_SUCCESS);
- }
-
- /* Adjust temperature */
- r = method->set_temperature(&state, &interp);
- if (r < 0) {
- fputs(_("Temperature adjustment failed.\n"), stderr);
- method->free(&state);
- exit(EXIT_FAILURE);
- }
+ if (mode != PROGRAM_MODE_PRINT) {
+ /* Adjust temperature */
+ r = method->set_temperature(&state, &interp);
+ if (r < 0) {
+ fputs(_("Temperature adjustment failed.\n"),
+ stderr);
+ method->free(&state);
+ exit(EXIT_FAILURE);
+ }
- /* In Quartz (OSX) the gamma adjustments will automatically
- revert when the process exits. Therefore, we have to loop
- until CTRL-C is received. */
- if (strcmp(method->name, "quartz") == 0) {
- fputs(_("Press ctrl-c to stop...\n"), stderr);
- pause();
+ /* In Quartz (macOS) the gamma adjustments will
+ automatically revert when the process exits.
+ Therefore, we have to loop until CTRL-C is received.
+ */
+ if (strcmp(method->name, "quartz") == 0) {
+ fputs(_("Press ctrl-c to stop...\n"), stderr);
+ pause();
+ }
}
}
break;
@@ -1582,8 +1975,7 @@ main(int argc, char *argv[])
if (verbose) printf(_("Color temperature: %uK\n"), temp_set);
/* Adjust temperature */
- color_setting_t manual;
- memcpy(&manual, &scheme.day, sizeof(color_setting_t));
+ color_setting_t manual = scheme.day;
manual.temperature = temp_set;
r = method->set_temperature(&state, &manual);
if (r < 0) {
@@ -1623,16 +2015,23 @@ main(int argc, char *argv[])
break;
case PROGRAM_MODE_CONTINUAL:
{
- r = run_continual_mode(&loc, &scheme,
+ r = run_continual_mode(provider, &location_state, &scheme,
method, &state,
- transition, verbose);
+ use_fade, verbose);
if (r < 0) exit(EXIT_FAILURE);
}
break;
}
/* Clean up gamma adjustment state */
- method->free(&state);
+ if (mode != PROGRAM_MODE_PRINT) {
+ method->free(&state);
+ }
+
+ /* Clean up location provider state */
+ if (need_location) {
+ provider->free(&location_state);
+ }
return EXIT_SUCCESS;
}
diff --git a/src/redshift.h b/src/redshift.h
index bac8e34..c659502 100644
--- a/src/redshift.h
+++ b/src/redshift.h
@@ -89,7 +89,9 @@ typedef void location_provider_free_func(void *state);
typedef void location_provider_print_help_func(FILE *f);
typedef int location_provider_set_option_func(void *state, const char *key,
const char *value);
-typedef int location_provider_get_location_func(void *state, location_t *loc);
+typedef int location_provider_get_fd_func(void *state);
+typedef int location_provider_handle_func(
+ void *state, location_t *location, int *available);
typedef struct {
char *name;
@@ -106,8 +108,9 @@ typedef struct {
/* Set an option key, value-pair. */
location_provider_set_option_func *set_option;
- /* Get current location. */
- location_provider_get_location_func *get_location;
+ /* Listen and handle location updates. */
+ location_provider_get_fd_func *get_fd;
+ location_provider_handle_func *handle;
} location_provider_t;
diff --git a/src/windows/appicon.rc b/src/windows/appicon.rc
new file mode 100644
index 0000000..9980b7e
--- /dev/null
+++ b/src/windows/appicon.rc
@@ -0,0 +1 @@
+AppIcon ICON redshift.ico
diff --git a/src/windows/redshift.ico b/src/windows/redshift.ico
new file mode 100644
index 0000000..751e6fa
--- /dev/null
+++ b/src/windows/redshift.ico
Binary files differ
diff --git a/src/windows/versioninfo.rc b/src/windows/versioninfo.rc
new file mode 100644
index 0000000..9ede49d
--- /dev/null
+++ b/src/windows/versioninfo.rc
@@ -0,0 +1,20 @@
+#include "config.h"
+
+1 VERSIONINFO
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "Redshift Open Source Project"
+ VALUE "FileDescription", "Redshift"
+ VALUE "OriginalFilename", "redshift.exe"
+ VALUE "ProductName", "Redshift"
+ VALUE "ProductVersion", PACKAGE_VERSION
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END