diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index 76eb74a..0000000
--- a/.drone.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-kind: pipeline
-name: default
-
-steps:
-- name: test
- image: python:3
- commands:
- - pip install -e .
- - pip install pytest
- - pytest
diff --git a/setup.cfg b/.flake8
similarity index 57%
rename from setup.cfg
rename to .flake8
index 96a01b4..6deafc2 100644
--- a/setup.cfg
+++ b/.flake8
@@ -1,5 +1,2 @@
-[wheel]
-universal = 1
-
[flake8]
max-line-length = 120
diff --git a/.gitignore b/.gitignore
index 3a34b64..61d5f42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
-dist
*.egg-info
+dist
build
+.venv
+__pycache__
+*.py[co]
diff --git a/.woodpecker.yml b/.woodpecker.yml
new file mode 100644
index 0000000..e5fa7c6
--- /dev/null
+++ b/.woodpecker.yml
@@ -0,0 +1,23 @@
+pipeline:
+ # tests:
+ # image: python:3
+ # commands:
+ # - pip install -U pip wheel pytest PyQt5 PyQtWebEngine
+ # - pytest
+ # when:
+ # branch: master
+ build:
+ image: python:3
+ commands:
+ - pip install -U pip wheel hatch hatch-vcs
+ - hatch build
+ when:
+ branch: master
+ release:
+ image: python:3
+ commands:
+ - pip install -U pip wheel hatch hatch-vcs
+ - hatch build
+ - hatch publish
+ when:
+ event: tag
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..f9b2aad
--- /dev/null
+++ b/pyproject.toml
@@ -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",
+ "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.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Topic :: Desktop Environment",
+ "Topic :: Internet :: WWW/HTTP :: Browsers",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+dependencies = [
+ "PyQt5",
+ "PyQtWebEngine",
+]
+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]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index c045526..0000000
--- a/setup.py
+++ /dev/null
@@ -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', 'PyQtWebEngine']
-)
diff --git a/webappify.py b/webappify.py
deleted file mode 100644
index 5ef49d8..0000000
--- a/webappify.py
+++ /dev/null
@@ -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 Quit 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()
diff --git a/webappify/__init__.py b/webappify/__init__.py
new file mode 100644
index 0000000..eaf663a
--- /dev/null
+++ b/webappify/__init__.py
@@ -0,0 +1,220 @@
+"""
+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, QtWebEngineWidgets
+
+SETTINGS = [
+ QtWebEngineWidgets.QWebEngineSettings.PluginsEnabled,
+ QtWebEngineWidgets.QWebEngineSettings.JavascriptCanAccessClipboard,
+ QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessRemoteUrls
+]
+
+# WebView = QtWebEngineWidgets.QWebEngineView
+
+
+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 = WebView(self)
+ 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 Quit 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 = QtWidgets.QAction('&Restore', self)
+ self.restore_action.triggered.connect(self._restoreWindow)
+ self.minimize_action = QtWidgets.QAction('Mi&nimize', self)
+ self.minimize_action.triggered.connect(self.close)
+ self.maximize_action = QtWidgets.QAction('Ma&ximize', self)
+ self.maximize_action.triggered.connect(self._maximizeWindow)
+ self.quit_action = QtWidgets.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, canMinimizeToTray=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()