Compare commits

...

34 commits
v2.2.0 ... main

Author SHA1 Message Date
676bbebf4c
bluetooth2: states and styling 2024-04-17 23:05:16 +02:00
617a12f96d
Merge branch 'fix/bluetooth' 2024-04-17 22:53:03 +02:00
5053bb0f1b
themes/awesome-fonts: fix bluetooth icons 2024-04-17 22:47:45 +02:00
21060a10a0
bluetooth2: only show connection count in cases of excessive numbers of connections 2024-04-17 22:46:18 +02:00
a56b86100a
fix updated nerdfonts 2023-12-06 22:17:49 +01:00
fd4b940d58
nic.py: adapting wifi-menu path 2023-12-06 22:01:20 +01:00
2bbac991db
battery: updating nerd font icons 2023-11-10 21:03:43 +01:00
255794cd1c
vpn: updating nerd font icon 2023-11-10 20:51:31 +01:00
2e81eed830
pulsein: replacing muted nerd font icon with slash in the other direction, just to align it with bluetooth2 2023-11-10 20:47:35 +01:00
60bfb19378
pulseout: adding nerd font icons for low, mid, heigh, muted 2023-11-10 20:44:16 +01:00
d7d4603855
wlrotation: adding awesome-fonts icons 2023-11-10 20:03:44 +01:00
cbd0c58b4a
wlrotation: refactored the entire module and integrated external services 2023-11-10 19:44:33 +01:00
bbc26c263c
wlrotation: fix nerd font icon 2023-11-10 16:51:07 +01:00
53de1b524a
bluetooth2: using dbus api, shortening output, some refactoring 2023-11-10 16:50:16 +01:00
c3d4fce74c
nic: fix syntax 2023-11-10 16:48:05 +01:00
8cc7c9de9b
icons: fix nerd font battery/ac 2023-11-10 16:46:38 +01:00
3ed26c62a5
vpn: shorter default label 2023-11-08 22:59:56 +01:00
900a0710c5
nic: add click handler 2023-11-08 22:56:23 +01:00
2e18d71284
battery: better support for pen battery and some formatting 2023-11-08 22:46:58 +01:00
61123eb7a0
fix wlrotation: migrated to new api 2023-11-08 21:52:08 +01:00
27be30263f
Merge branch 'feature/wlrotation' into merge 2023-11-08 20:51:27 +01:00
559517f345
update golor theme and icons 2023-11-08 20:28:49 +01:00
b45ff330c9
wlrotation: init 2023-11-08 00:38:39 +01:00
tobi-wan-kenobi
bfafd93643 fix: remove power from tests installation as a quickfix 2023-10-26 09:19:46 +02:00
tobi-wan-kenobi
c14ed1166d fix: autotest - try to pin down pip versions 2023-10-26 09:11:39 +02:00
tobi-wan-kenobi
9f6c9cc7d2 doc: update readthedocs.yaml 2023-10-26 09:08:43 +02:00
tobi-wan-kenobi
025d3fcb51 fix(module/shell): synchonous invocation was broken
For some (unknown) reason, redrawing while in the update loop breaks the
update (this probably warrants a closer look).

As a quickfix to restore functionality, remove the unnecessary redraw
call and move it into the async codepath, where it is actually needed.

fixes #1001
2023-10-26 09:00:09 +02:00
tobi-wan-kenobi
05622f985a fix(module/shell): expand user directory in command spec
Make sure that ~/ is expanded to the user's home directory in the
command specified for the shell module.

fixes #1002
2023-10-26 08:59:14 +02:00
tobi-wan-kenobi
c4a3f488aa
Merge pull request #1000 from Sigggii/main
Add power-profile module
2023-10-04 06:25:02 +02:00
siggi
68de299763 Merge remote-tracking branch 'origin/main' 2023-10-03 22:36:23 +02:00
siggi
85760926d7 Add Power-Profile module 2023-10-03 22:28:28 +02:00
tobi-wan-kenobi
3bc3c75ff4
Merge pull request #997 from zetxx/patch-2
Update modules.rst
2023-10-02 14:38:16 +02:00
Elin Angelov
d30e5c694e
Update modules.rst 2023-10-02 12:31:15 +03:00
tobi-wan-kenobi
b217ac9c9e docs: update module documentation 2023-10-01 10:07:03 +02:00
16 changed files with 549 additions and 232 deletions

View file

