Add module "keep_alive" feature. (#601)
By default, i3bar sends SIGSTOP to all children when it is not visible (for example, the screen sleeps or you enter full screen mode), stopping the i3pystatus process and all threads within it. For some modules, this is not desirable. This commit makes it possible for modules to define the "keep_alive" flag to indicate that they would like to continue executing regardless of the state of i3bar.
This commit is contained in:
parent
13a1caccd2
commit
c044d1ff67
@ -57,6 +57,8 @@ class ABCRadio(IntervalModule):
|
||||
# Destroy the player after this many seconds of inactivity
|
||||
PLAYER_LIFETIME = 5
|
||||
|
||||
# Do not suspend the player when i3bar is hidden.
|
||||
keep_alive = True
|
||||
show_info = {}
|
||||
player = None
|
||||
station_info = None
|
||||
|
@ -1,9 +1,11 @@
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from contextlib import contextmanager
|
||||
from threading import Condition
|
||||
from threading import Thread
|
||||
from i3pystatus.core.modules import IntervalModule
|
||||
|
||||
|
||||
class IOHandler:
|
||||
@ -54,7 +56,12 @@ class StandaloneIO(IOHandler):
|
||||
|
||||
n = -1
|
||||
proto = [
|
||||
{"version": 1, "click_events": True}, "[", "[]", ",[]",
|
||||
{
|
||||
"version": 1,
|
||||
"click_events": True,
|
||||
"stop_signal": signal.SIGUSR2,
|
||||
"cont_signal": signal.SIGUSR2
|
||||
}, "[", "[]", ",[]",
|
||||
]
|
||||
|
||||
def __init__(self, click_events, modules, interval=1):
|
||||
@ -72,7 +79,10 @@ class StandaloneIO(IOHandler):
|
||||
|
||||
self.refresh_cond = Condition()
|
||||
self.treshold_interval = 20.0
|
||||
|
||||
self.stopped = False
|
||||
signal.signal(signal.SIGUSR1, self.refresh_signal_handler)
|
||||
signal.signal(signal.SIGUSR2, self.suspend_signal_handler)
|
||||
|
||||
def read(self):
|
||||
self.compute_treshold_interval()
|
||||
@ -142,6 +152,26 @@ class StandaloneIO(IOHandler):
|
||||
|
||||
self.async_refresh()
|
||||
|
||||
def suspend_signal_handler(self, signo, frame):
|
||||
"""
|
||||
By default, i3bar sends SIGSTOP to all children when it is not visible (for example, the screen
|
||||
sleeps or you enter full screen mode). This stops the i3pystatus process and all threads within it.
|
||||
For some modules, this is not desirable. Thankfully, the i3bar protocol supports setting the "stop_signal"
|
||||
and "cont_signal" key/value pairs in the header to allow sending a custom signal when these events occur.
|
||||
|
||||
Here we use SIGUSR2 for both "stop_signal" and "cont_signal" and maintain a toggle to determine whether
|
||||
we have just been stopped or continued. When we have been stopped, notify the IntervalModule managers
|
||||
that they should suspend any module that does not set the keep_alive flag to a truthy value, and when we
|
||||
have been continued, notify the IntervalModule managers that they can resume execution of all modules.
|
||||
"""
|
||||
if signo != signal.SIGUSR2:
|
||||
return
|
||||
self.stopped = not self.stopped
|
||||
if self.stopped:
|
||||
[m.suspend() for m in IntervalModule.managers.values()]
|
||||
else:
|
||||
[m.resume() for m in IntervalModule.managers.values()]
|
||||
|
||||
|
||||
class JSONIO:
|
||||
def __init__(self, io, skiplines=2):
|
||||
|
@ -1,18 +1,25 @@
|
||||
import threading
|
||||
import time
|
||||
import sys
|
||||
import traceback
|
||||
from i3pystatus.core.util import partition
|
||||
|
||||
timer = time.perf_counter if hasattr(time, "perf_counter") else time.clock
|
||||
|
||||
|
||||
def unwrap_workload(workload):
|
||||
""" Obtain the module from it's wrapper. """
|
||||
while isinstance(workload, Wrapper):
|
||||
workload = workload.workload
|
||||
return workload
|
||||
|
||||
|
||||
class Thread(threading.Thread):
|
||||
def __init__(self, target_interval, workloads=None, start_barrier=1):
|
||||
super().__init__()
|
||||
self.workloads = workloads or []
|
||||
self.target_interval = target_interval
|
||||
self.start_barrier = start_barrier
|
||||
self._suspended = threading.Event()
|
||||
self.daemon = True
|
||||
|
||||
def __iter__(self):
|
||||
@ -37,9 +44,20 @@ class Thread(threading.Thread):
|
||||
|
||||
def execute_workloads(self):
|
||||
for workload in self:
|
||||
workload()
|
||||
if self.should_execute(workload):
|
||||
workload()
|
||||
self.workloads.sort(key=lambda workload: workload.time)
|
||||
|
||||
def should_execute(self, workload):
|
||||
"""
|
||||
If we have been suspended by i3bar, only execute those modules that set the keep_alive flag to a truthy
|
||||
value. See the docs on the suspend_signal_handler method of the io module for more information.
|
||||
"""
|
||||
if not self._suspended.is_set():
|
||||
return True
|
||||
workload = unwrap_workload(workload)
|
||||
return hasattr(workload, 'keep_alive') and getattr(workload, 'keep_alive')
|
||||
|
||||
def run(self):
|
||||
while self:
|
||||
self.execute_workloads()
|
||||
@ -53,6 +71,12 @@ class Thread(threading.Thread):
|
||||
return [remove] + self.branch(vtime - remove.time, bound)
|
||||
return []
|
||||
|
||||
def suspend(self):
|
||||
self._suspended.set()
|
||||
|
||||
def resume(self):
|
||||
self._suspended.clear()
|
||||
|
||||
|
||||
class Wrapper:
|
||||
def __init__(self, workload):
|
||||
@ -143,3 +167,11 @@ class Manager:
|
||||
def start(self):
|
||||
for thread in self.threads:
|
||||
thread.start()
|
||||
|
||||
def suspend(self):
|
||||
for thread in self.threads:
|
||||
thread.suspend()
|
||||
|
||||
def resume(self):
|
||||
for thread in self.threads:
|
||||
thread.resume()
|
||||
|
@ -316,6 +316,8 @@ class Network(IntervalModule, ColorRangeModule):
|
||||
("detect_active", "Attempt to detect the active interface"),
|
||||
)
|
||||
|
||||
# Continue processing statistics when i3bar is hidden.
|
||||
keep_alive = True
|
||||
interval = 1
|
||||
interface = 'eth0'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user