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",
|
"i3ipc",
|
||||||
"dateutil.parser",
|
"dateutil.parser",
|
||||||
"dateutil.relativedelta",
|
"dateutil.relativedelta",
|
||||||
"redshift_gtk.statusicon",
|
|
||||||
"xkbgroup",
|
"xkbgroup",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,14 +1,110 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
|
from subprocess import Popen, PIPE, CalledProcessError
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0') # nopep8
|
|
||||||
from gi.repository import Gtk, GLib
|
|
||||||
from redshift_gtk.statusicon import RedshiftController
|
|
||||||
|
|
||||||
from i3pystatus import IntervalModule, formatp
|
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):
|
class Redshift(IntervalModule):
|
||||||
"""
|
"""
|
||||||
Show status and control redshift - http://jonls.dk/redshift/.
|
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
|
its output, so you should remove redshift/redshift-gtk from your i3 config
|
||||||
before using this module.
|
before using this module.
|
||||||
|
|
||||||
Requires `redshift` and `redshift-gtk`.
|
Requires `redshift` installed.
|
||||||
|
|
||||||
.. rubric:: Available formatters
|
.. rubric:: Available formatters
|
||||||
|
|
||||||
@ -46,56 +142,15 @@ class Redshift(IntervalModule):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
self._controller = RedshiftController(self.redshift_parameters)
|
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.inhibit = self._controller.inhibited
|
||||||
self.period = self._controller.period
|
self.period = self._controller.period
|
||||||
self.temperature = self._controller.temperature
|
self.temperature = self._controller.temperature
|
||||||
self.latitude, self.longitude = self._controller.location
|
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):
|
def toggle_inhibit(self):
|
||||||
"""Enable/disable redshift"""
|
"""Enable/disable redshift"""
|
||||||
@ -107,10 +162,8 @@ class Redshift(IntervalModule):
|
|||||||
self.inhibit = True
|
self.inhibit = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.error:
|
self.update_values()
|
||||||
fdict = {"error": self.error}
|
try:
|
||||||
color = self.error_color
|
|
||||||
else:
|
|
||||||
fdict = {
|
fdict = {
|
||||||
"inhibit": self.format_inhibit[int(self.inhibit)],
|
"inhibit": self.format_inhibit[int(self.inhibit)],
|
||||||
"period": self.period,
|
"period": self.period,
|
||||||
@ -119,6 +172,9 @@ class Redshift(IntervalModule):
|
|||||||
"longitude": self.longitude,
|
"longitude": self.longitude,
|
||||||
}
|
}
|
||||||
color = self.color
|
color = self.color
|
||||||
|
except CalledProcessError as err:
|
||||||
|
fdict = {"error": err}
|
||||||
|
color = self.error_color
|
||||||
|
|
||||||
self.output = {
|
self.output = {
|
||||||
"full_text": formatp(self.format, **fdict),
|
"full_text": formatp(self.format, **fdict),
|
||||||
|
Loading…
Reference in New Issue
Block a user