diff --git a/lib/logitech_receiver/notify.py b/lib/logitech_receiver/notify.py new file mode 100644 index 0000000000..939c25e08f --- /dev/null +++ b/lib/logitech_receiver/notify.py @@ -0,0 +1,106 @@ +# -*- python-mode -*- + +## Copyright (C) 2024 Solaar contributors +## +## This program 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 2 of the License, or +## (at your option) any later version. +## +## This program 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 this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import logging + +logger = logging.getLogger(__name__) + +try: + import gi + gi.require_version('Notify', '0.7') + gi.require_version('Gtk', '3.0') + from gi.repository import GLib, Gtk, Notify # this import is allowed to fail making the entire feature unavailable + available = True +except (ValueError, ImportError): + available = False + +if available: + # cache references to shown notifications here to allow reuse + _notifications = {} + + def init(): + """Init the notifications system.""" + global available + if available: + if not Notify.is_initted(): + if logger.isEnabledFor(logging.INFO): + logger.info('starting desktop notifications') + try: + return Notify.init('solaar') # replace with better name later + except Exception: + logger.exception('initializing desktop notifications') + available = False + return available and Notify.is_initted() + + def uninit(): + if available and Notify.is_initted(): + if logger.isEnabledFor(logging.INFO): + logger.info('stopping desktop notifications') + _notifications.clear() + Notify.uninit() + + def show(dev, message, icon=None): + """Show a notification with title and text.""" + if available and (Notify.is_initted() or init()): + summary = dev.name + n = _notifications.get(summary) # reuse notification of same name + if n is None: + n = _notifications[summary] = Notify.Notification() + icon_name = device_icon_name(dev.name, dev.kind) if icon is None else icon + n.update(summary, message, icon_name) + n.set_urgency(Notify.Urgency.NORMAL) + n.set_hint('desktop-entry', GLib.Variant('s', 'solaar')) # replace with better name late + try: + # if logger.isEnabledFor(logging.DEBUG): + # logger.debug("showing %s", n) + n.show() + except Exception: + logger.exception('showing %s', n) + + _ICON_LISTS = {} + + def device_icon_list(name='_', kind=None): + icon_list = _ICON_LISTS.get(name) + if icon_list is None: + # names of possible icons, in reverse order of likelihood + # the theme will hopefully pick up the most appropriate + icon_list = ['preferences-desktop-peripherals'] + if kind: + if str(kind) == 'numpad': + icon_list += ('input-keyboard', 'input-dialpad') + elif str(kind) == 'touchpad': + icon_list += ('input-mouse', 'input-tablet') + elif str(kind) == 'trackball': + icon_list += ('input-mouse', ) + elif str(kind) == 'headset': + icon_list += ('audio-headphones', 'audio-headset') + icon_list += ('input-' + str(kind), ) + _ICON_LISTS[name] = icon_list + return icon_list + + def device_icon_name(name, kind=None): + _default_theme = Gtk.IconTheme.get_default() + icon_list = device_icon_list(name, kind) + for n in reversed(icon_list): + if _default_theme.has_icon(n): + return n + +else: + init = lambda: False + uninit = lambda: None + show = lambda dev, reason=None: None diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 33582a6430..44618d3435 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -29,6 +29,7 @@ from . import hidpp10_constants as _hidpp10_constants from . import hidpp20 as _hidpp20 from . import hidpp20_constants as _hidpp20_constants +from . import notify as _notify from . import special_keys as _special_keys from .base import _HIDPP_Notification as _HIDPP_Notification from .common import NamedInt as _NamedInt @@ -776,13 +777,9 @@ def setNewDpi(self, newDpiIdx): self.device.setting_callback(self.device, type(self.dpiSetting), [newDpi]) def displayNewDpi(self, newDpiIdx): - from solaar.ui import notify as _notify # import here to avoid circular import when running `solaar show`, if _notify.available: reason = 'DPI %d [min %d, max %d]' % (self.dpiChoices[newDpiIdx], self.dpiChoices[0], self.dpiChoices[-1]) - # if there is a progress percentage then the reason isn't shown - # asPercentage = int(float(newDpiIdx) / float(len(self.dpiChoices) - 1) * 100.) - # _notify.show(self.device, reason=reason, progress=asPercentage) - _notify.show(self.device, reason=reason) + _notify.show(self.device, reason) def press_action(self, key): # start tracking self.starting = True