Merge pull request #484 from m45t3r/redshift_new_controller

Refactor redshift module
This commit is contained in:
enkore 2017-01-12 03:35:26 +01:00 committed by GitHub
commit ff498e8b4e
2 changed files with 119 additions and 57 deletions

View File

@ -47,7 +47,6 @@ MOCK_MODULES = [
"i3ipc",
"dateutil.parser",
"dateutil.relativedelta",
"redshift_gtk.statusicon",
"xkbgroup",
]

View File

@ -1,14 +1,116 @@
import os
import re
import signal
import threading
import gi
gi.require_version('Gtk', '3.0') # nopep8
from gi.repository import Gtk, GLib
from redshift_gtk.statusicon import RedshiftController
from subprocess import Popen, PIPE
from i3pystatus import IntervalModule, formatp
class RedshiftController(threading.Thread):
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."""
threading.Thread.__init__(self)
# Regex to parse output
self._regex = re.compile(r'([\w ]+): (.+)')
# Initialize state variables
self._inhibited = False
self._temperature = 0
self._period = 'Unknown'
self._location = (0.0, 0.0)
self._pid = None
cmd = ["redshift"] + args
if "-v" not in cmd:
cmd += ["-v"]
env = os.environ.copy()
env['LANG'] = env['LANGUAGE'] = env['LC_ALL'] = env['LC_MESSAGES'] = 'C'
self._params = {
"args": cmd,
"env": env,
"bufsize": 1,
"stdout": PIPE,
"universal_newlines": True,
}
def parse_output(self, line):
"""Convert output to key value pairs"""
if line:
m = self._regex.match(line)
if m:
key = m.group(1)
value = m.group(2)
self.update_value(key, value)
def update_value(self, key, value):
"""Parse key value pairs to update their values"""
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
elif key == 'Color temperature':
new_temperature = int(value.rstrip('K'), 10)
if new_temperature != self._temperature:
self._temperature = new_temperature
elif key == 'Period':
new_period = value
if new_period != self._period:
self._period = 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
@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 self._pid and inhibit != self._inhibited:
os.kill(self._pid, signal.SIGUSR1)
self._inhibited = inhibit
def run(self):
with Popen(**self._params) as proc:
self._pid = proc.pid
for line in proc.stdout:
self.parse_output(line)
proc.wait(10)
class Redshift(IntervalModule):
"""
Show status and control redshift - http://jonls.dk/redshift/.
@ -17,7 +119,7 @@ class Redshift(IntervalModule):
its output, so you should remove redshift/redshift-gtk from your i3 config
before using this module.
Requires `redshift` and `redshift-gtk`.
Requires `redshift` installed.
.. rubric:: Available formatters
@ -46,56 +148,15 @@ class Redshift(IntervalModule):
def init(self):
self._controller = RedshiftController(self.redshift_parameters)
self._controller.daemon = True
self._controller.start()
self.update_values()
def update_values(self):
self.inhibit = self._controller.inhibited
self.period = self._controller.period
self.temperature = self._controller.temperature
self.latitude, self.longitude = self._controller.location
self.error = ""
# Setup signals to property changes
self._controller.connect('period-changed', self.period_change_cb)
self._controller.connect('temperature-changed', self.temperature_change_cb)
self._controller.connect('location-changed', self.location_change_cb)
self._controller.connect('error-occured', self.error_occured_cb)
def terminate_child(data=None):
self._controller.terminate_child()
return False
# Install signal handlers
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGTERM,
terminate_child, None)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT,
terminate_child, None)
try:
t = threading.Thread(target=Gtk.main)
t.daemon = True
t.start()
except Exception as e:
self._controller.kill_child()
self.output = {
"full_text": "Error creating new thread!",
"color": self.error_color
}
def period_change_cb(self, controller, period):
"""Callback when controller changes period"""
self.period = period
def temperature_change_cb(self, controller, temperature):
"""Callback when controller changes temperature"""
self.temperature = temperature
def location_change_cb(self, controller, latitude, longitude):
"""Callback when controlled changes location"""
self.latitude = latitude
self.longitude = longitude
def error_occured_cb(self, controller, error):
"""Callback when an error occurs in the controller"""
self.error = error
def toggle_inhibit(self):
"""Enable/disable redshift"""
@ -107,10 +168,8 @@ class Redshift(IntervalModule):
self.inhibit = True
def run(self):
if self.error:
fdict = {"error": self.error}
color = self.error_color
else:
if self._controller.is_alive():
self.update_values()
fdict = {
"inhibit": self.format_inhibit[int(self.inhibit)],
"period": self.period,
@ -118,9 +177,13 @@ class Redshift(IntervalModule):
"latitude": self.latitude,
"longitude": self.longitude,
}
output = formatp(self.format, **fdict)
color = self.color
else:
output = "redshift exited unexpectedly"
color = self.error_color
self.output = {
"full_text": formatp(self.format, **fdict),
"full_text": output,
"color": color,
}