Refactor redshift module
Now using a internal RedshiftController, based on the original statusbar.py, however written in pure Python instead of depending in GLib. Since it does not steal SIGUSR1 handler, it should fix issue #482.
This commit is contained in:
parent
de1fe9b72c
commit
9a8c1011a8
@ -47,7 +47,6 @@ MOCK_MODULES = [
|
||||
"i3ipc",
|
||||
"dateutil.parser",
|
||||
"dateutil.relativedelta",
|
||||
"redshift_gtk.statusicon",
|
||||
"xkbgroup",
|
||||
]
|
||||
|
||||
|
@ -1,14 +1,110 @@
|
||||
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, CalledProcessError
|
||||
|
||||
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)
|
||||
|
||||
cmd = ["redshift"]
|
||||
if "-v" not in cmd:
|
||||
cmd += ["-v"]
|
||||
|
||||
env = os.environ.copy()
|
||||
env['LANG'] = env['LANGUAGE'] = env['LC_ALL'] = env['LC_MESSAGES'] = 'C'
|
||||
self._p = Popen(cmd, env=env, stdout=PIPE, bufsize=1, 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 inhibit != self._inhibited:
|
||||
os.kill(self._p.pid, signal.SIGUSR1)
|
||||
self._inhibited = inhibit
|
||||
|
||||
def run(self):
|
||||
for line in self._p.stdout:
|
||||
self.parse_output(line)
|
||||
|
||||
self._p.stdout.close()
|
||||
return_code = self._p.wait()
|
||||
if return_code != 0:
|
||||
raise CalledProcessError("redshift exited with {} return code".format(return_code))
|
||||
|
||||
|
||||
class Redshift(IntervalModule):
|
||||
"""
|
||||
Show status and control redshift - http://jonls.dk/redshift/.
|
||||
@ -17,7 +113,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 +142,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 +162,8 @@ class Redshift(IntervalModule):
|
||||
self.inhibit = True
|
||||
|
||||
def run(self):
|
||||
if self.error:
|
||||
fdict = {"error": self.error}
|
||||
color = self.error_color
|
||||
else:
|
||||
self.update_values()
|
||||
try:
|
||||
fdict = {
|
||||
"inhibit": self.format_inhibit[int(self.inhibit)],
|
||||
"period": self.period,
|
||||
@ -119,6 +172,9 @@ class Redshift(IntervalModule):
|
||||
"longitude": self.longitude,
|
||||
}
|
||||
color = self.color
|
||||
except CalledProcessError as err:
|
||||
fdict = {"error": err}
|
||||
color = self.error_color
|
||||
|
||||
self.output = {
|
||||
"full_text": formatp(self.format, **fdict),
|
||||
|
Loading…
Reference in New Issue
Block a user