-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add popups, system tray and improve codebase (#1)
* Add GTK3 dialogs * Update dependencies * Added a simple system tray * Add enable/disable feature * Add system tray, and some notifications as popups * Improve config documentation * Improve code for mantainability and added dialogs for battery alerts - Created Settings class that is fed from load_settings to improve readability and typing for settings - Separated tresholds checks into a separate function for readability - Improve toml settings and comments - Remind now uses a timestamp to manage remind time instead of a time.sleep. More of that on the source * Add padding and increase font size to popups * Add title param to popups and added battery action popup * Dont report when battery is plugged
- Loading branch information
Showing
14 changed files
with
531 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
from .utils import get_battery_status, notify, notify_with_actions, execute_action | ||
from .settings_loader import load_settings | ||
from .entry import main | ||
from .settings_loader import load_settings |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import threading | ||
import time | ||
|
||
from pystray import Menu, MenuItem | ||
from .settings_loader import Settings | ||
from .tray import get_icon | ||
from .utils import execute_action, get_battery_status | ||
from .notifications import notify, alert_with_options, alert | ||
from .types import BatteryReport | ||
from typing import Optional | ||
from datetime import datetime | ||
|
||
settings = Settings.load() | ||
|
||
|
||
class BatteryAdvisor: | ||
"""Base Program that manages SysTray Icon and the battery checker service.""" | ||
|
||
def __init__(self): | ||
self.running = True | ||
|
||
def get_battery_reports(self) -> Optional[BatteryReport]: | ||
"""Returns thet status that needs to be reported to the user""" | ||
|
||
batt_percent, plugged = get_battery_status() | ||
|
||
if plugged: | ||
return None | ||
|
||
if batt_percent <= settings.battery_action_treshold: | ||
return BatteryReport.ACTION | ||
|
||
if batt_percent <= settings.critical_battery_treshold: | ||
return BatteryReport.CRITICAL | ||
|
||
if batt_percent <= 100: | ||
return BatteryReport.LOW | ||
|
||
return None | ||
|
||
def _battery_checker(self): | ||
""" | ||
Service that checks the battery status and notifies the user. | ||
This one runs in a separate thread because the SysTray icon must run on the main thread. | ||
""" | ||
|
||
print("Starting battery checker...") | ||
|
||
# Always get initial status to avoid false notifications | ||
_, was_plugged = get_battery_status() | ||
remind_timestamp = datetime.now() | ||
|
||
while True: | ||
if not self.running: | ||
time.sleep(3) | ||
continue | ||
|
||
print("Checking battery status...") | ||
_, plugged = get_battery_status() | ||
|
||
# Battery Plugged in notifications | ||
if plugged != was_plugged: | ||
was_plugged = plugged | ||
if plugged and settings.notify_plugged: | ||
notify("Battery Plugged In", "Battery is now charging") | ||
elif not plugged and settings.notify_unplugged: | ||
notify("Battery Unplugged", "Battery is now discharging.") | ||
|
||
report = self.get_battery_reports() | ||
print("Battery Report:", report) | ||
|
||
if report is None: | ||
time.sleep(settings.check_interval) | ||
continue | ||
|
||
if report == BatteryReport.ACTION: | ||
battery_action = settings.battery_action | ||
alert(message=f"Your battery will {battery_action.capitalize()} soon.") | ||
time.sleep(3) | ||
execute_action(battery_action, settings.actions) | ||
|
||
if report == BatteryReport.CRITICAL: | ||
alert_with_options( | ||
message="Your battery is critically low. Please plug in your charger.", | ||
options=settings.critical_battery_options, | ||
title="Battery Critically Low", | ||
) | ||
|
||
# Don't sleep for the amount of time to remind the user again. | ||
# Instead, check if the time has passed using timestamps. | ||
|
||
# This is to avoid excluding critical notifications or if a long remind time is set | ||
# or plugged/unplugged notifications because of sleep. | ||
# More like a QoL feature. ;) | ||
if ( | ||
report == BatteryReport.LOW | ||
and remind_timestamp.timestamp() <= datetime.now().timestamp() | ||
): | ||
r = alert_with_options( | ||
"Battery is low. Please plug in your charger.", | ||
settings.low_battery_options, | ||
title="Battery Low", | ||
) | ||
|
||
selected_action = settings.low_battery_options[r] | ||
|
||
if selected_action == "remind": | ||
remind_timestamp = datetime.fromtimestamp( | ||
remind_timestamp.timestamp() + settings.remind_time | ||
) | ||
|
||
else: | ||
execute_action(selected_action, settings.actions) | ||
|
||
time.sleep(settings.check_interval) | ||
|
||
def _on_enabled_click(self, icon, item): | ||
self.running = not self.running | ||
print("Battery Advisor is now", "enabled" if self.running else "disabled") | ||
|
||
def start(self): | ||
batt_thread = threading.Thread(target=self._battery_checker, daemon=True) | ||
batt_thread.start() | ||
|
||
menu = Menu( | ||
MenuItem( | ||
text="Enabled", | ||
checked=lambda item: self.running, | ||
action=self._on_enabled_click, | ||
) | ||
) | ||
get_icon(menu).run() | ||
print("Exiting...") | ||
return 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,5 @@ | ||
import time | ||
from .utils import get_battery_status, notify, notify_with_actions, execute_action | ||
from .settings_loader import load_settings | ||
|
||
settings = load_settings() | ||
|
||
LOW_BATTERY_TRESHOLD = settings["tresholds"]["low_battery_treshold"] | ||
CRITICAL_BATTERY_TRESHOLD = settings["tresholds"]["critical_battery_treshold"] | ||
BATTERY_ACTION_TRESHOLD = settings["tresholds"]["battery_action_treshold"] | ||
CHECK_INTERVAL = settings["advisor"]["check_interval"] | ||
|
||
# Configs | ||
NOTIFY_PLUGGED = settings["advisor"]["notify_plugged"] | ||
NOTIFY_UNPLUGGED = settings["advisor"]["notify_unplugged"] | ||
|
||
# Actions | ||
LOW_BATTERY_OPTIONS = settings["advisor"]["low_battery_options"] | ||
CRITICAL_BATTERY_OPTIONS = settings["advisor"]["critical_battery_options"] | ||
from .battery_advisor import BatteryAdvisor | ||
|
||
|
||
def main(): | ||
_, was_plugged = get_battery_status() | ||
|
||
while True: | ||
print("Checking battery status...") | ||
remind_time = 0 | ||
batt_percent, plugged = get_battery_status() | ||
|
||
# Battery Plugged in notifications | ||
if plugged != was_plugged: | ||
was_plugged = plugged | ||
if plugged and NOTIFY_PLUGGED: | ||
notify("Battery Plugged In", "Battery is now charging") | ||
elif not plugged and NOTIFY_UNPLUGGED: | ||
notify("Battery Unplugged", "Battery is now discharging.") | ||
|
||
if plugged: | ||
print("Battery is charging. Skipping checks.") | ||
print("Sleeping...") | ||
time.sleep(CHECK_INTERVAL) | ||
continue | ||
|
||
# Battery Low notifications | ||
if batt_percent <= BATTERY_ACTION_TRESHOLD: | ||
notify( | ||
"Battery Action", | ||
f"Your battery is at {batt_percent}%. Your system will {settings['advisor']['battery_action'].capitalize()} in a few.", | ||
) | ||
print("Reporting battery action.") | ||
print("Sleeping...") | ||
time.sleep(5) | ||
configured_action = settings["advisor"]["battery_action"] | ||
action_cmd = settings["actions"][configured_action] | ||
print("Executing Battery Action. Goodbye...") | ||
execute_action(action_cmd) | ||
|
||
if batt_percent <= CRITICAL_BATTERY_TRESHOLD: | ||
print("Reporting critical battery.") | ||
remind_time = notify_with_actions( | ||
title="CRITICAL BATTERY", | ||
message=f"Your battery is at {int(batt_percent)}%. Consider plugging your device.", | ||
options=CRITICAL_BATTERY_OPTIONS, | ||
actions=settings["actions"], | ||
remind_time=round( | ||
settings["advisor"]["remind_time"] / 2 | ||
), # Remind in half the remind time | ||
) | ||
|
||
elif batt_percent <= LOW_BATTERY_TRESHOLD: | ||
print("Reporting low battery.") | ||
remind_time = notify_with_actions( | ||
title="Low Battery", | ||
message=f"Consider plugging your device.", | ||
options=LOW_BATTERY_OPTIONS, | ||
actions=settings["actions"], | ||
remind_time=settings["advisor"]["remind_time"], | ||
) | ||
|
||
if remind_time > 0: | ||
print("A function returned a remind time!") | ||
time.sleep(remind_time) | ||
continue | ||
|
||
print("Sleeping...") | ||
time.sleep(CHECK_INTERVAL) | ||
BatteryAdvisor().start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import gi | ||
|
||
gi.require_version("Gtk", "3.0") | ||
gi.require_version("Pango", "1.0") | ||
from gi.repository import Gtk, Pango | ||
|
||
from ..utils import execute_action | ||
|
||
|
||
class MessageAlert(Gtk.Dialog): | ||
def __init__( | ||
self, | ||
title: str, | ||
message: str, | ||
): | ||
super().__init__(title, transient_for=None) | ||
|
||
content = self.get_content_area() | ||
_title_label = Gtk.Label(margin_bottom=13, margin_top=10) | ||
_title_label.set_markup(f"<b>{title}</b>") | ||
_msg_label = Gtk.Label( | ||
message, margin_bottom=12, margin_start=10, margin_end=10 | ||
) | ||
|
||
# Increase text size | ||
_title_label.override_font(Pango.FontDescription("Ubuntu 12")) | ||
_msg_label.override_font(Pango.FontDescription("Ubuntu 10")) | ||
|
||
content.add(_title_label) | ||
content.add(_msg_label) | ||
|
||
if self.__class__ == MessageAlert: | ||
self.add_button("Close", Gtk.ResponseType.CLOSE) | ||
|
||
self.show_all() | ||
|
||
def run(self): | ||
response = super().run() | ||
if response == -4 or response == -7 or response == -1: | ||
response = 0 | ||
|
||
return response | ||
|
||
|
||
class AlertWithButtons(MessageAlert): | ||
def __init__(self, title: str, message, actions: list[str]): | ||
super().__init__(message=message, title=title) | ||
|
||
for i, action in enumerate(actions): | ||
self.add_button(action.capitalize(), i) | ||
|
||
self.show_all() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import subprocess | ||
|
||
from .utils import _get_path_icon, execute_action | ||
from .gui.alerts import AlertWithButtons, MessageAlert | ||
|
||
EXPIRE_TIME = 160000 | ||
|
||
|
||
def notify(title: str, message: str): | ||
"""Sends a notification to the user""" | ||
subprocess.run(["notify-send", title, message, f"--icon={_get_path_icon()}"]) | ||
|
||
|
||
def alert(message: str, title: str = "Battery Advisor"): | ||
"""Sends a popup to the user""" | ||
dialog = MessageAlert(message=message, title=title) | ||
dialog.run() | ||
dialog.destroy() | ||
|
||
|
||
def alert_with_options( | ||
message: str, options: list[str], title: str = "Battery Advisor" | ||
) -> int: | ||
"""Sends a popup with a close option to the user. | ||
Returns | ||
------- | ||
int | ||
The selected option index | ||
""" | ||
|
||
dialog = AlertWithButtons(title=title, message=message, actions=options) | ||
selection = dialog.run() | ||
dialog.destroy() | ||
return selection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.