Compare commits
26 Commits
bc48a765d0
...
master
Author | SHA1 | Date | |
---|---|---|---|
b96e08436f | |||
46fc044b5c | |||
e9a599dfd8 | |||
a4fa4bf6ba | |||
dda00bace0 | |||
49fd14d7c2 | |||
8fa772267c | |||
201aa4b41a | |||
53b28e2937 | |||
09bf97c38e | |||
43215e5d94 | |||
435f9f80a8 | |||
387605fc41 | |||
5a282eb8bf | |||
57fc300299 | |||
dc697f970a | |||
a7988b120f | |||
c941cf5246 | |||
637cf3aa8f | |||
e2628bce55 | |||
6a84bd0749 | |||
1471b73403 | |||
12f2e0016b | |||
2f42cc72fd | |||
89c533eb10 | |||
8849899057 |
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
max_line_length = 120
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
@ -1,5 +1,2 @@
|
|||||||
[wheel]
|
|
||||||
universal = 1
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
dist
|
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
dist
|
||||||
build
|
build
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
*.py[co]
|
||||||
|
30
.woodpecker.yml
Normal file
30
.woodpecker.yml
Normal 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
|
@ -1,6 +1,16 @@
|
|||||||
Changelog
|
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
|
Version 0.3
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
9
LICENSE
Normal file
9
LICENSE
Normal 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
|
29
README.rst
29
README.rst
@ -1,8 +1,10 @@
|
|||||||
WebAppify
|
WebAppify
|
||||||
=========
|
=========
|
||||||
|
|
||||||
WebAppify is a simple module to easily create your own desktop apps of websites. WebAppify uses PyQt5 and QtWebKit or
|
|pypi| |license| |build|
|
||||||
QtWebEngine for displaying the web page, and works on Python 2.7 and Python 3.4 and up.
|
|
||||||
|
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.
|
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.
|
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
|
Additional Options
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Version 0.2 comes with the option of minimizing to the system tray. Simply pass ``canMinimizeToTray=True`` to the class
|
``can_minimize_to_tray``
|
||||||
and a tray icon will be installed with the necessary menu options.
|
''''''''''''''''''''''''
|
||||||
|
|
||||||
|
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
|
.. 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.
|
Clicking on the tray icon will show the window, while right-clicking will show the menu.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If your site needs Flash Player, you'll need the appropriate Flash Player plugin installed system-wide. For QtWebKit
|
The `canMinimizeToTray` version of this option was removed in 0.6.0
|
||||||
you will need the NPAPI plugin, and for QtWebEngine you will need the PPAPI plugin.
|
|
||||||
|
.. |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
57
pyproject.toml
Normal 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]
|
45
setup.py
45
setup.py
@ -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']
|
|
||||||
)
|
|
273
webappify.py
273
webappify.py
@ -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
237
webappify/__init__.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user