Wed, 18 Dec 2024 16:52:16 +0100
Adapted the web browser to Qt 6.8.
--- a/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionBar.py Wed Dec 18 16:51:10 2024 +0100 +++ b/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionBar.py Wed Dec 18 16:52:16 2024 +0100 @@ -20,6 +20,8 @@ class FeaturePermissionBar(EricAnimatedWidget): """ Class implementing the feature permission bar widget. + + Note: This is not needed for Qt 6.8+. """ DefaultHeight = 30 @@ -57,7 +59,9 @@ QWebEnginePage.Feature.MediaAudioVideoCapture: self.tr( "{0} wants to use your microphone and camera." ), - QWebEnginePage.Feature.MouseLock: self.tr("{0} wants to lock your mouse."), + QWebEnginePage.Feature.MouseLock: self.tr( + "{0} wants to lock your mouse." + ), QWebEnginePage.Feature.DesktopVideoCapture: self.tr( "{0} wants to capture video of your screen." ), @@ -76,7 +80,8 @@ QWebEnginePage.Feature.MediaAudioVideoCapture: "audio-video", QWebEnginePage.Feature.MouseLock: "mouse", QWebEnginePage.Feature.DesktopVideoCapture: "desktopVideoCapture", - QWebEnginePage.Feature.DesktopAudioVideoCapture: "desktopAudioVideoCapture", + QWebEnginePage.Feature.DesktopAudioVideoCapture: + "desktopAudioVideoCapture", QWebEnginePage.Feature.Notifications: "notification", } @@ -93,7 +98,9 @@ self.__rememberButton.setCheckable(True) self.__allowButton = QPushButton(self.tr("Allow"), self) self.__denyButton = QPushButton(self.tr("Deny"), self) - self.__discardButton = QPushButton(EricPixmapCache.getIcon("close"), "", self) + self.__discardButton = QPushButton( + EricPixmapCache.getIcon("close"), "", self + ) self.__allowButton.clicked.connect(self.__permissionGranted) self.__denyButton.clicked.connect(self.__permissionDenied) self.__discardButton.clicked.connect(self.__permissionUnknown) @@ -135,6 +142,7 @@ self.__page.loadStarted.disconnect(self.hide) super().hide() + @pyqtSlot() def __permissionDenied(self): """ Private slot handling the user pressing the deny button. @@ -157,6 +165,7 @@ self.hide() + @pyqtSlot() def __permissionGranted(self): """ Private slot handling the user pressing the allow button. @@ -179,9 +188,10 @@ self.hide() + @pyqtSlot() def __permissionUnknown(self): """ - Private slot handling the user closing the dialog without. + Private slot handling the user closing the dialog without a selection. """ if self.__page is None or self.__manager is None: return
--- a/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionManager.py Wed Dec 18 16:51:10 2024 +0100 +++ b/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionManager.py Wed Dec 18 16:52:16 2024 +0100 @@ -18,6 +18,8 @@ class FeaturePermissionManager(QObject): """ Class implementing the feature permission manager object. + + Note: This is not needed for Qt 6.8+. """ SettingsKeyFormat = "WebBrowser/FeaturePermissions/{0}"
--- a/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionsDialog.py Wed Dec 18 16:51:10 2024 +0100 +++ b/src/eric7/WebBrowser/FeaturePermissions/FeaturePermissionsDialog.py Wed Dec 18 16:52:16 2024 +0100 @@ -7,11 +7,19 @@ Module implementing the feature permission dialog. """ +import contextlib + from PyQt6.QtCore import Qt, pyqtSlot -from PyQt6.QtWebEngineCore import QWebEnginePage from PyQt6.QtWidgets import QAbstractItemView, QDialog, QTreeWidget, QTreeWidgetItem +try: + # Qt 6.8+ + from PyQt6.QtWebEngineCore import QWebEnginePermission +except ImportError: + # Qt <6.8 + from PyQt6.QtWebEngineCore import QWebEnginePage from eric7.EricGui import EricPixmapCache +from eric7.SystemUtilities import QtUtilities from .Ui_FeaturePermissionsDialog import Ui_FeaturePermissionsDialog @@ -26,8 +34,8 @@ Constructor @param featurePermissions dictionary with remembered feature - permissions - @type dict of dict of list + permissions (Qt <6.8) or a list of permission objects (Qt 6.8+) + @type dict of dict of list or list of QWebEnginePermission @param parent reference to the parent widget @type QWidget """ @@ -166,6 +174,36 @@ self.tr("Desktop Audio && Video"), ) + if QtUtilities.qVersionTuple() >= (6, 8, 0): + # Qt 6.8+ + self.clipboardList = QTreeWidget() + self.clipboardList.setAlternatingRowColors(True) + self.clipboardList.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.clipboardList.setRootIsDecorated(False) + self.clipboardList.setItemsExpandable(False) + self.clipboardList.setAllColumnsShowFocus(True) + self.clipboardList.setObjectName("camList") + self.clipboardList.setSortingEnabled(True) + self.clipboardList.headerItem().setText(0, self.tr("Host")) + self.clipboardList.headerItem().setText(1, self.tr("Permission")) + self.tabWidget.addTab( + self.clipboardList, EricPixmapCache.getIcon("clipboard"), self.tr("Clipboard") + ) + + self.localFontsList = QTreeWidget() + self.localFontsList.setAlternatingRowColors(True) + self.localFontsList.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.localFontsList.setRootIsDecorated(False) + self.localFontsList.setItemsExpandable(False) + self.localFontsList.setAllColumnsShowFocus(True) + self.localFontsList.setObjectName("camList") + self.localFontsList.setSortingEnabled(True) + self.localFontsList.headerItem().setText(0, self.tr("Host")) + self.localFontsList.headerItem().setText(1, self.tr("Permission")) + self.tabWidget.addTab( + self.localFontsList, EricPixmapCache.getIcon("font"), self.tr("Local Fonts") + ) + self.setTabOrder(self.tabWidget, self.notifList) self.setTabOrder(self.notifList, self.geoList) self.setTabOrder(self.geoList, self.micList) @@ -174,33 +212,74 @@ self.setTabOrder(self.micCamList, self.mouseLockList) self.setTabOrder(self.mouseLockList, self.deskVidList) self.setTabOrder(self.deskVidList, self.deskAudVidList) - self.setTabOrder(self.deskAudVidList, self.removeButton) + if QtUtilities.qVersionTuple() >= (6, 8, 0): + # Qt 6.8+ + self.setTabOrder(self.deskAudVidList, self.clipboardList) + self.setTabOrder(self.clipboardList, self.localFontsList) + self.setTabOrder(self.localFontsList, self.removeButton) + else: + self.setTabOrder(self.deskAudVidList, self.removeButton) self.setTabOrder(self.removeButton, self.removeAllButton) - self.__permissionStrings = { - QWebEnginePage.PermissionPolicy.PermissionGrantedByUser: self.tr("Allow"), - QWebEnginePage.PermissionPolicy.PermissionDeniedByUser: self.tr("Deny"), - } + if QtUtilities.qVersionTuple() >= (6, 8, 0): + # Qt 6.8+ + self.__permissionStrings = { + QWebEnginePermission.State.Granted: self.tr("Allow"), + QWebEnginePermission.State.Denied: self.tr("Deny"), + QWebEnginePermission.State.Ask: self.tr("Always Ask"), + QWebEnginePermission.State.Invalid: self.tr("Invalid"), + } - self.__permissionsLists = { - QWebEnginePage.Feature.Geolocation: self.geoList, - QWebEnginePage.Feature.MediaAudioCapture: self.micList, - QWebEnginePage.Feature.MediaVideoCapture: self.camList, - QWebEnginePage.Feature.MediaAudioVideoCapture: self.micCamList, - QWebEnginePage.Feature.MouseLock: self.mouseLockList, - QWebEnginePage.Feature.DesktopVideoCapture: self.deskVidList, - QWebEnginePage.Feature.DesktopAudioVideoCapture: self.deskAudVidList, - QWebEnginePage.Feature.Notifications: self.notifList, - } + self.__permissionsLists = { + QWebEnginePermission.PermissionType.Geolocation: self.geoList, + QWebEnginePermission.PermissionType.MediaAudioCapture: self.micList, + QWebEnginePermission.PermissionType.MediaVideoCapture: self.camList, + QWebEnginePermission.PermissionType.MediaAudioVideoCapture: self.micCamList, + QWebEnginePermission.PermissionType.MouseLock: self.mouseLockList, + QWebEnginePermission.PermissionType.DesktopVideoCapture: self.deskVidList, + QWebEnginePermission.PermissionType.DesktopAudioVideoCapture: self.deskAudVidList, + QWebEnginePermission.PermissionType.Notifications: self.notifList, + QWebEnginePermission.PermissionType.ClipboardReadWrite: self.clipboardList, + QWebEnginePermission.PermissionType.LocalFontsAccess: self.localFontsList, + } - for feature, permissionsList in self.__permissionsLists.items(): - for permission in featurePermissions[feature]: - for host in featurePermissions[feature][permission]: + for permission in featurePermissions: + with contextlib.suppress(KeyError): + permissionsList = self.__permissionsLists[permission.permissionType()] itm = QTreeWidgetItem( - permissionsList, [host, self.__permissionStrings[permission]] + permissionsList, [ + permission.origin().toString(), + self.__permissionStrings[permission.state()], + ] ) itm.setData(0, Qt.ItemDataRole.UserRole, permission) + else: + # Qt <6.8 + self.__permissionStrings = { + QWebEnginePage.PermissionPolicy.PermissionGrantedByUser: self.tr("Allow"), + QWebEnginePage.PermissionPolicy.PermissionDeniedByUser: self.tr("Deny"), + } + self.__permissionsLists = { + QWebEnginePage.Feature.Geolocation: self.geoList, + QWebEnginePage.Feature.MediaAudioCapture: self.micList, + QWebEnginePage.Feature.MediaVideoCapture: self.camList, + QWebEnginePage.Feature.MediaAudioVideoCapture: self.micCamList, + QWebEnginePage.Feature.MouseLock: self.mouseLockList, + QWebEnginePage.Feature.DesktopVideoCapture: self.deskVidList, + QWebEnginePage.Feature.DesktopAudioVideoCapture: self.deskAudVidList, + QWebEnginePage.Feature.Notifications: self.notifList, + } + + for feature, permissionsList in self.__permissionsLists.items(): + for permission in featurePermissions[feature]: + for host in featurePermissions[feature][permission]: + itm = QTreeWidgetItem( + permissionsList, [host, self.__permissionStrings[permission]] + ) + itm.setData(0, Qt.ItemDataRole.UserRole, permission) + + self.__removedPermissions = [] self.__previousCurrent = -1 self.tabWidget.currentChanged.connect(self.__currentTabChanged) self.tabWidget.setCurrentIndex(0) @@ -247,6 +326,7 @@ for itm in currentList.selectedItems(): row = currentList.indexOfTopLevelItem(itm) itm = currentList.takeTopLevelItem(row) + self.__removedPermissions.append(itm.data(0, Qt.ItemDataRole.UserRole)) del itm self.__updateButtons() @@ -258,6 +338,7 @@ currentList = self.tabWidget.currentWidget() while currentList.topLevelItemCount() > 0: itm = currentList.takeTopLevelItem(0) # __IGNORE_WARNING__ + self.__removedPermissions.append(itm.data(0, Qt.ItemDataRole.UserRole)) del itm self.__updateButtons() @@ -268,6 +349,7 @@ @return new feature permission settings @rtype dict of dict of list """ + # Qt <6.8 featurePermissions = {} for feature, permissionsList in self.__permissionsLists.items(): featurePermissions[feature] = { @@ -281,3 +363,11 @@ featurePermissions[feature][permission].append(host) return featurePermissions + + def persistChanges(self): + """ + Public method to persist the removed permissions. + """ + # Qt 6.8+ + for permission in self.__removedPermissions: + permission.reset()
--- a/src/eric7/WebBrowser/WebBrowserPage.py Wed Dec 18 16:51:10 2024 +0100 +++ b/src/eric7/WebBrowser/WebBrowserPage.py Wed Dec 18 16:52:16 2024 +0100 @@ -8,7 +8,10 @@ Module implementing the helpbrowser using QWebView. """ +import contextlib + from PyQt6.QtCore import ( + QCoreApplication, QEventLoop, QPoint, QTimer, @@ -24,6 +27,7 @@ from eric7 import EricUtilities, Preferences from eric7.EricWidgets import EricMessageBox from eric7.WebBrowser.WebBrowserWindow import WebBrowserWindow +from eric7.SystemUtilities import QtUtilities from .JavaScript.ExternalJsObject import ExternalJsObject from .Tools import Scripts @@ -38,6 +42,9 @@ except ImportError: SSL_AVAILABLE = False +with contextlib.suppress(ImportError): + from PyQt6.QtWebEngineCore import QWebEnginePermission + class WebBrowserPage(QWebEnginePage): """ @@ -66,6 +73,52 @@ sslConfigurationChanged = pyqtSignal() + if QtUtilities.qVersionTuple() >= (6, 8, 0): + PermissionTypeQuestions = { + QWebEnginePermission.PermissionType.Geolocation: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to access your location information?</p>", + ), + QWebEnginePermission.PermissionType.MediaAudioCapture: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to access your microphone?</p>", + ), + QWebEnginePermission.PermissionType.MediaVideoCapture: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to access your webcam?</p>", + ), + QWebEnginePermission.PermissionType.MediaAudioVideoCapture: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to access your microphone and webcam?</p>", + ), + QWebEnginePermission.PermissionType.MouseLock: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to lock your mouse cursor?</p>", + ), + QWebEnginePermission.PermissionType.DesktopVideoCapture: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to capture video of your desktop?</p>", + ), + QWebEnginePermission.PermissionType.DesktopAudioVideoCapture: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to capture audio and video of your desktop?</p>", + ), + QWebEnginePermission.PermissionType.Notifications: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to show notifications on your desktop?</p>", + ), + QWebEnginePermission.PermissionType.ClipboardReadWrite: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to read from and write to the clipboard?</p>", + ), + QWebEnginePermission.PermissionType.LocalFontsAccess: QCoreApplication.translate( + "WebBrowserPage", + "<p>Allow <b>{0}</b> to access fonts stored on this machine?</p>", + ), + } + else: + PermissionTypeQuestions = {} + def __init__(self, view, parent=None): """ Constructor @@ -82,7 +135,12 @@ self.__view = view - self.featurePermissionRequested.connect(self.__featurePermissionRequested) + try: + # Qt 6.8+ + self.permissionRequested.connect(self.__permissionRequested) + except AttributeError: + # Qt <6.8 + self.featurePermissionRequested.connect(self.__featurePermissionRequested) self.authenticationRequired.connect( lambda url, auth: WebBrowserWindow.networkManager().authentication( url, auth, self @@ -303,14 +361,35 @@ @param feature requested feature @type QWebEnginePage.Feature """ + # Qt <6.8 manager = WebBrowserWindow.featurePermissionManager() manager.requestFeaturePermission(self, url, feature) + def __permissionRequested(self, permission): + """ + Private slot handling a permission request. + + @param permission reference to the permission request object + @type QWebEnginePermission + """ + # Qt 6.8+ + question = self.PermissionTypeQuestions.get(permission.permissionType()) + if question and EricMessageBox.yesNo( + self.view(), + self.tr("Permission Request"), + question.format(permission.origin().host()), + yesDefault=True, + ): + permission.grant() + else: + permission.deny() + + def execJavaScript( self, script, worldId=QWebEngineScript.ScriptWorldId.MainWorld, timeout=500 ): """ - Public method to execute a JavaScript function synchroneously. + Public method to execute a JavaScript function synchronously. @param script JavaScript script source to be executed @type str
--- a/src/eric7/WebBrowser/WebBrowserWindow.py Wed Dec 18 16:51:10 2024 +0100 +++ b/src/eric7/WebBrowser/WebBrowserWindow.py Wed Dec 18 16:52:16 2024 +0100 @@ -4294,7 +4294,16 @@ """ Private slot to show the feature permission dialog. """ - self.featurePermissionManager().showFeaturePermissionsDialog(self) + if QtUtilities.qVersionTuple() >= (6, 8, 0): + # Qt 6.8+ + from .FeaturePermissions.FeaturePermissionsDialog import FeaturePermissionsDialog + + dlg = FeaturePermissionsDialog(self.webProfile().listAllPermissions(), parent=self) + if dlg.exec() == QDialog.DialogCode.Accepted: + dlg.persistChanges() + else: + # Qt <6.8 + self.featurePermissionManager().showFeaturePermissionsDialog(self) @pyqtSlot() def __showZoomValuesDialog(self): @@ -5392,9 +5401,9 @@ if cls._webProfile is None: if private: - cls._webProfile = QWebEngineProfile() + cls._webProfile = QWebEngineProfile.defaultProfile() else: - cls._webProfile = QWebEngineProfile.defaultProfile() + cls._webProfile = QWebEngineProfile("eric7") cls._webProfile.downloadRequested.connect(cls.downloadRequested) # add the default user agent string @@ -5439,6 +5448,9 @@ Preferences.getWebBrowser("SpellCheckLanguages") ) + # TODO: setPersistentPermissionPolicy for Qt 6.8+ + # setting key: "NoPersistentPermissions" for non-private only + # configure notifications cls._webProfile.setNotificationPresenter(cls.__showWebNotification)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-dark/clipboard.svg Wed Dec 18 16:52:16 2024 +0100 @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><defs><style type="text/css">.ColorScheme-Text { + color:#eff0f1; + }</style></defs><path class="ColorScheme-Text" d="m5.8571 1v2.5h-3.8571v17.5h18v-17.5h-3.8571v-2.5zm-2.5714 3.75h1.2857v2.5h12.857v-2.5h1.2857v15h-15.429zm2.5714 3.75v1.25h10.286v-1.25zm0 3.75v1.25h7.7143v-1.25zm0 3.75v1.25h3.8571v-1.25z" color="#eff0f1" fill="currentColor"/></svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-dark/font.svg Wed Dec 18 16:52:16 2024 +0100 @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + id="svg6" + version="1.1" + viewBox="0 0 22 22" + sodipodi:docname="font.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="46.681818" + inkscape:cx="11.010711" + inkscape:cy="11.182084" + inkscape:window-width="2580" + inkscape:window-height="1271" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <defs + id="defs3051"> + <style + id="current-color-scheme" + type="text/css">.ColorScheme-Text { color: #fcfcfc; } </style> + </defs> + <path + id="path4" + class="ColorScheme-Text" + d="M 9.7285153,1 5.8616536,14.705357 5.7180989,15.285714 4,21 H 5.9345703 L 7.8304036,14.660714 H 14.169597 L 16.06543,21 H 18 L 16.281901,15.285714 16.138347,14.705357 12.271485,1 H 9.9791667 Z m 1.2897137,2.7678571 2.542968,9.0624999 H 8.4388021 Z" + fill="currentColor" + style="stroke-width:1.29099" /> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-light/clipboard.svg Wed Dec 18 16:52:16 2024 +0100 @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><defs><style type="text/css">.ColorScheme-Text { + color:#eff0f1; + }</style></defs><path class="ColorScheme-Text" d="m5.8571 1v2.5h-3.8571v17.5h18v-17.5h-3.8571v-2.5zm-2.5714 3.75h1.2857v2.5h12.857v-2.5h1.2857v15h-15.429zm2.5714 3.75v1.25h10.286v-1.25zm0 3.75v1.25h7.7143v-1.25zm0 3.75v1.25h3.8571v-1.25z" color="#eff0f1" fill="#232629"/></svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/icons/breeze-light/font.svg Wed Dec 18 16:52:16 2024 +0100 @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + id="svg6" + version="1.1" + viewBox="0 0 22 22" + sodipodi:docname="font.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview6" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showgrid="false" + inkscape:zoom="46.681818" + inkscape:cx="11.010711" + inkscape:cy="11.117819" + inkscape:window-width="2580" + inkscape:window-height="1315" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <defs + id="defs3051"> + <style + id="current-color-scheme" + type="text/css">.ColorScheme-Text { + color:#232629; + }</style> + </defs> + <path + id="path4" + class="ColorScheme-Text" + d="M 9.365235,1 4.3935547,14.705357 4.2089843,15.285714 2,21 H 4.4873047 L 6.9248046,14.660714 H 15.075195 L 17.512696,21 H 20 L 17.791016,15.285714 17.606445,14.705357 12.634767,1 H 9.687501 Z m 1.658203,2.7678571 3.269531,9.0624999 H 7.707031 Z" + fill="currentColor" + style="stroke-width:1.46385" /> +</svg>