@ -29,7 +29,7 @@ jobs:
python -m pip install --upgrade pip
pip install -U coverage pytest pytest-mock freezegun
pip install 'pygit2<1' 'libvirt-python<6.3' 'feedparser<6' || true
pip install $(cat requirements/modules/*.txt | cut -d ' ' -f 1 | sort -u)
pip install $(cat requirements/modules/*.txt | grep -v power | cut -d ' ' -f 1 | sort -u)
- name: Install Code Climate dependency
run: |
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter

View file

@ -6,4 +6,4 @@ python:
build:
os: ubuntu-22.04
tools:
python: "3.11"
python: "3"

View file

@ -130,8 +130,14 @@ class Module(core.module.Module):
log.debug("adding new widget for {}".format(battery))
widget = self.add_widget(full_text=self.capacity, name=battery)
for w in self.widgets():
if util.format.asbool(self.parameter("decorate", True)) == False:
try:
with open("/sys/class/power_supply/{}/model_name".format(battery)) as f:
widget.set("pen", ("Pen" in f.read().strip()))
except Exception:
pass
if util.format.asbool(self.parameter("decorate", True)) == False:
for widget in self.widgets():
widget.set("theme.exclude", "suffix")
def hidden(self):
@ -147,15 +153,16 @@ class Module(core.module.Module):
capacity = self.__manager.capacity(widget.name)
widget.set("capacity", capacity)
widget.set("ac", self.__manager.isac_any(self._batteries))
widget.set("theme.minwidth", "100%")
# Read power conumption
if util.format.asbool(self.parameter("showpowerconsumption", False)):
output = "{}% ({})".format(
capacity, self.__manager.consumption(widget.name)
)
else:
elif capacity < 100:
output = "{}%".format(capacity)
else:
output = ""
if (
util.format.asbool(self.parameter("showremaining", True))
@ -167,6 +174,16 @@ class Module(core.module.Module):
output, util.format.duration(remaining, compact=True, unit=True)
)
# if bumblebee.util.asbool(self.parameter("rate", True)):
# try:
# with open("{}/power_now".format(widget.name)) as f:
# rate = (float(f.read())/1000000)
# if rate > 0:
# output = "{} {:.2f}w".format(output, rate)
# except Exception:
# pass
if util.format.asbool(self.parameter("showdevice", False)):
output = "{} ({})".format(output, widget.name)
@ -176,6 +193,9 @@ class Module(core.module.Module):
state = []
capacity = widget.get("capacity")
if widget.get("pen"):
state.append("PEN")
if capacity < 0:
log.debug("battery state: {}".format(state))
return ["critical", "unknown"]
@ -187,16 +207,10 @@ class Module(core.module.Module):
charge = self.__manager.charge_any(self._batteries)
else:
charge = self.__manager.charge(widget.name)
if charge == "Discharging":
if charge in ["Discharging", "Unknown"]:
state.append(
"discharging-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
)
)
elif charge == "Unknown":
state.append(
"unknown-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
min([5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], key=lambda i: abs(i - capacity))
)
)
else:

View file

@ -8,7 +8,6 @@ Parameters:
contributed by `martindoublem <https://github.com/martindoublem>`_ - many thanks!
"""
import os
import re
import subprocess
@ -22,7 +21,6 @@ import core.input
import util.cli
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.status))
@ -37,7 +35,7 @@ class Module(core.module.Module):
def status(self, widget):
"""Get status."""
return self._status
return self._status if self._status.isdigit() and int(self._status) > 1 else ""
def update(self):
"""Update current state."""
@ -46,7 +44,7 @@ class Module(core.module.Module):
)
if state > 0:
connected_devices = self.get_connected_devices()
self._status = "On - {}".format(connected_devices)
self._status = "{}".format(connected_devices)
else:
self._status = "Off"
adapters_cmd = "rfkill list | grep Bluetooth"
@ -58,31 +56,23 @@ class Module(core.module.Module):
def _toggle(self, widget=None):
"""Toggle bluetooth state."""
if "On" in self._status:
state = "false"
else:
state = "true"
cmd = (
"dbus-send --system --print-reply --dest=org.blueman.Mechanism /org/blueman/mechanism org.blueman.Mechanism.SetRfkillState boolean:%s"
% state
)
logging.debug("bt: toggling bluetooth")
util.cli.execute(cmd, ignore_errors=True)
SetRfkillState = self._bus.get_object("org.blueman.Mechanism", "/org/blueman/mechanism").get_dbus_method("SetRfkillState", dbus_interface="org.blueman.Mechanism")
SetRfkillState(self._status == "Off")
def state(self, widget):
"""Get current state."""
state = []
if self._status == "No Adapter Found":
if self._status in [ "No Adapter Found", "Off" ]:
state.append("critical")
elif self._status == "On - 0":
state.append("warning")
elif "On" in self._status and not (self._status == "On - 0"):
state.append("ON")
elif self._status == "0":
state.append("enabled")
else:
state.append("critical")
state.append("connected")
state.append("good")
return state
def get_connected_devices(self):
@ -92,12 +82,8 @@ class Module(core.module.Module):
).GetManagedObjects()
for path, interfaces in objects.items():
if "org.bluez.Device1" in interfaces:
if dbus.Interface(
self._bus.get_object("org.bluez", path),
"org.freedesktop.DBus.Properties",
).Get("org.bluez.Device1", "Connected"):
if dbus.Interface(self._bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties", ).Get("org.bluez.Device1", "Connected"):
devices += 1
return devices
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -0,0 +1,99 @@
# pylint: disable=C0111,R0903
"""
Displays the current Power-Profile active
Left-Click or Right-Click as well as Scrolling up / down changes the active Power-Profile
Prerequisites:
* dbus-python
* power-profiles-daemon
"""
import dbus
import core.module
import core.widget
import core.input
class PowerProfileManager:
def __init__(self):
self.POWER_PROFILES_NAME = "net.hadess.PowerProfiles"
self.POWER_PROFILES_PATH = "/net/hadess/PowerProfiles"
self.PP_PROPERTIES_CURRENT_POWER_PROFILE = "ActiveProfile"
self.PP_PROPERTIES_ALL_POWER_PROFILES = "Profiles"
self.DBUS_PROPERTIES = "org.freedesktop.DBus.Properties"
bus = dbus.SystemBus()
pp_proxy = bus.get_object(self.POWER_PROFILES_NAME, self.POWER_PROFILES_PATH)
self.pp_interface = dbus.Interface(pp_proxy, self.DBUS_PROPERTIES)
def get_current_power_profile(self):
return self.pp_interface.Get(
self.POWER_PROFILES_NAME, self.PP_PROPERTIES_CURRENT_POWER_PROFILE
)
def __get_all_power_profile_names(self):
power_profiles = self.pp_interface.Get(
self.POWER_PROFILES_NAME, self.PP_PROPERTIES_ALL_POWER_PROFILES
)
power_profiles_names = []
for pp in power_profiles:
power_profiles_names.append(pp["Profile"])
return power_profiles_names
def next_power_profile(self, event):
all_pp_names = self.__get_all_power_profile_names()
current_pp_index = self.__get_current_pp_index()
next_index = 0
if current_pp_index != (len(all_pp_names) - 1):
next_index = current_pp_index + 1
self.pp_interface.Set(
self.POWER_PROFILES_NAME,
self.PP_PROPERTIES_CURRENT_POWER_PROFILE,
all_pp_names[next_index],
)
def prev_power_profile(self, event):
all_pp_names = self.__get_all_power_profile_names()
current_pp_index = self.__get_current_pp_index()
last_index = len(all_pp_names) - 1
if current_pp_index is not 0:
last_index = current_pp_index - 1
self.pp_interface.Set(
self.POWER_PROFILES_NAME,
self.PP_PROPERTIES_CURRENT_POWER_PROFILE,
all_pp_names[last_index],
)
def __get_current_pp_index(self):
all_pp_names = self.__get_all_power_profile_names()
current_pp = self.get_current_power_profile()
return all_pp_names.index(current_pp)
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, core.widget.Widget(self.full_text))
self.pp_manager = PowerProfileManager()
core.input.register(
self, button=core.input.WHEEL_UP, cmd=self.pp_manager.next_power_profile
)
core.input.register(
self, button=core.input.WHEEL_DOWN, cmd=self.pp_manager.prev_power_profile
)
core.input.register(
self, button=core.input.LEFT_MOUSE, cmd=self.pp_manager.next_power_profile
)
core.input.register(
self, button=core.input.RIGHT_MOUSE, cmd=self.pp_manager.prev_power_profile
)
def full_text(self, widgets):
return self.pp_manager.get_current_power_profile()
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -41,6 +41,7 @@ class Module(core.module.Module):
super().__init__(config, theme, core.widget.Widget(self.get_output))
self.__command = self.parameter("command", 'echo "no command configured"')
self.__command = os.path.expanduser(self.__command)
self.__async = util.format.asbool(self.parameter("async"))
if self.__async:
@ -52,6 +53,7 @@ class Module(core.module.Module):
def set_output(self, value):
self.__output = value
core.event.trigger("update", [self.id], redraw_only=True)
@core.decorators.scrollable
def get_output(self, _):
@ -61,7 +63,6 @@ class Module(core.module.Module):
# if requested then run not async version and just execute command in this thread
if not self.__async:
self.__output = util.cli.execute(self.__command, shell=True, ignore_errors=True).strip()
core.event.trigger("update", [self.id], redraw_only=True)
return
# if previous thread didn't end yet then don't do anything

View file

@ -1,4 +1,5 @@
# pylint: disable=C0111,R0903
# -*- coding: utf-8 -*-
""" Displays the VPN profile that is currently in use.
@ -68,7 +69,7 @@ class Module(core.module.Module):
def vpn_status(self, widget):
if self.__connected_vpn_profile is None:
return "off"
return ""
return self.__connected_vpn_profile
def __on_vpndisconnect(self):

View file

@ -0,0 +1,126 @@
# pylint: disable=C0111,R0903
# -*- coding: utf-8 -*-
"""Shows a widget for each connected screen and allows the user to loop through different orientations.
Parameters:
* wlrotation.display : Name of the output display that will be rotated
+ wlrotation.auto : Boolean value if the display should be rotatet automatic by default
Requires the following executable:
* swaymsg
"""
import core.module
import core.input
import util.cli
import iio
import json
from math import degrees, atan2, sqrt
from os import environ, path
possible_orientations = ["normal", "90", "180", "270"]
class iioValue:
def __init__(self, channel):
self.channel = channel
self.scale = self.read('scale')
self.offset = self.read('offset')
def read(self, attr):
return float(self.channel.attrs[attr].value)
def value(self):
return (self.read('raw') + self.offset) * self.scale
class iioAccelDevice:
def __init__(self):
self.ctx = iio.Context() # store ctx pointer
d = self.ctx.find_device('accel_3d')
self.x = iioValue(d.find_channel('accel_x'))
self.y = iioValue(d.find_channel('accel_y'))
self.z = iioValue(d.find_channel('accel_z'))
def orientation(self):
"""
returns tuple of `[success, value]` where `success` indicates, if an accurate value could be meassured and `value` the sway output api compatible value or `normal` if success is `False`
"""
x_deg, y_deg, z_deg = self._deg()
if abs(z_deg) < 70: # checks if device is angled too shallow
if x_deg >= 70: return True, "270"
if x_deg <= -70: return True, "90"
if abs(x_deg) <= 20:
if y_deg < 0: return True, "normal"
if y_deg > 0: return True, "180"
return False, "normal"
def _deg(self):
gravity = 9.81
x, y, z = self.x.value() / gravity, self.y.value() / gravity, self.z.value() / gravity
return degrees(atan2(x, sqrt(pow(y, 2) + pow(z, 2)))), degrees(atan2(y, sqrt(pow(z, 2) + pow(x, 2)))), degrees(atan2(z, sqrt(pow(x, 2) + pow(y, 2))))
class Display():
def __init__(self, name, widget, display_data, auto=False):
self.name = name
self.widget = widget
self.accelDevice = iioAccelDevice()
self._lock_auto_rotation(not auto)
self.widget.set("orientation", display_data['transform'])
core.input.register(widget, button=core.input.LEFT_MOUSE, cmd=self.rotate_90deg)
core.input.register(widget, button=core.input.RIGHT_MOUSE, cmd=self.toggle)
def rotate_90deg(self, event):
# compute new orientation based on current orientation
current = self.widget.get("orientation")
self._set_rotation(possible_orientations[(possible_orientations.index(current) + 1) % len(possible_orientations)])
# disable auto rotation
self._lock_auto_rotation(True)
def toggle(self, event):
self._lock_auto_rotation(not self.locked)
def auto_rotate(self):
# automagically rotate the display based on sensor values
# this is only called if rotation lock is disabled
success, value = self.accelDevice.orientation()
if success:
self._set_rotation(value)
def _set_rotation(self, new_orientation):
self.widget.set("orientation", new_orientation)
util.cli.execute("swaymsg 'output {} transform {}'".format(self.name, new_orientation))
def _lock_auto_rotation(self, locked):
self.locked = locked
self.widget.set("locked", self.locked)
class Module(core.module.Module):
@core.decorators.every(seconds=1)
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.display = None
display_filter = self.parameter("display", None)
for display in json.loads(util.cli.execute("swaymsg -t get_outputs -r")):
name = display['name']
if display_filter == None or display_filter == name:
self.display = Display(name, self.add_widget(name=name), display, auto=util.format.asbool(self.parameter("auto", False)))
break # I assume that it makes only sense to rotate a single screen
def update(self):
if self.display == None:
return
if self.display.locked:
return
self.display.auto_rotate()
def state(self, widget):
state = []
state.append("locked" if widget.get("locked", True) else "auto")
return state
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -25,6 +25,7 @@ import subprocess
import core.module
import core.decorators
import core.input
import util.cli
import util.format
@ -58,6 +59,8 @@ class Module(core.module.Module):
self.iw = shutil.which("iw")
self._update_widgets(widgets)
core.input.register(self, button=core.input.LEFT_MOUSE, cmd='wifi-menu')
core.input.register(self, button=core.input.RIGHT_MOUSE, cmd='nm-connection-editor')
def update(self):
self._update_widgets(self.widgets())
@ -88,9 +91,7 @@ class Module(core.module.Module):
def _iswlan(self, intf):
# wifi, wlan, wlp, seems to work for me
if intf.startswith("w"):
return True
return False
return intf.startswith("w") and not intf.startswith("wwan")
def _istunnel(self, intf):
return intf.startswith("tun") or intf.startswith("wg")

View file

@ -198,6 +198,10 @@ class Module(core.module.Module):
def state(self, _):
if self.__muted:
return ["warning", "muted"]
return ["unmuted"]
if self.__volume >= .5:
return ["unmuted", "unmuted-high"]
if self.__volume >= .1:
return ["unmuted", "unmuted-mid"]
return ["unmuted", "unmuted-low"]
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

View file

@ -264,6 +264,8 @@ Parameters:
* pulsectl.autostart: If set to 'true' (default is 'false'), automatically starts the pulsectl daemon if it is not running
* pulsectl.percent_change: How much to change volume by when scrolling on the module (default is 2%)
* pulsectl.limit: Upper limit for setting the volume (default is 0%, which means 'no limit')
* pulsectl.popup-filter: Comma-separated list of device strings (if the device name contains it) to exclude
from the default device popup menu (e.g. Monitor for sources)
* pulsectl.showbars: 'true' for showing volume bars, requires --markup=pango;
'false' for not showing volume bars (default)
* pulsectl.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
@ -424,6 +426,7 @@ Requires the following executable:
* amixer
Parameters:
* amixer.card: Sound Card to use (default is 0)
* amixer.device: Device to use (default is Master,0)
* amixer.percent_change: How much to change volume by when scrolling on the module (default is 4%)
@ -431,6 +434,8 @@ contributed by `zetxx <https://github.com/zetxx>`_ - many thanks!
input handling contributed by `ardadem <https://github.com/ardadem>`_ - many thanks!
multiple audio cards contributed by `hugoeustaquio <https://github.com/hugoeustaquio>`_ - many thanks!
.. image:: ../screenshots/amixer.png
apt
@ -685,6 +690,49 @@ lacking the aforementioned pattern settings or they have wrong values.
contributed by `somospocos <https://github.com/somospocos>`_ - many thanks!
cpu3
~~~~
Multiwidget CPU module
Can display any combination of:
* max CPU frequency
* total CPU load in percents (integer value)
* per-core CPU load as graph - either mono or colored
* CPU temperature (in Celsius degrees)
* CPU fan speed
Requirements:
* the psutil Python module for the first three items from the list above
* sensors executable for the rest
Parameters:
* cpu3.layout: Space-separated list of widgets to add.
Possible widgets are:
* cpu3.maxfreq
* cpu3.cpuload
* cpu3.coresload
* cpu3.temp
* cpu3.fanspeed
* cpu3.colored: 1 for colored per core load graph, 0 for mono (default)
* cpu3.temp_json: json path to look for in the output of 'sensors -j';
required if cpu3.temp widget is used
* cpu3.fan_json: json path to look for in the output of 'sensors -j';
required if cpu3.fanspeed widget is used
Note: if you are getting 'n/a' for CPU temperature / fan speed, then you're
lacking the aforementioned json path settings or they have wrong values.
Example json paths:
* `cpu3.temp_json="coretemp-isa-0000.Package id 0.temp1_input"`
* `cpu3.fan_json="thinkpad-isa-0000.fan1.fan1_input"`
contributed by `SuperQ <https://github.com/SuperQ>`
based on cpu2 by `<somospocos <https://github.com/somospocos>`
currency
~~~~~~~~
@ -836,6 +884,9 @@ be running. Scripts will be executed when dunst gets unpaused.
Requires:
* dunst v1.5.0+
Parameters:
* dunstctl.disabled(Boolean): dunst state on start
contributed by `cristianmiranda <https://github.com/cristianmiranda>`_ - many thanks!
contributed by `joachimmathes <https://github.com/joachimmathes>`_ - many thanks!
@ -866,7 +917,9 @@ Displays first upcoming event in google calendar.
Events that are set as 'all-day' will not be shown.
Requires credentials.json from a google api application where the google calendar api is installed.
On first time run the browser will open and google will ask for permission for this app to access the google calendar and then save a .gcalendar_token.json file to the credentials_path directory which stores this permission.
On first time run the browser will open and google will ask for permission for this app to access
the google calendar and then save a .gcalendar_token.json file to the credentials_path directory
which stores this permission.
A refresh is done every 15 minutes.
@ -878,7 +931,7 @@ Parameters:
Requires these pip packages:
* google-api-python-client >= 1.8.0
* google-auth-httplib2
* google-auth-httplib2
* google-auth-oauthlib
getcrypto
@ -923,6 +976,29 @@ contributed by:
.. image:: ../screenshots/github.png
gitlab
~~~~~~
Displays the GitLab todo count:
* https://docs.gitlab.com/ee/user/todos.html
* https://docs.gitlab.com/ee/api/todos.html
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the GitLab todo query failed, the shown value is `n/a`
Parameters:
* gitlab.token: GitLab personal access token, the token needs to have the "read_api" scope.
* gitlab.host: Host of the GitLab instance, default is "gitlab.com".
* gitlab.actions: Comma separated actions to be parsed (e.g.: gitlab.actions=assigned,approval_required)
.. image:: ../screenshots/gitlab.png
gpmdp
~~~~~
@ -1107,6 +1183,7 @@ Parameters:
if {file} = '/foo/bar.baz', then {file2} = 'bar'
* mpd.host: MPD host to connect to. (mpc behaviour by default)
* mpd.port: MPD port to connect to. (mpc behaviour by default)
* mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main.
contributed by `alrayyes <https://github.com/alrayyes>`_ - many thanks!
@ -1126,9 +1203,7 @@ network_traffic
~~~~~~~~~~~~~~~
Displays network traffic
Requires the following library:
* netifaces
* No extra configuration needed
contributed by `izn <https://github.com/izn>`_ - many thanks!
@ -1155,7 +1230,7 @@ nvidiagpu
Displays GPU name, temperature and memory usage.
Parameters:
* nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{mem_used}/{mem_total} MiB')
* nvidiagpu.format: Format string (defaults to '{name}: {temp}°C %{usedmem}/{totalmem} MiB')
Available values are: {name} {temp} {mem_used} {mem_total} {fanspeed} {clock_gpu} {clock_mem} {gpu_usage_pct} {mem_usage_pct} {mem_io_pct}
Requires nvidia-smi
@ -1239,12 +1314,19 @@ Displays the pi-hole status (up/down) together with the number of ads that were
Parameters:
* pihole.address : pi-hole address (e.q: http://192.168.1.3)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
* pihole.apitoken : pi-hole API token (can be obtained in the pi-hole webinterface (Settings -> API)
OR (deprecated!)
* pihole.pwhash : pi-hole webinterface password hash (can be obtained from the /etc/pihole/SetupVars.conf file)
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
pipewire
~~~~~~~
~~~~~~~~
get volume level or control it
@ -1575,7 +1657,9 @@ Display a stock quote from finance.yahoo.com
Parameters:
* stock.symbols : Comma-separated list of symbols to fetch
* stock.change : Should we fetch change in stock value (defaults to True)
* stock.apikey : API key created on https://alphavantage.co
* stock.url : URL to use, defaults to "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={apikey}"
* stock.fields : Fields from the response to show, defaults to "01. symbol,05. price,10. change percent"
contributed by `msoulier <https://github.com/msoulier>`_ - many thanks!
@ -1610,11 +1694,11 @@ adds the possibility to
* reboot
the system.
Per default a confirmation dialog is shown before the actual action is performed.
Parameters:
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.confirm: show confirmation dialog before performing any action (default: true)
* system.reboot: specify a reboot command (defaults to 'reboot')
* system.shutdown: specify a shutdown command (defaults to 'shutdown -h now')
* system.logout: specify a logout command (defaults to 'i3exit logout')
@ -1713,6 +1797,27 @@ Parameters:
* todo_org.remaining: False by default. When true, will output the number of remaining todos instead of the number completed (i.e. 1/4 means 1 of 4 todos remaining, rather than 1 of 4 todos completed)
Based on the todo module by `codingo <https://github.com/codingo>`
todoist
~~~~~~~
Displays the nº of Todoist tasks that are due:
* https://developer.todoist.com/rest/v2/#get-active-tasks
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Todoist get active tasks query failed, the shown value is `n/a`
Parameters:
* todoist.token: Todoist api token, you can get it in https://todoist.com/app/settings/integrations/developer.
* todoist.filter: a filter statement defined by Todoist (https://todoist.com/help/articles/introduction-to-filters), eg: "!assigned to: others & (Overdue | due: today)"
.. image:: ../screenshots/todoist.png
traffic
~~~~~~~
@ -1751,6 +1856,27 @@ contributed by `ccoors <https://github.com/ccoors>`_ - many thanks!
.. image:: ../screenshots/uptime.png
usage
~~~~~
Module for ActivityWatch (https://activitywatch.net/)
Displays the amount of time the system was used actively.
Requirements:
* sqlite3 module for python
* ActivityWatch
Errors:
* when you get 'error: unable to open database file', modify the parameter 'database' to your ActivityWatch database file
-> often found by running 'locate aw-server/peewee-sqlite.v2.db'
Parameters:
* usage.database: path to your database file
* usage.format: Specify what gets printed to the bar
-> use 'HH', 'MM' or 'SS', they will get replaced by the number of hours, minutes and seconds, respectively
contributed by lasnikr (https://github.com/lasnikr)
vpn
~~~
@ -1770,6 +1896,34 @@ Displays the VPN profile that is currently in use.
contributed by `bbernhard <https://github.com/bbernhard>`_ - many thanks!
wakatime
~~~~~~~~
Displays the WakaTime daily/weekly/monthly times:
* https://wakatime.com/developers#stats
Uses `xdg-open` or `x-www-browser` to open web-pages.
Requires the following library:
* requests
Errors:
if the Wakatime status query failed, the shown value is `n/a`
Parameters:
* wakatime.token: Wakatime secret api key, you can get it in https://wakatime.com/settings/account.
* wakatime.range: Range of the output, default is "Today". Can be one of “Today”, “Yesterday”, “Last 7 Days”, “Last 7 Days from Yesterday”, “Last 14 Days”, “Last 30 Days”, “This Week”, “Last Week”, “This Month”, or “Last Month”.
* wakatime.format: Format of the output, default is "digital"
Valid inputs are:
* "decimal" -> 1.37
* "digital" -> 1:22
* "seconds" -> 4931.29
* "text" -> 1 hr 22 mins
* "%H:%M:%S" -> 01:22:31 (or any other valid format)
.. image:: ../screenshots/wakatime.png
watson
~~~~~~
@ -1778,6 +1932,10 @@ Displays the status of watson (time-tracking tool)
Requires the following executable:
* watson
Parameters:
* watson.format: Output format, defaults to "{project} [{tags}]"
Supported fields are: {project}, {tags}, {relative_start}, {absolute_start}
contributed by `bendardenne <https://github.com/bendardenne>`_ - many thanks!
weather
@ -1795,7 +1953,7 @@ Parameters:
* weather.unit: metric (default), kelvin, imperial
* weather.showcity: If set to true, show location information, otherwise hide it (defaults to true)
* weather.showminmax: If set to true, show the minimum and maximum temperature, otherwise hide it (defaults to false)
* weather.apikey: API key from http://api.openweathermap.org
* weather.apikey: API key from https://api.openweathermap.org
contributed by `TheEdgeOfRage <https://github.com/TheEdgeOfRage>`_ - many thanks!

View file

@ -0,0 +1,2 @@
dbus-python
power-profiles-daemon

View file

@ -0,0 +1,32 @@
from unittest.mock import patch, MagicMock
import unittest
import pytest
import core.config
import modules.contrib.power_profile
pytest.importorskip("dbus")
def build_powerprofile_module():
config = core.config.Config([])
return modules.contrib.power_profile.Module(config=config, theme=None)
class TestPowerProfileUnit(unittest.TestCase):
def __get_mock_dbus_get_method(self, mock_system_bus):
return (
mock_system_bus.return_value.get_object.return_value.get_dbus_method.return_value
)
def test_load_module(self):
__import__("modules.contrib.power-profile")
@patch("dbus.SystemBus")
def test_full_text(self, mock_system_bus):
mock_get = self.__get_mock_dbus_get_method(mock_system_bus)
mock_get.return_value = "balanced"
module = build_powerprofile_module()
module.update()
assert module.widgets()[0].full_text() == "balanced"

View file

@ -9,6 +9,10 @@
"fg": "#fbf1c7",
"bg": "#cc241d"
},
"good": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"default-separators": false,
"separator-block-width": 0
},
@ -34,16 +38,6 @@
"bg": "#859900"
}
},
"battery": {
"charged": {
"fg": "#1d2021",
"bg": "#b8bb26"
},
"AC": {
"fg": "#1d2021",
"bg": "#b8bb26"
}
},
"bluetooth": {
"ON": {
"fg": "#1d2021",

View file

@ -410,5 +410,8 @@
"speedtest": {
"running": { "prefix": [".", "..", "...", ".."] },
"not-running": { "prefix": "[start]" }
},
"power-profile": {
"prefix": "profile"
}
}

View file

@ -199,11 +199,14 @@
},
"pulseout": {
"muted": {
"prefix": ""
"prefix": "󰝟"
},
"unmuted": {
"prefix": ""
}
},
"unmuted-low": { "prefix": "󰕿" },
"unmuted-mid": { "prefix": "󰖀" },
"unmuted-high": { "prefix": "󰕾" }
},
"amixer": {
"muted": {
@ -223,7 +226,7 @@
},
"pulsein": {
"muted": {
"prefix": ""
"prefix": "󰍭"
},
"unmuted": {
"prefix": ""
@ -241,46 +244,22 @@
"prefix": "\uf17c"
},
"nic": {
"wireless-up": {
"prefix": ""
},
"wireless-down": {
"prefix": ""
},
"wired-up": {
"prefix": ""
},
"wired-down": {
"prefix": ""
},
"tunnel-up": {
"prefix": ""
},
"tunnel-down": {
"prefix": ""
}
"wireless-up": { "prefix": "" },
"wireless-down": { "prefix": "睊" },
"wired-up": { "prefix": "" },
"wired-down": { "prefix": "" },
"tunnel-up": { "prefix": "嬨" },
"tunnel-down": { "prefix": "嬨" }
},
"bluetooth": {
"ON": {
"prefix": ""
},
"OFF": {
"prefix": ""
},
"?": {
"prefix": ""
}
"ON": { "prefix": "󰂯" },
"OFF": { "prefix": "󰂲" },
"?": { "prefix": "󰂱" }
},
"bluetooth2": {
"ON": {
"prefix": ""
},
"warning": {
"prefix": ""
},
"critical": {
"prefix": ""
}
"connected": { "prefix": "󰂱" },
"enabled": { "prefix": "󰂯" },
"critical": { "prefix": "󰂲" }
},
"battery-upower": {
"charged": {
@ -349,136 +328,46 @@
}
},
"battery": {
"charged": {
"prefix": "",
"suffix": ""
},
"AC": {
"suffix": ""
},
"charged": { "prefix": "󰂄" },
"AC": { "suffix": "󱐥" },
"PEN": { "suffix": "󰏪" },
"charging": {
"prefix": [
"",
"",
"",
"",
""
],
"suffix": ""
"prefix": [ "󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅" ],
"suffix": ""
},
"discharging-10": {
"prefix": "",
"suffix": ""
},
"discharging-25": {
"prefix": "",
"suffix": ""
},
"discharging-50": {
"prefix": "",
"suffix": ""
},
"discharging-80": {
"prefix": "",
"suffix": ""
},
"discharging-100": {
"prefix": "",
"suffix": ""
},
"unlimited": {
"prefix": "",
"suffix": ""
},
"estimate": {
"prefix": ""
},
"unknown-10": {
"prefix": "",
"suffix": ""
},
"unknown-25": {
"prefix": "",
"suffix": ""
},
"unknown-50": {
"prefix": "",
"suffix": ""
},
"unknown-80": {
"prefix": "",
"suffix": ""
},
"unknown-100": {
"prefix": "",
"suffix": ""
}
"discharging-05": { "prefix": "󰂎", "suffix": "" },
"discharging-10": { "prefix": "󰁺", "suffix": "" },
"discharging-20": { "prefix": "󰁻", "suffix": "" },
"discharging-30": { "prefix": "󰁼", "suffix": "" },
"discharging-40": { "prefix": "󰁽", "suffix": "" },
"discharging-50": { "prefix": "󰁾", "suffix": "" },
"discharging-60": { "prefix": "󰁿", "suffix": "" },
"discharging-70": { "prefix": "󰂀", "suffix": "" },
"discharging-80": { "prefix": "󰂁", "suffix": "" },
"discharging-90": { "prefix": "󰂂", "suffix": "" },
"discharging-100": { "prefix": "󰁹" },
"unlimited": { "prefix": "", "suffix": "" },
"estimate": { "prefix": "" }
},
"battery_all": {
"charged": {
"prefix": "",
"suffix": ""
},
"AC": {
"suffix": ""
},
"charged": { "prefix": "", "suffix": "" },
"AC": { "suffix": "" },
"charging": {
"prefix": [
"",
"",
"",
"",
""
],
"prefix": [ "", "", "", "", "" ],
"suffix": ""
},
"discharging-10": {
"prefix": "",
"suffix": ""
},
"discharging-25": {
"prefix": "",
"suffix": ""
},
"discharging-50": {
"prefix": "",
"suffix": ""
},
"discharging-80": {
"prefix": "",
"suffix": ""
},
"discharging-100": {
"prefix": "",
"suffix": ""
},
"unlimited": {
"prefix": "",
"suffix": ""
},
"estimate": {
"prefix": ""
},
"unknown-10": {
"prefix": "",
"suffix": ""
},
"unknown-25": {
"prefix": "",
"suffix": ""
},
"unknown-50": {
"prefix": "",
"suffix": ""
},
"unknown-80": {
"prefix": "",
"suffix": ""
},
"unknown-100": {
"prefix": "",
"suffix": ""
}
"discharging-10": { "prefix": "", "suffix": "" },
"discharging-25": { "prefix": "", "suffix": "" },
"discharging-50": { "prefix": "", "suffix": "" },
"discharging-80": { "prefix": "", "suffix": "" },
"discharging-100": { "prefix": "", "suffix": "" },
"unlimited": { "prefix": "", "suffix": "" },
"estimate": { "prefix": "" },
"unknown-10": { "prefix": "", "suffix": "" },
"unknown-25": { "prefix": "", "suffix": "" },
"unknown-50": { "prefix": "", "suffix": "" },
"unknown-80": { "prefix": "", "suffix": "" },
"unknown-100": { "prefix": "", "suffix": "" }
},
"caffeine": {
"activated": {
@ -691,7 +580,7 @@
}
},
"vpn": {
"prefix": ""
"prefix": "󰖂"
},
"system": {
"prefix": "  "
@ -739,5 +628,12 @@
},
"thunderbird": {
"prefix": ""
},
"power-profile": {
"prefix": "\uF2C1"
},
"wlrotation": {
"auto": {"prefix": "󰑵"},
"locked": {"prefix": "󰑸"}
}
}