Merged minimize to tray

This commit is contained in:
Raoul Snyman 2017-06-13 15:47:50 -07:00
commit 10145545cb
7 changed files with 289 additions and 146 deletions

View File

@ -1,2 +1,3 @@
dist
*.egg-info
build

18
CHANGELOG.rst Normal file
View File

@ -0,0 +1,18 @@
Changelog
=========
Version 0.2
-----------
Released: 2017-06-13
- Added the ability tpo minimize to a system tray icon
- Moved the module into a file instead of a directory
- Updated setup.py for the module move
Version 0.1
-----------
Released: 2017-05-17
- Initial version

View File

@ -1,7 +1,8 @@
WebAppify
=========
WebAppify is a simple module to easily create your own desktop apps of websites.
WebAppify is a simple module to easily create your own desktop apps of websites. WebAppify uses PyQt5 and QtWebKit or
QtWebEngine for displaying the web page, and works on Python 2.7 and Python 3.4 and up.
To create your own desktop web app, import and set up the WebApp class.
@ -13,3 +14,20 @@ To create your own desktop web app, import and set up the WebApp class.
app.run()
This will create a window with the website, using the icon provided.
Additional Options
------------------
Version 0.2 comes with the option of minimizing to the system tray. Simply pass ``canMinimizeToTray=True`` to the class
and a tray icon will be installed with the necessary menu options.
.. code:: python
app = WebApp('OpenStreetMap', 'https://www.openstreetmap.org', 'osm.png', canMinimizeToTray=True)
Clicking on the tray icon will show the window, while right-clicking will show the menu.
.. note::
If your site needs Flash Player, you'll need the appropriate Flash Player plugin installed system-wide. For QtWebKit
you will need the NPAPI plugin, and for QtWebEngine you will need the PPAPI plugin.

View File

@ -0,0 +1,5 @@
[wheel]
universal = 1
[flake8]
max-line-length = 120

View File

