Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
 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_()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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))