Compare commits

..

1 Commits

Author SHA1 Message Date
Raoul Snyman f488106a4c Update WebAppify to the latest tech
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/pr/woodpecker Pipeline was successful Details
- Migrate to Hatch
- Migrate to Woodpecker CI
- Migrate to only Python 3
2022-06-10 11:42:03 -07:00
4 changed files with 79 additions and 131 deletions

2
.flake8 Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max-line-length = 120

View File

@ -1,11 +1,11 @@
pipeline: pipeline:
tests: # tests:
image: python:3 # image: python:3
commands: # commands:
- pip install -U pip wheel pytest PyQt5 PyQtWebEngine # - pip install -U pip wheel pytest PyQt5 PyQtWebEngine
- pytest # - pytest
when: # when:
branch: master # branch: master
build: build:
image: python:3 image: python:3
commands: commands:

View File

@ -1,6 +1,7 @@
[build-system] [build-system]
requires = [ requires = [
"hatchling>=1.3.1", "hatchling>=1.3.1",
"hatch-vcs"
] ]
build-backend = "hatchling.build" build-backend = "hatchling.build"
@ -25,12 +26,10 @@ classifiers = [
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.10",
"Topic :: Desktop Environment", "Topic :: Desktop Environment",
"Topic :: Internet :: WWW/HTTP :: Browsers", "Topic :: Internet :: WWW/HTTP :: Browsers",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
@ -47,7 +46,7 @@ dynamic = [
Homepage = "https://git.snyman.info/raoul/webappify" Homepage = "https://git.snyman.info/raoul/webappify"
[tool.hatch.version] [tool.hatch.version]
path = "webappify/version.py" source = "vcs"
[tool.hatch.build] [tool.hatch.build]
include = [ include = [
@ -55,4 +54,4 @@ include = [
] ]
[tool.hatch.build.targets.sdist] [tool.hatch.build.targets.sdist]
[tool.hatch.build.targets.bdist] [tool.hatch.build.targets.wheel]

View File

@ -24,208 +24,160 @@ This will create a window with the website, using the icon provided.
import sys import sys
import platform import platform
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
IS_PY2 = sys.version_info[0] == 2 SETTINGS = [
QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled,
QtWebEngineWidgets.QWebEngineSettings.JavascriptCanAccessClipboard,
QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessRemoteUrls
]
try: # WebView = QtWebEngineWidgets.QWebEngineView
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): class WebWindow(QtWidgets.QWidget):
""" """
A window with a single web view and nothing else A window with a single web view and nothing else
""" """
def __init__(self, app, title, url, icon, canMinimizeToTray=False): def __init__(self, app, title, url, icon, can_minimize_to_tray=False, canMinimizeToTray=False):
""" """
Create the window Create the window
""" """
super(WebWindow, self).__init__(None) super(WebWindow, self).__init__(None)
self.hasShownWarning = False self._has_shown_warning = False
self.app = app self.app = app
self.icon = QtGui.QIcon(icon) self.icon = QtGui.QIcon(icon)
self.canMinimizeToTray = canMinimizeToTray self.can_minimize_to_tray = can_minimize_to_tray or canMinimizeToTray
self.setWindowTitle(title) self.setWindowTitle(title)
self.setWindowIcon(self.icon) self.setWindowIcon(self.icon)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.layout = QtWidgets.QVBoxLayout(self) self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setContentsMargins(0, 0, 0, 0)
self.webview = WebView(self) self.webview = WebView(self)
if not HAS_WEBENGINE and HAS_WEBKIT:
self.webview.setPage(WebPage(self.webview))
for setting in SETTINGS: for setting in SETTINGS:
self.webview.settings().setAttribute(setting, True) self.webview.settings().setAttribute(setting, True)
self.webview.setUrl(QtCore.QUrl(url)) self.webview.setUrl(QtCore.QUrl(url))
self.layout.addWidget(self.webview) self.layout.addWidget(self.webview)
self.webview.titleChanged.connect(self.onTitleChanged) self.webview.titleChanged.connect(self.on_title_changed)
def _showWarning(self): def _show_warning(self):
""" """
Show a balloon message to inform the user that the app is minimized Show a balloon message to inform the user that the app is minimized
""" """
if not self.hasShownWarning: if not self.has_shown_warning:
self.trayIcon.showMessage(self.windowTitle(), 'This program will continue running in the system tray. ' 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 ' 'To close the program, choose <b>Quit</b> in the context menu of the system '
'tray icon.', QtWidgets.QSystemTrayIcon.Information, 5000) 'tray icon.', QtWidgets.QSystemTrayIcon.Information, 5000)
self.hasShownWarning = True self.has_shown_warning = True
def _updateTrayMenu(self): def _update_tray_menu(self):
""" """
Update the enabled/disabled status of the items in the tray icon menu Update the enabled/disabled status of the items in the tray icon menu
""" """
if not self.canMinimizeToTray: if not self.can_minimize_to_tray:
return return
self.restoreAction.setEnabled(not self.isVisible()) self.restore_action.setEnabled(not self.isVisible())
self.minimizeAction.setEnabled(self.isVisible() and not self.isMinimized()) self.minimize_action.setEnabled(self.isVisible() and not self.isMinimized())
self.maximizeAction.setEnabled(self.isVisible() and not self.isMaximized()) self.maximize_action.setEnabled(self.isVisible() and not self.isMaximized())
def _raiseWindow(self): def _restore_window(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 Restore the window and activate it
""" """
self.showNormal() self.showNormal()
self.activateWindow() self.activateWindow()
self._raiseWindow() self.raise_()
def _maximizeWindow(self): def _maximize_window(self):
""" """
Restore the window and activate it Restore the window and activate it
""" """
self.showMaximized() self.showMaximized()
self.activateWindow() self.activateWindow()
self._raiseWindow() self.raise_()
def _getTrayMenu(self): def _get_tray_menu(self):
""" """
Create and return the menu for the tray icon Create and return the menu for the tray icon
""" """
# Create the actions for the menu # Create the actions for the menu
self.restoreAction = QtWidgets.QAction('&Restore', self) self.restore_action = QtWidgets.QAction('&Restore', self)
self.restoreAction.triggered.connect(self._restoreWindow) self.restore_action.triggered.connect(self._restoreWindow)
self.minimizeAction = QtWidgets.QAction('Mi&nimize', self) self.minimize_action = QtWidgets.QAction('Mi&nimize', self)
self.minimizeAction.triggered.connect(self.close) self.minimize_action.triggered.connect(self.close)
self.maximizeAction = QtWidgets.QAction('Ma&ximize', self) self.maximize_action = QtWidgets.QAction('Ma&ximize', self)
self.maximizeAction.triggered.connect(self._maximizeWindow) self.maximize_action.triggered.connect(self._maximizeWindow)
self.quitAction = QtWidgets.QAction('&Quit', self) self.quit_action = QtWidgets.QAction('&Quit', self)
self.quitAction.triggered.connect(self.app.quit) self.quit_action.triggered.connect(self.app.quit)
# Create the menu and add the actions # Create the menu and add the actions
trayIconMenu = QtWidgets.QMenu(self) tray_icon_menu = QtWidgets.QMenu(self)
trayIconMenu.addAction(self.restoreAction) tray_icon_menu.addAction(self.restore_action)
trayIconMenu.addAction(self.minimizeAction) tray_icon_menu.addAction(self.minimize_action)
trayIconMenu.addAction(self.maximizeAction) tray_icon_menu.addAction(self.maximize_action)
trayIconMenu.addSeparator() tray_icon_menu.addSeparator()
trayIconMenu.addAction(self.quitAction) tray_icon_menu.addAction(self.quit_action)
return trayIconMenu return tray_icon_menu
def setupTrayIcon(self): def setup_tray_icon(self):
""" """
Set up the tray icon Set up the tray icon
""" """
self.trayIcon = QtWidgets.QSystemTrayIcon(self.icon, self) self.tray_icon = QtWidgets.QSystemTrayIcon(self.icon, self)
self.trayIcon.setContextMenu(self._getTrayMenu()) self.tray_icon.setContextMenu(self._get_tray_menu())
self.trayIcon.activated.connect(self.onTrayIconActivated) self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.trayIcon.show() self.tray_icon.show()
def closeEvent(self, event): def closeEvent(self, event):
""" """
Override the close event to minimize to the tray 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 we don't want to minimize to the tray, just close the window as per usual
if not self.canMinimizeToTray: if not self.can_minimize_to_tray:
super(WebWindow, self).closeEvent(event) super(WebWindow, self).closeEvent(event)
return return
# If we want to minimize to the tray, then just hide the window # 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()): if platform.platform().lower() == 'darwin' and (not event.spontaneous() or not self.isVisible()):
return return
else: else:
self._showWarning() self._show_warning()
self.hide() self.hide()
event.ignore() event.ignore()
# Update the menu to match # Update the menu to match
self._updateTrayMenu() self._update_tray_menu()
def showEvent(self, event): def showEvent(self, event):
""" """
Override the show event to catch max/min/etc events and update the tray icon menu accordingly Override the show event to catch max/min/etc events and update the tray icon menu accordingly
""" """
super(WebWindow, self).showEvent(event) super(WebWindow, self).showEvent(event)
self._updateTrayMenu() self._update_tray_menu()
def hideEvent(self, event): def hideEvent(self, event):
""" """
Override the hide event to catch max/min/etc events and update the tray icon menu accordingly Override the hide event to catch max/min/etc events and update the tray icon menu accordingly
""" """
super(WebWindow, self).hideEvent(event) super(WebWindow, self).hideEvent(event)
self._updateTrayMenu() self._update_tray_menu()
def changeEvent(self, event): def changeEvent(self, event):
""" """
Catch the minimize event and close the form Catch the minimize event and close the form
""" """
if self.canMinimizeToTray: if self.can_minimize_to_tray:
if event.type() == QtCore.QEvent.WindowStateChange and self.windowState() & QtCore.Qt.WindowMinimized: if event.type() == QtCore.QEvent.WindowStateChange and self.windowState() & QtCore.Qt.WindowMinimized:
self.close() self.close()
super(WebWindow, self).changeEvent(event) super(WebWindow, self).changeEvent(event)
def onTitleChanged(self, title): def on_title_changed(self, title):
""" """
React to title changes React to title changes
""" """
if title: if title:
self.setWindowTitle(title) self.setWindowTitle(title)
if self.canMinimizeToTray: if self.can_minimize_to_tray:
self.trayIcon.setToolTip(title) self.tray_icon.setToolTip(title)
def onTrayIconActivated(self, reason): def on_tray_icon_activated(self, reason):
""" """
React to the tray icon being activated React to the tray icon being activated
""" """
@ -246,12 +198,12 @@ class WebApp(QtWidgets.QApplication):
""" """
super(WebApp, self).__init__(sys.argv) super(WebApp, self).__init__(sys.argv)
self.window = None self.window = None
self.trayIcon = None self.tray_icon = None
self.title = title self.title = title
self.url = url self.url = url
self.icon = icon self.icon = icon
self.canMinimizeToTray = QtWidgets.QSystemTrayIcon.isSystemTrayAvailable() and canMinimizeToTray self.can_minimize_to_tray = QtWidgets.QSystemTrayIcon.isSystemTrayAvailable() and can_minimize_to_tray
if self.canMinimizeToTray: if self.can_minimize_to_tray:
self.setQuitOnLastWindowClosed(False) self.setQuitOnLastWindowClosed(False)
self.setWindowIcon(QtGui.QIcon(self.icon)) self.setWindowIcon(QtGui.QIcon(self.icon))
self.setApplicationName(title) self.setApplicationName(title)
@ -261,13 +213,8 @@ class WebApp(QtWidgets.QApplication):
""" """
Set up the window and the tray icon, and run the app Set up the window and the tray icon, and run the app
""" """
self.window = WebWindow(self, self.title, self.url, self.icon, self.canMinimizeToTray) self.window = WebWindow(self, self.title, self.url, self.icon, self.can_minimize_to_tray)
if self.canMinimizeToTray: if self.can_minimize_to_tray:
self.window.setupTrayIcon() self.window.setup_tray_icon()
self.window.showMaximized() self.window.showMaximized()
# Get the "exec" method depending on Python 2 or 3 return self.exec()
if IS_PY2:
runner = getattr(self, 'exec_')
else:
runner = getattr(self, 'exec')
return runner()