Compare commits

..

26 Commits

Author SHA1 Message Date
b96e08436f Merge pull request 'Remove deprecated canMinimizeToTray option' (#13) from remove-deprecated-option into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #13
2024-09-27 17:33:07 +00:00
46fc044b5c Remove deprecated canMinimizeToTray option
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-09-27 12:24:13 -05:00
e9a599dfd8 Merge pull request 'Fix other issues arising from the migration' (#12) from fix-other-migration-issues into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/release/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Reviewed-on: #12
2024-09-27 16:59:46 +00:00
a4fa4bf6ba Fix other issues arising from the migration
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-09-27 11:52:05 -05:00
dda00bace0 Merge pull request 'Update the README' (#11) from update-readme into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #11
2024-09-27 16:32:32 +00:00
49fd14d7c2 Update the README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-09-27 11:30:23 -05:00
8fa772267c Merge pull request 'Fix CI build file' (#10) from fix-release-workflow into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/release/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Reviewed-on: #10
2024-09-27 16:24:58 +00:00
201aa4b41a Fix CI build file
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
2024-09-27 11:20:58 -05:00
53b28e2937 Merge pull request 'Migrate to Qt6 and PySide6' (#9) from migrate-to-qt6-pyside6 into master
Some checks failed
ci/woodpecker/release/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
Reviewed-on: #9
2024-09-27 15:02:07 +00:00
09bf97c38e Migrate to Qt6 and PySide6 2024-09-27 10:00:58 -05:00
43215e5d94 Merge pull request 'Update README with badges' (#8) from readme-badges into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #8
2022-06-13 17:00:40 +00:00
435f9f80a8 Update README with badges
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
- Add badges with links
- Add LICENSE file
2022-06-13 09:58:09 -07:00
387605fc41 Merge pull request 'Add a git pull to see if that helps' (#7) from try-git-pull into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #7
2022-06-13 15:02:46 +00:00
5a282eb8bf Add a git pull to see if that helps
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-06-13 08:01:41 -07:00
57fc300299 Merge pull request 'Fix woodpecker secrets' (#6) from update-woodpecker-ci into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #6
2022-06-10 19:54:11 +00:00
dc697f970a Fix woodpecker secrets
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-06-10 12:53:47 -07:00
a7988b120f Merge pull request 'Add secrets for publishing' (#5) from update-woodpecker-ci into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #5
2022-06-10 19:48:28 +00:00
c941cf5246 Add secrets for publishing
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-06-10 12:44:59 -07:00
637cf3aa8f Merge pull request 'Update README and CHANGELOG for 0.4.0 release' (#4) from update-changelog-readme into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #4
2022-06-10 19:41:56 +00:00
e2628bce55 Update README and CHANGELOG for 0.4.0 release
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2022-06-10 12:40:10 -07:00
6a84bd0749 Merge pull request 'Update WebAppify to the latest tech' (#3) from update-webappify into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #3
2022-06-10 19:31:40 +00:00
1471b73403 Update WebAppify to the latest tech
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
- Migrate to Hatch
- Migrate to Woodpecker CI
- Migrate to only Python 3
- Add .editorconfig
- Expand .gitignore
2022-06-10 12:30:10 -07:00
12f2e0016b Merge pull request 'Add Drone config' (#2) from add-drone-ci-config into master
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #2
2021-05-26 04:01:29 +00:00
2f42cc72fd
Add Drone config 2021-05-25 20:58:43 -07:00
89c533eb10 Merge pull request 'Update dependencies to depend on PyQtWebEngine' (#1) from update-deps into master
Reviewed-on: #1
2021-01-11 22:33:45 +00:00
8849899057
Update dependencies to depend on PyQtWebEngine 2021-01-11 15:31:49 -07:00
11 changed files with 377 additions and 329 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
root = true
[*]
max_line_length = 120
[*.py]
indent_style = space
indent_size = 4

View File

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

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
dist
*.egg-info
dist
build
.venv
__pycache__
*.py[co]

30
.woodpecker.yml Normal file
View File

@ -0,0 +1,30 @@
steps:
# tests:
# image: python:3
# commands:
# - pip install -U pip wheel pytest PyQt5 PyQtWebEngine
# - pytest
# when:
# branch: master
build:
image: python:3
commands:
- git fetch origin
- pip install -U pip wheel hatch hatch-vcs
- hatch build
when:
branch: master
release:
image: python:3
commands:
- git fetch origin
- pip install -U pip wheel hatch hatch-vcs
- hatch build
- hatch publish
environment:
HATCH_INDEX_USER:
from_secret: hatch_pypi_user
HATCH_INDEX_AUTH:
from_secret: hatch_pypi_auth
when:
event: tag

View File

@ -1,6 +1,16 @@
Changelog
=========
Version 0.4.0
-------------
Released 2022-06-10
- Rename ``canMinimizeToTray`` to ``can_minimize_to_tray``
- Remove support for Python versions less than 3.8
- Move to hatch for package management
- Update README
Version 0.3
-----------

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 Raoul Snyman
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF

View File

@ -1,8 +1,10 @@
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.
|pypi| |license| |build|
WebAppify is a simple module to easily create your own desktop apps of websites. WebAppify uses PySide6 and QtWebEngine
for displaying the web page, and works on Python 3.10 and up.
To create your own desktop web app, import and set up the WebApp class.
@ -15,19 +17,32 @@ To create your own desktop web app, import and set up the WebApp class.
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.
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.
``can_minimize_to_tray``
''''''''''''''''''''''''
To install a system tray icon, and minimize your application to the system tray, simply pass
``can_minimize_to_tray=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)
app = WebApp('OpenStreetMap', 'https://www.openstreetmap.org', 'osm.png', can_minimize_to_tray=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.
The `canMinimizeToTray` version of this option was removed in 0.6.0
.. |pypi| image:: https://img.shields.io/pypi/v/WebAppify
:target: https://pypi.org/project/webappify/
.. |license| image:: https://img.shields.io/pypi/l/WebAppify
:target: https://git.snyman.info/raoul/webappify/src/branch/master/LICENSE
.. |build| image:: https://ci.snyman.info/api/badges/raoul/webappify/status.svg
:target: https://ci.snyman.info/raoul/webappify

57
pyproject.toml Normal file
View File

@ -0,0 +1,57 @@
[build-system]
requires = [
"hatchling>=1.3.1",
"hatch-vcs"
]
build-backend = "hatchling.build"
[project]
name = "WebAppify"
description = "Create desktop apps of your favourite websites"
readme = "README.rst"
license = "MIT"
authors = [
{ name = "Raoul Snyman", email = "raoul@snyman.info" },
]
keywords = [
"Qt",
"Qt6",
"website",
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: MacOS X",
"Environment :: Win32 (MS Windows)",
"Environment :: X11 Applications",
"Environment :: X11 Applications :: Qt",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Desktop Environment",
"Topic :: Internet :: WWW/HTTP :: Browsers",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"PySide6",
]
dynamic = [
"version",
]
[project.urls]
Homepage = "https://git.snyman.info/raoul/webappify"
[tool.hatch.version]
source = "vcs"
[tool.hatch.build]
include = [
"/webappify",
]
[tool.hatch.build.targets.sdist]
[tool.hatch.build.targets.wheel]

View File

@ -1,45 +0,0 @@
"""
The webappify package
"""
import os
from codecs import open
from setuptools import setup
HERE = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(HERE, 'README.rst'), encoding='utf8') as f:
LONG_DESCRIPTION = f.read()
setup(
name='WebAppify',
version='0.3',
description='Create desktop apps of your favourite websites',
long_description=LONG_DESCRIPTION,
url='https://launchpad.net/webappify',
author='Raoul Snyman',
author_email='raoul@snyman.info',
license='MIT',
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: MacOS X',
'Environment :: Win32 (MS Windows)',
'Environment :: X11 Applications',
'Environment :: X11 Applications :: Qt',
'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',
'Programming Language :: Python :: 3.6',
'Topic :: Desktop Environment',
'Topic :: Internet :: WWW/HTTP :: Browsers',
'Topic :: Software Development :: Libraries :: Python Modules'
],
keywords='Qt website',
py_modules=['webappify'],
install_requires=['PyQt5']
)

View File

@ -1,273 +0,0 @@
"""
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
IS_PY2 = sys.version_info[0] == 2
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 = QtGui.QIcon(icon)
self.canMinimizeToTray = canMinimizeToTray
self.setWindowTitle(title)
self.setWindowIcon(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.', QtWidgets.QSystemTrayIcon.Information, 5000)
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 _raiseWindow(self):
"""
Raise the Window, depending on the version of Python
"""
# Get the "raise" method depending on Python 2 or 3
if IS_PY2:
raiser = getattr(self, 'raise_')
else:
raiser = getattr(self, 'raise')
raiser()
def _restoreWindow(self):
"""
Restore the window and activate it
"""
self.showNormal()
self.activateWindow()
self._raiseWindow()
def _maximizeWindow(self):
"""
Restore the window and activate it
"""
self.showMaximized()
self.activateWindow()
self._raiseWindow()
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._restoreWindow)
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._maximizeWindow)
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(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:
if self.isVisible():
self.close()
else:
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 IS_PY2:
runner = getattr(self, 'exec_')
else:
runner = getattr(self, 'exec')
return runner()

237
webappify/__init__.py Normal file
View File

@ -0,0 +1,237 @@
"""
WebAppify
=========
WebAppify is a simple module to easily create your own desktop apps of websites. WebAppify uses PySide6 and
QtWebEngine for displaying the web page, and works on Python 3.10 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.
"""
import logging
import sys
import platform
from PySide6 import QtCore, QtGui, QtWidgets, QtWebEngineCore, QtWebEngineWidgets
SETTINGS = [
QtWebEngineCore.QWebEngineSettings.PluginsEnabled,
QtWebEngineCore.QWebEngineSettings.JavascriptCanAccessClipboard,
QtWebEngineCore.QWebEngineSettings.LocalContentCanAccessRemoteUrls
]
LOG_LEVELS = {
QtWebEngineCore.QWebEnginePage.InfoMessageLevel: logging.INFO,
QtWebEngineCore.QWebEnginePage.WarningMessageLevel: logging.WARNING,
QtWebEngineCore.QWebEnginePage.ErrorMessageLevel: logging.ERROR
}
log = logging.getLogger(__name__)
class WebPage(QtWebEngineCore.QWebEnginePage):
"""
A custom QWebEnginePage which logs JS console messages to the Python logging system
"""
def javaScriptConsoleMessage(self, level, message, line_number, source_id):
"""
Custom logger to log console messages to the Python logging system
"""
log.log(LOG_LEVELS[level], f'{source_id}:{line_number} {message}')
class WebWindow(QtWidgets.QWidget):
"""
A window with a single web view and nothing else
"""
def __init__(self, app, title, url, icon, can_minimize_to_tray=False, canMinimizeToTray=False):
"""
Create the window
"""
super(WebWindow, self).__init__(None)
self._has_shown_warning = False
self.app = app
self.icon = QtGui.QIcon(icon)
self.can_minimize_to_tray = can_minimize_to_tray or canMinimizeToTray
self.setWindowTitle(title)
self.setWindowIcon(self.icon)
self.setContentsMargins(0, 0, 0, 0)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.webview = QtWebEngineWidgets.QWebEngineView(self)
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.on_title_changed)
def _show_warning(self):
"""
Show a balloon message to inform the user that the app is minimized
"""
if not self._has_shown_warning:
self.tray_icon.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.', QtWidgets.QSystemTrayIcon.Information, 5000)
self._has_shown_warning = True
def _update_tray_menu(self):
"""
Update the enabled/disabled status of the items in the tray icon menu
"""
if not self.can_minimize_to_tray:
return
self.restore_action.setEnabled(not self.isVisible())
self.minimize_action.setEnabled(self.isVisible() and not self.isMinimized())
self.maximize_action.setEnabled(self.isVisible() and not self.isMaximized())
def _restore_window(self):
"""
Restore the window and activate it
"""
self.showNormal()
self.activateWindow()
self.raise_()
def _maximize_window(self):
"""
Restore the window and activate it
"""
self.showMaximized()
self.activateWindow()
self.raise_()
def _get_tray_menu(self):
"""
Create and return the menu for the tray icon
"""
# Create the actions for the menu
self.restore_action = QtGui.QAction('&Restore', self)
self.restore_action.triggered.connect(self._restore_window)
self.minimize_action = QtGui.QAction('Mi&nimize', self)
self.minimize_action.triggered.connect(self.close)
self.maximize_action = QtGui.QAction('Ma&ximize', self)
self.maximize_action.triggered.connect(self._maximize_window)
self.quit_action = QtGui.QAction('&Quit', self)
self.quit_action.triggered.connect(self.app.quit)
# Create the menu and add the actions
tray_icon_menu = QtWidgets.QMenu(self)
tray_icon_menu.addAction(self.restore_action)
tray_icon_menu.addAction(self.minimize_action)
tray_icon_menu.addAction(self.maximize_action)
tray_icon_menu.addSeparator()
tray_icon_menu.addAction(self.quit_action)
return tray_icon_menu
def setup_tray_icon(self):
"""
Set up the tray icon
"""
self.tray_icon = QtWidgets.QSystemTrayIcon(self.icon, self)
self.tray_icon.setContextMenu(self._get_tray_menu())
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.tray_icon.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.can_minimize_to_tray:
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._show_warning()
self.hide()
event.ignore()
# Update the menu to match
self._update_tray_menu()
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._update_tray_menu()
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._update_tray_menu()
def changeEvent(self, event):
"""
Catch the minimize event and close the form
"""
if self.can_minimize_to_tray:
if event.type() == QtCore.QEvent.WindowStateChange and self.windowState() & QtCore.Qt.WindowMinimized:
self.close()
super(WebWindow, self).changeEvent(event)
def on_title_changed(self, title):
"""
React to title changes
"""
if title:
self.setWindowTitle(title)
if self.can_minimize_to_tray:
self.tray_icon.setToolTip(title)
def on_tray_icon_activated(self, reason):
"""
React to the tray icon being activated
"""
if reason == QtWidgets.QSystemTrayIcon.Trigger:
if self.isVisible():
self.close()
else:
self.showNormal()
class WebApp(QtWidgets.QApplication):
"""
A generic application to open a web page in a desktop app
"""
def __init__(self, title, url, icon, can_minimize_to_tray=False):
"""
Create an application which loads a URL into a window
"""
super(WebApp, self).__init__(sys.argv)
self.window = None
self.tray_icon = None
self.title = title
self.url = url
self.icon = icon
self.can_minimize_to_tray = QtWidgets.QSystemTrayIcon.isSystemTrayAvailable() and can_minimize_to_tray
if self.can_minimize_to_tray:
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.can_minimize_to_tray)
if self.can_minimize_to_tray:
self.window.setup_tray_icon()
self.window.showMaximized()
return self.exec()