@ -3,7 +3,7 @@ The webappify package
"""
import os
from codecs import open
from setuptools import setup, find_packages
from setuptools import setup
HERE = os.path.abspath(os.path.dirname(__file__))
@ -13,7 +13,7 @@ with open(os.path.join(HERE, 'README.rst'), encoding='utf8') as f:
setup(
name='WebAppify',
version='0.1',
version='0.2',
description='Create desktop apps of your favourite websites',
long_description=LONG_DESCRIPTION,
url='https://launchpad.net/webappify',
@ -29,6 +29,8 @@ setup(
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
@ -38,6 +40,6 @@ setup(
'Topic :: Software Development :: Libraries :: Python Modules'
],
keywords='Qt website',
packages=find_packages(),
py_modules=['webappify'],
install_requires=['PyQt5']
)

241
webappify.py Normal file
View File

@ -0,0 +1,241 @@
"""
WebAppify
=========
WebAppify is a simple module to easily create your own desktop apps of websites. WebAppify uses PyQt5 and QtWebKit or
QtWebEngine for displaying the web page, and works on Python 2.7 and Python 3.4 and up.
To create your own desktop web app, import and set up the WebApp class.
.. code:: python
from webappify import WebApp
app = WebApp('OpenStreetMap', 'https://www.openstreetmap.org', 'osm.png')
app.run()
This will create a window with the website, using the icon provided.
.. note::
If your site needs Flash Player, you'll need the appropriate Flash Player plugin installed system-wide. For QtWebKit
you will need the NPAPI plugin, and for QtWebEngine you will need the PPAPI plugin.
"""
import sys
import platform
from PyQt5 import QtCore, QtGui, QtWidgets
try:
from PyQt5 import QtWebEngineWidgets
HAS_WEBENGINE = True
except ImportError:
HAS_WEBENGINE = False
try:
from PyQt5 import QtWebKit, QtWebKitWidgets
HAS_WEBKIT = True
except ImportError:
HAS_WEBKIT = False
if HAS_WEBENGINE:
SETTINGS = [
QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled,
QtWebEngineWidgets.QWebEngineSettings.JavascriptCanAccessClipboard,
QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessRemoteUrls
]
WebView = QtWebEngineWidgets.QWebEngineView
elif HAS_WEBKIT:
SETTINGS = [
QtWebKit.QWebSettings.PluginsEnabled,
QtWebKit.QWebSettings.JavascriptCanOpenWindows,
QtWebKit.QWebSettings.JavascriptCanCloseWindows,
QtWebKit.QWebSettings.JavascriptCanAccessClipboard,
QtWebKit.QWebSettings.OfflineStorageDatabaseEnabled,
QtWebKit.QWebSettings.OfflineWebApplicationCacheEnabled,
QtWebKit.QWebSettings.LocalStorageEnabled,
QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls
]
WebView = QtWebKitWidgets.QWebView
class WebPage(QtWebKitWidgets.QWebPage):
"""Custom class for overriding the user agent to make WebKit look like Chrome"""
def userAgentForUrl(self, url):
return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
'Chrome/28.0.1500.52 Safari/537.36'
else:
print('Cannot detect either QtWebEngine or QtWebKit!')
sys.exit(1)
class WebWindow(QtWidgets.QWidget):
"""
A window with a single web view and nothing else
"""
def __init__(self, app, title, url, icon, canMinimizeToTray=False):
"""
Create the window
"""
super(WebWindow, self).__init__(None)
self.hasShownWarning = False
self.app = app
self.icon = icon
self.canMinimizeToTray = canMinimizeToTray
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(self.icon))
self.setContentsMargins(0, 0, 0, 0)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.webview = WebView(self)
if not HAS_WEBENGINE and HAS_WEBKIT:
self.webview.setPage(WebPage(self.webview))
for setting in SETTINGS:
self.webview.settings().setAttribute(setting, True)
self.webview.setUrl(QtCore.QUrl(url))
self.layout.addWidget(self.webview)
self.webview.titleChanged.connect(self.onTitleChanged)
def _showWarning(self):
"""
Show a balloon message to inform the user that the app is minimized
"""
if not self.hasShownWarning:
self.trayIcon.showMessage(self.windowTitle(), 'This program will continue running in the system tray. '
'To close the program, choose <b>Quit</b> in the context menu of the system '
'tray icon.')
self.hasShownWarning = True
def _updateTrayMenu(self):
"""
Update the enabled/disabled status of the items in the tray icon menu
"""
if not self.canMinimizeToTray:
return
self.restoreAction.setEnabled(not self.isVisible())
self.minimizeAction.setEnabled(self.isVisible() and not self.isMinimized())
self.maximizeAction.setEnabled(self.isVisible() and not self.isMaximized())
def _getTrayMenu(self):
"""
Create and return the menu for the tray icon
"""
# Create the actions for the menu
self.restoreAction = QtWidgets.QAction('&Restore', self)
self.restoreAction.triggered.connect(self.showNormal)
self.minimizeAction = QtWidgets.QAction('Mi&nimize', self)
self.minimizeAction.triggered.connect(self.close)
self.maximizeAction = QtWidgets.QAction('Ma&ximize', self)
self.maximizeAction.triggered.connect(self.showMaximized)
self.quitAction = QtWidgets.QAction('&Quit', self)
self.quitAction.triggered.connect(self.app.quit)
# Create the menu and add the actions
trayIconMenu = QtWidgets.QMenu(self)
trayIconMenu.addAction(self.restoreAction)
trayIconMenu.addAction(self.minimizeAction)
trayIconMenu.addAction(self.maximizeAction)
trayIconMenu.addSeparator()
trayIconMenu.addAction(self.quitAction)
return trayIconMenu
def setupTrayIcon(self):
"""
Set up the tray icon
"""
self.trayIcon = QtWidgets.QSystemTrayIcon(QtGui.QIcon(self.icon), self)
self.trayIcon.setContextMenu(self._getTrayMenu())
self.trayIcon.activated.connect(self.onTrayIconActivated)
self.trayIcon.show()
def closeEvent(self, event):
"""
Override the close event to minimize to the tray
"""
# If we don't want to minimize to the tray, just close the window as per usual
if not self.canMinimizeToTray:
super(WebWindow, self).closeEvent(event)
return
# If we want to minimize to the tray, then just hide the window
if platform.platform().lower() == 'darwin' and (not event.spontaneous() or not self.isVisible()):
return
else:
self._showWarning()
self.hide()
event.ignore()
# Update the menu to match
self._updateTrayMenu()
def showEvent(self, event):
"""
Override the show event to catch max/min/etc events and update the tray icon menu accordingly
"""
super(WebWindow, self).showEvent(event)
self._updateTrayMenu()
def hideEvent(self, event):
"""
Override the hide event to catch max/min/etc events and update the tray icon menu accordingly
"""
super(WebWindow, self).hideEvent(event)
self._updateTrayMenu()
def changeEvent(self, event):
"""
Catch the minimize event and close the form
"""
if self.canMinimizeToTray:
if event.type() == QtCore.QEvent.WindowStateChange and self.windowState() & QtCore.Qt.WindowMinimized:
self.close()
super(WebWindow, self).changeEvent(event)
def onTitleChanged(self, title):
"""
React to title changes
"""
if title:
self.setWindowTitle(title)
if self.canMinimizeToTray:
self.trayIcon.setToolTip(title)
def onTrayIconActivated(self, reason):
"""
React to the tray icon being activated
"""
if reason == QtWidgets.QSystemTrayIcon.Trigger:
self.showNormal()
class WebApp(QtWidgets.QApplication):
"""
A generic application to open a web page in a desktop app
"""
def __init__(self, title, url, icon, canMinimizeToTray=False):
"""
Create an application which loads a URL into a window
"""
super(WebApp, self).__init__(sys.argv)
self.window = None
self.trayIcon = None
self.title = title
self.url = url
self.icon = icon
self.canMinimizeToTray = QtWidgets.QSystemTrayIcon.isSystemTrayAvailable() and canMinimizeToTray
if self.canMinimizeToTray:
self.setQuitOnLastWindowClosed(False)
self.setWindowIcon(QtGui.QIcon(self.icon))
self.setApplicationName(title)
self.setApplicationDisplayName(title)
def run(self):
"""
Set up the window and the tray icon, and run the app
"""
self.window = WebWindow(self, self.title, self.url, self.icon, self.canMinimizeToTray)
if self.canMinimizeToTray:
self.window.setupTrayIcon()
self.window.showMaximized()
# Get the "exec" method depending on Python 2 or 3
if sys.version_info[0] == 2:
runner = getattr(self, 'exec_')
else:
runner = getattr(self, 'exec')
return runner()

View File

@ -1,142 +0,0 @@
"""
The :mod:`webapp` module contains a WebApp class which can be used to create simple "app windows"
for websites. To use it, do this::
from webapp import WebApp
app = WebApp('GMail', 'https://mail.google.com', 'gmail.png')
app.run()
.. note:
If your site needs Flash Player, you'll need the appropriate Flash Player plugin installed
system-wide. For WebKit, this is the NPAPI plugin, and for WebEngine, this is the PPAPI plugin.
"""
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
try:
from PyQt5 import QtWebEngineWidgets
HAS_WEBENGINE = True
except ImportError:
HAS_WEBENGINE = False
try:
from PyQt5 import QtWebKit, QtWebKitWidgets
HAS_WEBKIT = True
except ImportError:
HAS_WEBKIT = False
if HAS_WEBENGINE:
SETTINGS = [
QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled,
QtWebEngineWidgets.QWebEngineSettings.JavascriptCanAccessClipboard,
QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessRemoteUrls
]
WebView = QtWebEngineWidgets.QWebEngineView
elif HAS_WEBKIT:
SETTINGS = [
QtWebKit.QWebSettings.AutoLoadImages,
QtWebKit.QWebSettings.JavascriptEnabled,
QtWebKit.QWebSettings.JavaEnabled,
QtWebKit.QWebSettings.PluginsEnabled,
QtWebKit.QWebSettings.JavascriptCanOpenWindows,
QtWebKit.QWebSettings.JavascriptCanCloseWindows,
QtWebKit.QWebSettings.JavascriptCanAccessClipboard,
QtWebKit.QWebSettings.DeveloperExtrasEnabled,
QtWebKit.QWebSettings.OfflineStorageDatabaseEnabled,
QtWebKit.QWebSettings.OfflineWebApplicationCacheEnabled,
QtWebKit.QWebSettings.LocalStorageEnabled,
QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls,
QtWebKit.QWebSettings.LocalContentCanAccessFileUrls,
QtWebKit.QWebSettings.AcceleratedCompositingEnabled
]
WebView = QtWebKitWidgets.QWebView
class WebPage(QtWebKitWidgets.QWebPage):
"""Custom class for overriding the user agent to make WebKit look like Chrome"""
def userAgentForUrl(self, url):
return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
'Chrome/28.0.1500.52 Safari/537.36'
else:
print('Cannot detect either QtWebEngine or QtWebKit!')
sys.exit(1)
class WebWindow(QtWidgets.QWidget):
"""
Window
"""
def __init__(self, title, url, icon, parent=None):
"""
Create the window
"""
super().__init__(parent)
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(icon))
self.setContentsMargins(0, 0, 0, 0)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.webview = WebView(self)
if HAS_WEBKIT:
self.webview.setPage(WebPage(self.webview))
for setting in SETTINGS:
self.webview.settings().setAttribute(setting, True)
self.webview.titleChanged.connect(self.onTitleChanged)
self.webview.setUrl(QtCore.QUrl(url))
self.layout.addWidget(self.webview)
def onTitleChanged(self, title):
"""
React to title changes
"""
if title:
self.setWindowTitle(title)
class WebApp(QtWidgets.QApplication):
"""
A generic application to open a web page in a desktop app
"""
def __init__(self, title, url, icon, hasTray=False, canMinimizeToTray=False):
"""
Create an application which loads a URL into a window
"""
super().__init__(sys.argv)
self.beforeHooks = []
self.afterHooks = []
self.window = None
self.title = title
self.url = url
self.icon = icon
self.hasTray = hasTray
self.canMinimizeToTray = canMinimizeToTray
self.setWindowIcon(QtGui.QIcon(self.icon))
def setupTrayIcon(self):
"""
Set up the tray icon
"""
if not QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
# No reason to continue if the OS doesn't support system tray icons
return
self.trayIcon = QtWidgets.QSystemTrayIcon(QtGui.QIcon(self.icon), self.window)
self.trayIcon.show()
def addBeforeHook(self, hook):
"""
Add a function to run before setting everything up
"""
if hook:
self.beforeHooks.append(hook)
def run(self):
"""
Run the app
"""
self.window = WebWindow(self.title, self.url, self.icon)
if self.hasTray:
self.setupTrayIcon()
self.window.showMaximized()
return self.exec()