From bed9220fc5ee7a97bc79befa33e5fec49a99e611 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 29 Oct 2014 22:18:54 +0530 Subject: [PATCH] Re-enable the system tray icon on linux. System tray icons now work in any desktop environment that supports the StatusNotifier spec, such as Ubuntu Unity, KDE 4+, GNOME 3, etc. Since calibre 2.0, the system tray icon functionality in linux had been disabled because of bugs in Qt 5. calibre now contains its own system tray icon implementation to work around Qt 5 bugs. --- src/calibre/gui2/__init__.py | 2 + src/calibre/gui2/jobs.py | 16 +++++ src/calibre/gui2/preferences/look_feel.py | 4 -- src/calibre/gui2/ui.py | 72 ++++++++++------------- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 6968100026..a1b8e77ccc 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -895,6 +895,8 @@ class Application(QApplication): qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] self.pi = plugins['progress_indicator'][0] QApplication.__init__(self, qargs) + if islinux or isbsd: + self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ) self.setup_styles(force_calibre_style) f = QFont(QApplication.font()) if (f.family(), f.pointSize()) == ('Sans Serif', 9): # Hard coded Qt settings, no user preference detected diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index 61a46bb705..f6aecc912a 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -16,6 +16,7 @@ from PyQt5.Qt import (QAbstractTableModel, QModelIndex, Qt, QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, QByteArray, QSortFilterProxyModel) +from calibre.constants import islinux, isbsd from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob from calibre.gui2 import (Dispatcher, error_dialog, question_dialog, @@ -446,6 +447,8 @@ class DetailView(QDialog, Ui_Dialog): # {{{ class JobsButton(QFrame): # {{{ + tray_tooltip_updated = pyqtSignal(object) + def __init__(self, horizontal=False, size=48, parent=None): QFrame.__init__(self, parent) if horizontal: @@ -506,6 +509,17 @@ class JobsButton(QFrame): # {{{ src = unicode(self._jobs.text()) return int(re.search(r'\d+', src).group()) + def tray_tooltip(self, num=0): + if num == 0: + text = _('No running jobs') + elif num == 1: + text = _('One running job') + else: + text = _('%d running jobs') % num + if not (islinux or isbsd): + text = 'calibre: ' + text + return text + def job_added(self, nnum): jobs = self._jobs src = unicode(jobs.text()) @@ -513,6 +527,7 @@ class JobsButton(QFrame): # {{{ text = src.replace(str(num), str(nnum)) jobs.setText(text) self.start() + self.tray_tooltip_updated.emit(self.tray_tooltip(nnum)) def job_done(self, nnum): jobs = self._jobs @@ -522,6 +537,7 @@ class JobsButton(QFrame): # {{{ jobs.setText(text) if nnum == 0: self.no_more_jobs() + self.tray_tooltip_updated.emit(self.tray_tooltip(nnum)) def no_more_jobs(self): if self.is_running: diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 356ca4907e..3d200b89bb 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -14,7 +14,6 @@ from PyQt5.Qt import ( QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout) from calibre import human_readable -from calibre.constants import islinux, isbsd from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog @@ -180,9 +179,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('show_avg_rating', config) r('disable_animations', config) r('systray_icon', config, restart_required=True) - if islinux or isbsd: - self.opt_systray_icon.setEnabled(False) - self.opt_systray_icon.setText(_('System tray icon is disabled because of bugs in Qt 5')) r('show_splash_screen', gprefs) r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index d2ec5ee38a..727788726e 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -16,12 +16,12 @@ from collections import OrderedDict from io import BytesIO import apsw -from PyQt5.Qt import (Qt, QTimer, QHelpEvent, QAction, - QMenu, QIcon, pyqtSignal, QUrl, QFont, - QDialog, QSystemTrayIcon, QApplication) +from PyQt5.Qt import ( + Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog, + QApplication, QSystemTrayIcon) from calibre import prints, force_unicode -from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG, islinux, isbsd +from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG from calibre.utils.config import prefs, dynamic from calibre.utils.ipc.server import Server from calibre.db.legacy import LibraryDatabase @@ -47,6 +47,7 @@ from calibre.gui2.auto_add import AutoAdder from calibre.gui2.proceed import ProceedQuestion from calibre.gui2.dialogs.message_box import JobError from calibre.gui2.job_indicator import Pointer +from calibre.gui2.dbus_export.widgets import factory from calibre.library import current_library_name class Listener(Thread): # {{{ @@ -80,23 +81,6 @@ class Listener(Thread): # {{{ # }}} -class SystemTrayIcon(QSystemTrayIcon): # {{{ - - tooltip_requested = pyqtSignal(object) - - def __init__(self, icon, parent): - QSystemTrayIcon.__init__(self, icon, parent) - - def event(self, ev): - if ev.type() == ev.ToolTip: - evh = QHelpEvent(ev) - self.tooltip_requested.emit( - (self, evh.globalPos())) - return True - return QSystemTrayIcon.event(self, ev) - -# }}} - _gui = None def get_gui(): @@ -270,20 +254,19 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.default_thumbnail = None self.tb_wrapper = textwrap.TextWrapper(width=40) self.viewers = collections.deque() - self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self) - self.system_tray_icon.setToolTip('calibre') - self.system_tray_icon.tooltip_requested.connect( - self.job_manager.show_tooltip) - systray_ok = config['systray_icon'] and not (islinux or isbsd) - # System tray icons are broken on linux, see - # https://bugreports.qt-project.org/browse/QTBUG-31762 - if not systray_ok: - self.system_tray_icon.hide() + self.system_tray_icon = None + if config['systray_icon']: + self.system_tray_icon = factory(app_id='com.calibre-ebook.gui').create_system_tray_icon(parent=self, title='calibre') + if self.system_tray_icon is not None: + self.system_tray_icon.setIcon(QIcon(I('lt.png'))) + self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip()) + self.system_tray_icon.setVisible(True) + self.jobs_button.tray_tooltip_updated.connect(self.system_tray_icon.setToolTip) else: - self.system_tray_icon.show() + prints('Failed to create system tray icon') self.system_tray_menu = QMenu(self) - self.restore_action = self.system_tray_menu.addAction( - QIcon(I('page.png')), _('&Restore')) + self.toggle_to_tray_action = self.system_tray_menu.addAction(QIcon(I('page.png')), '') + self.toggle_to_tray_action.triggered.connect(self.system_tray_icon_activated) self.system_tray_menu.addAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setStatusTip(self.donate_button.toolTip()) @@ -294,12 +277,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.system_tray_menu.addAction(self.quit_action) self.keyboard.register_shortcut('quit calibre', _('Quit calibre'), default_keys=('Ctrl+Q',), action=self.quit_action) - self.system_tray_icon.setContextMenu(self.system_tray_menu) + if self.system_tray_icon is not None: + self.system_tray_icon.setContextMenu(self.system_tray_menu) + self.system_tray_icon.activated.connect(self.system_tray_icon_activated) self.quit_action.triggered[bool].connect(self.quit) self.donate_action.triggered[bool].connect(self.donate) - self.restore_action.triggered.connect(self.show_windows) - self.system_tray_icon.activated.connect( - self.system_tray_icon_activated) self.esc_action = QAction(self) self.addAction(self.esc_action) @@ -355,7 +337,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if splash_screen is not None: splash_screen.hide() - if self.system_tray_icon.isVisible() and opts.start_in_tray: + if self.system_tray_icon is not None and self.system_tray_icon.isVisible() and opts.start_in_tray: self.hide_windows() self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) @@ -441,6 +423,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.iactions['Connect Share'].check_smartdevice_menus() QTimer.singleShot(1, self.start_smartdevice) + QTimer.singleShot(100, self.update_toggle_to_tray_action) def esc(self, *args): self.clear_button.click() @@ -512,8 +495,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def no_op(self, *args): pass - def system_tray_icon_activated(self, r): - if r == QSystemTrayIcon.Trigger: + def system_tray_icon_activated(self, r=False): + if r in (QSystemTrayIcon.Trigger, QSystemTrayIcon.MiddleClick, False): if self.isVisible(): self.hide_windows() else: @@ -533,18 +516,25 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ skip_dialog_name=skip_dialog_name, skip_dialog_skipped_value=skipped_value) + def update_toggle_to_tray_action(self, *args): + if hasattr(self, 'toggle_to_tray_action'): + self.toggle_to_tray_action.setText( + _('Hide main window') if self.isVisible() else _('Show main window')) + def hide_windows(self): for window in QApplication.topLevelWidgets(): if isinstance(window, (MainWindow, QDialog)) and \ window.isVisible(): window.hide() setattr(window, '__systray_minimized', True) + self.update_toggle_to_tray_action() def show_windows(self, *args): for window in QApplication.topLevelWidgets(): if getattr(window, '__systray_minimized', False): window.show() setattr(window, '__systray_minimized', False) + self.update_toggle_to_tray_action() def test_server(self, *args): if self.content_server is not None and \ @@ -937,7 +927,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def closeEvent(self, e): self.write_settings() - if self.system_tray_icon.isVisible(): + if self.system_tray_icon is not None and self.system_tray_icon.isVisible(): if not dynamic['systray_msg'] and not isosx: info_dialog(self, 'calibre', 'calibre '+ _('will keep running in the system tray. To close it, '