Merge branch 'tobi-wan-kenobi:main' into main

This commit is contained in:
Dhananjay Tanpure 2022-03-07 08:58:13 +05:30 committed by GitHub
commit 3de6f9f4b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 468 additions and 13 deletions

70
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '31 0 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -8,6 +8,7 @@
[![Code Climate](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) [![Code Climate](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/gpa.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
[![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage) [![Test Coverage](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/coverage.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/coverage)
[![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status) [![Issue Count](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status/badges/issue_count.svg)](https://codeclimate.com/github/tobi-wan-kenobi/bumblebee-status)
[![CodeQL](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/tobi-wan-kenobi/bumblebee-status/actions/workflows/codeql-analysis.yml)
![License](https://img.shields.io/github/license/tobi-wan-kenobi/bumblebee-status) ![License](https://img.shields.io/github/license/tobi-wan-kenobi/bumblebee-status)
**Many, many thanks to all contributors! I am still amazed by and deeply grateful for how many PRs this project gets.** **Many, many thanks to all contributors! I am still amazed by and deeply grateful for how many PRs this project gets.**

View file

@ -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) event_id = __event_id(obj.id if obj is not None else "", button)
logging.debug("registering callback {}".format(event_id)) logging.debug("registering callback {}".format(event_id))
core.event.unregister(event_id) # make sure there's always only one input event core.event.unregister(event_id) # make sure there's always only one input event
if callable(cmd): if callable(cmd):
core.event.register_exclusive(event_id, 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: else:
core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait)) core.event.register_exclusive(event_id, lambda event: __execute(event, cmd, wait))

View 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

View 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

View file

@ -16,13 +16,13 @@ class Module(core.module.Module):
self.__ip = "" self.__ip = ""
def public_ip(self, widget): def public_ip(self, widget):
return self.__ip return self.__ip or "n/a"
def update(self): def update(self):
try: try:
self.__ip = util.location.public_ip() self.__ip = util.location.public_ip()
except Exception: except Exception:
self.__ip = "n/a" self.__ip = None
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -55,7 +55,7 @@ class Module(core.module.Module):
self._state = [] self._state = []
self._newspaper_filename = tempfile.mktemp(".html") self._newspaper_file = tempfile.NamedTemporaryFile(mode="w", suffix=".html")
self._last_refresh = 0 self._last_refresh = 0
self._last_update = 0 self._last_update = 0
@ -308,10 +308,11 @@ class Module(core.module.Module):
while newspaper_items: while newspaper_items:
content += self._create_news_section(newspaper_items) content += self._create_news_section(newspaper_items)
open(self._newspaper_filename, "w").write( self._newspaper_file.write(
HTML_TEMPLATE.replace("[[CONTENT]]", content) 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._update_history("newspaper")
self._save_history() self._save_history()

View file

@ -39,7 +39,11 @@ class Module(core.module.Module):
self.__sun = None self.__sun = None
if not lat or not lon: if not lat or not lon:
try:
lat, lon = util.location.coordinates() lat, lon = util.location.coordinates()
except Exception:
pass
if lat and lon: if lat and lon:
self.__sun = Sun(float(lat), float(lon)) self.__sun = Sun(float(lat), float(lon))
@ -55,6 +59,10 @@ class Module(core.module.Module):
return "n/a" return "n/a"
def __calculate_times(self): def __calculate_times(self):
if not self.__sun:
self.__sunset = self.__sunrise = None
return
self.__isup = False self.__isup = False
order_matters = True order_matters = True

View file

@ -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.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.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.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 import re
@ -28,7 +30,7 @@ import util.format
class Module(core.module.Module): class Module(core.module.Module):
@core.decorators.every(seconds=10) @core.decorators.every(seconds=5)
def __init__(self, config, theme): def __init__(self, config, theme):
widgets = [] widgets = []
super().__init__(config, theme, widgets) super().__init__(config, theme, widgets)
@ -45,7 +47,15 @@ class Module(core.module.Module):
self._states["exclude"].append(state[1:]) self._states["exclude"].append(state[1:])
else: else:
self._states["include"].append(state) 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.iw = shutil.which("iw")
self._update_widgets(widgets) self._update_widgets(widgets)
@ -64,6 +74,14 @@ class Module(core.module.Module):
iftype = "wireless" if self._iswlan(intf) else "wired" iftype = "wireless" if self._iswlan(intf) else "wired"
iftype = "tunnel" if self._istunnel(intf) else iftype 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"))) states.append("{}-{}".format(iftype, widget.get("state")))
return states return states
@ -116,6 +134,9 @@ class Module(core.module.Module):
): ):
continue continue
strength_dbm = self.get_strength_dbm(intf)
strength_percent = self.convert_strength_dbm_percent(strength_dbm)
widget = self.widget(intf) widget = self.widget(intf)
if not widget: if not widget:
widget = self.add_widget(name=intf) widget = self.add_widget(name=intf)
@ -126,12 +147,14 @@ class Module(core.module.Module):
ip=", ".join(addr), ip=", ".join(addr),
intf=intf, intf=intf,
state=state, state=state,
strength=str(strength_percent) + "%" if strength_percent else "",
ssid=self.get_ssid(intf), ssid=self.get_ssid(intf),
).split() ).split()
) )
) )
widget.set("intf", intf) widget.set("intf", intf)
widget.set("state", state) widget.set("state", state)
widget.set("strength", strength_percent)
def get_ssid(self, intf): def get_ssid(self, intf):
if not self._iswlan(intf) or self._istunnel(intf) or not self.iw: if not self._iswlan(intf) or self._istunnel(intf) or not self.iw:
@ -145,5 +168,23 @@ class Module(core.module.Module):
return "" 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 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -54,7 +54,7 @@ def get_redshift_value(module):
for line in res.split("\n"): for line in res.split("\n"):
line = line.lower() line = line.lower()
if "temperature" in line: if "temperature" in line:
widget.set("temp", line.split(" ")[2]) widget.set("temp", line.split(" ")[2].upper())
if "period" in line: if "period" in line:
state = line.split(" ")[1] state = line.split(" ")[1]
if "day" in state: if "day" in state:

View file

@ -9,7 +9,7 @@ Parameters:
import core.module import core.module
import core.widget import core.widget
import core.decorators import core.decorators
import core.input
class Module(core.module.Module): class Module(core.module.Module):
@core.decorators.every(minutes=60) @core.decorators.every(minutes=60)
@ -20,5 +20,8 @@ class Module(core.module.Module):
def text(self, _): def text(self, _):
return self.__text return self.__text
def update_text(self, event):
self.__text = core.input.button_name(event["button"])
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -59,11 +59,11 @@ def __load():
__next = time.time() + 60 * 30 # error - try again every 30m __next = time.time() + 60 * 30 # error - try again every 30m
def __get(name, default=None): def __get(name):
global __data global __data
if not __data or __expired(): if not __data or __expired():
__load() __load()
return __data.get(name, default) return __data[name]
def reset(): def reset():

View file

@ -26,6 +26,15 @@ class PublicIPTest(TestCase):
assert widget(module).full_text() == '5.12.220.2' assert widget(module).full_text() == '5.12.220.2'
@mock.patch('util.location.public_ip')
def test_public_ip(self, public_ip_mock):
public_ip_mock.return_value = None
module = build_module()
module.update()
assert widget(module).full_text() == 'n/a'
@mock.patch('util.location.public_ip') @mock.patch('util.location.public_ip')
def test_public_ip_with_exception(self, public_ip_mock): def test_public_ip_with_exception(self, public_ip_mock):
public_ip_mock.side_effect = Exception public_ip_mock.side_effect = Exception

View file

@ -7,6 +7,7 @@
"default-separators": false "default-separators": false
}, },
"date": { "prefix": "" }, "date": { "prefix": "" },
"persian_date": { "prefix": "" },
"time": { "prefix": "" }, "time": { "prefix": "" },
"datetime": { "prefix": "" }, "datetime": { "prefix": "" },
"datetz": { "prefix": "" }, "datetz": { "prefix": "" },

