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.
This commit is contained in:
Kovid Goyal 2014-10-29 22:18:54 +05:30
parent b679db73ed
commit bed9220fc5
4 changed files with 49 additions and 45 deletions

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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, '