bumblebee-status/bumblebee-status
Tobias Witek 582c828deb [core] guard against concurrent updates
when a "regular" update (once per interval) and a input-triggered update
(e.g. mouse click on a widget) collide, this can cause the theme colors
to be interleaved wrongly.

fixes #661
2020-06-23 20:20:36 +02:00

160 lines
4.8 KiB
Python
Executable file

#!/usr/bin/env python
import os
import sys
import json
import time
import socket
import select
import logging
import threading
import bumblebee_status.discover
bumblebee_status.discover.discover()
import core.config
import core.output
import core.module
import core.input
import core.event
import util.format
started = False
class CommandSocket(object):
def __init__(self):
self.__name = "/tmp/.bumblebee-status.{}".format(os.getpid())
self.__socket = None
def __enter__(self):
self.__socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.__socket.bind(self.__name)
self.__socket.listen(5)
return self.__socket
def __exit__(self, type, value, traceback):
self.__socket.close()
os.unlink(self.__name)
def handle_input(output, update_lock):
with CommandSocket() as cmdsocket:
poll = select.poll()
poll.register(sys.stdin.fileno(), select.POLLIN)
poll.register(cmdsocket, select.POLLIN)
while True:
events = poll.poll()
modules = {}
for fileno, event in events:
if fileno == cmdsocket.fileno():
tmp, _ = cmdsocket.accept()
line = tmp.recv(4096).decode()
tmp.close()
logging.debug("socket event {}".format(line))
else:
line = "["
while line.startswith("["):
line = sys.stdin.readline().strip(",").strip()
logging.info("input event: {}".format(line))
try:
event = json.loads(line)
core.input.trigger(event)
if "name" in event:
modules[event["name"]] = True
except ValueError:
pass
update_lock.acquire()
core.event.trigger("update", modules.keys())
core.event.trigger("draw")
update_lock.release()
poll.unregister(sys.stdin.fileno())
def main():
config = core.config.Config(sys.argv[1:])
level = logging.DEBUG if config.debug() else logging.ERROR
if config.logfile():
logging.basicConfig(
level=level,
format="[%(asctime)s] %(module)-16s %(levelname)-8s %(message)s",
filename=os.path.abspath(os.path.expanduser(config.logfile())),
)
else:
logging.basicConfig(
level=level,
format="[%(asctime)s] %(module)-16s %(levelname)-8s %(message)s",
stream=sys.stderr,
)
theme = core.theme.Theme(config.theme(), config.iconset())
output = core.output.i3(theme, config)
modules = []
core.input.register(None, core.input.WHEEL_UP, "i3-msg workspace prev_on_output")
core.input.register(None, core.input.WHEEL_DOWN, "i3-msg workspace next_on_output")
update_lock = threading.Lock()
input_thread = threading.Thread(target=handle_input, args=(output, update_lock, ))
input_thread.daemon = True
input_thread.start()
if config.debug():
modules.append(core.module.load("debug", config, theme))
for module in config.modules():
modules.append(core.module.load(module, config, theme))
modules[-1].register_callbacks()
if config.reverse():
modules.reverse()
output.modules(modules)
if util.format.asbool(config.get("engine.collapsible", True)) == True:
core.input.register(None, core.input.MIDDLE_MOUSE, output.toggle_minimize)
core.event.trigger("start")
started = True
while True:
if update_lock.acquire(blocking=False) == True:
core.event.trigger("update")
core.event.trigger("draw")
update_lock.release()
output.wait(config.interval())
core.event.trigger("stop")
if __name__ == "__main__":
try:
if sys.version_info.major < 3:
raise Exception("at least Python 3.4 required (Python 2.x not supported)")
main()
except Exception as e:
# really basic errors -> make sure these are shown in the status bar by minimal config
if not started:
print("{\"version\":1}")
print("[")
while True:
sys.stdout.write(
json.dumps(
[
{
"full_text": " {} ".format(e),
"background": "#ff0000",
"color": "#ffffff",
"name": "error",
"instance": "the-only-one",
}
]
)
)
sys.stdout.write(",\n")
sys.stdout.flush()
time.sleep(5)
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4