class SubwindowOverlay(QWidget): """Show styles settings sub window.""" def createWindow(self, mainWindow): """Shows an overlay for ingame.""" try: parent = None super().__init__(parent) self.mainWindow = mainWindow self.controller = mainWindow.controller self.view = QWebEngineView(self) layout = QVBoxLayout(self) self.setLayout(layout) layout.addWidget(self.view) file_path = scctool.settings.getResFile('overlay/main.html') local_url = QUrl.fromLocalFile(file_path) self.view.setAttribute(Qt.WA_TransparentForMouseEvents) self.view.setStyleSheet("background:transparent") self.view.load(local_url) except Exception as e: module_logger.exception("message") def closeWindow(self): """Close window.""" self.passEvent = True self.close() def closeEvent(self, event): """Handle close event.""" event.accept()
def capture(self, url, file): view = QWebEngineView() view.setAttribute(QtCore.Qt.WA_DeleteOnClose) view.loadFinished.connect(partial(self.done, view, file)) view.load(QUrl(url)) self.app.exec_()
class _WebEngineRendererHelper(QObject): """ This helper class is doing the real work. It is required to allow WebEngineRenderer.render() to be called "asynchronously" (but always from Qt's GUI thread). """ def __init__(self, parent): """ Copies the properties from the parent (WebEngineRenderer) object, creates the required instances of QWebPage, QWebView and QMainWindow and registers some Slots. """ QObject.__init__(self) # Copy properties from parent for key, value in parent.__dict__.items(): setattr(self, key, value) # Determine Proxy settings proxy = QNetworkProxy(QNetworkProxy.NoProxy) if 'http_proxy' in os.environ: proxy_url = QUrl(os.environ['http_proxy']) if proxy_url.scheme().startswith('http'): protocol = QNetworkProxy.HttpProxy else: protocol = QNetworkProxy.Socks5Proxy proxy = QNetworkProxy(protocol, proxy_url.host(), proxy_url.port(), proxy_url.userName(), proxy_url.password()) # Create and connect required PyQt5 objects self._page = CustomWebPage(logger=self.logger, ignore_alert=self.ignoreAlert, ignore_confirm=self.ignoreConfirm, ignore_prompt=self.ignorePrompt, interrupt_js=self.interruptJavaScript) self._qt_proxy = QNetworkProxy() self._qt_proxy.setApplicationProxy(proxy) self._view = QWebEngineView() self._view.setPage(self._page) _window_flags = Qt.WindowFlags() self._window = QMainWindow(flags=_window_flags) self._window.setCentralWidget(self._view) self._qt_network_access_manager = QNetworkAccessManager() # Import QWebSettings for key, value in self.qWebSettings.items(): self._page.settings().setAttribute(key, value) # Connect required event listeners # assert False, "Not finish" self._page.loadFinished.connect(self._on_load_finished) self._page.loadStarted.connect(self._on_load_started) self._qt_network_access_manager.sslErrors.connect(self._on_ssl_errors) self._qt_network_access_manager.finished.connect(self._on_each_reply) # The way we will use this, it seems to be unnecessary to have Scrollbars enabled self._scroll_area = QAbstractScrollArea() self._scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # Show this widget self._window.show() def __del__(self): """ Clean up Qt5 objects. """ self._window.close() del self._window del self._view del self._page def render(self, res): """ The real worker. Loads the page (_load_page) and awaits the end of the given 'delay'. While it is waiting outstanding QApplication events are processed. After the given delay, the Window or Widget (depends on the value of 'grabWholeWindow' is drawn into a QScreen and post processed (_post_process_image). """ self._load_page(res, self.width, self.height, self.timeout) # Wait for end of timer. In this time, process # other outstanding Qt events. if self.wait > 0: if self.logger: self.logger.debug("Waiting {} seconds ".format(self.wait)) wait_to_time = time.time() + self.wait while time.time() < wait_to_time: if self.app.hasPendingEvents(): self.app.processEvents() if self.renderTransparentBackground: # Another possible drawing solution image = QImage(self._page.viewportSize(), QImage.Format_ARGB32) image.fill(QColor(255, 0, 0, 0).rgba()) # http://ariya.blogspot.com/2009/04/transparent-qwebview-and-qwebpage.html palette = self._view.palette() palette.setBrush(QPalette.Base, Qt.transparent) self._page.setPalette(palette) self._view.setAttribute(Qt.WA_OpaquePaintEvent, False) painter = QPainter(image) painter.setBackgroundMode(Qt.TransparentMode) self._window.render(painter) painter.end() else: if self.grabWholeWindow: # Note that this does not fully ensure that the # window still has the focus when the screen is # grabbed. This might result in a race condition. self._view.activateWindow() qt_screen = self.app.primaryScreen() image = qt_screen.grabWindow(sip.voidptr(0)) else: image = self._window.grab() return self._post_process_image(image) def _load_page(self, res, width, height, timeout): """ This method implements the logic for retrieving and displaying the requested page. """ # This is an event-based application. So we have to wait until # "loadFinished(bool)" raised. cancel_at = time.time() + timeout self.__loading = True self.__loadingResult = False # Default # When "res" is of type tuple, it has two elements where the first # element is the HTML code to render and the second element is a string # setting the base URL for the interpreted HTML code. # When resource is of type str or unicode, it is handled as URL which # shall be loaded if type(res) == tuple: url = res[1] else: url = res if self.encodedUrl: qt_url = QUrl().fromEncoded(url) else: qt_url = QUrl(url) # Set the required cookies, if any self.cookieJar = CookieJar(self.cookies, qt_url) self._qt_network_access_manager.setCookieJar(self.cookieJar) # Load the page if type(res) == tuple: self._page.setHtml(res[0], qt_url) # HTML, baseUrl else: self._page.load(qt_url) while self.__loading: if timeout > 0 and time.time() >= cancel_at: raise RuntimeError("Request timed out on {}".format(res)) while self.app.hasPendingEvents() and self.__loading: self.app.processEvents() if self.logger: self.logger.debug("Processing result") if not self.__loading_result: if self.logger: self.logger.warning("Failed to load {}".format(res)) # Set initial viewport (the size of the "window") size = self._page.contentsSize() if self.logger: self.logger.debug("contentsSize: %s", size) if width > 0: size.setWidth(width) if height > 0: size.setHeight(height) self._window.resize(size.toSize()) def _post_process_image(self, q_image): """ If 'scaleToWidth' or 'scaleToHeight' are set to a value greater than zero this method will scale the image using the method defined in 'scaleRatio'. """ if self.scaleToWidth > 0 or self.scaleToHeight > 0: # Scale this image if self.scaleRatio == 'keep': ratio = Qt.KeepAspectRatio elif self.scaleRatio in ['expand', 'crop']: ratio = Qt.KeepAspectRatioByExpanding else: # 'ignore' ratio = Qt.IgnoreAspectRatio q_image = q_image.scaled(self.scaleToWidth, self.scaleToHeight, ratio, Qt.SmoothTransformation) if self.scaleRatio == 'crop': q_image = q_image.copy(0, 0, self.scaleToWidth, self.scaleToHeight) return q_image @pyqtSlot(QNetworkReply, name='finished') def _on_each_reply(self, reply): """ Logs each requested uri """ self.logger.debug("Received {}".format(reply.url().toString())) # Event for "loadStarted()" signal @pyqtSlot(name='loadStarted') def _on_load_started(self): """ Slot that sets the '__loading' property to true """ if self.logger: self.logger.debug("loading started") self.__loading = True # Event for "loadFinished(bool)" signal @pyqtSlot(bool, name='loadFinished') def _on_load_finished(self, result): """ Slot that sets the '__loading' property to false and stores the result code in '__loading_result'. """ if self.logger: self.logger.debug("loading finished with result %s", result) self.__loading = False self.__loading_result = result # Event for "sslErrors(QNetworkReply *,const QList<QSslError>&)" signal @pyqtSlot('QNetworkReply*', 'QList<QSslError>', name='sslErrors') def _on_ssl_errors(self, reply, errors): """ Slot that writes SSL warnings into the log but ignores them. """ for e in errors: if self.logger: self.logger.warn("SSL: " + e.errorString()) reply.ignoreSslErrors() @property def window(self): return self._window
class Overlay(QtCore.QObject): def __init__(self, up, configFileName, name, url): super().__init__() self.configFileName = configFileName self.parent = up self.app = up.app self.url = url self.size = None self.name = name self.overlay = None self.settings = None self.position = None self.enabled = True self.showtitle = True self.mutedeaf = True self.showtitle = True def load(self): config = ConfigParser(interpolation=None) config.read(self.configFileName) self.posXL = config.getint(self.name, 'xl', fallback=0) self.posXR = config.getint(self.name, 'xr', fallback=200) self.posYT = config.getint(self.name, 'yt', fallback=50) self.posYB = config.getint(self.name, 'yb', fallback=450) self.right = config.getboolean(self.name, 'rightalign', fallback=False) self.mutedeaf = config.getboolean( self.name, 'mutedeaf', fallback=True) self.chatresize = config.getboolean( self.name, 'chatresize', fallback=True) self.screenName = config.get(self.name, 'screen', fallback='None') #self.url = config.get(self.name, 'url', fallback=None) self.enabled = config.getboolean(self.name, 'enabled', fallback=True) self.showtitle = config.getboolean(self.name, 'title', fallback=True) self.hideinactive = config.getboolean( self.name, 'hideinactive', fallback=True) self.chooseScreen() # TODO Check, is there a better logic location for this? if self.enabled: self.showOverlay() def moveOverlay(self): if self.overlay: self.overlay.resize(self.posXR-self.posXL, self.posYB-self.posYT) self.overlay.move(self.posXL + self.screenOffset.left(), self.posYT + self.screenOffset.top()) def on_url(self, url): if self.overlay: self.overlay.load(QtCore.QUrl(url)) self.url = url self.save() self.settings.close() self.settings = None def on_save_position(self, url): self.save() self.position.close() self.position = None @pyqtSlot() def save(self): config = ConfigParser(interpolation=None) config.read(self.configFileName) if not config.has_section(self.name): config.add_section(self.name) config.set(self.name, 'xl', '%d' % (self.posXL)) config.set(self.name, 'xr', '%d' % (self.posXR)) config.set(self.name, 'yt', '%d' % (self.posYT)) config.set(self.name, 'yb', '%d' % (self.posYB)) config.set(self.name, 'rightalign', '%d' % (int(self.right))) config.set(self.name, 'mutedeaf', '%d' % (int(self.mutedeaf))) config.set(self.name, 'chatresize', '%d' % (int(self.chatresize))) config.set(self.name, 'screen', self.screenName) config.set(self.name, 'enabled', '%d' % (int(self.enabled))) config.set(self.name, 'title', '%d' % (int(self.showtitle))) config.set(self.name, 'hideinactive', '%d' % (int(self.hideinactive))) if self.url: config.set(self.name, 'url', self.url) if ',' in self.url: self.parent.reinit() with open(self.configFileName, 'w') as file: config.write(file) @pyqtSlot() def on_click(self): self.runJS( "document.getElementsByClassName('source-url')[0].value;", self.on_url) @pyqtSlot() def skip_stream_button(self): skipIntro = "buttons = document.getElementsByTagName('button');for(i=0;i<buttons.length;i++){if(buttons[i].innerHTML=='Install for OBS'){buttons[i].click()}}" hideLogo = "document.getElementsByClassName('install-logo')[0].style.setProperty('display','none');" resizeContents = "document.getElementsByClassName('content')[0].style.setProperty('top','30px');" resizeHeader = "document.getElementsByClassName('header')[0].style.setProperty('height','35px');" hidePreview = "document.getElementsByClassName('config-link')[0].style.setProperty('height','300px');document.getElementsByClassName('config-link')[0].style.setProperty('overflow','hidden');" hideClose = "document.getElementsByClassName('close')[0].style.setProperty('display','none');" chooseVoice = "for( let button of document.getElementsByTagName('button')){ if(button.getAttribute('value') == 'voice'){ button.click(); } }" chooseChat = "for( let button of document.getElementsByTagName('button')){ if(button.getAttribute('value') == 'chat'){ button.click(); } }" infoListener = "if(typeof console.oldlog === 'undefined'){console.oldlog=console.log;}window.consoleCatchers=[];console.log = function(text,input){if(typeof input !== 'undefined'){window.consoleCatchers.forEach(function(item,index){item(input)})}else{console.oldlog(text);}};" catchGuild = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_GUILD'){window.guilds=input.data.id}})" catchChannel = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_CHANNELS'){window.channels = input.data.channels;}})" self.runJS(skipIntro) self.runJS(hideLogo) self.runJS(resizeContents) self.runJS(hidePreview) self.runJS(resizeHeader) self.runJS(hideClose) self.runJS(infoListener) self.runJS(catchGuild) self.runJS(catchChannel) if self.url: if 'overlay/voice' in self.url: self.runJS(chooseVoice) else: self.runJS(chooseChat) def enableConsoleCatcher(self): if self.overlay: tweak = "if(typeof console.oldlog === 'undefined'){console.oldlog=console.log;}window.consoleCatchers=[];console.log = function(text,input){if(typeof input !== 'undefined'){window.consoleCatchers.forEach(function(item,index){item(input)})}else{console.oldlog(text);}};" self.overlay.page().runJavaScript(tweak) def enableShowVoiceTitle(self): if self.overlay: tweak = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_CHANNEL'){chan=input.data.name;(function() { css = document.getElementById('title-css'); if (css == null) { css = document.createElement('style'); css.type='text/css'; css.id='title-css'; document.head.appendChild(css); } css.innerText='.voice-container:before{content:\"'+chan+'\";background:rgba(30, 33, 36, 0.95);padding:4px 6px;border-radius: 3px;}';})()}})" self.overlay.page().runJavaScript(tweak) def enableHideInactive(self): if self.overlay and self.url: if 'overlay/voice' in self.url: tweak = "document.getElementById('app-mount').style='display:none';window.consoleCatchers.push(function(input){if(input.cmd=='AUTHENTICATE'){console.error(input.data.user);window.iAm=input.data.user.username;}if(input.evt=='VOICE_STATE_CREATE' || input.evt=='VOICE_STATE_UPDATE'){if(input.data.nick.toUpperCase()==window.iAm.toUpperCase()){document.getElementById('app-mount').style='display:block';console.error('Showing '+chan)}}if(input.evt=='VOICE_STATE_DELETE'){if(input.data.nick.toUpperCase()==window.iAm.toUpperCase()){document.getElementById('app-mount').style='display:none';console.error('Hiding '+chan)}}});" self.overlay.page().runJavaScript(tweak) def enableMuteDeaf(self): if self.overlay: tweak = "window.consoleCatchers.push(function(input){if(input.evt == 'VOICE_STATE_UPDATE'){name=input.data.nick;uState = input.data.voice_state;muteicon = '';if(uState.self_mute || uState.mute){muteicon='<img src=\\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAABhMAAAYJQE8CCw1AAAAB3RJTUUH5AUGCx0VMm5EjgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABzSURBVDjLxZIxCsAwCEW/oT1P7z93zZJjeIYMv0sCIaBoodTJDz6/JgJfBslOsns1xYONvK66JCeqAC4ALTz+dJvOo0lu/zS87p2C98IdHlq9Buo5D62h17amScMk78hBWXB/DUdP2fyBaINjJiJy4o94AM8J8ksz/MQjAAAAAElFTkSuQmCC\\' style=\\'height:0.9em;\\'>';}deaficon = '';if(uState.self_deaf || uState.deaf){deaficon='<img src=\\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAABhMAAAYJQE8CCw1AAAAB3RJTUUH5AUGCx077rhJQQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAACNSURBVDjLtZPNCcAgDIUboSs4iXTGLuI2XjpBz87g4fWiENr8iNBAQPR9ef7EbfsjAEQAN4A2UtCcGtyMzFxjwVlyBHAwTRFh52gqHDVnF+6L1XJ/w31cp7YvOX/0xlOJ254qYJ1ZLTAmPWeuDVxARDurfBFR8jovMLEKWxG6c1qB55pEuQOpE8vKz30AhEdNuXK0IugAAAAASUVORK5CYII=\\' style=\\'height:0.9em;\\'>';}spans = document.getElementsByTagName('span');for(i=0;i<spans.length;i++){if(spans[i].innerHTML.startsWith(name)){text = name + muteicon + deaficon;spans[i].innerHTML = text;}}}});" self.overlay.page().runJavaScript(tweak) def runJS(self, string, retFunc=None): if retFunc: self.settingWebView.page().runJavaScript(string, retFunc) else: self.settingWebView.page().runJavaScript(string) def applyTweaks(self): self.enableConsoleCatcher() if self.right: self.addCSS( 'cssrightalign', 'li.voice-state{ direction:rtl; }.avatar{ float:right !important; }.user{ display:flex; }.voice-container{margin-top:30px;}.voice-container:before{position:fixed;right:0px;top:0px;}') else: self.delCSS('cssrightalign') if self.showtitle: self.enableShowVoiceTitle() if self.mutedeaf: self.enableMuteDeaf() if self.hideinactive: self.enableHideInactive() if self.chatresize: self.addCSS( 'cssflexybox', 'div.chat-container { width: 100%; height: 100%; top: 0; left: 0; position: fixed; display: flex; flex-direction: column; } div.chat-container > .messages { box-sizing: border-box; width: 100%; flex: 1; }') else: self.delCSS('cssflexybox') def addCSS(self, name, css): if self.overlay: js = '(function() { css = document.getElementById(\'%s\'); if (css == null) { css = document.createElement(\'style\'); css.type=\'text/css\'; css.id=\'%s\'; document.head.appendChild(css); } css.innerText=\'%s\';})()' % (name, name, css) self.overlay.page().runJavaScript(js) def delCSS(self, name): if self.overlay: js = "(function() { css = document.getElementById('%s'); if(css!=null){ css.parentNode.removeChild(css);} })()" % ( name) self.overlay.page().runJavaScript(js) @pyqtSlot() def toggleEnabled(self, button=None): self.enabled = self.enabledButton.isChecked() if self.enabled: self.showOverlay() else: self.hideOverlay() @pyqtSlot() def toggleTitle(self, button=None): self.showtitle = self.showTitle.isChecked() if self.showtitle: self.enableShowVoiceTitle() @pyqtSlot() def toggleMuteDeaf(self, button=None): self.mutedeaf = self.muteDeaf.isChecked() if self.muteDeaf.isChecked(): self.enableMuteDeaf() @pyqtSlot() def toggleHideInactive(self, button=None): self.hideinactive = self.hideInactive.isChecked() if self.hideinactive: self.enableHideInactive() @pyqtSlot() def toggleChatResize(self, button=None): self.chatresize = self.chatResize.isChecked() self.applyTweaks() @pyqtSlot() def toggleRightAlign(self, button=None): self.right = self.rightAlign.isChecked() self.applyTweaks() @pyqtSlot() def changeValueFL(self): self.posXL = self.settingsDistanceFromLeft.value() self.moveOverlay() @pyqtSlot() def changeValueFR(self): self.posXR = self.settingsDistanceFromRight.value() self.moveOverlay() @pyqtSlot() def changeValueFT(self): self.posYT = self.settingsDistanceFromTop.value() self.moveOverlay() @pyqtSlot() def changeValueFB(self): self.posYB = self.settingsDistanceFromBottom.value() self.moveOverlay() def fillPositionWindowOptions(self): self.settingsDistanceFromLeft.valueChanged[int].connect( self.changeValueFL) self.settingsDistanceFromLeft.setMaximum(self.size.width()) self.settingsDistanceFromLeft.setValue(self.posXL) self.settingsDistanceFromRight.valueChanged[int].connect( self.changeValueFR) self.settingsDistanceFromRight.setMaximum(self.size.width()) self.settingsDistanceFromRight.setValue(self.posXR) self.settingsDistanceFromTop.valueChanged[int].connect( self.changeValueFT) self.settingsDistanceFromTop.setMaximum(self.size.height()) self.settingsDistanceFromTop.setInvertedAppearance(True) self.settingsDistanceFromTop.setValue(self.posYT) self.settingsDistanceFromBottom.valueChanged[int].connect( self.changeValueFB) self.settingsDistanceFromBottom.setMaximum(self.size.height()) self.settingsDistanceFromBottom.setInvertedAppearance(True) self.settingsDistanceFromBottom.setValue(self.posYB) def populateScreenList(self): self.ignoreScreenComboBox = True screenList = self.app.screens() self.settingsScreen.clear() for i, s in enumerate(screenList): self.settingsScreen.addItem(s.name()) if s.name() == self.screenName: self.settingsScreen.setCurrentIndex(i) self.ignoreScreenComboBox = False self.chooseScreen() def changeScreen(self, index): if not self.ignoreScreenComboBox: self.screenName = self.settingsScreen.currentText() self.chooseScreen() def chooseScreen(self): screen = None screenList = self.app.screens() logger.debug("Discovered screens: %r", [s.name() for s in screenList]) for s in screenList: if s.name() == self.screenName: screen = s logger.debug("Chose screen %s", screen.name()) break # The chosen screen is not in this list. Drop to primary else: screen = self.app.primaryScreen() logger.warning( "Chose screen %r as fallback because %r could not be matched", screen.name(), self.screenName) # Fill Info! self.size = screen.size() self.screenName = s.name() self.screenOffset = screen.availableGeometry() if self.position: self.settingsAspectRatio.updateScreen( self.size.width(), self.size.height()) self.fillPositionWindowOptions() self.screenShot(screen) self.moveOverlay() def showPosition(self): if self.position is not None: self.position.show() else: # Positional Settings Window self.position = QtWidgets.QWidget() self.position.setWindowTitle('Overlay %s Position' % (self.name)) self.positionbox = QtWidgets.QVBoxLayout() # Use a grid to lay out screen & sliders self.settingsGridWidget = QtWidgets.QWidget() self.settingsGrid = QtWidgets.QGridLayout() # Use the custom Aspect widget to keep the whole thing looking # as close to the user experience as possible self.settingsAspectRatio = AspectRatioWidget( self.settingsGridWidget) # Grid contents self.settingsPreview = ResizingImage() self.settingsPreview.setMinimumSize(1, 1) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.settingsPreview.setSizePolicy(sizePolicy) self.settingsDistanceFromLeft = QtWidgets.QSlider( QtCore.Qt.Horizontal) self.settingsDistanceFromRight = QtWidgets.QSlider( QtCore.Qt.Horizontal) self.settingsDistanceFromTop = QtWidgets.QSlider( QtCore.Qt.Vertical) self.settingsDistanceFromBottom = QtWidgets.QSlider( QtCore.Qt.Vertical) # Screen chooser self.settingsScreen = QtWidgets.QComboBox() # self.position.setMinimumSize(600,600) # Save button self.settingSave = QtWidgets.QPushButton("Save") # Fill Screens, Choose the screen if config is set self.populateScreenList() self.settingSave.clicked.connect(self.on_save_position) self.settingsScreen.currentIndexChanged.connect(self.changeScreen) self.settingsGrid.addWidget(self.settingsPreview, 0, 0) self.settingsGrid.addWidget(self.settingsDistanceFromLeft, 1, 0) self.settingsGrid.addWidget(self.settingsDistanceFromRight, 2, 0) self.settingsGrid.addWidget(self.settingsDistanceFromTop, 0, 1) self.settingsGrid.addWidget(self.settingsDistanceFromBottom, 0, 2) self.settingsGridWidget.setLayout(self.settingsGrid) self.positionbox.addWidget(self.settingsScreen) self.positionbox.addWidget(self.settingsAspectRatio) self.position.setLayout(self.positionbox) self.positionbox.addWidget(self.settingSave) self.fillPositionWindowOptions() self.position.show() def showSettings(self): if self.settings is not None: self.settings.show() else: self.settings = QtWidgets.QWidget() self.settings.setWindowTitle('Overlay %s Layout' % (self.name)) self.settingsbox = QtWidgets.QVBoxLayout() self.settingWebView = QWebEngineView() self.rightAlign = QtWidgets.QCheckBox("Right Align") self.muteDeaf = QtWidgets.QCheckBox("Show mute and deafen") self.chatResize = QtWidgets.QCheckBox("Large chat box") self.showTitle = QtWidgets.QCheckBox("Show room title") self.hideInactive = QtWidgets.QCheckBox( "Hide voice channel when inactive") self.enabledButton = QtWidgets.QCheckBox("Enabled") self.settingTakeUrl = QtWidgets.QPushButton("Use this Room") self.settingTakeAllUrl = QtWidgets.QPushButton("Use all Rooms") self.settings.setMinimumSize(400, 400) self.settingTakeUrl.clicked.connect(self.on_click) self.settingTakeAllUrl.clicked.connect(self.getAllRooms) self.settingWebView.loadFinished.connect(self.skip_stream_button) self.rightAlign.stateChanged.connect(self.toggleRightAlign) self.rightAlign.setChecked(self.right) self.muteDeaf.stateChanged.connect(self.toggleMuteDeaf) self.muteDeaf.setChecked(self.mutedeaf) self.showTitle.stateChanged.connect(self.toggleTitle) self.showTitle.setChecked(self.showtitle) self.hideInactive.stateChanged.connect(self.toggleHideInactive) self.hideInactive.setChecked(self.hideinactive) self.enabledButton.stateChanged.connect(self.toggleEnabled) self.enabledButton.setChecked(self.enabled) self.chatResize.stateChanged.connect(self.toggleChatResize) self.chatResize.setChecked(self.chatresize) self.settingWebView.load(QtCore.QUrl( "https://streamkit.discord.com/overlay")) self.settingsbox.addWidget(self.settingWebView) self.settingsbox.addWidget(self.rightAlign) self.settingsbox.addWidget(self.muteDeaf) self.settingsbox.addWidget(self.chatResize) self.settingsbox.addWidget(self.showTitle) self.settingsbox.addWidget(self.hideInactive) self.settingsbox.addWidget(self.enabledButton) self.settingsbox.addWidget(self.settingTakeUrl) self.settingsbox.addWidget(self.settingTakeAllUrl) self.settings.setLayout(self.settingsbox) self.settings.show() def screenShot(self, screen): screenshot = screen.grabWindow(0) self.settingsPreview.setImage(screenshot) self.settingsPreview.setContentsMargins(0, 0, 0, 0) def showOverlay(self): if self.overlay: return self.overlay = QWebEngineView() self.overlay.page().setBackgroundColor(QtCore.Qt.transparent) self.overlay.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) self.overlay.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True) self.overlay.setWindowFlags( QtCore.Qt.X11BypassWindowManagerHint | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowDoesNotAcceptFocus | QtCore.Qt.NoDropShadowWindowHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMinimizeButtonHint ) self.overlay.loadFinished.connect(self.applyTweaks) self.overlay.load(QtCore.QUrl(self.url)) self.overlay.setStyleSheet("background:transparent;") self.overlay.show() self.moveOverlay() def hideOverlay(self): if self.overlay: self.overlay.close() self.overlay = None def delete(self): self.hideOverlay() if self.settings: self.settings.close() if self.position: self.position.close() self.overlay = None def getAllRooms(self): getChannel = "[window.channels, window.guilds]" self.runJS(getChannel, self.gotAllRooms) def gotAllRooms(self, message): sep = '' url = '' for chan in message[0]: if chan['type'] == 2: url += sep+"https://streamkit.discord.com/overlay/voice/%s/%s?icon=true&online=true&logo=white&text_color=%%23ffffff&text_size=14&text_outline_color=%%23000000&text_outline_size=0&text_shadow_color=%%23000000&text_shadow_size=0&bg_color=%%231e2124&bg_opacity=0.95&bg_shadow_color=%%23000000&bg_shadow_size=0&limit_speaking=false&small_avatars=false&hide_names=false&fade_chat=0" % (message[1], chan[ 'id']) sep = ',' if self.overlay: self.overlay.load(QtCore.QUrl(url)) self.url = url self.save() self.settings.close() self.settings = None
class ChromiumBrowserTab(BrowserTab): def __init__(self, render_options, verbosity): super().__init__(render_options, verbosity) self.profile = QWebEngineProfile() # don't share cookies self.web_page = ChromiumWebPage(self.profile) self.web_view = QWebEngineView() self.web_view.setPage(self.web_page) self.web_view.setAttribute(Qt.WA_DeleteOnClose, True) # TODO: is it ok? :) # self.web_view.setAttribute(Qt.WA_DontShowOnScreen, True) # FIXME: required for screenshots? # Also, without .show() in JS window.innerWidth/innerHeight are zeros self.web_view.show() self._setup_webpage_events() self._set_default_webpage_options() self._html_d = None # ensure that default window size is not 640x480. self.set_viewport(defaults.VIEWPORT_SIZE) def _setup_webpage_events(self): self._load_finished = WrappedSignal(self.web_view.loadFinished) self._render_terminated = WrappedSignal(self.web_view.renderProcessTerminated) self.web_view.renderProcessTerminated.connect(self._on_render_terminated) self.web_view.loadFinished.connect(self._on_load_finished) # main_frame.urlChanged.connect(self._on_url_changed) # main_frame.javaScriptWindowObjectCleared.connect( # self._on_javascript_window_object_cleared) # self.logger.add_web_page(self.web_page) def _set_default_webpage_options(self): """ Set QWebPage options. TODO: allow to customize defaults. """ settings = self.web_page.settings() settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True) settings.setAttribute(QWebEngineSettings.JavascriptCanOpenWindows, False) settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) # TODO: requires Qt 5.10 # settings.setAttribute(QWebEngineSettings.ShowScrollBars, False) # TODO # if self.visible: # settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # TODO: options # self.set_js_enabled(True) # self.set_plugins_enabled(defaults.PLUGINS_ENABLED) # self.set_request_body_enabled(defaults.REQUEST_BODY_ENABLED) # self.set_response_body_enabled(defaults.RESPONSE_BODY_ENABLED) # self.set_indexeddb_enabled(defaults.INDEXEDDB_ENABLED) # self.set_webgl_enabled(defaults.WEBGL_ENABLED) # self.set_html5_media_enabled(defaults.HTML5_MEDIA_ENABLED) # self.set_media_source_enabled(defaults.MEDIA_SOURCE_ENABLED) def go(self, url, callback, errback): callback_id = self._load_finished.connect( self._on_content_ready, callback=callback, errback=errback, ) self.logger.log("callback %s is connected to loadFinished" % callback_id, min_level=3) self.web_view.load(QUrl(url)) @skip_if_closing def _on_content_ready(self, ok, callback, errback, callback_id): """ This method is called when a QWebEnginePage finishes loading. """ self.logger.log("loadFinished: disconnecting callback %s" % callback_id, min_level=3) self._load_finished.disconnect(callback_id) if ok: callback() else: error_info = RenderErrorInfo( type='Unknown', code=0, text="loadFinished ok=False", url=self.web_view.url().toString() ) errback(error_info) def _on_load_finished(self, ok): self.logger.log("loadFinished, ok=%s" % ok, min_level=2) def _on_render_terminated(self, status, code): status_details = RenderProcessTerminationStatus.get(status, 'unknown') self.logger.log("renderProcessTerminated: %s (%s), exit_code=%s" % ( status, status_details, code), min_level=1) def html(self): """ Return HTML of the current main frame """ self.logger.log("getting HTML", min_level=2) if self._html_d is not None: self.logger.log("HTML is already requested", min_level=1) return self._html_d self._html_d = defer.Deferred() self.web_view.page().toHtml(self._on_html_ready) return self._html_d def _on_html_ready(self, html): self.logger.log("HTML ready", min_level=2) self._html_d.callback(html) self._html_d = None def png(self, width=None, height=None, b64=False, render_all=False, scale_method=None, region=None): """ Return screenshot in PNG format """ # FIXME: move to base class self.logger.log( "Getting PNG: width=%s, height=%s, " "render_all=%s, scale_method=%s, region=%s" % (width, height, render_all, scale_method, region), min_level=2) if render_all: raise ValueError("render_all=True is not supported yet") image = self._get_image('PNG', width, height, render_all, scale_method, region=region) result = image.to_png() if b64: result = base64.b64encode(result).decode('utf-8') # self.store_har_timing("_onPngRendered") return result def jpeg(self, width=None, height=None, b64=False, render_all=False, scale_method=None, quality=None, region=None): """ Return screenshot in JPEG format. """ # FIXME: move to base class self.logger.log( "Getting JPEG: width=%s, height=%s, " "render_all=%s, scale_method=%s, quality=%s, region=%s" % (width, height, render_all, scale_method, quality, region), min_level=2) if render_all: raise ValueError("render_all=True is not supported yet") image = self._get_image('JPEG', width, height, render_all, scale_method, region=region) result = image.to_jpeg(quality=quality) if b64: result = base64.b64encode(result).decode('utf-8') # self.store_har_timing("_onJpegRendered") return result def _get_image(self, image_format, width, height, render_all, scale_method, region): renderer = QtChromiumScreenshotRenderer( self.web_page, self.logger, image_format, width=width, height=height, scale_method=scale_method, region=region) return renderer.render_qwebpage() def set_viewport(self, size, raise_if_empty=False): """ Set viewport size. If size is "full" viewport size is detected automatically. If can also be "<width>x<height>". FIXME: Currently the implementation just resizes the window, which causes Splash to crash on large sizes(?). Actully it is not changing the viewport. XXX: As an effect, this function changes window.outerWidth/outerHeight, while in Webkit implementation window.innerWidth/innerHeight is changed. """ if size == 'full': size = self.web_page.contentsSize() self.logger.log("Contents size: %s" % size, min_level=2) if size.isEmpty(): if raise_if_empty: raise RuntimeError("Cannot detect viewport size") else: size = defaults.VIEWPORT_SIZE self.logger.log("Viewport is empty, falling back to: %s" % size) if not isinstance(size, (QSize, QSizeF)): validate_size_str(size) size = parse_size(size) w, h = int(size.width()), int(size.height()) # XXX: it was crashing with large windows, but then the problem # seemed to go away. Need to keep an eye on it. # # FIXME: don't resize the window? # # FIXME: figure out exact limits # MAX_WIDTH = 1280 # MAX_HEIGHT = 1920 # # if w > MAX_WIDTH: # raise RuntimeError("Width {} > {} is currently prohibited".format( # w, MAX_WIDTH # )) # # if h > MAX_HEIGHT: # raise RuntimeError("Height {} > {} is currently prohibited".format( # h, MAX_HEIGHT # )) self.web_view.resize(w, h) # self._force_relayout() self.logger.log("viewport size is set to %sx%s" % (w, h), min_level=2) self.logger.log("real viewport size: %s" % self.web_view.size(), min_level=2) return w, h def stop_loading(self): self.logger.log("stop_loading", min_level=2) self.web_view.stop() @skip_if_closing def close(self): """ Destroy this tab """ super().close() self.web_view.stop() self.web_view.close() self.web_page.deleteLater() self.web_view.deleteLater()
class PageThumbnailer(QObject): """ Class implementing a thumbnail creator for web sites. @signal thumbnailCreated(QPixmap) emitted after the thumbnail has been created """ thumbnailCreated = pyqtSignal(QPixmap) def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(PageThumbnailer, self).__init__(parent) self.__size = QSize(231, 130) self.__loadTitle = False self.__title = "" self.__url = QUrl() self.__view = QWebEngineView() self.__view.setAttribute(Qt.WA_DontShowOnScreen) self.__view.resize(1920, 1080) self.__view.show() def setSize(self, size): """ Public method to set the size of the image. @param size size of the image (QSize) """ if size.isValid(): self.__size = QSize(size) def setUrl(self, url): """ Public method to set the URL of the site to be thumbnailed. @param url URL of the web site (QUrl) """ if url.isValid(): self.__url = QUrl(url) def url(self): """ Public method to get the URL of the thumbnail. @return URL of the thumbnail (QUrl) """ return QUrl(self.__url) def loadTitle(self): """ Public method to check, if the title is loaded from the web site. @return flag indicating, that the title is loaded (boolean) """ return self.__loadTitle def setLoadTitle(self, load): """ Public method to set a flag indicating to load the title from the web site. @param load flag indicating to load the title (boolean) """ self.__loadTitle = load def title(self): """ Public method to get the title of the thumbnail. @return title of the thumbnail (string) """ if self.__title: title = self.__title else: title = self.__url.host() if not title: title = self.__url.toString() return title def start(self): """ Public method to start the thumbnailing action. """ self.__view.loadFinished.connect(self.__createThumbnail) self.__view.load(self.__url) def __createThumbnail(self, status): """ Private slot creating the thumbnail of the web site. @param status flag indicating a successful load of the web site (boolean) """ if not status: self.thumbnailCreated.emit(QPixmap()) return QTimer.singleShot(1000, self.__grabThumbnail) def __grabThumbnail(self): """ Private slot to grab the thumbnail image from the view. """ self.__title = self.__view.title() image = QImage(self.__view.size(), QImage.Format_ARGB32) painter = QPainter(image) self.__view.render(painter) painter.end() scaledImage = image.scaled(self.__size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) self.thumbnailCreated.emit(QPixmap.fromImage(scaledImage))