diff --git a/data/org.gnome.hamster.gschema.xml b/data/org.gnome.hamster.gschema.xml index dce4c1611..e8cc5f7f7 100644 --- a/data/org.gnome.hamster.gschema.xml +++ b/data/org.gnome.hamster.gschema.xml @@ -18,5 +18,20 @@ then the activity belongs to the previous hamster day. + + + 15 + After how many minutes to notify the user + + + + false + Enable notifications + + + + false + Send Notifications when no activity is set + diff --git a/data/preferences.ui b/data/preferences.ui index 37c81ecb3..213d78f0a 100644 --- a/data/preferences.ui +++ b/data/preferences.ui @@ -2,6 +2,11 @@ + + 100 + 1 + 10 + False @@ -40,6 +45,89 @@ start vertical 8 + + + False + vertical + + + Send notifications + True + True + False + True + + + False + True + -1 + + + + + True + False + 20 + vertical + 2 + + + True + False + Remind of current activity every: + + + False + True + 0 + + + + + True + True + adjustment1 + 120 + 0 + 0 + right + + + False + True + 1 + + + + + Also remind when no activity is set + True + True + False + True + + + + False + True + 2 + + + + + False + True + 2 + 4 + + + + + False + True + 0 + + True diff --git a/src/hamster-cli.py b/src/hamster-cli.py index 9c6786787..a0176fab9 100644 --- a/src/hamster-cli.py +++ b/src/hamster-cli.py @@ -46,7 +46,7 @@ from hamster.lib import default_logger, stuff from hamster.lib import datetime as dt from hamster.lib.fact import Fact - +from hamster.lib.notifsmanager import notifs_mgr logger = default_logger(__file__) diff --git a/src/hamster/lib/configuration.py b/src/hamster/lib/configuration.py index 24b42c668..c477531cc 100644 --- a/src/hamster/lib/configuration.py +++ b/src/hamster/lib/configuration.py @@ -181,5 +181,19 @@ def day_start(self): hours, minutes = divmod(day_start_minutes, 60) return dt.time(hours, minutes) + @property + def notify_interval(self): + """Notifications every X minutes""" + return self.get("notify-interval-minutes") + + @property + def notifications_enabled(self): + """Enable/Disable notifications""" + return self.get("notifications-enabled") + + @property + def notify_on_idle(self) -> bool: + """Enable/Disable notifications when no activity is set""" + return self.get("notify-on-idle-enabled") conf = GSettingsStore() diff --git a/src/hamster/lib/notifsmanager.py b/src/hamster/lib/notifsmanager.py new file mode 100644 index 000000000..b8c07d783 --- /dev/null +++ b/src/hamster/lib/notifsmanager.py @@ -0,0 +1,126 @@ +# - coding: utf-8 - + +# Copyright (C) 2020 Sharaf Zaman + +# This file is part of Project Hamster. + +# Project Hamster is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# Project Hamster is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with Project Hamster. If not, see . + +import logging +logger = logging.getLogger(__name__) # noqa: E402 +import datetime +import dbus +import hamster.client + +from gi.repository import Gtk +from gi.repository import GObject as gobject +from hamster.lib.configuration import conf + + +class Notification(object): + def __init__(self): + self.bus = dbus.SessionBus() + self.appname = "Hamster Time Tracker" + self.replace_id = 0 + self.summary = "Hamster Time Tracker" + self.hints = {} + self.actions = [] + self.data = {} + self.timeout = -1 + + info = Gtk.IconTheme().lookup_icon("hamster-time-tracker", 0, 0) + self.icon = info.get_filename() + + def show(self, message): + """ + Show notitification + returns: True if successful + """ + try: + self.server = dbus.Interface(self.bus.get_object("org.freedesktop.Notifications", + "/org/freedesktop/Notifications"), + dbus_interface="org.freedesktop.Notifications") + except dbus.exceptions.DBusException as e: + # TODO: Log? + logger.error(e) + logger.warning("Notifications will be disabled") + return False + + try: + self.notif_id = self.server.Notify( + self.appname, + self.replace_id, + self.icon, + self.summary, + message, + self.actions, + self.hints, + self.timeout) + except: + return False + + return True + + def close(self): + try: self.server.CloseNotification(self.notif_id) + except: pass + + + +class NotificationsManager(gobject.GObject): + def __init__(self): + self.notify_interval = conf.notify_interval + self.minutes_passed = 0 + self.notification = Notification() + gobject.timeout_add_seconds(60, self.check_interval) + + def notify_interval_changed(self, value): + self.minutes_passed = 0 + self.notify_interval = value + + def check_interval(self): + if not conf.notifications_enabled: + self.minutes_passed = 0 + return True + + self.minutes_passed += 1 + + storage = hamster.client.Storage() + facts = storage.get_todays_facts() + + if self.minutes_passed == self.notify_interval: + # if the activity is still active + if len(facts) > 0 and facts[-1].end_time is None: + timedelta_secs = (datetime.datetime.now() - facts[-1].start_time).seconds + hours, rem = divmod(timedelta_secs, 60 * 60) + minutes, seconds = divmod(rem, 60) + if hours != 0: + msg = str.format("Working on {} for {} hours and {} minutes", facts[-1].activity, hours, minutes) + else: + msg = str.format("Working on {} for {} minutes", facts[-1].activity, minutes) + self.notification.show(msg) + elif conf.notify_on_idle: + self.notification.show("No Activity") + + self.notification.close() + + self.minutes_passed = 0 + + return True + + def send_test(self): + return self.notification.show("This is a test notification!") + + +notifs_mgr = NotificationsManager() diff --git a/src/hamster/preferences.py b/src/hamster/preferences.py index c41b1d35c..f66e5a0fb 100644 --- a/src/hamster/preferences.py +++ b/src/hamster/preferences.py @@ -25,6 +25,7 @@ from hamster.lib import datetime as dt from hamster.lib import stuff from hamster.lib.configuration import Controller, runtime, conf +from hamster.lib.notifsmanager import notifs_mgr def get_prev(selection, model): @@ -133,11 +134,21 @@ def __init__(self): (selection, selection.connect('changed', self.category_changed_cb, self.category_store)) ]) + # Tracking tab self.day_start = widgets.TimeInput(dt.time(5,30)) self.get_widget("day_start_placeholder").add(self.day_start) + self.notify_scale = self.get_widget("notify-interval-scale") + self.notify_scale.set_range(1, 120) + + self.notifs_enabled_toggle = self.get_widget("notifs-enabled-toggle") + self.notification_box = self.get_widget("notification-box") + self.load_config() + self.notify_scale.connect("value-changed", self._notify_interval_value_changed) + self.notifs_enabled_toggle.connect("toggled", self._on_notifications_toggled) + # Allow enable drag and drop of rows including row move self.activity_tree.enable_model_drag_source(gdk.ModifierType.BUTTON1_MASK, self.TARGETS, @@ -174,6 +185,14 @@ def load_config(self, *args): self.tags = [tag["name"] for tag in runtime.storage.get_tags(only_autocomplete=True)] self.get_widget("autocomplete_tags").set_text(", ".join(self.tags)) + # enable/disable notification related settings + self.notifs_enabled_toggle.set_active(conf.notifications_enabled) + self.notification_box.set_sensitive(conf.notifications_enabled) + + self.notify_scale.set_value(conf.notify_interval) + self.get_widget("notify-on-idle").set_active(conf.notify_on_idle) + + def on_autocomplete_tags_view_focus_out_event(self, view, event): buf = self.get_widget("autocomplete_tags") updated_tags = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0) @@ -509,3 +528,24 @@ def on_day_start_changed(self, widget): conf.set("day-start-minutes", day_start) def on_close_button_clicked(self, button): self.close_window() + + def _on_notifications_toggled(self, checkbox): + # TODO: Show error message next to the widget + # Test only if activated + if checkbox.get_active(): + if notifs_mgr.send_test(): + self.notification_box.set_sensitive(checkbox.get_active()) + conf.set("notifications-enabled", checkbox.get_active()) + else: + checkbox.set_active(False) + + self.notification_box.set_sensitive(checkbox.get_active()) + conf.set("notifications-enabled", checkbox.get_active()) + + def _notify_on_idle_toggled(self, checkbox): + conf.set("notify-on-idle-enabled", checkbox.get_active()) + + def _notify_interval_value_changed(self, range): + notify_interval = range.get_value() + conf.set("notify-interval-minutes", notify_interval) + notifs_mgr.notify_interval_changed(notify_interval)