54
themes/rose-pine.json Normal file
View file

@ -0,0 +1,54 @@
{
"icons": ["awesome-fonts"],
"defaults": {
"separator-block-width": 0,
"warning": {
"fg": "#232136",
"bg": "#f6c177"
},
"critical": {
"fg": "#232136",
"bg": "#eb6f92"
}
},
"cycle": [
{ "fg": "#232136", "bg": "#ea9a97" },
{ "fg": "#e0def4", "bg": "#393552" }
],
"dnf": {
"good": {
"fg": "#232136",
"bg": "#9ccfd8"
}
},
"pacman": {
"good": {
"fg": "#232136",
"bg": "#9ccfd8"
}
},
"battery": {
"charged": {
"fg": "#232136",
"bg": "#9ccfd8"
},
"AC": {
"fg": "#232136",
"bg": "#9ccfd8"
}
},
"pomodoro": {
"paused": {
"fg": "#232136",
"bg": "#f6c177"
},
"work": {
"fg": "#232136",
"bg": "#9ccfd8"
},
"break": {
"fg": "#232136",
"bg": "#c4a7e7"
}
}
}

View file

@ -0,0 +1,78 @@
{
"icons": [ "paxy97", "awesome-fonts" ],
"defaults": {
"warning": {
"fg": "#353839",
"bg": "#b38a32"
},
"critical": {
"fg": "#353839",
"bg": "#d94070"
},
"default-separators": false,
"separator-block-width": 0
},
"fg": "#353839",
"bg": "#c4b6a3",
"dnf": {
"good": {
"fg": "#353839",
"bg": "#378c5d"
}
},
"apt": {
"good": {
"fg": "#353839",
"bg": "#378c5d"
}
},
"battery": {
"charged": {
"fg": "#353839",
"bg": "#378c5d"
},
"AC": {
"fg": "#353839",
"bg": "#378c5d"
}
},
"bluetooth": {
"ON": {
"fg": "#353839",
"bg": "#378c5d"
}
},
"git": {
"modified": { "bg": "#174572" },
"deleted": { "bg": "#ba1d58" },
"new": { "bg": "#967117" }
},
"pomodoro": {
"paused": {
"fg": "#353839",
"bg": "#d79921"
},
"work": {
"fg": "#353839",
"bg": "#378c5d"
},
"break": {
"fg": "#353839",
"bg": "#378c5d"
}
},
"keys": {
"Key.cmd": {
"bg": "#477ab7"
},
"Key.shift": {
"bg": "#b38a32"
},
"Key.ctrl": {
"bg": "#377c8b"
},
"Key.alt": {
"bg": "#e05b1f"
}
}
}