1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2015 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing the feature permission dialog.
8"""
9
10from PyQt5.QtCore import pyqtSlot, Qt
11from PyQt5.QtWidgets import (
12    QDialog, QTreeWidgetItem, QTreeWidget, QAbstractItemView
13)
14from PyQt5.QtWebEngineWidgets import QWebEnginePage
15
16import UI.PixmapCache
17
18from .Ui_FeaturePermissionsDialog import Ui_FeaturePermissionsDialog
19
20
21class FeaturePermissionsDialog(QDialog, Ui_FeaturePermissionsDialog):
22    """
23    Class implementing the feature permission dialog.
24    """
25    def __init__(self, featurePermissions, parent=None):
26        """
27        Constructor
28
29        @param featurePermissions dictionary with remembered feature
30            permissions
31        @type dict of dict of list
32        @param parent reference to the parent widget
33        @type QWidget
34        """
35        super().__init__(parent)
36        self.setupUi(self)
37
38        # add the various lists
39
40        if hasattr(QWebEnginePage, "Notifications"):
41            # this was re-added in Qt 5.13.0
42            self.notifList = QTreeWidget()
43            self.notifList.setAlternatingRowColors(True)
44            self.notifList.setSelectionMode(
45                QAbstractItemView.SelectionMode.ExtendedSelection)
46            self.notifList.setRootIsDecorated(False)
47            self.notifList.setItemsExpandable(False)
48            self.notifList.setAllColumnsShowFocus(True)
49            self.notifList.setObjectName("notifList")
50            self.notifList.setSortingEnabled(True)
51            self.notifList.headerItem().setText(0, self.tr("Host"))
52            self.notifList.headerItem().setText(1, self.tr("Permission"))
53            self.tabWidget.addTab(
54                self.notifList,
55                UI.PixmapCache.getIcon("notification"),
56                self.tr("Notifications"))
57
58        self.geoList = QTreeWidget()
59        self.geoList.setAlternatingRowColors(True)
60        self.geoList.setSelectionMode(
61            QAbstractItemView.SelectionMode.ExtendedSelection)
62        self.geoList.setRootIsDecorated(False)
63        self.geoList.setItemsExpandable(False)
64        self.geoList.setAllColumnsShowFocus(True)
65        self.geoList.setObjectName("geoList")
66        self.geoList.setSortingEnabled(True)
67        self.geoList.headerItem().setText(0, self.tr("Host"))
68        self.geoList.headerItem().setText(1, self.tr("Permission"))
69        self.tabWidget.addTab(
70            self.geoList,
71            UI.PixmapCache.getIcon("geolocation"),
72            self.tr("Geolocation"))
73
74        self.micList = QTreeWidget()
75        self.micList.setAlternatingRowColors(True)
76        self.micList.setSelectionMode(
77            QAbstractItemView.SelectionMode.ExtendedSelection)
78        self.micList.setRootIsDecorated(False)
79        self.micList.setItemsExpandable(False)
80        self.micList.setAllColumnsShowFocus(True)
81        self.micList.setObjectName("micList")
82        self.micList.setSortingEnabled(True)
83        self.micList.headerItem().setText(0, self.tr("Host"))
84        self.micList.headerItem().setText(1, self.tr("Permission"))
85        self.tabWidget.addTab(
86            self.micList,
87            UI.PixmapCache.getIcon("audiocapture"),
88            self.tr("Microphone"))
89
90        self.camList = QTreeWidget()
91        self.camList.setAlternatingRowColors(True)
92        self.camList.setSelectionMode(
93            QAbstractItemView.SelectionMode.ExtendedSelection)
94        self.camList.setRootIsDecorated(False)
95        self.camList.setItemsExpandable(False)
96        self.camList.setAllColumnsShowFocus(True)
97        self.camList.setObjectName("camList")
98        self.camList.setSortingEnabled(True)
99        self.camList.headerItem().setText(0, self.tr("Host"))
100        self.camList.headerItem().setText(1, self.tr("Permission"))
101        self.tabWidget.addTab(
102            self.camList,
103            UI.PixmapCache.getIcon("camera"),
104            self.tr("Camera"))
105
106        self.micCamList = QTreeWidget()
107        self.micCamList.setAlternatingRowColors(True)
108        self.micCamList.setSelectionMode(
109            QAbstractItemView.SelectionMode.ExtendedSelection)
110        self.micCamList.setRootIsDecorated(False)
111        self.micCamList.setItemsExpandable(False)
112        self.micCamList.setAllColumnsShowFocus(True)
113        self.micCamList.setObjectName("micCamList")
114        self.micCamList.setSortingEnabled(True)
115        self.micCamList.headerItem().setText(0, self.tr("Host"))
116        self.micCamList.headerItem().setText(1, self.tr("Permission"))
117        self.tabWidget.addTab(
118            self.micCamList,
119            UI.PixmapCache.getIcon("audio-video"),
120            self.tr("Microphone && Camera"))
121
122        self.mouseLockList = QTreeWidget()
123        self.mouseLockList.setAlternatingRowColors(True)
124        self.mouseLockList.setSelectionMode(
125            QAbstractItemView.SelectionMode.ExtendedSelection)
126        self.mouseLockList.setRootIsDecorated(False)
127        self.mouseLockList.setItemsExpandable(False)
128        self.mouseLockList.setAllColumnsShowFocus(True)
129        self.mouseLockList.setObjectName("mouseLockList")
130        self.mouseLockList.setSortingEnabled(True)
131        self.mouseLockList.headerItem().setText(0, self.tr("Host"))
132        self.mouseLockList.headerItem().setText(1, self.tr("Permission"))
133        self.tabWidget.addTab(
134            self.mouseLockList,
135            UI.PixmapCache.getIcon("mouse"),
136            self.tr("Mouse Lock"))
137
138        if hasattr(QWebEnginePage, "DesktopVideoCapture"):
139            # these are shown as of Qt 5.10.0/PyQt 5.10.0
140            self.deskVidList = QTreeWidget()
141            self.deskVidList.setAlternatingRowColors(True)
142            self.deskVidList.setSelectionMode(
143                QAbstractItemView.SelectionMode.ExtendedSelection)
144            self.deskVidList.setRootIsDecorated(False)
145            self.deskVidList.setItemsExpandable(False)
146            self.deskVidList.setAllColumnsShowFocus(True)
147            self.deskVidList.setObjectName("deskVidList")
148            self.deskVidList.setSortingEnabled(True)
149            self.deskVidList.headerItem().setText(0, self.tr("Host"))
150            self.deskVidList.headerItem().setText(1, self.tr("Permission"))
151            self.tabWidget.addTab(
152                self.deskVidList,
153                UI.PixmapCache.getIcon("desktopVideoCapture"),
154                self.tr("Desktop Video"))
155
156            self.deskAudVidList = QTreeWidget()
157            self.deskAudVidList.setAlternatingRowColors(True)
158            self.deskAudVidList.setSelectionMode(
159                QAbstractItemView.SelectionMode.ExtendedSelection)
160            self.deskAudVidList.setRootIsDecorated(False)
161            self.deskAudVidList.setItemsExpandable(False)
162            self.deskAudVidList.setAllColumnsShowFocus(True)
163            self.deskAudVidList.setObjectName("deskAudVidList")
164            self.deskAudVidList.setSortingEnabled(True)
165            self.deskAudVidList.headerItem().setText(0, self.tr("Host"))
166            self.deskAudVidList.headerItem().setText(1, self.tr("Permission"))
167            self.tabWidget.addTab(
168                self.deskAudVidList,
169                UI.PixmapCache.getIcon("desktopAudioVideoCapture"),
170                self.tr("Desktop Audio && Video"))
171
172        if hasattr(QWebEnginePage, "Notifications"):
173            self.setTabOrder(self.tabWidget, self.notifList)
174            self.setTabOrder(self.notifList, self.geoList)
175        else:
176            self.setTabOrder(self.tabWidget, self.geoList)
177        self.setTabOrder(self.geoList, self.micList)
178        self.setTabOrder(self.micList, self.camList)
179        self.setTabOrder(self.camList, self.micCamList)
180        self.setTabOrder(self.micCamList, self.mouseLockList)
181        if hasattr(QWebEnginePage, "DesktopVideoCapture"):
182            self.setTabOrder(self.mouseLockList, self.deskVidList)
183            self.setTabOrder(self.deskVidList, self.deskAudVidList)
184            self.setTabOrder(self.deskAudVidList, self.removeButton)
185        else:
186            self.setTabOrder(self.mouseLockList, self.removeButton)
187        self.setTabOrder(self.removeButton, self.removeAllButton)
188
189        self.__permissionStrings = {
190            QWebEnginePage.PermissionPolicy.PermissionGrantedByUser:
191                self.tr("Allow"),
192            QWebEnginePage.PermissionPolicy.PermissionDeniedByUser:
193                self.tr("Deny"),
194        }
195
196        self.__permissionsLists = {
197            QWebEnginePage.Feature.Geolocation: self.geoList,
198            QWebEnginePage.Feature.MediaAudioCapture: self.micList,
199            QWebEnginePage.Feature.MediaVideoCapture: self.camList,
200            QWebEnginePage.Feature.MediaAudioVideoCapture: self.micCamList,
201            QWebEnginePage.Feature.MouseLock: self.mouseLockList,
202        }
203        if hasattr(QWebEnginePage, "DesktopVideoCapture"):
204            self.__permissionsLists.update({
205                QWebEnginePage.Feature.DesktopVideoCapture:
206                    self.deskVidList,
207                QWebEnginePage.Feature.DesktopAudioVideoCapture:
208                    self.deskAudVidList,
209            })
210        if hasattr(QWebEnginePage, "Notifications"):
211            self.__permissionsLists[QWebEnginePage.Feature.Notifications] = (
212                self.notifList
213            )
214
215        for feature, permissionsList in self.__permissionsLists.items():
216            for permission in featurePermissions[feature]:
217                for host in featurePermissions[feature][permission]:
218                    itm = QTreeWidgetItem(
219                        permissionsList,
220                        [host, self.__permissionStrings[permission]])
221                    itm.setData(0, Qt.ItemDataRole.UserRole, permission)
222
223        self.__previousCurrent = -1
224        self.tabWidget.currentChanged.connect(self.__currentTabChanged)
225        self.tabWidget.setCurrentIndex(0)
226
227    @pyqtSlot(int)
228    def __currentTabChanged(self, index):
229        """
230        Private slot handling changes of the selected tab.
231
232        @param index index of the current tab
233        @type int
234        """
235        if self.__previousCurrent >= 0:
236            previousList = self.tabWidget.widget(self.__previousCurrent)
237            previousList.itemSelectionChanged.disconnect(
238                self.__itemSelectionChanged)
239
240        self.__updateButtons()
241
242        currentList = self.tabWidget.currentWidget()
243        currentList.itemSelectionChanged.connect(self.__itemSelectionChanged)
244        self.__previousCurrent = index
245
246    def __updateButtons(self):
247        """
248        Private method to update the buttons.
249        """
250        currentList = self.tabWidget.currentWidget()
251        self.removeAllButton.setEnabled(
252            currentList.topLevelItemCount() > 0)
253        self.removeButton.setEnabled(
254            len(currentList.selectedItems()) > 0)
255
256    @pyqtSlot()
257    def __itemSelectionChanged(self):
258        """
259        Private slot handling changes in the current list of selected items.
260        """
261        self.__updateButtons()
262
263    @pyqtSlot()
264    def on_removeButton_clicked(self):
265        """
266        Private slot to remove selected entries.
267        """
268        currentList = self.tabWidget.currentWidget()
269        for itm in currentList.selectedItems():
270            row = currentList.indexOfTopLevelItem(itm)
271            itm = currentList.takeTopLevelItem(row)
272            del itm
273        self.__updateButtons()
274
275    @pyqtSlot()
276    def on_removeAllButton_clicked(self):
277        """
278        Private slot to remove all entries.
279        """
280        currentList = self.tabWidget.currentWidget()
281        while currentList.topLevelItemCount() > 0:
282            itm = currentList.takeTopLevelItem(0)      # __IGNORE_WARNING__
283            del itm
284        self.__updateButtons()
285
286    def getData(self):
287        """
288        Public method to retrieve the dialog contents.
289
290        @return new feature permission settings
291        @rtype dict of dict of list
292        """
293        featurePermissions = {}
294        for feature, permissionsList in self.__permissionsLists.items():
295            featurePermissions[feature] = {
296                QWebEnginePage.PermissionPolicy.PermissionGrantedByUser: [],
297                QWebEnginePage.PermissionPolicy.PermissionDeniedByUser: [],
298            }
299            for row in range(permissionsList.topLevelItemCount()):
300                itm = permissionsList.topLevelItem(row)
301                host = itm.text(0)
302                permission = itm.data(0, Qt.ItemDataRole.UserRole)
303                featurePermissions[feature][permission].append(host)
304
305        return featurePermissions
306