diff --git a/bumblebee_status/core/config.py b/bumblebee_status/core/config.py index 1d4ebaa..0b564b3 100644 --- a/bumblebee_status/core/config.py +++ b/bumblebee_status/core/config.py @@ -240,11 +240,16 @@ class Config(util.store.Store): :param filename: path to the file to load """ - def load_config(self, filename): - if os.path.exists(filename): + def load_config(self, filename, content=None): + if os.path.exists(filename) or content != None: log.info("loading {}".format(filename)) tmp = RawConfigParser() - tmp.read(u"{}".format(filename)) + tmp.optionxform = str + + if content: + tmp.read_string(content) + else: + tmp.read(u"{}".format(filename)) if tmp.has_section("module-parameters"): for key, value in tmp.items("module-parameters"): diff --git a/bumblebee_status/modules/contrib/dunstctl.py b/bumblebee_status/modules/contrib/dunstctl.py index f082f1b..1ad43df 100644 --- a/bumblebee_status/modules/contrib/dunstctl.py +++ b/bumblebee_status/modules/contrib/dunstctl.py @@ -28,6 +28,8 @@ class Module(core.module.Module): self.__states = {"unknown": ["unknown", "critical"], "true": ["muted", "warning"], "false": ["unmuted"]} + if util.format.asbool(self.parameter("disabled", False)): + util.cli.execute("dunstctl set-paused true", ignore_errors=True) def toggle_state(self, event): util.cli.execute("dunstctl set-paused toggle", ignore_errors=True) diff --git a/bumblebee_status/modules/contrib/shell.py b/bumblebee_status/modules/contrib/shell.py index 566de42..e8461b9 100644 --- a/bumblebee_status/modules/contrib/shell.py +++ b/bumblebee_status/modules/contrib/shell.py @@ -61,6 +61,7 @@ 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 diff --git a/bumblebee_status/modules/contrib/title.py b/bumblebee_status/modules/contrib/title.py index 055369e..1c98b42 100644 --- a/bumblebee_status/modules/contrib/title.py +++ b/bumblebee_status/modules/contrib/title.py @@ -50,8 +50,9 @@ class Module(core.module.Module): # create a connection with i3ipc self.__i3 = i3ipc.Connection() - # event is called both on focus change and title change + # event is called both on focus change and title change, and on workspace change self.__i3.on("window", lambda __p_i3, __p_e: self.__pollTitle()) + self.__i3.on("workspace", lambda __p_i3, __p_e: self.__pollTitle()) # begin listening for events threading.Thread(target=self.__i3.main).start() diff --git a/bumblebee_status/modules/core/pulsectl.py b/bumblebee_status/modules/core/pulsectl.py index 701f6ef..fd77c26 100644 --- a/bumblebee_status/modules/core/pulsectl.py +++ b/bumblebee_status/modules/core/pulsectl.py @@ -110,8 +110,8 @@ class Module(core.module.Module): res = f"{res} {util.graph.hbar(self.__volume*100)}" if self.__show_device_name: - friendly_name = self.parameter(self.__devicename.lower(), self.__devicename) - icon = self.parameter("icon." + self.__devicename.lower(), "") + friendly_name = self.parameter(self.__devicename, self.__devicename) + icon = self.parameter("icon." + self.__devicename, "") res = ( icon + " " + friendly_name + " | " + res if icon != "" diff --git a/bumblebee_status/util/cli.py b/bumblebee_status/util/cli.py index d14dc6a..4ef25d9 100644 --- a/bumblebee_status/util/cli.py +++ b/bumblebee_status/util/cli.py @@ -52,7 +52,19 @@ def execute( raise RuntimeError("{} not found".format(cmd)) if wait: - out, _ = proc.communicate() + timeout = 60 + try: + out, _ = proc.communicate(timeout=timeout) + except subprocess.TimeoutExpired as e: + logging.warning( + f""" + Communication with process pid={proc.pid} hangs for more + than {timeout} seconds. + If this is not expected, the process is stale, or + you might have run in stdout / stderr deadlock. + """ + ) + out, _ = proc.communicate() if proc.returncode != 0: err = "{} exited with code {}".format(cmd, proc.returncode) logging.warning(err) diff --git a/docs/introduction.rst b/docs/introduction.rst index 3831d4b..891b14c 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -44,6 +44,14 @@ like this: -t } +Line continuations (breaking a single line into multiple lines) is allowed in +the i3 configuration, but please ensure that all lines except the final one need to have a trailing +"\". +This is explained in detail here: +[i3 user guide: line continuation](https://i3wm.org/docs/userguide.html#line_continuation) + + + You can retrieve a list of modules (and their parameters) and themes by entering: diff --git a/docs/modules.rst b/docs/modules.rst index c7a78de..0b3ff9d 100644 --- a/docs/modules.rst +++ b/docs/modules.rst @@ -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 `_ - many thanks! input handling contributed by `ardadem `_ - many thanks! +multiple audio cards contributed by `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 `_ - 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 ` +based on cpu2 by `` + 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 `_ - many thanks! contributed by `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 `_ - many thanks! @@ -1126,9 +1203,7 @@ network_traffic ~~~~~~~~~~~~~~~ Displays network traffic - -Requires the following library: - * netifaces + * No extra configuration needed contributed by `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 `_ - 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 `_ - 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 ` +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 `_ - 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 `_ - 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 `_ - 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 `_ - many thanks! diff --git a/tests/core/test_config.py b/tests/core/test_config.py index 762c674..02695cd 100644 --- a/tests/core/test_config.py +++ b/tests/core/test_config.py @@ -113,6 +113,12 @@ def test_missing_parameter(): assert cfg.get("test.key") == None assert cfg.get("test.key", "no-value-set") == "no-value-set" +def test_file_case_sensitivity(): + cfg = core.config.Config([]) + cfg.load_config("", content="[module-parameters]\ntest.key = VaLuE\ntest.KeY2 = value") + + assert cfg.get("test.key") == "VaLuE" + assert cfg.get("test.KeY2") == "value" # # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4