mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
b679db73ed
commit
bed9220fc5
@ -895,6 +895,8 @@ class Application(QApplication):
|
|||||||
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args]
|
||||||
self.pi = plugins['progress_indicator'][0]
|
self.pi = plugins['progress_indicator'][0]
|
||||||
QApplication.__init__(self, qargs)
|
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)
|
self.setup_styles(force_calibre_style)
|
||||||
f = QFont(QApplication.font())
|
f = QFont(QApplication.font())
|
||||||
if (f.family(), f.pointSize()) == ('Sans Serif', 9): # Hard coded Qt settings, no user preference detected
|
if (f.family(), f.pointSize()) == ('Sans Serif', 9): # Hard coded Qt settings, no user preference detected
|
||||||
|
@ -16,6 +16,7 @@ from PyQt5.Qt import (QAbstractTableModel, QModelIndex, Qt,
|
|||||||
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction,
|
QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction,
|
||||||
QByteArray, QSortFilterProxyModel)
|
QByteArray, QSortFilterProxyModel)
|
||||||
|
|
||||||
|
from calibre.constants import islinux, isbsd
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.utils.ipc.job import ParallelJob
|
from calibre.utils.ipc.job import ParallelJob
|
||||||
from calibre.gui2 import (Dispatcher, error_dialog, question_dialog,
|
from calibre.gui2 import (Dispatcher, error_dialog, question_dialog,
|
||||||
@ -446,6 +447,8 @@ class DetailView(QDialog, Ui_Dialog): # {{{
|
|||||||
|
|
||||||
class JobsButton(QFrame): # {{{
|
class JobsButton(QFrame): # {{{
|
||||||
|
|
||||||
|
tray_tooltip_updated = pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, horizontal=False, size=48, parent=None):
|
def __init__(self, horizontal=False, size=48, parent=None):
|
||||||
QFrame.__init__(self, parent)
|
QFrame.__init__(self, parent)
|
||||||
if horizontal:
|
if horizontal:
|
||||||
@ -506,6 +509,17 @@ class JobsButton(QFrame): # {{{
|
|||||||
src = unicode(self._jobs.text())
|
src = unicode(self._jobs.text())
|
||||||
return int(re.search(r'\d+', src).group())
|
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):
|
def job_added(self, nnum):
|
||||||
jobs = self._jobs
|
jobs = self._jobs
|
||||||
src = unicode(jobs.text())
|
src = unicode(jobs.text())
|
||||||
@ -513,6 +527,7 @@ class JobsButton(QFrame): # {{{
|
|||||||
text = src.replace(str(num), str(nnum))
|
text = src.replace(str(num), str(nnum))
|
||||||
jobs.setText(text)
|
jobs.setText(text)
|
||||||
self.start()
|
self.start()
|
||||||
|
self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))
|
||||||
|
|
||||||
def job_done(self, nnum):
|
def job_done(self, nnum):
|
||||||
jobs = self._jobs
|
jobs = self._jobs
|
||||||
@ -522,6 +537,7 @@ class JobsButton(QFrame): # {{{
|
|||||||
jobs.setText(text)
|
jobs.setText(text)
|
||||||
if nnum == 0:
|
if nnum == 0:
|
||||||
self.no_more_jobs()
|
self.no_more_jobs()
|
||||||
|
self.tray_tooltip_updated.emit(self.tray_tooltip(nnum))
|
||||||
|
|
||||||
def no_more_jobs(self):
|
def no_more_jobs(self):
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
|
@ -14,7 +14,6 @@ from PyQt5.Qt import (
|
|||||||
QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout)
|
QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout)
|
||||||
|
|
||||||
from calibre import human_readable
|
from calibre import human_readable
|
||||||
from calibre.constants import islinux, isbsd
|
|
||||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
|
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
|
||||||
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
||||||
from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog
|
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('show_avg_rating', config)
|
||||||
r('disable_animations', config)
|
r('disable_animations', config)
|
||||||
r('systray_icon', config, restart_required=True)
|
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('show_splash_screen', gprefs)
|
||||||
r('disable_tray_notification', config)
|
r('disable_tray_notification', config)
|
||||||
r('use_roman_numerals_for_series_number', config)
|
r('use_roman_numerals_for_series_number', config)
|
||||||
|
@ -16,12 +16,12 @@ from collections import OrderedDict
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import apsw
|
import apsw
|
||||||
from PyQt5.Qt import (Qt, QTimer, QHelpEvent, QAction,
|
from PyQt5.Qt import (
|
||||||
QMenu, QIcon, pyqtSignal, QUrl, QFont,
|
Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog,
|
||||||
QDialog, QSystemTrayIcon, QApplication)
|
QApplication, QSystemTrayIcon)
|
||||||
|
|
||||||
from calibre import prints, force_unicode
|
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.config import prefs, dynamic
|
||||||
from calibre.utils.ipc.server import Server
|
from calibre.utils.ipc.server import Server
|
||||||
from calibre.db.legacy import LibraryDatabase
|
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.proceed import ProceedQuestion
|
||||||
from calibre.gui2.dialogs.message_box import JobError
|
from calibre.gui2.dialogs.message_box import JobError
|
||||||
from calibre.gui2.job_indicator import Pointer
|
from calibre.gui2.job_indicator import Pointer
|
||||||
|
from calibre.gui2.dbus_export.widgets import factory
|
||||||
from calibre.library import current_library_name
|
from calibre.library import current_library_name
|
||||||
|
|
||||||
class Listener(Thread): # {{{
|
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
|
_gui = None
|
||||||
|
|
||||||
def get_gui():
|
def get_gui():
|
||||||
@ -270,20 +254,19 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
self.default_thumbnail = None
|
self.default_thumbnail = None
|
||||||
self.tb_wrapper = textwrap.TextWrapper(width=40)
|
self.tb_wrapper = textwrap.TextWrapper(width=40)
|
||||||
self.viewers = collections.deque()
|
self.viewers = collections.deque()
|
||||||
self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self)
|
self.system_tray_icon = None
|
||||||
self.system_tray_icon.setToolTip('calibre')
|
if config['systray_icon']:
|
||||||
self.system_tray_icon.tooltip_requested.connect(
|
self.system_tray_icon = factory(app_id='com.calibre-ebook.gui').create_system_tray_icon(parent=self, title='calibre')
|
||||||
self.job_manager.show_tooltip)
|
if self.system_tray_icon is not None:
|
||||||
systray_ok = config['systray_icon'] and not (islinux or isbsd)
|
self.system_tray_icon.setIcon(QIcon(I('lt.png')))
|
||||||
# System tray icons are broken on linux, see
|
self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip())
|
||||||
# https://bugreports.qt-project.org/browse/QTBUG-31762
|
self.system_tray_icon.setVisible(True)
|
||||||
if not systray_ok:
|
self.jobs_button.tray_tooltip_updated.connect(self.system_tray_icon.setToolTip)
|
||||||
self.system_tray_icon.hide()
|
|
||||||
else:
|
else:
|
||||||
self.system_tray_icon.show()
|
prints('Failed to create system tray icon')
|
||||||
self.system_tray_menu = QMenu(self)
|
self.system_tray_menu = QMenu(self)
|
||||||
self.restore_action = self.system_tray_menu.addAction(
|
self.toggle_to_tray_action = self.system_tray_menu.addAction(QIcon(I('page.png')), '')
|
||||||
QIcon(I('page.png')), _('&Restore'))
|
self.toggle_to_tray_action.triggered.connect(self.system_tray_icon_activated)
|
||||||
self.system_tray_menu.addAction(self.donate_action)
|
self.system_tray_menu.addAction(self.donate_action)
|
||||||
self.donate_button.setDefaultAction(self.donate_action)
|
self.donate_button.setDefaultAction(self.donate_action)
|
||||||
self.donate_button.setStatusTip(self.donate_button.toolTip())
|
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.system_tray_menu.addAction(self.quit_action)
|
||||||
self.keyboard.register_shortcut('quit calibre', _('Quit calibre'),
|
self.keyboard.register_shortcut('quit calibre', _('Quit calibre'),
|
||||||
default_keys=('Ctrl+Q',), action=self.quit_action)
|
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.quit_action.triggered[bool].connect(self.quit)
|
||||||
self.donate_action.triggered[bool].connect(self.donate)
|
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.esc_action = QAction(self)
|
||||||
self.addAction(self.esc_action)
|
self.addAction(self.esc_action)
|
||||||
@ -355,7 +337,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
if splash_screen is not None:
|
if splash_screen is not None:
|
||||||
splash_screen.hide()
|
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.hide_windows()
|
||||||
self.library_view.model().count_changed_signal.connect(
|
self.library_view.model().count_changed_signal.connect(
|
||||||
self.iactions['Choose Library'].count_changed)
|
self.iactions['Choose Library'].count_changed)
|
||||||
@ -441,6 +423,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
self.iactions['Connect Share'].check_smartdevice_menus()
|
self.iactions['Connect Share'].check_smartdevice_menus()
|
||||||
QTimer.singleShot(1, self.start_smartdevice)
|
QTimer.singleShot(1, self.start_smartdevice)
|
||||||
|
QTimer.singleShot(100, self.update_toggle_to_tray_action)
|
||||||
|
|
||||||
def esc(self, *args):
|
def esc(self, *args):
|
||||||
self.clear_button.click()
|
self.clear_button.click()
|
||||||
@ -512,8 +495,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
def no_op(self, *args):
|
def no_op(self, *args):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def system_tray_icon_activated(self, r):
|
def system_tray_icon_activated(self, r=False):
|
||||||
if r == QSystemTrayIcon.Trigger:
|
if r in (QSystemTrayIcon.Trigger, QSystemTrayIcon.MiddleClick, False):
|
||||||
if self.isVisible():
|
if self.isVisible():
|
||||||
self.hide_windows()
|
self.hide_windows()
|
||||||
else:
|
else:
|
||||||
@ -533,18 +516,25 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
skip_dialog_name=skip_dialog_name,
|
skip_dialog_name=skip_dialog_name,
|
||||||
skip_dialog_skipped_value=skipped_value)
|
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):
|
def hide_windows(self):
|
||||||
for window in QApplication.topLevelWidgets():
|
for window in QApplication.topLevelWidgets():
|
||||||
if isinstance(window, (MainWindow, QDialog)) and \
|
if isinstance(window, (MainWindow, QDialog)) and \
|
||||||
window.isVisible():
|
window.isVisible():
|
||||||
window.hide()
|
window.hide()
|
||||||
setattr(window, '__systray_minimized', True)
|
setattr(window, '__systray_minimized', True)
|
||||||
|
self.update_toggle_to_tray_action()
|
||||||
|
|
||||||
def show_windows(self, *args):
|
def show_windows(self, *args):
|
||||||
for window in QApplication.topLevelWidgets():
|
for window in QApplication.topLevelWidgets():
|
||||||
if getattr(window, '__systray_minimized', False):
|
if getattr(window, '__systray_minimized', False):
|
||||||
window.show()
|
window.show()
|
||||||
setattr(window, '__systray_minimized', False)
|
setattr(window, '__systray_minimized', False)
|
||||||
|
self.update_toggle_to_tray_action()
|
||||||
|
|
||||||
def test_server(self, *args):
|
def test_server(self, *args):
|
||||||
if self.content_server is not None and \
|
if self.content_server is not None and \
|
||||||
@ -937,7 +927,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
|
|||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
self.write_settings()
|
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:
|
if not dynamic['systray_msg'] and not isosx:
|
||||||
info_dialog(self, 'calibre', 'calibre '+
|
info_dialog(self, 'calibre', 'calibre '+
|
||||||
_('will keep running in the system tray. To close it, '
|
_('will keep running in the system tray. To close it, '
|
||||||
|
Loading…
x
Reference in New Issue
Block a user