diff options
Diffstat (limited to 'deluge/ui/gtk3/systemtray.py')
-rw-r--r-- | deluge/ui/gtk3/systemtray.py | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/deluge/ui/gtk3/systemtray.py b/deluge/ui/gtk3/systemtray.py new file mode 100644 index 0000000..5318cf2 --- /dev/null +++ b/deluge/ui/gtk3/systemtray.py @@ -0,0 +1,445 @@ +# +# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +import logging +import os + +from gi import require_version +from gi.repository.Gtk import Builder, RadioMenuItem, StatusIcon + +import deluge.component as component +from deluge.common import ( + fspeed, + get_pixmap, + osx_check, + resource_filename, + windows_check, +) +from deluge.configmanager import ConfigManager +from deluge.ui.client import client + +from .common import build_menu_radio_list, get_logo +from .dialogs import OtherDialog + +try: + try: + require_version('AyatanaAppIndicator3', '0.1') + from gi.repository import AyatanaAppIndicator3 as AppIndicator3 + except (ValueError, ImportError): + require_version('AppIndicator3', '0.1') + from gi.repository import AppIndicator3 +except (ValueError, ImportError): + AppIndicator3 = None + +log = logging.getLogger(__name__) + + +class SystemTray(component.Component): + def __init__(self): + component.Component.__init__(self, 'SystemTray', interval=4) + self.mainwindow = component.get('MainWindow') + self.config = ConfigManager('gtk3ui.conf') + # List of widgets that need to be hidden when not connected to a host + self.hide_widget_list = [ + 'menuitem_add_torrent', + 'menuitem_pause_session', + 'menuitem_resume_session', + 'menuitem_download_limit', + 'menuitem_upload_limit', + 'menuitem_quitdaemon', + 'separatormenuitem1', + 'separatormenuitem2', + 'separatormenuitem3', + 'separatormenuitem4', + ] + self.config.register_set_function( + 'enable_system_tray', self.on_enable_system_tray_set + ) + # bit of a hack to prevent function from doing something on startup + self.__enabled_set_once = False + self.config.register_set_function( + 'enable_appindicator', self.on_enable_appindicator_set + ) + + self.max_download_speed = -1.0 + self.download_rate = 0.0 + self.max_upload_speed = -1.0 + self.upload_rate = 0.0 + + self.config_value_changed_dict = { + 'max_download_speed': self._on_max_download_speed, + 'max_upload_speed': self._on_max_upload_speed, + } + + def enable(self): + """Enables the system tray icon.""" + self.builder = Builder() + self.builder.add_from_file( + resource_filename(__package__, os.path.join('glade', 'tray_menu.ui')) + ) + + self.builder.connect_signals(self) + + self.tray_menu = self.builder.get_object('tray_menu') + + if AppIndicator3 and self.config['enable_appindicator']: + log.debug('Enabling the Application Indicator...') + self.indicator = AppIndicator3.Indicator.new( + 'deluge', + 'deluge-panel', + AppIndicator3.IndicatorCategory.APPLICATION_STATUS, + ) + self.indicator.set_property('title', _('Deluge')) + + # Pass the menu to the Application Indicator + self.indicator.set_menu(self.tray_menu) + + # Make sure the status of the Show Window MenuItem is correct + self._sig_win_hide = self.mainwindow.window.connect( + 'hide', self._on_window_hide + ) + self._sig_win_show = self.mainwindow.window.connect( + 'show', self._on_window_show + ) + if self.mainwindow.visible(): + self.builder.get_object('menuitem_show_deluge').set_active(True) + else: + self.builder.get_object('menuitem_show_deluge').set_active(False) + + # Show the Application Indicator + self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) + + else: + log.debug('Enabling the system tray icon..') + if windows_check() or osx_check(): + self.tray = StatusIcon.new_from_pixbuf(get_logo(32)) + else: + self.tray = StatusIcon.new_from_icon_name('deluge-panel') + + self.tray.connect('activate', self.on_tray_clicked) + self.tray.connect('popup-menu', self.on_tray_popup) + + self.builder.get_object('download-limit-image').set_from_file( + get_pixmap('downloading16.png') + ) + self.builder.get_object('upload-limit-image').set_from_file( + get_pixmap('seeding16.png') + ) + + client.register_event_handler( + 'ConfigValueChangedEvent', self.config_value_changed + ) + if client.connected(): + # We're connected so we need to get some values from the core + self.__start() + else: + # Hide menu widgets because we're not connected to a host. + for widget in self.hide_widget_list: + self.builder.get_object(widget).hide() + + def __start(self): + if self.config['enable_system_tray']: + if self.config['standalone']: + try: + self.hide_widget_list.remove('menuitem_quitdaemon') + self.hide_widget_list.remove('separatormenuitem4') + except ValueError: + pass + self.builder.get_object('menuitem_quitdaemon').hide() + self.builder.get_object('separatormenuitem4').hide() + + # Show widgets in the hide list because we've connected to a host + for widget in self.hide_widget_list: + self.builder.get_object(widget).show() + + # Build the bandwidth speed limit menus + self.build_tray_bwsetsubmenu() + + # Get some config values + def update_config_values(configs): + self._on_max_download_speed(configs['max_download_speed']) + self._on_max_upload_speed(configs['max_upload_speed']) + + client.core.get_config_values( + ['max_download_speed', 'max_upload_speed'] + ).addCallback(update_config_values) + + def start(self): + self.__start() + + def stop(self): + if self.config['enable_system_tray'] and not self.config['enable_appindicator']: + try: + # Hide widgets in hide list because we're not connected to a host + for widget in self.hide_widget_list: + self.builder.get_object(widget).hide() + except Exception as ex: + log.debug('Unable to hide system tray menu widgets: %s', ex) + + self.tray.set_tooltip_text(_('Deluge') + '\n' + _('Not Connected...')) + + def shutdown(self): + if self.config['enable_system_tray']: + if AppIndicator3 and self.config['enable_appindicator']: + self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) + else: + self.tray.set_visible(False) + + def send_status_request(self): + client.core.get_session_status( + ['payload_upload_rate', 'payload_download_rate'] + ).addCallback(self._on_get_session_status) + + def config_value_changed(self, key, value): + """This is called when we received a config_value_changed signal from + the core.""" + if key in self.config_value_changed_dict: + self.config_value_changed_dict[key](value) + + def _on_max_download_speed(self, max_download_speed): + if self.max_download_speed != max_download_speed: + self.max_download_speed = max_download_speed + self.build_tray_bwsetsubmenu() + + def _on_max_upload_speed(self, max_upload_speed): + if self.max_upload_speed != max_upload_speed: + self.max_upload_speed = max_upload_speed + self.build_tray_bwsetsubmenu() + + def _on_get_session_status(self, status): + self.download_rate = fspeed(status['payload_download_rate'], shortform=True) + self.upload_rate = fspeed(status['payload_upload_rate'], shortform=True) + + def update(self): + if not self.config['enable_system_tray']: + return + + # Tool tip text not available for appindicator + if AppIndicator3 and self.config['enable_appindicator']: + if self.mainwindow.visible(): + self.builder.get_object('menuitem_show_deluge').set_active(True) + else: + self.builder.get_object('menuitem_show_deluge').set_active(False) + return + + # Set the tool tip text + max_download_speed = self.max_download_speed + max_upload_speed = self.max_upload_speed + + if max_download_speed == -1: + max_download_speed = _('Unlimited') + else: + max_download_speed = '{} {}'.format(max_download_speed, _('K/s')) + if max_upload_speed == -1: + max_upload_speed = _('Unlimited') + else: + max_upload_speed = '{} {}'.format(max_upload_speed, _('K/s')) + + msg = '{}\n{}: {} ({})\n{}: {} ({})'.format( + _('Deluge'), + _('Down'), + self.download_rate, + max_download_speed, + _('Up'), + self.upload_rate, + max_upload_speed, + ) + + # Set the tooltip + self.tray.set_tooltip_text(msg) + + self.send_status_request() + + def build_tray_bwsetsubmenu(self): + # Create the Download speed list sub-menu + submenu_bwdownset = build_menu_radio_list( + self.config['tray_download_speed_list'], + self.on_tray_setbwdown, + self.max_download_speed, + _('K/s'), + show_notset=True, + show_other=True, + ) + + # Create the Upload speed list sub-menu + submenu_bwupset = build_menu_radio_list( + self.config['tray_upload_speed_list'], + self.on_tray_setbwup, + self.max_upload_speed, + _('K/s'), + show_notset=True, + show_other=True, + ) + # Add the sub-menus to the tray menu + self.builder.get_object('menuitem_download_limit').set_submenu( + submenu_bwdownset + ) + self.builder.get_object('menuitem_upload_limit').set_submenu(submenu_bwupset) + + # Show the sub-menus for all to see + submenu_bwdownset.show_all() + submenu_bwupset.show_all() + + def disable(self, invert_app_ind_conf=False): + """Disables the system tray icon or Appindicator.""" + try: + if invert_app_ind_conf: + app_ind_conf = not self.config['enable_appindicator'] + else: + app_ind_conf = self.config['enable_appindicator'] + if AppIndicator3 and app_ind_conf: + if hasattr(self, '_sig_win_hide'): + self.mainwindow.window.disconnect(self._sig_win_hide) + self.mainwindow.window.disconnect(self._sig_win_show) + log.debug('Disabling the application indicator..') + + self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) + del self.indicator + else: + log.debug('Disabling the system tray icon..') + self.tray.set_visible(False) + del self.tray + del self.builder + del self.tray_menu + except Exception as ex: + log.debug('Unable to disable system tray: %s', ex) + + def blink(self, value): + try: + self.tray.set_blinking(value) + except AttributeError: + # If self.tray is not defined then ignore. This happens when the + # tray icon is not being used. + pass + + def on_enable_system_tray_set(self, key, value): + """Called whenever the 'enable_system_tray' config key is modified""" + if value: + self.enable() + else: + self.disable() + + def on_enable_appindicator_set(self, key, value): + """Called whenever the 'enable_appindicator' config key is modified""" + if self.__enabled_set_once: + self.disable(True) + self.enable() + self.__enabled_set_once = True + + def on_tray_clicked(self, icon): + """Called when the tray icon is left clicked.""" + self.blink(False) + + if self.mainwindow.active(): + self.mainwindow.hide() + else: + self.mainwindow.present() + + def on_tray_popup(self, status_icon, button, activate_time): + """Called when the tray icon is right clicked.""" + self.blink(False) + + if self.mainwindow.visible(): + self.builder.get_object('menuitem_show_deluge').set_active(True) + else: + self.builder.get_object('menuitem_show_deluge').set_active(False) + + popup_function = StatusIcon.position_menu + if windows_check() or osx_check(): + popup_function = None + button = 0 + self.tray_menu.popup( + None, None, popup_function, status_icon, button, activate_time + ) + + def on_menuitem_show_deluge_activate(self, menuitem): + log.debug('on_menuitem_show_deluge_activate') + if menuitem.get_active() and not self.mainwindow.visible(): + self.mainwindow.present() + elif not menuitem.get_active() and self.mainwindow.visible(): + self.mainwindow.hide() + + def on_menuitem_add_torrent_activate(self, menuitem): + log.debug('on_menuitem_add_torrent_activate') + component.get('AddTorrentDialog').show() + + def on_menuitem_pause_session_activate(self, menuitem): + log.debug('on_menuitem_pause_session_activate') + client.core.pause_session() + + def on_menuitem_resume_session_activate(self, menuitem): + log.debug('on_menuitem_resume_session_activate') + client.core.resume_session() + + def on_menuitem_quit_activate(self, menuitem): + log.debug('on_menuitem_quit_activate') + self.mainwindow.quit() + + def on_menuitem_quitdaemon_activate(self, menuitem): + log.debug('on_menuitem_quitdaemon_activate') + self.mainwindow.quit(shutdown=True) + + def on_tray_setbwdown(self, widget, data=None): + if isinstance(widget, RadioMenuItem): + # ignore previous radiomenuitem value + if not widget.get_active(): + return + self.setbwlimit( + widget, + _('Download Speed Limit'), + _('Set the maximum download speed'), + 'max_download_speed', + 'tray_download_speed_list', + self.max_download_speed, + 'downloading.svg', + ) + + def on_tray_setbwup(self, widget, data=None): + if isinstance(widget, RadioMenuItem): + # ignore previous radiomenuitem value + if not widget.get_active(): + return + self.setbwlimit( + widget, + _('Upload Speed Limit'), + _('Set the maximum upload speed'), + 'max_upload_speed', + 'tray_upload_speed_list', + self.max_upload_speed, + 'seeding.svg', + ) + + def _on_window_hide(self, widget, data=None): + """_on_window_hide - update the menuitem's status""" + log.debug('_on_window_hide') + self.builder.get_object('menuitem_show_deluge').set_active(False) + + def _on_window_show(self, widget, data=None): + """_on_window_show - update the menuitem's status""" + log.debug('_on_window_show') + self.builder.get_object('menuitem_show_deluge').set_active(True) + + def setbwlimit(self, widget, header, text, core_key, ui_key, default, image): + """Sets the bandwidth limit based on the user selection.""" + + def set_value(value): + log.debug('setbwlimit: %s', value) + if value is None: + return + elif value == 0: + value = -1 + # Set the config in the core + client.core.set_config({core_key: value}) + + if widget.get_name() == 'unlimited': + set_value(-1) + elif widget.get_name() == 'other': + dialog = OtherDialog(header, text, _('K/s'), image, default) + dialog.run().addCallback(set_value) + else: + set_value(widget.get_children()[0].get_text().split(' ')[0]) |