Merge branch 'tobi-wan-kenobi:main' into main
This commit is contained in:
commit
3de6f9f4b9
16 changed files with 468 additions and 13 deletions
|
@ -54,8 +54,11 @@ def register(obj, button=None, cmd=None, wait=False):
|
|||
event_id = __event_id(obj.id if obj is not None else "", button)
|
||||
logging.debug("registering callback {}".format(event_id))
|
||||
core.event.unregister(event_id) # make sure there's always only one input event
|
||||
|
||||
if callable(cmd):
|
||||
core.event.register_exclusive(event_id, cmd)
|
||||
elif obj and hasattr(obj, cmd) and callable(getattr(obj, cmd)):
|
||||
core.event.register_exclusive(event_id, lambda event: getattr(obj, cmd)(event))
|
||||
else:
|
||||
core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait))
|
||||
|
||||
|
|
141
bumblebee_status/modules/contrib/pactl.py
Normal file
141
bumblebee_status/modules/contrib/pactl.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
""" Displays the current default sink.
|
||||
|
||||
Left click opens a popup menu that lists all available sinks and allows to change the default sink.
|
||||
|
||||
Per default, this module uses the sink names returned by "pactl list sinks short"
|
||||
|
||||
sample output of "pactl list sinks short":
|
||||
|
||||
2 alsa_output.pci-0000_00_1f.3.analog-stereo module-alsa-card.c s16le 2ch 44100Hz SUSPENDED
|
||||
3 alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo module-alsa-card.c s16le 2ch 44100Hz SUSPENDE
|
||||
|
||||
As "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" is not a particularly nice name, its possible to map the name to more a
|
||||
user friendly name. e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
|
||||
bumblebee-status config entry: pactl.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
|
||||
|
||||
The module also allows to specify individual (unicode) icons for all the sinks. e.g in order to use the icon 🎧 for the
|
||||
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
|
||||
pactl.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
|
||||
|
||||
Requirements:
|
||||
* pulseaudio
|
||||
* pactl
|
||||
"""
|
||||
|
||||
import logging
|
||||
import functools
|
||||
|
||||
import core.module
|
||||
import core.widget
|
||||
import core.input
|
||||
|
||||
import util.cli
|
||||
import util.popup
|
||||
|
||||
|
||||
class Sink(object):
|
||||
def __init__(self, id, internal_name, friendly_name, icon):
|
||||
super().__init__()
|
||||
self.__id = id
|
||||
self.__internal_name = internal_name
|
||||
self.__friendly_name = friendly_name
|
||||
self.__icon = icon
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.__id
|
||||
|
||||
@property
|
||||
def internal_name(self):
|
||||
return self.__internal_name
|
||||
|
||||
@property
|
||||
def friendly_name(self):
|
||||
return self.__friendly_name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self.__icon
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
display_name = (
|
||||
self.__icon + " " + self.__friendly_name
|
||||
if self.__icon != ""
|
||||
else self.__friendly_name
|
||||
)
|
||||
return display_name
|
||||
|
||||
|
||||
class Module(core.module.Module):
|
||||
def __init__(self, config, theme):
|
||||
super().__init__(config, theme, core.widget.Widget(self.default_sink))
|
||||
|
||||
self.__default_sink = None
|
||||
|
||||
res = util.cli.execute("pactl list sinks short")
|
||||
lines = res.splitlines()
|
||||
|
||||
self.__sinks = []
|
||||
for line in lines:
|
||||
info = line.split("\t")
|
||||
try:
|
||||
friendly_name = self.parameter(info[1], info[1])
|
||||
icon = self.parameter("icon." + info[1], "")
|
||||
self.__sinks.append(Sink(info[0], info[1], friendly_name, icon))
|
||||
except:
|
||||
logging.exception("Couldn't parse sink")
|
||||
pass
|
||||
|
||||
core.input.register(self, button=core.input.LEFT_MOUSE, cmd=self.popup)
|
||||
|
||||
def __sink(self, internal_sink_name):
|
||||
for sink in self.__sinks:
|
||||
if internal_sink_name == sink.internal_name:
|
||||
return sink
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
res = util.cli.execute("pactl info")
|
||||
lines = res.splitlines()
|
||||
self.__default_sink = None
|
||||
for line in lines:
|
||||
if not line.startswith("Default Sink:"):
|
||||
continue
|
||||
internal_sink_name = line.replace("Default Sink: ", "")
|
||||
self.__default_sink = self.__sink(internal_sink_name)
|
||||
break
|
||||
except Exception as e:
|
||||
logging.exception("Could not get pactl info")
|
||||
self.__default_sink = None
|
||||
|
||||
def default_sink(self, widget):
|
||||
if self.__default_sink is None:
|
||||
return "unknown"
|
||||
return self.__default_sink.display_name
|
||||
|
||||
def __on_sink_selected(self, sink):
|
||||
try:
|
||||
util.cli.execute("pactl set-default-sink {}".format(sink.id))
|
||||
self.__default_sink = sink
|
||||
except Exception as e:
|
||||
logging.exception("Couldn't set default sink")
|
||||
|
||||
def popup(self, widget):
|
||||
menu = util.popup.menu()
|
||||
|
||||
for sink in self.__sinks:
|
||||
menu.add_menuitem(
|
||||
sink.friendly_name,
|
||||
callback=functools.partial(self.__on_sink_selected, sink),
|
||||
)
|
||||
menu.show(widget)
|
||||
|
||||
def state(self, widget):
|
||||
return []
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
45
bumblebee_status/modules/contrib/persian_date.py
Normal file
45
bumblebee_status/modules/contrib/persian_date.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# pylint: disable=C0111,R0903
|
||||
|
||||
"""Displays the current date and time in Persian(Jalali) Calendar.
|
||||
|
||||
Requires the following python packages:
|
||||
* jdatetime
|
||||
|
||||
Parameters:
|
||||
* datetime.format: strftime()-compatible formatting string. default: "%A %d %B" e.g., "جمعه ۱۳ اسفند"
|
||||
* datetime.locale: locale to use. default: "fa_IR"
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import jdatetime
|
||||
import locale
|
||||
|
||||
import core.module
|
||||
import core.widget
|
||||
import core.input
|
||||
|
||||
|
||||
class Module(core.module.Module):
|
||||
def __init__(self, config, theme):
|
||||
super().__init__(config, theme, core.widget.Widget(self.full_text))
|
||||
|
||||
l = ("fa_IR", "UTF-8")
|
||||
lcl = self.parameter("locale", ".".join(l))
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, lcl.split("."))
|
||||
except Exception as e:
|
||||
locale.setlocale(locale.LC_ALL, ("fa_IR", "UTF-8"))
|
||||
|
||||
def default_format(self):
|
||||
return "%A %d %B"
|
||||
|
||||
def full_text(self, widget):
|
||||
enc = locale.getpreferredencoding()
|
||||
fmt = self.parameter("format", self.default_format())
|
||||
retval = jdatetime.datetime.now().strftime(fmt)
|
||||
if hasattr(retval, "decode"):
|
||||
return retval.decode(enc)
|
||||
return retval
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
|
@ -16,13 +16,13 @@ class Module(core.module.Module):
|
|||
self.__ip = ""
|
||||
|
||||
def public_ip(self, widget):
|
||||
return self.__ip
|
||||
return self.__ip or "n/a"
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
self.__ip = util.location.public_ip()
|
||||
except Exception:
|
||||
self.__ip = "n/a"
|
||||
self.__ip = None
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||
|
|
|
@ -55,7 +55,7 @@ class Module(core.module.Module):
|
|||
|
||||
self._state = []
|
||||
|
||||
self._newspaper_filename = tempfile.mktemp(".html")
|
||||
self._newspaper_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html")
|
||||
|
||||
self._last_refresh = 0
|
||||
self._last_update = 0
|
||||
|
@ -308,10 +308,11 @@ class Module(core.module.Module):
|
|||
|
||||
while newspaper_items:
|
||||
content += self._create_news_section(newspaper_items)
|
||||
open(self._newspaper_filename, "w").write(
|
||||
self._newspaper_file.write(
|
||||
HTML_TEMPLATE.replace("[[CONTENT]]", content)
|
||||
)
|
||||
webbrowser.open("file://" + self._newspaper_filename)
|
||||
self._newspaper_file.flush()
|
||||
webbrowser.open("file://" + self._newspaper_file.name)
|
||||
self._update_history("newspaper")
|
||||
self._save_history()
|
||||
|
||||
|
|
|
@ -39,7 +39,11 @@ class Module(core.module.Module):
|
|||
self.__sun = None
|
||||
|
||||
if not lat or not lon:
|
||||
lat, lon = util.location.coordinates()
|
||||
try:
|
||||
lat, lon = util.location.coordinates()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if lat and lon:
|
||||
self.__sun = Sun(float(lat), float(lon))
|
||||
|
||||
|
@ -55,6 +59,10 @@ class Module(core.module.Module):
|
|||
return "n/a"
|
||||
|
||||
def __calculate_times(self):
|
||||
if not self.__sun:
|
||||
self.__sunset = self.__sunrise = None
|
||||
return
|
||||
|
||||
self.__isup = False
|
||||
|
||||
order_matters = True
|
||||
|
|
|
@ -13,7 +13,9 @@ Parameters:
|
|||
* nic.exclude: Comma-separated list of interface prefixes (supporting regular expressions) to exclude (defaults to 'lo,virbr,docker,vboxnet,veth,br,.*:avahi')
|
||||
* nic.include: Comma-separated list of interfaces to include
|
||||
* nic.states: Comma-separated list of states to show (prefix with '^' to invert - i.e. ^down -> show all devices that are not in state down)
|
||||
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid}')
|
||||
* nic.format: Format string (defaults to '{intf} {state} {ip} {ssid} {strength}')
|
||||
* nic.strength_warning: Integer to set the threshold for warning state (defaults to 50)
|
||||
* nic.strength_critical: Integer to set the threshold for critical state (defaults to 30)
|
||||
"""
|
||||
|
||||
import re
|
||||
|
@ -28,7 +30,7 @@ import util.format
|
|||
|
||||
|
||||
class Module(core.module.Module):
|
||||
@core.decorators.every(seconds=10)
|
||||
@core.decorators.every(seconds=5)
|
||||
def __init__(self, config, theme):
|
||||
widgets = []
|
||||
super().__init__(config, theme, widgets)
|
||||
|
@ -45,7 +47,15 @@ class Module(core.module.Module):
|
|||
self._states["exclude"].append(state[1:])
|
||||
else:
|
||||
self._states["include"].append(state)
|
||||
self._format = self.parameter("format", "{intf} {state} {ip} {ssid}")
|
||||
self._format = self.parameter("format", "{intf} {state} {ip} {ssid} {strength}")
|
||||
|
||||
self._strength_threshold_critical = self.parameter("strength_critical", 30)
|
||||
self._strength_threshold_warning = self.parameter("strength_warning", 50)
|
||||
|
||||
# Limits for the accepted dBm values of wifi strength
|
||||
self.__strength_dbm_lower_bound = -110
|
||||
self.__strength_dbm_upper_bound = -30
|
||||
|
||||
self.iw = shutil.which("iw")
|
||||
self._update_widgets(widgets)
|
||||
|
||||
|
@ -64,6 +74,14 @@ class Module(core.module.Module):
|
|||
iftype = "wireless" if self._iswlan(intf) else "wired"
|
||||
iftype = "tunnel" if self._istunnel(intf) else iftype
|
||||
|
||||
# "strength" is none if interface type is not wlan
|
||||
strength = widget.get("strength")
|
||||
if self._iswlan(intf) and strength:
|
||||
if strength < self._strength_threshold_critical:
|
||||
states.append("critical")
|
||||
elif strength < self._strength_threshold_warning:
|
||||
states.append("warning")
|
||||
|
||||
states.append("{}-{}".format(iftype, widget.get("state")))
|
||||
|
||||
return states
|
||||
|
@ -116,6 +134,9 @@ class Module(core.module.Module):
|
|||
):
|
||||
continue
|
||||
|
||||
strength_dbm = self.get_strength_dbm(intf)
|
||||
strength_percent = self.convert_strength_dbm_percent(strength_dbm)
|
||||
|
||||
widget = self.widget(intf)
|
||||
if not widget:
|
||||
widget = self.add_widget(name=intf)
|
||||
|
@ -126,12 +147,14 @@ class Module(core.module.Module):
|
|||
ip=", ".join(addr),
|
||||
intf=intf,
|
||||
state=state,
|
||||
strength=str(strength_percent) + "%" if strength_percent else "",
|
||||
ssid=self.get_ssid(intf),
|
||||
).split()
|
||||
)
|
||||
)
|
||||
widget.set("intf", intf)
|
||||
widget.set("state", state)
|
||||
widget.set("strength", strength_percent)
|
||||
|
||||
def get_ssid(self, intf):
|
||||
if not self._iswlan(intf) or self._istunnel(intf) or not self.iw:
|
||||
|
@ -145,5 +168,23 @@ class Module(core.module.Module):
|
|||
|
||||
return ""
|
||||
|
||||
def get_strength_dbm(self, intf):
|
||||
if not self._iswlan(intf) or self._istunnel(intf) or not self.iw:
|
||||
return None
|
||||
|
||||
with open("/proc/net/wireless", "r") as file:
|
||||
for line in file:
|
||||
if intf in line:
|
||||
# Remove trailing . by slicing it off ;)
|
||||
strength_dbm = line.split()[3][:-1]
|
||||
return util.format.asint(strength_dbm,
|
||||
minimum=self.__strength_dbm_lower_bound,
|
||||
maximum=self.__strength_dbm_upper_bound)
|
||||
|
||||
return None
|
||||
|
||||
def convert_strength_dbm_percent(self, signal):
|
||||
return int(100 * ((signal + 100) / 70.0)) if signal else None
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||
|
|
|
@ -54,7 +54,7 @@ def get_redshift_value(module):
|
|||
for line in res.split("\n"):
|
||||
line = line.lower()
|
||||
if "temperature" in line:
|
||||
widget.set("temp", line.split(" ")[2])
|
||||
widget.set("temp", line.split(" ")[2].upper())
|
||||
if "period" in line:
|
||||
state = line.split(" ")[1]
|
||||
if "day" in state:
|
||||
|
|
|
@ -9,7 +9,7 @@ Parameters:
|
|||
import core.module
|
||||
import core.widget
|
||||
import core.decorators
|
||||
|
||||
import core.input
|
||||
|
||||
class Module(core.module.Module):
|
||||
@core.decorators.every(minutes=60)
|
||||
|
@ -20,5 +20,8 @@ class Module(core.module.Module):
|
|||
def text(self, _):
|
||||
return self.__text
|
||||
|
||||
def update_text(self, event):
|
||||
self.__text = core.input.button_name(event["button"])
|
||||
|
||||
|
||||
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|
||||
|
|
|
@ -59,11 +59,11 @@ def __load():
|
|||
__next = time.time() + 60 * 30 # error - try again every 30m
|
||||
|
||||
|
||||
def __get(name, default=None):
|
||||
def __get(name):
|
||||
global __data
|
||||
if not __data or __expired():
|
||||
__load()
|
||||
return __data.get(name, default)
|
||||
return __data[name]
|
||||
|
||||
|
||||
def reset():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue