def __init__(self, diskCacheEnabled, parent = None): QNetworkAccessManager.__init__(self, parent) self.finished.connect(self.handleFinished) if diskCacheEnabled == 'yes': m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) self.setCache(m_networkDiskCache)
def __init__(self, directory=None, maxSize=104857600, parent=None): QNetworkDiskCache.__init__(self, parent=parent) if directory is None: directory = str(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) self.setMaximumCacheSize(maxSize) self.setCacheDirectory(directory)
def __init__(self, diskCacheEnabled, ignoreSslErrors, parent=None): QNetworkAccessManager.__init__(self, parent) self.m_ignoreSslErrors = ignoreSslErrors if parent.m_verbose: self.finished.connect(self.handleFinished) if diskCacheEnabled == 'yes': m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) self.setCache(m_networkDiskCache) do_action('NetworkAccessManagerInit', Bunch(locals()))
def __init__(self, parent=None): QObject.__init__(self, parent=None) assert QThread.currentThread() is QApplication.instance().thread() netmanager = self._NETMANAGER_REF and self._NETMANAGER_REF() if netmanager is None: netmanager = QNetworkAccessManager() cache = QNetworkDiskCache() cache.setCacheDirectory( os.path.join(settings.widget_settings_dir(), __name__ + ".ImageLoader.Cache") ) netmanager.setCache(cache) ImageLoader._NETMANAGER_REF = weakref.ref(netmanager) self._netmanager = netmanager
def __init__(self, parent=None): QObject.__init__(self, parent) assert QThread.currentThread() is QApplication.instance().thread() netmanager = self._NETMANAGER_REF and self._NETMANAGER_REF() if netmanager is None: netmanager = QNetworkAccessManager() cache = QNetworkDiskCache() cache.setCacheDirectory( os.path.join(settings.widget_settings_dir(), __name__ + ".ImageLoader.Cache") ) netmanager.setCache(cache) ImageLoader._NETMANAGER_REF = weakref.ref(netmanager) self._netmanager = netmanager
def __init__(self, diskCacheEnabled, ignoreSslErrors, parent=None): QNetworkAccessManager.__init__(self, parent) self.m_ignoreSslErrors = ignoreSslErrors self.m_idCounter = 0 self.m_ids = {} self.m_started = [] self.finished.connect(self.handleFinished) if diskCacheEnabled: m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) self.setCache(m_networkDiskCache) do_action('NetworkAccessManagerInit')
def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), cache_dir='/tmp/ghost.py'): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a ghost run oustside ' +\ 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) self.page = GhostWebPage(Ghost._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) self.set_viewport_size(*viewport_size) self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show()
def construct(path=defaults.CACHE_PATH, size=defaults.CACHE_SIZE): log.msg("Initializing cache on %s (maxsize: %d Mb)" % (path, size)) cache = QNetworkDiskCache() cache.setCacheDirectory(path) cache.setMaximumCacheSize(size * 1024**2) cache.cacheSize() # forces immediate initialization return cache
def __init__(self, *args, **kwargs): cache_dir = kwargs.pop("cache_dir", "/tmp/ghost.py") cache_size = kwargs.pop("cache_size", 0) self._prevent_download = kwargs.pop("prevent_download", []) super(NetworkAccessManager, self).__init__(*args, **kwargs) if cache_size > 0: cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 1024 * 1024) self.setCache(cache) # Manages the authentication for the proxy self.proxyAuthenticationRequired.connect(self._authenticateProxy) self.authenticationRequired.connect(self._authenticate) # Handles redirects self.finished.connect(self._replyFinished)
def __init__(self, diskCacheEnabled, ignoreSslErrors, parent=None): QNetworkAccessManager.__init__(self, parent) self.m_ignoreSslErrors = ignoreSslErrors self.m_idCounter = 0 self.m_ids = {} self.m_started = [] self.finished.connect(self.handleFinished) if diskCacheEnabled: m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory( QDesktopServices.storageLocation( QDesktopServices.CacheLocation)) self.setCache(m_networkDiskCache) do_action('NetworkAccessManagerInit')
def __init__(self, proxy, allowed_media, allowed_regex, cache_size=100, cache_dir='.webkit_cache'): """ See JQueryBrowser for details of arguments cache_size is the maximum size of the webkit cache (MB) """ QNetworkAccessManager.__init__(self) # initialize the manager cache #QDesktopServices.storageLocation(QDesktopServices.CacheLocation) cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 1024 * 1024) # need to convert cache value to bytes self.setCache(cache) self.allowed_regex = allowed_regex # allowed content extensions self.banned_extensions = common.MEDIA_EXTENSIONS for ext in allowed_media: if ext in self.banned_extensions: self.banned_extensions.remove(ext) # and proxy self.setProxy(proxy)
def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # We don't need History QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, True) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug)
def prepare(self, metaData): """ Public method to prepare the disk cache file. @param metaData meta data for a URL (QNetworkCacheMetaData) @return reference to the IO device (QIODevice) """ if QWebSettings.globalSettings().testAttribute( QWebSettings.PrivateBrowsingEnabled): return None return QNetworkDiskCache.prepare(self, metaData)
def __init__(self, proxy, allowed_extensions, cache_size=100, cache_dir='.webkit_cache'): """ proxy is a QNetworkProxy allowed_extensions is a list of extensions to allow cache_size is the maximum size of the cache (MB) """ QNetworkAccessManager.__init__(self) # initialize the manager cache cache = QNetworkDiskCache() #QDesktopServices.storageLocation(QDesktopServices.CacheLocation) cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 1024 * 1024) # need to convert cache value to bytes self.setCache(cache) # allowed content extensions self.banned_extensions = common.MEDIA_EXTENSIONS for ext in allowed_extensions: if ext in self.banned_extensions: self.banned_extensions.remove(ext) # and proxy if proxy: self.setProxy(proxy)
def __init__(self, parent, args): super(NetworkAccessManager, self).__init__(parent) self.m_userName = self.m_password = '' self.m_ignoreSslErrors = args.ignore_ssl_errors self.m_idCounter = 0 self.m_ids = {} self.m_started = [] if args.cookies_file: self.setCookieJar(CookieJar(self, args.cookies_file)) if args.disk_cache: m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory( QDesktopServices.storageLocation( QDesktopServices.CacheLocation)) if args.max_disk_cache_size > 0: m_networkDiskCache.setMaximumCacheSize( args.max_disk_cache_size * 1024) self.setCache(m_networkDiskCache) self.authenticationRequired.connect(self.provideAuthentication) self.finished.connect(self.handleFinished) do_action('NetworkAccessManagerInit')
def _fetch_inventory(self, url): cache_dir = config.cache_dir() cache_dir = os.path.join(cache_dir, "help", type(self).__qualname__) try: os.makedirs(cache_dir) except OSError: pass url = QUrl(self.inventory) if not url.isLocalFile(): # fetch and cache the inventory file. manager = QNetworkAccessManager(self) cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) manager.setCache(cache) req = QNetworkRequest(url) self._reply = manager.get(req) manager.finished.connect(self._on_finished) else: self._load_inventory(open(str(url.toLocalFile()), "rb"))
def _fetch_inventory(self): cache_dir = config.cache_dir() cache_dir = os.path.join(cache_dir, "help", "intersphinx") try: os.makedirs(cache_dir) except OSError: pass url = QUrl(self.inventory) if not self.islocal: # fetch and cache the inventory file manager = QNetworkAccessManager(self) cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) manager.setCache(cache) req = QNetworkRequest(url) self._reply = manager.get(req) manager.finished.connect(self._on_finished) else: self._load_inventory(open(unicode(url.toLocalFile()), "rb"))
def __init__(self, parent, args): super(NetworkAccessManager, self).__init__(parent) self.m_userName = self.m_password = '' self.m_ignoreSslErrors = args.ignore_ssl_errors self.m_idCounter = 0 self.m_ids = {} self.m_started = [] if args.cookies_file: self.setCookieJar(CookieJar(self, args.cookies_file)) if args.disk_cache: m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) if args.max_disk_cache_size > 0: m_networkDiskCache.setMaximumCacheSize(args.max_disk_cache_size * 1024) self.setCache(m_networkDiskCache) self.authenticationRequired.connect(self.provideAuthentication) self.finished.connect(self.handleFinished) do_action('NetworkAccessManagerInit')
def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # We don't want Flash (it causes a lot of trouble in some distros) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False) # We don't need Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute( QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug)
def __init__(self, parent, auth, cookieFile, diskCacheEnabled, ignoreSslErrors): QNetworkAccessManager.__init__(self, parent) self.m_ignoreSslErrors = ignoreSslErrors self.m_idCounter = 0 self.m_ids = {} self.m_started = [] self.finished.connect(self.handleFinished) if auth: self.m_authUser = auth[0] self.m_authPass = auth[1] self.authenticationRequired.connect(self.provideAuthentication) if cookieFile: self.setCookieJar(CookieJar(self, cookieFile)) if diskCacheEnabled: m_networkDiskCache = QNetworkDiskCache() m_networkDiskCache.setCacheDirectory(QDesktopServices.storageLocation(QDesktopServices.CacheLocation)) self.setCache(m_networkDiskCache) do_action('NetworkAccessManagerInit')
def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # We don't need History QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, True) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug)
def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # We don't want Flash (it causes a lot of trouble in some distros) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False) # We don't need Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug)
def __init__(self, proxy, forbidden_extensions, allowed_regex, cache_size=100, cache_dir='.webkit_cache'): """ See WebkitBrowser for details of arguments cache_size: the maximum size of the webkit cache (MB) """ QNetworkAccessManager.__init__(self) # and proxy self.setProxy(proxy) # initialize the manager cache QDesktopServices.storageLocation(QDesktopServices.CacheLocation) cache = QNetworkDiskCache() cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 1024 * 1024) # need to convert cache value to bytes self.setCache(cache) self.allowed_regex = allowed_regex self.forbidden_extensions = forbidden_extensions
def __init__(self, *args, **kwargs): cache_dir = kwargs.pop("cache_dir", "/tmp/ghost.py") cache_size = kwargs.pop("cache_size", 0) self._prevent_download = kwargs.pop("prevent_download", []) super(NetworkAccessManager, self).__init__(*args, **kwargs) if cache_size > 0: cache = QNetworkDiskCache(self) cache.setCacheDirectory(cache_dir) cache.setMaximumCacheSize(cache_size * 1024 * 1024) self.setCache(cache) # Manages the authentication for the proxy self.proxyAuthenticationRequired.connect(self._authenticateProxy) self.authenticationRequired.connect(self._authenticate) # Handles redirects self.finished.connect(self._replyFinished)
class ScudCloud(QtGui.QMainWindow): forceClose = False messages = 0 speller = Speller() title = 'ScudCloud' def __init__(self, debug=False, parent=None, minimized=None, urgent_hint=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.debug = debug self.minimized = minimized self.urgent_hint = urgent_hint self.setWindowTitle(self.title) self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.notifier.enabled = self.settings.value('Notifications', defaultValue=True, type=bool) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: if isinstance(self.identifier, str): self.domains = self.identifier.split(",") else: self.domains = self.identifier self.startURL = self.normalize(self.domains[0]) else: self.domains = [] self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(self.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') self.tickler = QTimer(self) self.tickler.setInterval(1800000) # Watch for ScreenLock events if DBusQtMainLoop is not None: DBusQtMainLoop(set_as_default=True) sessionBus = dbus.SessionBus() # Ubuntu 12.04 and other distros sessionBus.add_match_string( "type='signal',interface='org.gnome.ScreenSaver'") # Ubuntu 14.04 and above sessionBus.add_match_string( "type='signal',interface='com.ubuntu.Upstart0_6'") sessionBus.add_message_filter(self.screenListener) self.tickler.timeout.connect(self.sendTickle) # If dbus is not present, tickler timer will act like a blocker to not send tickle too often else: self.tickler.setSingleShot(True) self.tickler.start() def screenListener(self, bus, message): event = message.get_member() # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above if event == "ActiveChanged" or event == "EventEmitted": arg = message.get_args_list()[0] # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above if (arg == True or arg == "desktop-lock") and self.tickler.isActive(): self.tickler.stop() elif (arg == False or arg == "desktop-unlock") and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def sendTickle(self): for i in range(0, self.stackedWidget.count()): self.stackedWidget.widget(i).sendTickle() def addWrapper(self, url): webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.page().networkAccessManager().setCache(self.diskCache) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # We don't want Flash (it causes a lot of trouble in some distros) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False) # We don't need Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute( QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "False") else: self.settings.setValue("Menu", "True") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addTeam(self): self.switchTo(Resources.SIGNIN_URL) def addMenu(self): # We'll register the webpage shorcuts with the window too (Fixes #338) undo = self.current().pageAction(QtWebKit.QWebPage.Undo) redo = self.current().pageAction(QtWebKit.QWebPage.Redo) cut = self.current().pageAction(QtWebKit.QWebPage.Cut) copy = self.current().pageAction(QtWebKit.QWebPage.Copy) paste = self.current().pageAction(QtWebKit.QWebPage.Paste) back = self.current().pageAction(QtWebKit.QWebPage.Back) forward = self.current().pageAction(QtWebKit.QWebPage.Forward) reload = self.current().pageAction(QtWebKit.QWebPage.Reload) self.menus = { "file": { "preferences": self.createAction("Preferences", lambda: self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda: self.addTeam()), "signout": self.createAction("Signout", lambda: self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.createAction( undo.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Undo), undo.shortcut()), "redo": self.createAction( redo.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Redo), redo.shortcut()), "cut": self.createAction( cut.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Cut), cut.shortcut()), "copy": self.createAction( copy.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Copy), copy.shortcut()), "paste": self.createAction( paste.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Paste), paste.shortcut()), "back": self.createAction( back.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Back), back.shortcut()), "forward": self.createAction( forward.text(), lambda: self.current().page() .triggerAction(QtWebKit.QWebPage.Forward), forward.shortcut()), "reload": self.createAction( reload.text(), lambda: self.current().page().triggerAction( QtWebKit.QWebPage.Reload), reload.shortcut()), }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12) }, "help": { "help": self.createAction("Help and Feedback", lambda: self.current().help(), QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", lambda: self.current().helpCenter()), "about": self.createAction("About", lambda: self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["back"]) editMenu.addAction(self.menus["edit"]["forward"]) editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def normalize(self, url): if url.endswith(".slack.com"): url += "/" elif not url.endswith(".slack.com/"): url = "https://" + url + ".slack.com/" return url def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if len(self.domains) == 0: self.domains.append(teams[0]['team_url']) team_list = [t['team_url'] for t in teams] for t in teams: for i in range(0, len(self.domains)): self.domains[i] = self.normalize(self.domains[i]) # When team_icon is missing, the team already exists (Fixes #381, #391) if 'team_icon' in t: if self.domains[i] in team_list: add = next(item for item in teams if item['team_url'] == self.domains[i]) if 'team_icon' in add: self.leftPane.addTeam(add['id'], add['team_name'], add['team_url'], add['team_icon']['image_44'], add == teams[0]) # Adding new teams and saving loading positions if t['team_url'] not in self.domains: self.leftPane.addTeam( t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0]) self.domains.append(t['team_url']) self.settings.setValue("Domain", self.domains) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) exists = True break if not exists: self.addWrapper(url) def eventFilter(self, obj, event): if event.type( ) == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event) def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes if DBusQtMainLoop is None and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def titleChanged(self): self.setWindowTitle(self.current().title()) def setForceClose(self): self.forceClose = True def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("Domain", self.domains) self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.forceClose = False def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.setForceClose() self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if hasattr(c, '__getitem__') and c['is_member']: item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c['name']) item.property_set("id", c['name']) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon): if self.debug: print("Notification: title [{}] message [{}] icon [{}]".format( title, message, icon)) self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() if self.urgent_hint is True: QApplication.alert(self) def count(self): total = 0 unreads = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) highlights = widget.highlights unreads += widget.unreads total += highlights if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) if unreads > 0: self.setWindowTitle("*{}".format(self.title)) else: self.setWindowTitle(self.title) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.setWindowTitle("[{}]{}".format(str(total), self.title)) self.messages = total
def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING, display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"), image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True, plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'): """GRobot manages a QWebPage. @param user_agent: The default User-Agent header. @param operate_timeout: Operation timeout. @param loading_timeout: The page loading timeout. @param log_level: The optional logging level. @param display: A boolean that tells GRobot to displays UI. @param viewport_size: A tupple that sets initial viewport size. @param accept_language: Set the webkit accept language. @param ignore_ssl_errors: A boolean that forces ignore ssl errors. @param cache_dir: A directory path where to store cache datas. @param image_enabled: Enable images. @param plugins_enabled: Enable plugins (like Flash). @param java_enabled: Enable Java JRE. @param javascript_enabled: Enable Javascript. @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) @param develop: Enable the Webkit Inspector. @param proxy: Set a Socks5,HTTP{S} Proxy @param sleep: Sleep `sleep` second,after operate @param jquery_namespace: Set the jQuery namespace. """ GRobot.exit_lock.acquire() if GRobot._kill_loop: gevent.kill(GRobot._kill_loop) GRobot._kill_loop=None logger.setLevel(log_level) plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ] GRobot._liveRobot += 1 self.develop = develop self.inspector = None self.plugin = False self.exitLoop = False self._deleted = False self.set_proxy(proxy) self.sleep = sleep self.jquery_namespace = jquery_namespace self.popup_messages = None self.accept_language = accept_language self._loaded = True self._confirm_expected = None self._prompt_expected = None self._upload_file = None self._alert = None self.http_resources = [] self.user_agent = user_agent self.loading_timeout = loading_timeout self.operate_timeout = operate_timeout self.ignore_ssl_errors = ignore_ssl_errors if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \ and not hasattr(GRobot, 'xvfb'): try: os.environ['DISPLAY'] = ':99' GRobot.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a GRobot run oustside ' + \ 'an X instance') self.display = display if not GRobot._app: GRobot._app = QApplication.instance() or QApplication(['GRobot']) if plugin_path: for p in plugin_path: GRobot._app.addLibraryPath(p) self.page = GRobotWebPage(self, GRobot._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() #TODO:Think about how to handle the network accessible signal #self.manager.networkAccessibleChanged.connect() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired \ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired \ .connect(self._authenticate) self.main_frame = self.page.mainFrame() self.webview = None self.viewport_size = viewport_size self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() if display else self.webview.hide() self.set_viewport_size(*viewport_size) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop) self.enable_image = image_enabled self.enable_javascript = javascript_enabled #always open link in current window instead of new window self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page.linkClicked.connect(self._link_clicked) #start the qt main loop GRobot._loop = QtMainLoop(GRobot._app) GRobot._loop.start() GRobot.exit_lock.release()
class Ghost(object): """Ghost manages a QWebPage. :param user_agent: The default User-Agent header. :param wait_timeout: Maximum step duration in second. :param wait_callback: An optional callable that is periodically executed until Ghost stops waiting. :param log_level: The optional logging level. :param display: A boolean that tells ghost to displays UI. :param viewport_size: A tuple that sets initial viewport size. :param ignore_ssl_errors: A boolean that forces ignore ssl errors. :param cache_dir: A directory path where to store cache datas. :param plugins_enabled: Enable plugins (like Flash). :param java_enabled: Enable Java JRE. :param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) :param download_images: Indicate if the browser should download images """ _alert = None _confirm_expected = None _prompt_expected = None _upload_file = None _app = None def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "ghost.py"), plugins_enabled=False, java_enabled=False, plugin_path=[ '/usr/lib/mozilla/plugins', ], download_images=True, qt_debug=False): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.ignore_ssl_errors = ignore_ssl_errors self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Error('Xvfb is required to a ghost run outside ' + 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) qInstallMsgHandler(QTMessageProxy(qt_debug)) if plugin_path: for p in plugin_path: Ghost._app.addLibraryPath(p) self.popup_messages = [] self.page = GhostWebPage(Ghost._app, self) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute( QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) self.page.settings().setAttribute(QtWebKit.QWebSettings.AutoLoadImages, download_images) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: class MyQWebView(QtWebKit.QWebView): def sizeHint(self): return QSize(*viewport_size) self.webview = MyQWebView() if plugins_enabled: self.webview.settings().setAttribute( QtWebKit.QWebSettings.PluginsEnabled, True) if java_enabled: self.webview.settings().setAttribute( QtWebKit.QWebSettings.JavaEnabled, True) self.webview.setPage(self.page) self.webview.show() else: self.webview = None def __del__(self): self.exit() def capture(self, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Returns snapshot as QImage. :param region: An optional tuple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ if region is None and selector is not None: region = self.region_for_selector(selector) if region: x1, y1, x2, y2 = region w, h = (x2 - x1), (y2 - y1) image = QImage(QSize(x2, y2), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() image = image.copy(x1, y1, w, h) else: self.main_frame.setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff) self.main_frame.setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff) self.page.setViewportSize(self.main_frame.contentsSize()) image = QImage(self.page.viewportSize(), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() return image def capture_to(self, path, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Saves snapshot as image. :param path: The destination path. :param region: An optional tuple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ self.capture(region=region, format=format, selector=selector).save(path) def print_to_pdf( self, path, paper_size=(8.5, 11.0), paper_margins=(0, 0, 0, 0), paper_units=QPrinter.Inch, zoom_factor=1.0, ): """Saves page as a pdf file. See qt4 QPrinter documentation for more detailed explanations of options. :param path: The destination path. :param paper_size: A 2-tuple indicating size of page to print to. :param paper_margins: A 4-tuple indicating size of each margin. :param paper_units: Units for pager_size, pager_margins. :param zoom_factor: Scale the output content. """ assert len(paper_size) == 2 assert len(paper_margins) == 4 printer = QPrinter(mode=QPrinter.ScreenResolution) printer.setOutputFormat(QPrinter.PdfFormat) printer.setPaperSize(QtCore.QSizeF(*paper_size), paper_units) printer.setPageMargins(*(paper_margins + (paper_units, ))) printer.setFullPage(True) printer.setOutputFileName(path) if self.webview is None: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.setZoomFactor(zoom_factor) self.webview.print_(printer) @can_load_page def click(self, selector): """Click the targeted element. :param selector: A CSS3 selector to targeted element. """ if not self.exists(selector): raise Error("Can't find element to click") return self.evaluate(""" var element = document.querySelector("%s"); var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click", true, true, window, 1, 1, 1, 1, 1, false, false, false, false, 0, element); element.dispatchEvent(evt) """ % selector) class confirm: """Statement that tells Ghost how to deal with javascript confirm(). :param confirm: A boolean to set confirmation. :param callable: A callable that returns a boolean for confirmation. """ def __init__(self, confirm=True, callback=None): self.confirm = confirm self.callback = callback def __enter__(self): Ghost._confirm_expected = (self.confirm, self.callback) def __exit__(self, type, value, traceback): Ghost._confirm_expected = None @property def content(self, to_unicode=True): """Returns current frame HTML as a string. :param to_unicode: Whether to convert html to unicode or not """ if to_unicode: return unicode(self.main_frame.toHtml()) else: return self.main_frame.toHtml() @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) def clear_alert_message(self): """Clears the alert message""" self._alert = None @can_load_page def evaluate(self, script): """Evaluates script in page frame. :param script: The script to evaluate. """ return (self.main_frame.evaluateJavaScript("%s" % script), self._release_last_resources()) def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. :param path: The path of the file. :param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def exists(self, selector): """Checks if element exists for given selector. :param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() def exit(self): """Exits application and related.""" if self.display: self.webview.close() Ghost._app.quit() del self.manager del self.page del self.main_frame if hasattr(self, 'xvfb'): self.xvfb.terminate() @can_load_page def fill(self, selector, values): """Fills a form with provided values. :param selector: A CSS selector to the target form to fill. :param values: A dict containing the values. """ if not self.exists(selector): raise Error("Can't find form") resources = [] for field in values: r, res = self.set_field_value("%s [name=%s]" % (selector, field), values[field]) resources.extend(res) return True, resources @can_load_page def fire_on(self, selector, method): """Call method on element matching given selector. :param selector: A CSS selector to the target element. :param method: The name of the method to fire. :param expect_loading: Specifies if a page loading is expected. """ return self.evaluate('document.querySelector("%s").%s();' % \ (selector, method)) def global_exists(self, global_name): """Checks if javascript global exists. :param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name)[0] def hide(self): """Close the webview.""" try: self.webview.close() except: raise Error("no webview to close") def load_cookies(self, cookie_storage, keep_old=False): """load from cookielib's CookieJar or Set-Cookie3 format text file. :param cookie_storage: file location string on disk or CookieJar instance. :param keep_old: Don't reset, keep cookies not overridden. """ def toQtCookieJar(PyCookieJar, QtCookieJar): allCookies = QtCookieJar.cookies if keep_old else [] for pc in PyCookieJar: qc = toQtCookie(pc) allCookies.append(qc) QtCookieJar.setAllCookies(allCookies) def toQtCookie(PyCookie): qc = QNetworkCookie(PyCookie.name, PyCookie.value) qc.setSecure(PyCookie.secure) if PyCookie.path_specified: qc.setPath(PyCookie.path) if PyCookie.domain != "": qc.setDomain(PyCookie.domain) if PyCookie.expires != 0: t = QDateTime() t.setTime_t(PyCookie.expires) qc.setExpirationDate(t) # not yet handled(maybe less useful): # py cookie.rest / QNetworkCookie.setHttpOnly() return qc if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) cj.load() toQtCookieJar(cj, self.cookie_jar) elif cookie_storage.__class__.__name__.endswith('CookieJar'): toQtCookieJar(cookie_storage, self.cookie_jar) else: raise ValueError, 'unsupported cookie_storage type.' def open(self, address, method='get', headers={}, auth=None, body=None, default_popup_response=None): """Opens a web page. :param address: The resource URL. :param method: The Http method. :param headers: An optional dict of extra request hearders. :param auth: An optional tuple of HTTP auth (username, password). :param body: An optional string containing a payload. :param default_popup_response: the default response for any confirm/ alert/prompt popup from the Javascript (replaces the need for the with blocks) :return: Page resource, All loaded resources. """ body = body or QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Error("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl(0) for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.main_frame.load(request, method, body) self.loaded = False if default_popup_response is not None: Ghost._prompt_expected = (default_popup_response, None) Ghost._confirm_expected = (default_popup_response, None) return self.wait_for_page_loaded() class prompt: """Statement that tells Ghost how to deal with javascript prompt(). :param value: A string value to fill in prompt. :param callback: A callable that returns the value to fill in. """ def __init__(self, value='', callback=None): self.value = value self.callback = callback def __enter__(self): Ghost._prompt_expected = (self.value, self.callback) def __exit__(self, type, value, traceback): Ghost._prompt_expected = None def region_for_selector(self, selector): """Returns frame region for given selector as tuple. :param selector: The targeted element. """ geo = self.main_frame.findFirstElement(selector).geometry() try: region = (geo.left(), geo.top(), geo.right(), geo.bottom()) except: raise Error("can't get region for selector '%s'" % selector) return region def save_cookies(self, cookie_storage): """Save to cookielib's CookieJar or Set-Cookie3 format text file. :param cookie_storage: file location string or CookieJar instance. """ def toPyCookieJar(QtCookieJar, PyCookieJar): for c in QtCookieJar.allCookies(): PyCookieJar.set_cookie(toPyCookie(c)) def toPyCookie(QtCookie): port = None port_specified = False secure = QtCookie.isSecure() name = str(QtCookie.name()) value = str(QtCookie.value()) v = str(QtCookie.path()) path_specified = bool(v != "") path = v if path_specified else None v = str(QtCookie.domain()) domain_specified = bool(v != "") domain = v domain_initial_dot = v.startswith( '.') if domain_specified else None v = long(QtCookie.expirationDate().toTime_t()) # Long type boundary on 32bit platfroms; avoid ValueError expires = 2147483647 if v > 2147483647 else v rest = {} discard = False return Cookie(0, name, value, port, port_specified, domain, domain_specified, domain_initial_dot, path, path_specified, secure, expires, discard, None, None, rest) if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) toPyCookieJar(self.cookie_jar, cj) cj.save() elif cookie_storage.__class__.__name__.endswith('CookieJar'): toPyCookieJar(self.cookie_jar, cookie_storage) else: raise ValueError, 'unsupported cookie_storage type.' @can_load_page def set_field_value(self, selector, value, blur=True): """Sets the value of the field matched by given selector. :param selector: A CSS selector that target the field. :param value: The value to fill in. :param blur: An optional boolean that force blur when filled in. """ def _set_checkbox_value(el, value): el.setFocus() if value is True: el.setAttribute('checked', 'checked') else: el.removeAttribute('checked') def _set_checkboxes_value(els, value): for el in els: if el.attribute('value') == value: _set_checkbox_value(el, True) else: _set_checkbox_value(el, False) def _set_radio_value(els, value): for el in els: if el.attribute('value') == value: el.setFocus() el.setAttribute('checked', 'checked') def _set_text_value(el, value): el.setFocus() el.setAttribute('value', value) def _set_select_value(el, value): el.setFocus() self.evaluate( 'document.querySelector("%s").value = "%s";' % (selector.replace('"', '\"'), value.replace('"', '\"'))) def _set_textarea_value(el, value): el.setFocus() el.setPlainText(value) res, ressources = None, [] element = self.main_frame.findFirstElement(selector) if element.isNull(): raise Error('can\'t find element for %s"' % selector) if element.tagName() == "SELECT": _set_select_value(element, value) elif element.tagName() == "TEXTAREA": _set_textarea_value(element, value) elif element.tagName() == "INPUT": if element.attribute('type') in [ "color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week" ]: _set_text_value(element, value) elif element.attribute('type') == "checkbox": els = self.main_frame.findAllElements(selector) if els.count() > 1: _set_checkboxes_value(els, value) else: _set_checkbox_value(element, value) elif element.attribute('type') == "radio": _set_radio_value(self.main_frame.findAllElements(selector), value) elif element.attribute('type') == "file": Ghost._upload_file = value res, resources = self.click(selector) Ghost._upload_file = None else: raise Error('unsuported field tag') if blur: self.fire_on(selector, 'blur') return res, ressources def set_proxy(self, type, host='localhost', port=8888, user='', password=''): """Set up proxy for FURTHER connections. :param type: proxy type to use: \ none/default/socks5/https/http. :param host: proxy server ip or host name. :param port: proxy port. """ _types = { 'default': QNetworkProxy.DefaultProxy, 'none': QNetworkProxy.NoProxy, 'socks5': QNetworkProxy.Socks5Proxy, 'https': QNetworkProxy.HttpProxy, 'http': QNetworkProxy.HttpCachingProxy } if type is None: type = 'none' type = type.lower() if type in ['none', 'default']: self.manager.setProxy(QNetworkProxy(_types[type])) return elif type in _types: proxy = QNetworkProxy(_types[type], hostName=host, port=port, user=user, password=password) self.manager.setProxy(proxy) else: raise ValueError, 'Unsupported proxy type:' + type \ + '\nsupported types are: none/socks5/http/https/default' def set_viewport_size(self, width, height): """Sets the page viewport size. :param width: An integer that sets width pixel count. :param height: An integer that sets height pixel count. """ self.page.setViewportSize(QSize(width, height)) def append_popup_message(self, message): self.popup_messages.append(unicode(message)) def show(self): """Show current page inside a QWebView. """ self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def sleep(self, value): started_at = time.time() while True: if time.time() > (started_at + value): break time.sleep(0.01) Ghost._app.processEvents() def wait_for(self, condition, timeout_message): """Waits until condition is True. :param condition: A callable that returns the condition. :param timeout_message: The exception message on timeout. """ started_at = time.time() while not condition(): if time.time() > (started_at + self.wait_timeout): raise TimeoutError(timeout_message) time.sleep(0.01) Ghost._app.processEvents() if self.wait_callback is not None: self.wait_callback() def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: Ghost._alert is not None, 'User has not been alerted.') msg = Ghost._alert Ghost._alert = None return msg, self._release_last_resources() def wait_for_page_loaded(self): """Waits until page is loaded, assumed that a page as been requested. """ self.wait_for(lambda: self.loaded, 'Unable to load requested page') resources = self._release_last_resources() page = None url = self.main_frame.url().toString() url_without_hash = url.split("#")[0] for resource in resources: if url == resource.url or url_without_hash == resource.url: page = resource return page, resources def wait_for_selector(self, selector): """Waits until selector match an element on the frame. :param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) return True, self._release_last_resources() def wait_for_text(self, text): """Waits until given text appear on main frame. :param text: The text to wait for. """ self.wait_for(lambda: text in self.content, 'Can\'t find "%s" in current frame' % text) return True, self._release_last_resources() def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. :param mix: The QNetworkReply or QNetworkProxy object. :param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _page_loaded(self): """Called back when page is loaded. """ self.loaded = True self.cache.clear() def _page_load_started(self): """Called back when page load started. """ self.loaded = False def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources = self.http_resources self.http_resources = [] return last_resources def _request_ended(self, reply): """Adds an HttpResource object to http_resources. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): Logger.log("[%s] bytesAvailable()= %s" % (str(reply.url()), reply.bytesAvailable()), level="debug") # Some web pages return cache headers that mandates not to cache the # reply, which means we won't find this QNetworkReply in the cache # object. In this case bytesAvailable will return > 0. # Such pages are www.etsy.com # This is a bit of a hack and due to the async nature of QT, might # not work at times. We should move to using some proxied implementation # of QNetworkManager and QNetworkReply in order to get the contents # of the requests properly rather than relying on the cache. if reply.bytesAvailable() > 0: content = reply.peek(reply.bytesAvailable()) else: content = None self.http_resources.append( HttpResource(reply, self.cache, content=content)) def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. :param reply: The QNetworkReply object. """ self.wait_for(lambda: reply.isFinished(), 'Download timeout.') if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append( HttpResource(reply, self.cache, reply.readAll())) def _on_manager_ssl_errors(self, reply, errors): url = unicode(reply.url().toString()) if self.ignore_ssl_errors: reply.ignoreSslErrors() else: Logger.log('SSL certificate error: %s' % url, level='warning')
def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "ghost.py"), plugins_enabled=False, java_enabled=False, plugin_path=[ '/usr/lib/mozilla/plugins', ], download_images=True, qt_debug=False): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.ignore_ssl_errors = ignore_ssl_errors self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Error('Xvfb is required to a ghost run outside ' + 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) qInstallMsgHandler(QTMessageProxy(qt_debug)) if plugin_path: for p in plugin_path: Ghost._app.addLibraryPath(p) self.popup_messages = [] self.page = GhostWebPage(Ghost._app, self) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute( QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) self.page.settings().setAttribute(QtWebKit.QWebSettings.AutoLoadImages, download_images) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: class MyQWebView(QtWebKit.QWebView): def sizeHint(self): return QSize(*viewport_size) self.webview = MyQWebView() if plugins_enabled: self.webview.settings().setAttribute( QtWebKit.QWebSettings.PluginsEnabled, True) if java_enabled: self.webview.settings().setAttribute( QtWebKit.QWebSettings.JavaEnabled, True) self.webview.setPage(self.page) self.webview.show() else: self.webview = None
def prepare(self, metaData): metaData.setSaveToDisk(True) return QNetworkDiskCache.prepare(self, metaData)
def __init__( self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "ghost.py"), ): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.ignore_ssl_errors = ignore_ssl_errors self.loaded = True if not sys.platform.startswith("win") and not "DISPLAY" in os.environ and not hasattr(Ghost, "xvfb"): try: os.environ["DISPLAY"] = ":99" Ghost.xvfb = subprocess.Popen(["Xvfb", ":99"]) except OSError: raise Exception("Xvfb is required to a ghost run oustside " + "an X instance") self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(["ghost"]) self.page = GhostWebPage(Ghost._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) self.page.setForwardUnsupportedContent(True) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired.connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired.connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show()
def setCache(self, cacheDir, cacheSize): cache = QNetworkDiskCache() cache.setCacheDirectory(cacheDir) cache.setMaximumCacheSize(cacheSize * 1024 * 1024) self.page().networkAccessManager().setCache(cache)
class ScudCloud(QtGui.QMainWindow): plugins = True debug = False forceClose = False messages = 0 speller = Speller() def __init__(self, parent = None, settings_path = ""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: self.startURL = self.domain() self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') def addWrapper(self, url): webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.page().networkAccessManager().setCache(self.diskCache) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "False") else: self.settings.setValue("Menu", "True") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", lambda : self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda : self.switchTo(Resources.SIGNIN_URL)), "signout": self.createAction("Signout", lambda : self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12) }, "help": { "help": self.createAction("Help and Feedback", lambda : self.current().help(), QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", lambda : self.current().helpCenter()), "about": self.createAction("About", lambda : self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) self.editMenu = menu.addMenu("&Edit") self.updateEditMenu() viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) if Unity is None: viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def updateEditMenu(self): self.editMenu.clear() self.menus["edit"] = { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "back": self.current().pageAction(QtWebKit.QWebPage.Back), "forward": self.current().pageAction(QtWebKit.QWebPage.Forward), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload) } self.editMenu.addAction(self.menus["edit"]["undo"]) self.editMenu.addAction(self.menus["edit"]["redo"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["cut"]) self.editMenu.addAction(self.menus["edit"]["copy"]) self.editMenu.addAction(self.menus["edit"]["paste"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["back"]) self.editMenu.addAction(self.menus["edit"]["forward"]) self.editMenu.addAction(self.menus["edit"]["reload"]) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://"+self.identifier+".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): for t in teams: # If team_icon is not present, it's because team is already connected if 'team_icon' in t: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0]) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) exists = True break if not exists: self.addWrapper(url) self.updateEditMenu() def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event); def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def setForceClose(self): self.forceClose = True def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) # Let's save the first team registered as default qUrl = self.stackedWidget.widget(0).url() if self.identifier is None and Resources.MESSAGES_URL_RE.match(qUrl.toString()): self.settings.setValue("Domain", 'https://'+qUrl.host()) self.forceClose = False def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.setForceClose() self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new () item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name']) item.property_set ("id", c['name']) item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon=None): self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) messages = widget.highlights if messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) if messages is not None: total+=messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total
def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "ghost.py"), plugins_enabled=False, java_enabled=False, plugin_path=['/usr/lib/mozilla/plugins',], download_images=True, qt_debug=False): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.ignore_ssl_errors = ignore_ssl_errors self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Error('Xvfb is required to a ghost run outside ' + 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) qInstallMsgHandler(QTMessageProxy(qt_debug)) if plugin_path: for p in plugin_path: Ghost._app.addLibraryPath(p) self.popup_messages = [] self.page = GhostWebPage(Ghost._app, self) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) self.page.settings().setAttribute(QtWebKit.QWebSettings.AutoLoadImages, download_images) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: class MyQWebView(QtWebKit.QWebView): def sizeHint(self): return QSize(*viewport_size) self.webview = MyQWebView() if plugins_enabled: self.webview.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True) if java_enabled: self.webview.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, True) self.webview.setPage(self.page) self.webview.show() else: self.webview = None
def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING, display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"), image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True, plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'): """GRobot manages a QWebPage. @param user_agent: The default User-Agent header. @param operate_timeout: Operation timeout. @param loading_timeout: The page loading timeout. @param log_level: The optional logging level. @param display: A boolean that tells GRobot to displays UI. @param viewport_size: A tupple that sets initial viewport size. @param accept_language: Set the webkit accept language. @param ignore_ssl_errors: A boolean that forces ignore ssl errors. @param cache_dir: A directory path where to store cache datas. @param image_enabled: Enable images. @param plugins_enabled: Enable plugins (like Flash). @param java_enabled: Enable Java JRE. @param javascript_enabled: Enable Javascript. @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) @param develop: Enable the Webkit Inspector. @param proxy: Set a Socks5,HTTP{S} Proxy @param sleep: Sleep `sleep` second,after operate @param jquery_namespace: Set the jQuery namespace. """ GRobot.exit_lock.acquire() logger.setLevel(log_level) plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ] GRobot._liveRobot += 1 self.develop = develop self.inspector = None self.plugin = False self.exitLoop = False self.set_proxy(proxy) self.sleep = sleep self.jquery_namespace = jquery_namespace self.popup_messages = None self.accept_language = accept_language self._loaded = True self._confirm_expected = None self._prompt_expected = None self._upload_file = None self._alert = None self.http_resources = [] self.user_agent = user_agent self.loading_timeout = loading_timeout self.operate_timeout = operate_timeout self.ignore_ssl_errors = ignore_ssl_errors if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \ and not hasattr(GRobot, 'xvfb'): try: os.environ['DISPLAY'] = ':99' GRobot.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a GRobot run oustside ' + \ 'an X instance') self.display = display if not GRobot._app: GRobot._app = QApplication.instance() or QApplication(['GRobot']) if plugin_path: for p in plugin_path: GRobot._app.addLibraryPath(p) self.page = GRobotWebPage(self, GRobot._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() #TODO:Think about how to handle the network accessible signal #self.manager.networkAccessibleChanged.connect() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired \ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired \ .connect(self._authenticate) self.main_frame = self.page.mainFrame() self.webview = None self.viewport_size = viewport_size self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() if display else self.webview.hide() self.set_viewport_size(*viewport_size) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop) self.enable_image = image_enabled self.enable_javascript = javascript_enabled #always open link in current window instead of new window self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page.linkClicked.connect(self._link_clicked) #start the qt main loop GRobot._loop = QtMainLoop(GRobot._app) GRobot._loop.start() GRobot.exit_lock.release()
class GRobot(object): _loop = None _liveRobot = 0 _app = None exit_lock = RLock() def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING, display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"), image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True, plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'): """GRobot manages a QWebPage. @param user_agent: The default User-Agent header. @param operate_timeout: Operation timeout. @param loading_timeout: The page loading timeout. @param log_level: The optional logging level. @param display: A boolean that tells GRobot to displays UI. @param viewport_size: A tupple that sets initial viewport size. @param accept_language: Set the webkit accept language. @param ignore_ssl_errors: A boolean that forces ignore ssl errors. @param cache_dir: A directory path where to store cache datas. @param image_enabled: Enable images. @param plugins_enabled: Enable plugins (like Flash). @param java_enabled: Enable Java JRE. @param javascript_enabled: Enable Javascript. @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) @param develop: Enable the Webkit Inspector. @param proxy: Set a Socks5,HTTP{S} Proxy @param sleep: Sleep `sleep` second,after operate @param jquery_namespace: Set the jQuery namespace. """ GRobot.exit_lock.acquire() logger.setLevel(log_level) plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ] GRobot._liveRobot += 1 self.develop = develop self.inspector = None self.plugin = False self.exitLoop = False self.set_proxy(proxy) self.sleep = sleep self.jquery_namespace = jquery_namespace self.popup_messages = None self.accept_language = accept_language self._loaded = True self._confirm_expected = None self._prompt_expected = None self._upload_file = None self._alert = None self.http_resources = [] self.user_agent = user_agent self.loading_timeout = loading_timeout self.operate_timeout = operate_timeout self.ignore_ssl_errors = ignore_ssl_errors if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \ and not hasattr(GRobot, 'xvfb'): try: os.environ['DISPLAY'] = ':99' GRobot.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a GRobot run oustside ' + \ 'an X instance') self.display = display if not GRobot._app: GRobot._app = QApplication.instance() or QApplication(['GRobot']) if plugin_path: for p in plugin_path: GRobot._app.addLibraryPath(p) self.page = GRobotWebPage(self, GRobot._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() #TODO:Think about how to handle the network accessible signal #self.manager.networkAccessibleChanged.connect() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired \ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired \ .connect(self._authenticate) self.main_frame = self.page.mainFrame() self.webview = None self.viewport_size = viewport_size self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() if display else self.webview.hide() self.set_viewport_size(*viewport_size) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop) self.enable_image = image_enabled self.enable_javascript = javascript_enabled #always open link in current window instead of new window self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page.linkClicked.connect(self._link_clicked) #start the qt main loop GRobot._loop = QtMainLoop(GRobot._app) GRobot._loop.start() GRobot.exit_lock.release() @property def popup_messages(self): return self._popup_messages @popup_messages.setter def popup_messages(self, value): self._popup_messages = unicode(value) @property def url(self): return unicode(self.main_frame.url().toString()) @property def content(self): """Returns current frame HTML as a string.""" return unicode(self.page.currentFrame().toHtml()) @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() @property def enable_image(self): """Disable the page images can speed up page loading. """ return self._enable_image @enable_image.setter def enable_image(self, value): self.page.settings().setAttribute(QWebSettings.AutoLoadImages, value) self._enable_image = value #TODO:It seems not work? # @enable_image.deleter # def enable_image(self): # raise NotImplemented @property def enable_javascript(self): """Disable the page javascript can speed up page loading. """ return self._enable_javascript @enable_javascript.setter def enable_javascript(self, value): self.page.settings().setAttribute(QWebSettings.JavascriptEnabled, value) self._enable_javascript = value def open(self, address, method='get', headers=None, auth=None, body=None, default_popup_response=None): """Opens a web page. @param address: The resource URL. @param method: The Http method. @param headers: An optional dict of extra request hearders. @param auth: An optional tupple of HTTP auth (username, password). @param body: An optional string containing a payload. @param default_popup_response: the default response for any confirm/ alert/prompt popup from the Javascript (replaces the need for the with blocks) """ headers = headers or {} body = body or QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Exception("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl = 0 for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.page.mainFrame().load(request, method, body) self._loaded = False if default_popup_response is not None: self._prompt_expected = (default_popup_response, None) self._confirm_expected = (default_popup_response, None) return self.wait_for_page_loaded() def set_viewport_size(self, width, height): """Sets the page viewport size. @param width: An integer that sets width pixel count. @param height: An integer that sets height pixel count. """ if self.display: self.webview.resize(QSize(width, height)) self.page.setViewportSize(QSize(width, height)) def set_proxy(self, proxy=None): """Set the proxy or using system configuration as None,supported socks5 http{s}. @param proxy: Example:socks5://username:[email protected]:7070 """ proxy_type = None if proxy: parse = urlparse(proxy) scheme = parse.scheme hostname = parse.hostname port = parse.port username = parse.username or '' password = parse.password or '' if scheme == 'socks5': proxy_type = QNetworkProxy.Socks5Proxy elif scheme in ('http', 'https'): proxy_type = QNetworkProxy.HttpProxy if proxy_type: self.page.networkAccessManager().setProxy( QNetworkProxy(proxy_type, hostname, port, username, password) ) else: QNetworkProxyFactory.setUseSystemConfiguration(True) def first_element_position(self, selector): try: return self.elements_position(selector)[0] except IndexError: logger.warning("Can't locate selector " + selector) return None def elements_position(self, selector): """Get the position of elements whose match selector @param selector: @return: position of QPoint """ attr, pattern, val = self.parser_selector(selector, attr='identifier') strip = lambda v: v.strip() if pattern: val = locals()[pattern](val) def identifier(query): return id(query) or name(query) def name(query): return css("*[name='%s']" % query) def id(query): return css('#' + query) def link(query): return xpath(u"//a[@text()='%s']" % query.replace("\'", "\\'")) def css(query): result = [] for ele in self.main_frame.findAllElements(query): if not ele.isNull(): result.append(ele.geometry().center()) return result def xpath(query): positions = self.evaluate(u""" function GetAbsoluteLocationEx(element) { if ( arguments.length != 1 || element == null ) { return null; } var elmt = element; var offsetTop = elmt.offsetTop; var offsetLeft = elmt.offsetLeft; var offsetWidth = elmt.offsetWidth; var offsetHeight = elmt.offsetHeight; while( elmt = elmt.offsetParent ) { // add this judge if ( elmt.style.position == 'absolute' || elmt.style.position == 'relative' || ( elmt.style.overflow != 'visible' && elmt.style.overflow != '' ) ) { break; } offsetTop += elmt.offsetTop; offsetLeft += elmt.offsetLeft; } return { absoluteTop: offsetTop, absoluteLeft: offsetLeft, offsetWidth: offsetWidth, offsetHeight: offsetHeight }; } result=[]; for (var r = document.evaluate('%s', document, null, 5, null), n; n = r.iterateNext();) { pos=GetAbsoluteLocationEx(n) result.push([pos.absoluteLeft+pos.offsetWidth/2.0,pos.absoluteTop+pos.offsetHeight/2.0]); } result """ % query.replace("\'", "\\'")) return map(lambda x: QPoint(*tuple(x)), positions) return locals()[attr](val) def _move_page_center_to(self, qpoint): size = self.page.viewportSize() self.main_frame.setScrollPosition(qpoint - QPoint(size.width(), size.height()) / 2) def reload(self): """Reload page. @return: """ self.trigger_action('Reload', expect_loading=True) def back(self): self.trigger_action('Back') def forward(self): self.trigger_action('Forward') @can_load_page def trigger_action(self, action): """Trigger QWebPage::WebAction @param action: """ self.page.triggerAction(getattr(QWebPage, action)) def parser_selector(self, selector, attr=None, pattern=None, val=None): index = selector.find('=') if index <= 0: val = selector else: attr = selector[:index] value_ = selector[index + 1:] index = value_.find(':') if index > 0: pattern = value_[:index] val = value_[index + 1:] return attr, pattern, val @can_load_page @have_a_break def click(self, selector): qpoint = self.first_element_position(selector) if qpoint: return self._click_position(qpoint) @can_load_page def _click_position(self, qpoint): self._move_page_center_to(qpoint) self.webview.repaint() pos = qpoint - self.main_frame.scrollPosition() self._move_to_position(pos) QTest.mouseClick(self.webview, Qt.LeftButton, pos=pos) gevent.sleep(1) return pos def qpoint_to_tuple(self, qpoint): return qpoint.x(), qpoint.y() @have_a_break def move_to(self, selector): qpoint = self.first_element_position(selector) if qpoint: self._move_to_position(qpoint) return qpoint_to_tuple(qpoint) def move_at(self, x, y): self._move_to_position(QPoint(x, y)) def _move_to_position(self, qpoint): QTest.mouseMove(self.webview, pos=qpoint) return qpoint @have_a_break def click_at(self, x, y): self._click_position(QPoint(x, y)) @have_a_break def key_clicks(self, selector, text): if selector: self.click(selector) QTest.keyClicks(self.webview, text, delay=50) @have_a_break def type(self, selector, text): position = self.click(selector) ele = self._hit_element_from(position) ele.setFocus() ele.evaluateJavaScript( u""" core.events.setValue(this, '%s') """ % (text.replace("\n", "\\n").replace("\'", "\\'")) ) logger.debug('type %s %s' % (selector, text)) def _hit_element_from(self, position): return self.main_frame.hitTestContent(position).element() def first_element(self, selector): position = self.first_element_position(selector) if position: return self.main_frame.hitTestContent(position).element(), position def wait_forever(self): self.wait_for(lambda: False, time_for_stop=-1) @have_a_break def check(self, selector, checked=True): ele, position = self.first_element(selector) if ele and ele.tagName() == 'INPUT': if ele.attribute('type') in ['checkbox', 'radio']: ele_checked = ele.attribute('checked') == 'checked' or False if ele_checked != checked: self._click_position(position) else: raise ValueError, "%s is not a checkbox or radio" % selector @have_a_break def select(self, selector, value): def _select(query, select_by, select): select.evaluateJavaScript(u""" triggerEvent(this, 'focus', false); var changed = false; var optionToSelect = '%s'; for (var i = 0; i < this.options.length; i++) { var option = this.options[i]; if (option.selected && option.%s != optionToSelect) { option.selected = false; changed = true; } else if (!option.selected && option.%s == optionToSelect) { option.selected = true; changed = true; } } if (changed) { triggerEvent(this, 'change', true); } """ % ( query.replace("\'", "\\'"), select_by, select_by)) def _add_selection(query, select_by, select, selected): select.evaluateJavaScript(u""" triggerEvent(this, 'focus', false); var optionToSelect = '%s'; for (var i = 0; i < this.options.length; i++) { var option = this.options[i]; if (option.%s == optionToSelect) { option.selected = %s; triggerEvent(this, 'change', true); } } """ % ( query.replace("\'", "\\'"), select_by, selected and 'true' or 'false')) ele, position = self.first_element(selector) if ele and ele.tagName() == 'SELECT': ele.setFocus() if ele.attribute('multiple') == 'multiple': assert isinstance(value, list) for value_, selected in value: attr, pattern, val = self.parser_selector(value_, attr='text') _add_selection(val, attr, ele, selected) else: attr, pattern, val = self.parser_selector(value, attr='text') _select(val, attr, ele) def choose_file(self, selector, file): self._upload_file = file self.click(selector) self._upload_file = None def capture(self, selector=None): """Capture the images of selector. @param selector: Css selector. @return: Images """ elements = self.main_frame.documentElement().findAll(selector) imgs = [] for element in elements: geo = element.geometry() img = QImage(geo.width(), geo.height(), QImage.Format_ARGB32) painter = QPainter(img) element.render(painter) painter.end() imgs.append(img) return imgs def capture_to(self, path, selector=None): """Capture the images of selector to files. @param path: File path with index suffix. @param selector: Css selector. @return: The paths of saving. """ _, ext = os.path.splitext(path) ext = ext[1:] imgs = self.capture(selector) result = [] for index, img in enumerate(imgs): filepath = '%s.%s' % (path, index) if img.save(filepath, ext.upper()): result.append(filepath) return result def capture_to_buf(self, selector=None): """capture the images of selector to StringIO @param selector: Css selector. @return: The StringIO list. """ images = self.capture(selector) result = [] for image in images: ba = QByteArray() buf = QBuffer(ba) buf.open(QIODevice.ReadWrite) image.save(buf, 'jpg') stream = StringIO(str(buf.buffer())) result.append(stream) return result @can_load_page def evaluate(self, script): """Evaluates script in page frame. @param script: The script to evaluate. """ result = self.main_frame.evaluateJavaScript("%s" % script) # if isinstance(result,QString): # result=unicode(result) return result def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. @param path: The path of the file. @param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def __del__(self): """Depend on the CG of Python. """ self._exit() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) def exists(self, selector): """Checks if element exists for given selector. @param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() #TODO: Still not work. # def remove_css(self): # """Remore the css,speed up page loading. # # @return: # """ # # return self.evaluate("""var targetelement="link";//determine element type to create nodelist from # var targetattr="href"//determine corresponding attribute to test for # var allsuspects=document.getElementsByTagName(targetelement) # for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove # if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null ) # allsuspects[i].parentNode.removeChild(allsuspects[i]); //remove element by calling parentNode.removeChild() # } # """) def filter_resources(self, pattern): """Filter resources with pattern. @param pattern: Match pattern. @param resources: @return: @raise: """ if isinstance(pattern, basestring): is_match = lambda x: pattern == x elif isinstance(pattern, _pattern_type): is_match = lambda x: pattern.match(x) elif hasattr(pattern, '__call__'): is_match = pattern else: raise TypeError, 'pattern must be one of str,re.compile,callable' return filter(lambda x: is_match(x.request_url), self.http_resources)[:] def save(self, path): """Save current page content to the path. @param path: The path to save. """ f = open(path, 'w') f.write(self.content.encode('utf-8')) f.close() def global_exists(self, global_name): """Checks if javascript global exists. @param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name) def load_cookies( self, cookie_storage, keep_old=False ): """load from cookielib's CookieJar or Set-Cookie3 format text file. @param cookie_storage: file location string on disk or CookieJar instance. @param keep_old: Don't reset, keep cookies not overridden. """ def toQtCookieJar( PyCookieJar, QtCookieJar ): allCookies = QtCookieJar.cookies if keep_old else [] for pc in PyCookieJar: qc = toQtCookie(pc) allCookies.append(qc) QtCookieJar.setAllCookies(allCookies) def toQtCookie(PyCookie): qc = QNetworkCookie(PyCookie.name, PyCookie.value) qc.setSecure(PyCookie.secure) if PyCookie.path_specified: qc.setPath(PyCookie.path) if PyCookie.domain != "": qc.setDomain(PyCookie.domain) if PyCookie.expires != 0: t = QDateTime() t.setTime_t(PyCookie.expires) qc.setExpirationDate(t) # not yet handled(maybe less useful): # py cookie.rest / QNetworkCookie.setHttpOnly() return qc if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) cj.load() toQtCookieJar(cj, self.cookie_jar) elif cookie_storage.__class__.__name__.endswith('CookieJar'): toQtCookieJar(cookie_storage, self.cookie_jar) else: raise ValueError, 'unsupported cookie_storage type.' def save_cookies(self, cookie_storage): """Save to cookielib's CookieJar or Set-Cookie3 format text file. @param cookie_storage: file location string or CookieJar instance. """ def toPyCookieJar(QtCookieJar, PyCookieJar): for c in QtCookieJar.allCookies(): PyCookieJar.set_cookie(toPyCookie(c)) def toPyCookie(QtCookie): port = None port_specified = False secure = QtCookie.isSecure() name = str(QtCookie.name()) value = str(QtCookie.value()) v = str(QtCookie.path()) path_specified = bool(v != "") path = v if path_specified else None v = str(QtCookie.domain()) domain_specified = bool(v != "") domain = v domain_initial_dot = v.startswith('.') if domain_specified else None v = long(QtCookie.expirationDate().toTime_t()) # Long type boundary on 32bit platfroms; avoid ValueError expires = 2147483647 if v > 2147483647 else v rest = {} discard = False return Cookie(0, name, value, port, port_specified, domain , domain_specified, domain_initial_dot, path, path_specified , secure, expires, discard, None, None, rest) if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) toPyCookieJar(self.cookie_jar, cj) cj.save() elif cookie_storage.__class__.__name__.endswith('CookieJar'): toPyCookieJar(self.cookie_jar, cookie_storage) else: raise ValueError, 'unsupported cookie_storage type.' def wait_for_confirm(self, confirm=True, callback=None): """Statement that tells GRobot how to deal with javascript confirm(). @param confirm: A bollean that confirm. @param callable: A callable that returns a boolean for confirmation. """ self._robot._confirm_expected = (confirm, callback) self._robot.wait_for(lambda: self._robot._confirm_expected is None) return self.popup_messages def wait_for_text(self, text, time_for_stop=None): """Waits until given text appear on main frame. @param text: The text to wait for. @return: """ logger.debug("Wait for text %s" % text) self.wait_for(lambda: text in self.content, "Can\'t find '%s' in current frame" % text, time_for_stop=time_for_stop) return self.wait_for_page_loaded() def wait_for_xpath(self, expression, time_for_stop=None): self.wait_for(lambda: XPath(self.content).execute(expression), "Can't find xpath=%s in current frame" % expression, time_for_stop=time_for_stop) return self.wait_for_page_loaded() def wait_for_selector(self, selector): """Waits until selector match an element on the frame. @param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) def wait_for_page_loaded(self, time_for_stop=None): """Waits until page is loaded, assumed that a page as been requested. """ return self.wait_for(lambda: self._loaded, 'Unable to load requested page', time_for_stop=time_for_stop) def wait_for(self, condition, timeout_message='', time_for_stop=None): """Waits until condition is True. @param condition: A callable that returns the condition. @param timeout_message: The exception message on timeout.-1 means never timeout. """ if self._loaded: time_for_stop = time_for_stop or self.operate_timeout else: time_for_stop = time_for_stop or self.loading_timeout started_at = time.time() while not condition(): if time_for_stop != -1 and time.time() > (started_at + time_for_stop): if self._loaded: raise OperateTimeout, timeout_message else: # raise LoadingTimeout, timeout_message self.trigger_action('Stop') #QWebPage::Stop self._loaded = True logger.warning("Page loading timeout.Force to stop the page") break gevent.sleep(2) def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: self._alert is not None, 'User has not been alerted.') msg, self._alert = self._alert, None return msg def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources, self.http_resources = self.http_resources[:], [] return last_resources def _page_loaded(self, success): if self.develop and self.display: if self.inspector is None: self.inspector = QWebInspector() self.inspector.setPage(self.page) self.inspector.show() scripts = [ 'atoms.js', 'htmlutils.js', ] if self.jquery_namespace: scripts.append('jquery-1.9.1.min.js', ) for script in scripts: self.evaluate_js_file(os.path.dirname(__file__) + '/javascripts/' + script) if self.jquery_namespace: self.evaluate(u"%s=jQuery.noConflict();" % self.jquery_namespace) self._loaded = True # self.cache.clear() logger.debug("Page load finished") def _page_load_started(self): logger.debug("Start load page") self._loaded = False def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. @param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache, reply.readAll())) def _link_clicked(self, href): """Contorl the page link clicked event,forbid open new window. @param href: The href attribute of a tag. """ self.main_frame.load(href) def _request_ended(self, reply): """Adds an HttpResource object to http_resources. @param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache)) def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. @param mix: The QNetworkReply or QNetworkProxy object. @param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _on_manager_ssl_errors(self, reply, errors): """Ingore all the ssl error @param reply: @param errors: """ url = unicode(reply.url().toString()) if self.ignore_ssl_errors: reply.ignoreSslErrors() else: logger.warning('SSL certificate error: %s' % url) def _exit(self): """Destroy the Qt main event loop. """ GRobot.exit_lock.acquire() if self.inspector: self.inspector.close() sip.delete(self.inspector) if self.display: self.webview.close() sip.delete(self.webview) if self.page and not sip.isdeleted(self.page): sip.delete(self.page) GRobot._liveRobot -= 1 if GRobot._liveRobot == 0 and GRobot._loop is not None: GRobot._loop.stop() GRobot._loop = None GRobot._app = None if hasattr(self, 'xvfb'): GRobot.xvfb.terminate() GRobot.exit_lock.release()
plugins_dir = os.path.dirname(plugin_dir) # python path setting sys.path.append(plugins_dir) # initialize output directory initOutputDir() plugin_name = os.path.basename(plugin_dir) suite = unittest.TestLoader().discover(plugin_name + ".tests") unittest.TextTestRunner(verbosity=2).run(suite) if __name__ == "__main__": gui_mode = True QGISAPP = QgsApplication(sys.argv, gui_mode) QGISAPP.initQgis() print "=" * 70 print QGISAPP.showSettings() print "=" * 70 # set up network disk cache manager = QgsNetworkAccessManager.instance() cache = QNetworkDiskCache(manager) cache.setCacheDirectory(pluginPath(os.path.join("tests", "cache"))) cache.setMaximumCacheSize(50 * 1024 * 1024) manager.setCache(cache) # run test! runTest()
class GRobot(object): _loop = None _liveRobot = 0 _app = None _kill_loop=None exit_lock = RLock() def __init__(self, user_agent=default_user_agent, operate_timeout=10, loading_timeout=60, log_level=logging.WARNING, display=False, viewport_size=(1024, 768), accept_language='en,*', ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "GRobot"), image_enabled=True, plugins_enabled=False, java_enabled=False, javascript_enabled=True, plugin_path=None, develop=False, proxy=None, sleep=0.5, jquery_namespace='GRobot'): """GRobot manages a QWebPage. @param user_agent: The default User-Agent header. @param operate_timeout: Operation timeout. @param loading_timeout: The page loading timeout. @param log_level: The optional logging level. @param display: A boolean that tells GRobot to displays UI. @param viewport_size: A tupple that sets initial viewport size. @param accept_language: Set the webkit accept language. @param ignore_ssl_errors: A boolean that forces ignore ssl errors. @param cache_dir: A directory path where to store cache datas. @param image_enabled: Enable images. @param plugins_enabled: Enable plugins (like Flash). @param java_enabled: Enable Java JRE. @param javascript_enabled: Enable Javascript. @param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) @param develop: Enable the Webkit Inspector. @param proxy: Set a Socks5,HTTP{S} Proxy @param sleep: Sleep `sleep` second,after operate @param jquery_namespace: Set the jQuery namespace. """ GRobot.exit_lock.acquire() if GRobot._kill_loop: gevent.kill(GRobot._kill_loop) GRobot._kill_loop=None logger.setLevel(log_level) plugin_path = plugin_path or ['/usr/lib/mozilla/plugins', ] GRobot._liveRobot += 1 self.develop = develop self.inspector = None self.plugin = False self.exitLoop = False self._deleted = False self.set_proxy(proxy) self.sleep = sleep self.jquery_namespace = jquery_namespace self.popup_messages = None self.accept_language = accept_language self._loaded = True self._confirm_expected = None self._prompt_expected = None self._upload_file = None self._alert = None self.http_resources = [] self.user_agent = user_agent self.loading_timeout = loading_timeout self.operate_timeout = operate_timeout self.ignore_ssl_errors = ignore_ssl_errors if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ \ and not hasattr(GRobot, 'xvfb'): try: os.environ['DISPLAY'] = ':99' GRobot.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a GRobot run oustside ' + \ 'an X instance') self.display = display if not GRobot._app: GRobot._app = QApplication.instance() or QApplication(['GRobot']) if plugin_path: for p in plugin_path: GRobot._app.addLibraryPath(p) self.page = GRobotWebPage(self, GRobot._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() #TODO:Think about how to handle the network accessible signal #self.manager.networkAccessibleChanged.connect() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired \ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired \ .connect(self._authenticate) self.main_frame = self.page.mainFrame() self.webview = None self.viewport_size = viewport_size self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() if display else self.webview.hide() self.set_viewport_size(*viewport_size) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.page.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.develop) self.enable_image = image_enabled self.enable_javascript = javascript_enabled #always open link in current window instead of new window self.page.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.page.linkClicked.connect(self._link_clicked) #start the qt main loop GRobot._loop = QtMainLoop(GRobot._app) GRobot._loop.start() GRobot.exit_lock.release() @property def popup_messages(self): return self._popup_messages @popup_messages.setter def popup_messages(self, value): self._popup_messages = str(value) @property def url(self): return str(self.main_frame.url().toString()) def content(self): """Returns current frame HTML as a string.""" return str(self.main_frame.toHtml()) @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() @property def enable_image(self): """Disable the page images can speed up page loading. """ return self._enable_image @enable_image.setter def enable_image(self, value): self.page.settings().setAttribute(QWebSettings.AutoLoadImages, value) self._enable_image = value #TODO:It seems not work? # @enable_image.deleter # def enable_image(self): # raise NotImplemented @property def enable_javascript(self): """Disable the page javascript can speed up page loading. """ return self._enable_javascript @enable_javascript.setter def enable_javascript(self, value): self.page.settings().setAttribute(QWebSettings.JavascriptEnabled, value) self._enable_javascript = value def open(self, address, method='get', headers=None, auth=None, body=None, default_popup_response=None): """Opens a web page. @param address: The resource URL. @param method: The Http method. @param headers: An optional dict of extra request hearders. @param auth: An optional tupple of HTTP auth (username, password). @param body: An optional string containing a payload. @param default_popup_response: the default response for any confirm/ alert/prompt popup from the Javascript (replaces the need for the with blocks) """ headers = headers or {} body = body or QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Exception("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl = 0 for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.page.mainFrame().load(request, method, body) self._loaded = False if default_popup_response is not None: self._prompt_expected = (default_popup_response, None) self._confirm_expected = (default_popup_response, None) return self.wait_for_page_loaded() def set_viewport_size(self, width, height): """Sets the page viewport size. @param width: An integer that sets width pixel count. @param height: An integer that sets height pixel count. """ if self.display: self.webview.resize(QSize(width, height)) self.page.setViewportSize(QSize(width, height)) def set_proxy(self, proxy=None): """Set the proxy or using system configuration as None,supported socks5 http{s}. @param proxy: Example:socks5://username:[email protected]:7070 """ proxy_type = None if proxy: parse = urlparse(proxy) scheme = parse.scheme hostname = parse.hostname port = parse.port username = parse.username or '' password = parse.password or '' if scheme == 'socks5': proxy_type = QNetworkProxy.Socks5Proxy elif scheme in ('http', 'https'): proxy_type = QNetworkProxy.HttpProxy if proxy_type: self.page.networkAccessManager().setProxy( QNetworkProxy(proxy_type, hostname, port, username, password) ) else: QNetworkProxyFactory.setUseSystemConfiguration(True) def first_element_position(self, selector): try: return self.elements_position(selector)[0] except IndexError: logger.warning("Can't locate selector " + selector) return None def elements_position(self, selector): """Get the position of elements whose match selector @param selector: @return: position of QPoint """ attr, pattern, val = self.parser_selector(selector, attr='identifier') strip = lambda v: v.strip() if pattern: val = locals()[pattern](val) def identifier(query): return id(query) or name(query) def name(query): return css("*[name='%s']" % query) def id(query): return css('#' + query) def link(query): return xpath("//a[text()='%s']" % query.replace("\'", "\\'")) def css(query): result = [] for ele in self.main_frame.findAllElements(query): if not ele.isNull(): result.append(ele.geometry().center()) return result def xpath(query): positions = self.evaluate(""" function GetAbsoluteLocationEx(element) { if ( arguments.length != 1 || element == null ) { return null; } var elmt = element; var offsetTop = elmt.offsetTop; var offsetLeft = elmt.offsetLeft; var offsetWidth = elmt.offsetWidth; var offsetHeight = elmt.offsetHeight; while( elmt = elmt.offsetParent ) { // add this judge if ( elmt.style.position == 'absolute' || elmt.style.position == 'relative' || ( elmt.style.overflow != 'visible' && elmt.style.overflow != '' ) ) { break; } offsetTop += elmt.offsetTop; offsetLeft += elmt.offsetLeft; } return { absoluteTop: offsetTop, absoluteLeft: offsetLeft, offsetWidth: offsetWidth, offsetHeight: offsetHeight }; } result=[]; for (var r = document.evaluate('%s', document, null, 5, null), n; n = r.iterateNext();) { pos=GetAbsoluteLocationEx(n) result.push([pos.absoluteLeft+pos.offsetWidth/2.0,pos.absoluteTop+pos.offsetHeight/2.0]); } result """ % query.replace("\'", "\\'")) return [QPoint(*tuple(x)) for x in positions] return locals()[attr](val) def _move_page_center_to(self, qpoint): size = self.page.viewportSize() self.main_frame.setScrollPosition(qpoint - QPoint(size.width(), size.height()) / 2) def reload(self): """Reload page. @return: """ self.trigger_action('Reload', expect_loading=True) def back(self): self.trigger_action('Back') def forward(self): self.trigger_action('Forward') @can_load_page def trigger_action(self, action): """Trigger QWebPage::WebAction @param action: """ self.page.triggerAction(getattr(QWebPage, action)) def parser_selector(self, selector, attr=None, pattern=None, val=None): index = selector.find('=') if index <= 0: val = selector else: attr = selector[:index] value_ = selector[index + 1:] index = value_.find(':') if index > 0: pattern = value_[:index] val = value_[index + 1:] return attr, pattern, val @can_load_page @have_a_break def click(self, selector): qpoint = self.first_element_position(selector) if qpoint: return self.qpoint_to_tuple(self._click_position(qpoint)) @can_load_page @have_a_break def test(self): return self.qpoint_to_tuple(QPoint(1, 2)) @can_load_page def _click_position(self, qpoint): self._move_page_center_to(qpoint) self.webview.repaint() pos = qpoint - self.main_frame.scrollPosition() self._move_to_position(pos) QTest.mouseClick(self.webview, Qt.LeftButton, pos=pos) gevent.sleep(1) return pos def qpoint_to_tuple(self, qpoint): return qpoint.x(), qpoint.y() @have_a_break def move_to(self, selector): qpoint = self.first_element_position(selector) if qpoint: self._move_to_position(qpoint) return qpoint_to_tuple(qpoint) def move_at(self, x, y): return self._move_to_position(QPoint(x, y)) def _move_to_position(self, qpoint): QTest.mouseMove(self.webview, pos=qpoint) return qpoint @can_load_page @have_a_break def click_at(self, x, y): return self._click_position(QPoint(x, y)) @have_a_break def key_clicks(self, selector, text): if selector: self.click(selector) QTest.keyClicks(self.webview, text, delay=50) @have_a_break def type(self, selector, text): position = self.click(selector) ele = self._hit_element_from(QPoint(*position)) ele.setFocus() ele.evaluateJavaScript( """ core.events.setValue(this, '%s') """ % (text.replace("\n", "\\n").replace("\'", "\\'")) ) logger.debug('type %s %s' % (selector, text)) def _hit_element_from(self, position): return self.main_frame.hitTestContent(position).element() def first_element(self, selector): position = self.first_element_position(selector) if position: return self.main_frame.hitTestContent(position).element(), position def wait_forever(self): self.wait_for(lambda: False, time_for_stop=-1) @have_a_break def check(self, selector, checked=True): ele, position = self.first_element(selector) if ele and ele.tagName() == 'INPUT': if ele.attribute('type') in ['checkbox', 'radio']: ele_checked = ele.attribute('checked') == 'checked' or False if ele_checked != checked: self._click_position(position) else: raise ValueError("%s is not a checkbox or radio" % selector) @have_a_break def select(self, selector, value): def _select(query, select_by, select): select.evaluateJavaScript(""" triggerEvent(this, 'focus', false); var changed = false; var optionToSelect = '%s'; for (var i = 0; i < this.options.length; i++) { var option = this.options[i]; if (option.selected && option.%s != optionToSelect) { option.selected = false; changed = true; } else if (!option.selected && option.%s == optionToSelect) { option.selected = true; changed = true; } } if (changed) { triggerEvent(this, 'change', true); } """ % ( query.replace("\'", "\\'"), select_by, select_by)) def _add_selection(query, select_by, select, selected): select.evaluateJavaScript(""" triggerEvent(this, 'focus', false); var optionToSelect = '%s'; for (var i = 0; i < this.options.length; i++) { var option = this.options[i]; if (option.%s == optionToSelect) { option.selected = %s; triggerEvent(this, 'change', true); } } """ % ( query.replace("\'", "\\'"), select_by, selected and 'true' or 'false')) ele, position = self.first_element(selector) if ele and ele.tagName() == 'SELECT': ele.setFocus() if ele.attribute('multiple') == 'multiple': assert isinstance(value, list) for value_, selected in value: attr, pattern, val = self.parser_selector(value_, attr='text') _add_selection(val, attr, ele, selected) else: attr, pattern, val = self.parser_selector(value, attr='text') _select(val, attr, ele) def choose_file(self, selector, file): self._upload_file = file self.click(selector) self._upload_file = None def capture(self, selector=None): """Capture the images of selector. @param selector: Css selector. @return: Images """ elements = self.main_frame.documentElement().findAll(selector) imgs = [] for element in elements: geo = element.geometry() img = QImage(geo.width(), geo.height(), QImage.Format_ARGB32) painter = QPainter(img) element.render(painter) painter.end() imgs.append(img) return imgs def capture_to(self, path, selector=None): """Capture the images of selector to files. @param path: File path with index suffix. @param selector: Css selector. @return: The paths of saving. """ _, ext = os.path.splitext(path) ext = ext[1:] imgs = self.capture(selector) result = [] for index, img in enumerate(imgs): filepath = '%s.%s' % (path, index) if img.save(filepath, ext.upper()): result.append(filepath) return result def capture_to_buf(self, selector=None): """capture the images of selector to StringIO @param selector: Css selector. @return: The StringIO list. """ images = self.capture(selector) result = [] for image in images: ba = QByteArray() buf = QBuffer(ba) buf.open(QIODevice.ReadWrite) image.save(buf, 'jpg') stream = StringIO(str(buf.buffer())) result.append(stream) return result @can_load_page def evaluate(self, script): """Evaluates script in page frame. @param script: The script to evaluate. """ result = self.main_frame.evaluateJavaScript("%s" % script) # if isinstance(result,QString): # result=unicode(result) return result def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. @param path: The path of the file. @param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def __del__(self): """Depend on the CG of Python. """ self.exit() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) def exists(self, selector): """Checks if element exists for given selector. @param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() #TODO: Still not work. # def remove_css(self): # """Remore the css,speed up page loading. # # @return: # """ # # return self.evaluate("""var targetelement="link";//determine element type to create nodelist from # var targetattr="href"//determine corresponding attribute to test for # var allsuspects=document.getElementsByTagName(targetelement) # for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove # if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null ) # allsuspects[i].parentNode.removeChild(allsuspects[i]); //remove element by calling parentNode.removeChild() # } # """) def filter_resources(self, pattern): """Filter resources with pattern. @param pattern: Match pattern. @param resources: @return: @raise: """ if isinstance(pattern, str): is_match = lambda x: pattern == x elif isinstance(pattern, _pattern_type): is_match = lambda x: pattern.match(x) elif hasattr(pattern, '__call__'): is_match = pattern else: raise TypeError('pattern must be one of str,re.compile,callable') return filter(lambda x: is_match(x.request_url), self.http_resources)[:] def save(self, path): """Save current page content to the path. @param path: The path to save. """ f = open(path, 'w') f.write(self.content().encode('utf-8')) f.close() def global_exists(self, global_name): """Checks if javascript global exists. @param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name) def load_cookies( self, cookie_storage, keep_old=False ): """load from cookielib's CookieJar or Set-Cookie3 format text file. @param cookie_storage: file location string on disk or CookieJar instance. @param keep_old: Don't reset, keep cookies not overridden. """ def toQtCookieJar( PyCookieJar, QtCookieJar ): allCookies = QtCookieJar.cookies if keep_old else [] for pc in PyCookieJar: qc = toQtCookie(pc) allCookies.append(qc) QtCookieJar.setAllCookies(allCookies) def toQtCookie(PyCookie): qc = QNetworkCookie(PyCookie.name, PyCookie.value) qc.setSecure(PyCookie.secure) if PyCookie.path_specified: qc.setPath(PyCookie.path) if PyCookie.domain != "": qc.setDomain(PyCookie.domain) if PyCookie.expires != 0: t = QDateTime() t.setTime_t(PyCookie.expires) qc.setExpirationDate(t) # not yet handled(maybe less useful): # py cookie.rest / QNetworkCookie.setHttpOnly() return qc if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) cj.load() toQtCookieJar(cj, self.cookie_jar) elif cookie_storage.__class__.__name__.endswith('CookieJar'): toQtCookieJar(cookie_storage, self.cookie_jar) else: raise ValueError('unsupported cookie_storage type.') def save_cookies(self, cookie_storage): """Save to cookielib's CookieJar or Set-Cookie3 format text file. @param cookie_storage: file location string or CookieJar instance. """ def toPyCookieJar(QtCookieJar, PyCookieJar): for c in QtCookieJar.allCookies(): PyCookieJar.set_cookie(toPyCookie(c)) def toPyCookie(QtCookie): port = None port_specified = False secure = QtCookie.isSecure() name = str(QtCookie.name()) value = str(QtCookie.value()) v = str(QtCookie.path()) path_specified = bool(v != "") path = v if path_specified else None v = str(QtCookie.domain()) domain_specified = bool(v != "") domain = v domain_initial_dot = v.startswith('.') if domain_specified else None v = int(QtCookie.expirationDate().toTime_t()) # Long type boundary on 32bit platfroms; avoid ValueError expires = 2147483647 if v > 2147483647 else v rest = {} discard = False return Cookie(0, name, value, port, port_specified, domain , domain_specified, domain_initial_dot, path, path_specified , secure, expires, discard, None, None, rest) if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) toPyCookieJar(self.cookie_jar, cj) cj.save() elif cookie_storage.__class__.__name__.endswith('CookieJar'): toPyCookieJar(self.cookie_jar, cookie_storage) else: raise ValueError('unsupported cookie_storage type.') def wait_for_confirm(self, confirm=True, callback=None): """Statement that tells GRobot how to deal with javascript confirm(). @param confirm: A bollean that confirm. @param callable: A callable that returns a boolean for confirmation. """ self._robot._confirm_expected = (confirm, callback) self._robot.wait_for(lambda: self._robot._confirm_expected is None) return self.popup_messages def wait_for_text(self, text, time_for_stop=None): """Waits until given text appear on main frame. @param text: The text to wait for. @return: """ logger.debug("Wait for text %s" % text) self.wait_for(lambda: text in self.content(), "Can\'t find '%s' in current frame" % text, time_for_stop=time_for_stop) return self.wait_for_page_loaded() def wait_for_xpath(self, expression, time_for_stop=None): self.wait_for(lambda: XPath(self.content()).execute(expression), "Can't find xpath=%s in current frame" % expression, time_for_stop=time_for_stop) return self.wait_for_page_loaded() def wait_for_selector(self, selector): """Waits until selector match an element on the frame. @param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) def wait_for_page_loaded(self, time_for_stop=None): """Waits until page is loaded, assumed that a page as been requested. """ return self.wait_for(lambda: self._loaded, 'Unable to load requested page', time_for_stop=time_for_stop) def wait_for(self, condition, timeout_message='', time_for_stop=None): """Waits until condition is True. @param condition: A callable that returns the condition. @param timeout_message: The exception message on timeout.-1 means never timeout. """ if self._loaded: time_for_stop = time_for_stop or self.operate_timeout else: time_for_stop = time_for_stop or self.loading_timeout started_at = time.time() while not condition(): if time_for_stop != -1 and time.time() > (started_at + time_for_stop): if self._loaded: raise OperateTimeout(timeout_message) else: # raise LoadingTimeout, timeout_message self.trigger_action('Stop') #QWebPage::Stop self._loaded = True logger.warning("Page loading timeout.Force to stop the page") break gevent.sleep(2) def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: self._alert is not None, 'User has not been alerted.') msg, self._alert = self._alert, None return msg def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources, self.http_resources = self.http_resources[:], [] return last_resources def _page_loaded(self, success): if self.develop and self.display: if self.inspector is None: self.inspector = QWebInspector() self.inspector.setPage(self.page) self.inspector.show() scripts = [ 'atoms.js', 'htmlutils.js', ] if self.jquery_namespace: scripts.append('jquery-1.9.1.min.js', ) for script in scripts: self.evaluate_js_file(os.path.dirname(__file__) + '/javascripts/' + script) if self.jquery_namespace: self.evaluate("%s=jQuery.noConflict();" % self.jquery_namespace) self._loaded = True # self.cache.clear() logger.debug("Page load finished") def _page_load_started(self): logger.debug("Start load page") self._loaded = False def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. @param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache, reply.readAll())) def _link_clicked(self, href): """Contorl the page link clicked event,forbid open new window. @param href: The href attribute of a tag. """ self.main_frame.load(href) def _request_ended(self, reply): """Adds an HttpResource object to http_resources. @param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache)) def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. @param mix: The QNetworkReply or QNetworkProxy object. @param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _on_manager_ssl_errors(self, reply, errors): """Ingore all the ssl error @param reply: @param errors: """ url = str(reply.url().toString()) if self.ignore_ssl_errors: reply.ignoreSslErrors() else: logger.warning('SSL certificate error: %s' % url) def exit(self): """Destroy the Qt main event loop. """ GRobot.exit_lock.acquire() if not self._deleted: if self.inspector: self.inspector.close() sip.delete(self.inspector) if self.display: self.webview.close() sip.delete(self.webview) if self.page and not sip.isdeleted(self.page): sip.delete(self.page) GRobot._liveRobot -= 1 if GRobot._liveRobot == 0 and GRobot._loop is not None: GRobot._kill_loop=gevent.spawn_later(20,self.kill_loop) self._deleted = True GRobot.exit_lock.release() def kill_loop(self): GRobot._loop.stop() GRobot._loop = None GRobot._app = None if hasattr(self, 'xvfb'): GRobot.xvfb.terminate()
def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), cache_dir='/tmp/ghost.py'): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a ghost run oustside ' +\ 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) self.page = GhostWebPage(Ghost._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) self.page.setForwardUnsupportedContent(True) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show()
class ScudCloud(QtGui.QMainWindow): forceClose = False messages = 0 speller = Speller() title = 'ScudCloud' def __init__(self, debug = False, parent = None, minimized = None, settings_path = ""): super(ScudCloud, self).__init__(parent) self.debug = debug self.minimized = minimized self.setWindowTitle(self.title) self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id("scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: if isinstance(self.identifier, str): self.domains = self.identifier.split(",") else: self.domains = self.identifier self.startURL = self.normalize(self.domains[0]) else: self.domains = [] self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(self.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') self.tickler = QTimer(self) self.tickler.setInterval(1800000) # Watch for ScreenLock events if DBusQtMainLoop is not None: DBusQtMainLoop(set_as_default=True) sessionBus = dbus.SessionBus() # Ubuntu 12.04 and other distros sessionBus.add_match_string("type='signal',interface='org.gnome.ScreenSaver'") # Ubuntu 14.04 and above sessionBus.add_match_string("type='signal',interface='com.ubuntu.Upstart0_6'") sessionBus.add_message_filter(self.screenListener) self.tickler.timeout.connect(self.sendTickle) # If dbus is not present, tickler timer will act like a blocker to not send tickle too often else: self.tickler.setSingleShot(True) self.tickler.start() def screenListener(self, bus, message): event = message.get_member() # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above if event == "ActiveChanged" or event == "EventEmitted": arg = message.get_args_list()[0] # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above if (arg == True or arg == "desktop-lock") and self.tickler.isActive(): self.tickler.stop() elif (arg == False or arg == "desktop-unlock") and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def sendTickle(self): for i in range(0, self.stackedWidget.count()): self.stackedWidget.widget(i).sendTickle() def addWrapper(self, url): webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.page().networkAccessManager().setCache(self.diskCache) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # We don't want Flash (it causes a lot of trouble in some distros) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False) # We don't need Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute(QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute(QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "False") else: self.settings.setValue("Menu", "True") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): # We'll register the webpage shorcuts with the window too (Fixes #338) undo = self.current().pageAction(QtWebKit.QWebPage.Undo) redo = self.current().pageAction(QtWebKit.QWebPage.Redo) cut = self.current().pageAction(QtWebKit.QWebPage.Cut) copy = self.current().pageAction(QtWebKit.QWebPage.Copy) paste = self.current().pageAction(QtWebKit.QWebPage.Paste) back = self.current().pageAction(QtWebKit.QWebPage.Back) forward = self.current().pageAction(QtWebKit.QWebPage.Forward) reload = self.current().pageAction(QtWebKit.QWebPage.Reload) self.menus = { "file": { "preferences": self.createAction("Preferences", lambda : self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda : self.switchTo(Resources.SIGNIN_URL)), "signout": self.createAction("Signout", lambda : self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.createAction(undo.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Undo), undo.shortcut()), "redo": self.createAction(redo.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Redo), redo.shortcut()), "cut": self.createAction(cut.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Cut), cut.shortcut()), "copy": self.createAction(copy.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Copy), copy.shortcut()), "paste": self.createAction(paste.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Paste), paste.shortcut()), "back": self.createAction(back.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Back), back.shortcut()), "forward": self.createAction(forward.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Forward), forward.shortcut()), "reload": self.createAction(reload.text(), lambda : self.current().page().triggerAction(QtWebKit.QWebPage.Reload), reload.shortcut()), }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12) }, "help": { "help": self.createAction("Help and Feedback", lambda : self.current().help(), QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", lambda : self.current().helpCenter()), "about": self.createAction("About", lambda : self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["back"]) editMenu.addAction(self.menus["edit"]["forward"]) editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def normalize(self, url): if url.endswith(".slack.com"): url+= "/" elif not url.endswith(".slack.com/"): url = "https://"+url+".slack.com/" return url def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if len(self.domains) == 0: self.domains.append(teams[0]['team_url']) team_list = [t['team_url'] for t in teams] for t in teams: for i in range(0, len(self.domains)): self.domains[i] = self.normalize(self.domains[i]) # When team_icon is missing, the team already exists (Fixes #381, #391) if 'team_icon' in t: if self.domains[i] in team_list: add = next(item for item in teams if item['team_url'] == self.domains[i]) if 'team_icon' in add: self.leftPane.addTeam(add['id'], add['team_name'], add['team_url'], add['team_icon']['image_44'], add == teams[0]) # Adding new teams and saving loading positions if t['team_url'] not in self.domains: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0]) self.domains.append(t['team_url']) self.settings.setValue("Domain", self.domains) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) exists = True break if not exists: self.addWrapper(url) def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and (modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event); def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes if DBusQtMainLoop is None and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def titleChanged(self): self.setWindowTitle(self.current().title()) def setForceClose(self): self.forceClose = True def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.forceClose = False def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.setForceClose() self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new () item.property_set (Dbusmenu.MENUITEM_PROP_LABEL, "#"+c['name']) item.property_set ("id", c['name']) item.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon): if self.debug: print("Notification: title [{}] message [{}] icon [{}]".format(title, message, icon)) self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 unreads = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) highlights = widget.highlights unreads+= widget.unreads total+=highlights if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) if unreads > 0: self.setWindowTitle("*{}".format(self.title)) else: self.setWindowTitle(self.title) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.setWindowTitle("[{}]{}".format(str(total), self.title)) self.messages = total
class Ghost(object): """Ghost manages a QWebPage. :param user_agent: The default User-Agent header. :param wait_timeout: Maximum step duration in second. :param wait_callback: An optional callable that is periodically executed until Ghost stops waiting. :param log_level: The optional logging level. :param display: A boolean that tells ghost to displays UI. :param viewport_size: A tupple that sets initial viewport size. """ _alert = None _confirm_expected = None _prompt_expected = None _upload_file = None _app = None def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), cache_dir='/tmp/ghost.py'): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a ghost run oustside ' +\ 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) self.page = GhostWebPage(Ghost._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) self.page.setForwardUnsupportedContent(True) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def __del__(self): self.exit() def capture(self, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Returns snapshot as QImage. :param region: An optional tupple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ if region is None and selector is not None: region = self.region_for_selector(selector) if region: x1, y1, x2, y2 = region w, h = (x2 - x1), (y2 - y1) image = QImage(QSize(x2, y2), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() image = image.copy(x1, y1, w, h) else: image = QImage(self.page.viewportSize(), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() return image def capture_to(self, path, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Saves snapshot as image. :param path: The destination path. :param region: An optional tupple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ self.capture(region=region, format=format, selector=selector).save(path) @client_utils_required @can_load_page def click(self, selector): """Click the targeted element. :param selector: A CSS3 selector to targeted element. """ if not self.exists(selector): raise Exception("Can't find element to click") return self.evaluate('GhostUtils.click("%s");' % selector) class confirm: """Statement that tells Ghost how to deal with javascript confirm(). :param confirm: A bollean that confirm. :param callable: A callable that returns a boolean for confirmation. """ def __init__(self, confirm=True, callback=None): self.confirm = confirm self.callback = callback def __enter__(self): Ghost._confirm_expected = (self.confirm, self.callback) def __exit__(self, type, value, traceback): Ghost._confirm_expected = None @property def content(self): """Returns current frame HTML as a string.""" return unicode(self.main_frame.toHtml()) @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) @can_load_page def evaluate(self, script): """Evaluates script in page frame. :param script: The script to evaluate. """ return (self.main_frame.evaluateJavaScript("%s" % script), self._release_last_resources()) def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. :param path: The path of the file. :param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def exists(self, selector): """Checks if element exists for given selector. :param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() def exit(self): """Exits application and relateds.""" if self.display: self.webview.close() Ghost._app.quit() del self.manager del self.page del self.main_frame if hasattr(self, 'xvfb'): self.xvfb.terminate() @can_load_page def fill(self, selector, values): """Fills a form with provided values. :param selector: A CSS selector to the target form to fill. :param values: A dict containing the values. """ if not self.exists(selector): raise Exception("Can't find form") resources = [] for field in values: r, res = self.set_field_value("%s [name=%s]" % (selector, field), values[field]) resources.extend(res) return True, resources @client_utils_required @can_load_page def fire_on(self, selector, method): """Call method on element matching given selector. :param selector: A CSS selector to the target element. :param method: The name of the method to fire. :param expect_loading: Specifies if a page loading is expected. """ return self.evaluate('GhostUtils.fireOn("%s", "%s");' % (selector, method)) def global_exists(self, global_name): """Checks if javascript global exists. :param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name)[0] def hide(self): """Close the webview.""" try: self.webview.close() except: raise Exception("no webview to close") def open(self, address, method='get', headers={}, auth=None): """Opens a web page. :param address: The resource URL. :param method: The Http method. :param headers: An optional dict of extra request hearders. :param auth: An optional tupple of HTTP auth (username, password). :return: Page resource, All loaded resources. """ body = QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Exception("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl(0) for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.main_frame.load(request, method, body) self.loaded = False return self.wait_for_page_loaded() class prompt: """Statement that tells Ghost how to deal with javascript prompt(). :param value: A string value to fill in prompt. :param callback: A callable that returns the value to fill in. """ def __init__(self, value='', callback=None): self.value = value self.callback = callback def __enter__(self): Ghost._prompt_expected = (self.value, self.callback) def __exit__(self, type, value, traceback): Ghost._prompt_expected = None @client_utils_required def region_for_selector(self, selector): """Returns frame region for given selector as tupple. :param selector: The targeted element. """ geo = self.main_frame.findFirstElement(selector).geometry() try: region = (geo.left(), geo.top(), geo.right(), geo.bottom()) except: raise Exception("can't get region for selector '%s'" % selector) return region @can_load_page @client_utils_required def set_field_value(self, selector, value, blur=True): """Sets the value of the field matched by given selector. :param selector: A CSS selector that target the field. :param value: The value to fill in. :param blur: An optional boolean that force blur when filled in. """ def _set_text_value(selector, value): return self.evaluate('document.querySelector("%s").value=%s;' % (selector, json.dumps(value))) res, resources = None, [] element = self.main_frame.findFirstElement(selector) if element.isNull(): raise Exception('can\'t find element for %s"' % selector) self.fire_on(selector, 'focus') if element.tagName() in ["TEXTAREA", "SELECT"]: res, resources = _set_text_value(selector, value) elif element.tagName() == "INPUT": if element.attribute('type') in [ "color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week" ]: res, resources = _set_text_value(selector, value) elif element.attribute('type') == "checkbox": res, resources = self.evaluate( 'GhostUtils.setCheckboxValue("%s", %s);' % (selector, json.dumps(value))) elif element.attribute('type') == "radio": res, resources = self.evaluate( 'GhostUtils.setRadioValue("%s", %s);' % (selector, json.dumps(value))) elif element.attribute('type') == "file": Ghost._upload_file = value res, resources = self.click(selector) Ghost._upload_file = None else: raise Exception('unsuported field tag') if blur: self.fire_on(selector, 'blur') return res, resources def set_viewport_size(self, width, height): """Sets the page viewport size. :param width: An integer that sets width pixel count. :param height: An integer that sets height pixel count. """ self.page.setViewportSize(QSize(width, height)) def show(self): """Show current page inside a QWebView. """ self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def wait_for(self, condition, timeout_message): """Waits until condition is True. :param condition: A callable that returns the condition. :param timeout_message: The exception message on timeout. """ started_at = time.time() while not condition(): if time.time() > (started_at + self.wait_timeout): raise Exception(timeout_message) time.sleep(0.01) Ghost._app.processEvents() if self.wait_callback is not None: self.wait_callback() def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: Ghost._alert is not None, 'User has not been alerted.') msg = Ghost._alert Ghost._alert = None return msg, self._release_last_resources() def wait_for_page_loaded(self): """Waits until page is loaded, assumed that a page as been requested. """ self.wait_for(lambda: self.loaded, 'Unable to load requested page') resources = self._release_last_resources() page = None url = self.main_frame.url().toString() for resource in resources: if url == resource.url: page = resource return page, resources def wait_for_selector(self, selector): """Waits until selector match an element on the frame. :param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) return True, self._release_last_resources() def wait_for_text(self, text): """Waits until given text appear on main frame. :param text: The text to wait for. """ self.wait_for(lambda: text in self.content, 'Can\'t find "%s" in current frame' % text) return True, self._release_last_resources() def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. :param mix: The QNetworkReply or QNetworkProxy object. :param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _page_loaded(self): """Called back when page is loaded. """ self.loaded = True self.cache.clear() def _page_load_started(self): """Called back when page load started. """ self.loaded = False def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources = self.http_resources self.http_resources = [] return last_resources def _request_ended(self, reply): """Adds an HttpResource object to http_resources. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache)) def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append( HttpResource(reply, self.cache, reply.readAll()))
class Ghost(object): """Ghost manages a QWebPage. :param user_agent: The default User-Agent header. :param wait_timeout: Maximum step duration in second. :param wait_callback: An optional callable that is periodically executed until Ghost stops waiting. :param log_level: The optional logging level. :param display: A boolean that tells ghost to displays UI. :param viewport_size: A tuple that sets initial viewport size. :param ignore_ssl_errors: A boolean that forces ignore ssl errors. :param cache_dir: A directory path where to store cache datas. :param plugins_enabled: Enable plugins (like Flash). :param java_enabled: Enable Java JRE. :param plugin_path: Array with paths to plugin directories (default ['/usr/lib/mozilla/plugins']) :param download_images: Indicate if the browser should download images """ _alert = None _confirm_expected = None _prompt_expected = None _upload_file = None _app = None def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), ignore_ssl_errors=True, cache_dir=os.path.join(tempfile.gettempdir(), "ghost.py"), plugins_enabled=False, java_enabled=False, plugin_path=['/usr/lib/mozilla/plugins',], download_images=True, qt_debug=False): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.ignore_ssl_errors = ignore_ssl_errors self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Error('Xvfb is required to a ghost run outside ' + 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) qInstallMsgHandler(QTMessageProxy(qt_debug)) if plugin_path: for p in plugin_path: Ghost._app.addLibraryPath(p) self.popup_messages = [] self.page = GhostWebPage(Ghost._app, self) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.LocalStorageEnabled, True) self.page.setForwardUnsupportedContent(True) self.page.settings().setAttribute(QtWebKit.QWebSettings.AutoLoadImages, download_images) self.page.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, plugins_enabled) self.page.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, java_enabled) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) self.manager.sslErrors.connect(self._on_manager_ssl_errors) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: class MyQWebView(QtWebKit.QWebView): def sizeHint(self): return QSize(*viewport_size) self.webview = MyQWebView() if plugins_enabled: self.webview.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True) if java_enabled: self.webview.settings().setAttribute(QtWebKit.QWebSettings.JavaEnabled, True) self.webview.setPage(self.page) self.webview.show() else: self.webview = None def __del__(self): self.exit() def capture(self, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Returns snapshot as QImage. :param region: An optional tuple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ if region is None and selector is not None: region = self.region_for_selector(selector) if region: x1, y1, x2, y2 = region w, h = (x2 - x1), (y2 - y1) image = QImage(QSize(x2, y2), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() image = image.copy(x1, y1, w, h) else: self.main_frame.setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff) self.main_frame.setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff) self.page.setViewportSize(self.main_frame.contentsSize()) image = QImage(self.page.viewportSize(), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() return image def capture_to(self, path, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Saves snapshot as image. :param path: The destination path. :param region: An optional tuple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ self.capture(region=region, format=format, selector=selector).save(path) def print_to_pdf(self, path, paper_size = (8.5, 11.0), paper_margins = (0, 0, 0, 0), paper_units = QPrinter.Inch, zoom_factor = 1.0, ): """Saves page as a pdf file. See qt4 QPrinter documentation for more detailed explanations of options. :param path: The destination path. :param paper_size: A 2-tuple indicating size of page to print to. :param paper_margins: A 4-tuple indicating size of each margin. :param paper_units: Units for pager_size, pager_margins. :param zoom_factor: Scale the output content. """ assert len(paper_size) == 2 assert len(paper_margins) == 4 printer = QPrinter(mode = QPrinter.ScreenResolution) printer.setOutputFormat(QPrinter.PdfFormat) printer.setPaperSize(QtCore.QSizeF(*paper_size), paper_units) printer.setPageMargins(*(paper_margins + (paper_units,))) printer.setFullPage(True) printer.setOutputFileName(path) if self.webview is None: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.setZoomFactor(zoom_factor) self.webview.print_(printer) @can_load_page def click(self, selector): """Click the targeted element. :param selector: A CSS3 selector to targeted element. """ if not self.exists(selector): raise Error("Can't find element to click") return self.evaluate(""" var element = document.querySelector("%s"); var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click", true, true, window, 1, 1, 1, 1, 1, false, false, false, false, 0, element); element.dispatchEvent(evt) """ % selector) class confirm: """Statement that tells Ghost how to deal with javascript confirm(). :param confirm: A boolean to set confirmation. :param callable: A callable that returns a boolean for confirmation. """ def __init__(self, confirm=True, callback=None): self.confirm = confirm self.callback = callback def __enter__(self): Ghost._confirm_expected = (self.confirm, self.callback) def __exit__(self, type, value, traceback): Ghost._confirm_expected = None @property def content(self, to_unicode=True): """Returns current frame HTML as a string. :param to_unicode: Whether to convert html to unicode or not """ if to_unicode: return unicode(self.main_frame.toHtml()) else: return self.main_frame.toHtml() @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) def clear_alert_message(self): """Clears the alert message""" self._alert = None @can_load_page def evaluate(self, script): """Evaluates script in page frame. :param script: The script to evaluate. """ return (self.main_frame.evaluateJavaScript("%s" % script), self._release_last_resources()) def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. :param path: The path of the file. :param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def exists(self, selector): """Checks if element exists for given selector. :param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() def exit(self): """Exits application and related.""" if self.display: self.webview.close() Ghost._app.quit() del self.manager del self.page del self.main_frame if hasattr(self, 'xvfb'): self.xvfb.terminate() @can_load_page def fill(self, selector, values): """Fills a form with provided values. :param selector: A CSS selector to the target form to fill. :param values: A dict containing the values. """ if not self.exists(selector): raise Error("Can't find form") resources = [] for field in values: r, res = self.set_field_value("%s [name=%s]" % (selector, field), values[field]) resources.extend(res) return True, resources @can_load_page def fire_on(self, selector, method): """Call method on element matching given selector. :param selector: A CSS selector to the target element. :param method: The name of the method to fire. :param expect_loading: Specifies if a page loading is expected. """ return self.evaluate('document.querySelector("%s").%s();' % \ (selector, method)) def global_exists(self, global_name): """Checks if javascript global exists. :param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name)[0] def hide(self): """Close the webview.""" try: self.webview.close() except: raise Error("no webview to close") def load_cookies( self, cookie_storage, keep_old=False ): """load from cookielib's CookieJar or Set-Cookie3 format text file. :param cookie_storage: file location string on disk or CookieJar instance. :param keep_old: Don't reset, keep cookies not overridden. """ def toQtCookieJar( PyCookieJar, QtCookieJar ): allCookies = QtCookieJar.cookies if keep_old else [] for pc in PyCookieJar: qc = toQtCookie(pc) allCookies.append(qc) QtCookieJar.setAllCookies(allCookies) def toQtCookie(PyCookie): qc = QNetworkCookie( PyCookie.name, PyCookie.value ) qc.setSecure(PyCookie.secure) if PyCookie.path_specified: qc.setPath(PyCookie.path) if PyCookie.domain != "" : qc.setDomain(PyCookie.domain) if PyCookie.expires != 0: t = QDateTime() t.setTime_t(PyCookie.expires) qc.setExpirationDate(t) # not yet handled(maybe less useful): # py cookie.rest / QNetworkCookie.setHttpOnly() return qc if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) cj.load() toQtCookieJar(cj, self.cookie_jar) elif cookie_storage.__class__.__name__.endswith('CookieJar') : toQtCookieJar(cookie_storage, self.cookie_jar) else: raise ValueError, 'unsupported cookie_storage type.' def open(self, address, method='get', headers={}, auth=None, body=None, default_popup_response=None): """Opens a web page. :param address: The resource URL. :param method: The Http method. :param headers: An optional dict of extra request hearders. :param auth: An optional tuple of HTTP auth (username, password). :param body: An optional string containing a payload. :param default_popup_response: the default response for any confirm/ alert/prompt popup from the Javascript (replaces the need for the with blocks) :return: Page resource, All loaded resources. """ body = body or QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Error("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl(0) for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.main_frame.load(request, method, body) self.loaded = False Ghost._prompt_expected = (default_popup_response, None) Ghost._confirm_expected = (default_popup_response, None) return self.wait_for_page_loaded() class prompt: """Statement that tells Ghost how to deal with javascript prompt(). :param value: A string value to fill in prompt. :param callback: A callable that returns the value to fill in. """ def __init__(self, value='', callback=None): self.value = value self.callback = callback def __enter__(self): Ghost._prompt_expected = (self.value, self.callback) def __exit__(self, type, value, traceback): Ghost._prompt_expected = None def region_for_selector(self, selector): """Returns frame region for given selector as tuple. :param selector: The targeted element. """ geo = self.main_frame.findFirstElement(selector).geometry() try: region = (geo.left(), geo.top(), geo.right(), geo.bottom()) except: raise Error("can't get region for selector '%s'" % selector) return region def save_cookies(self, cookie_storage): """Save to cookielib's CookieJar or Set-Cookie3 format text file. :param cookie_storage: file location string or CookieJar instance. """ def toPyCookieJar(QtCookieJar, PyCookieJar): for c in QtCookieJar.allCookies(): PyCookieJar.set_cookie(toPyCookie(c)) def toPyCookie(QtCookie): port=None port_specified=False secure=QtCookie.isSecure() name=str(QtCookie.name()) value=str(QtCookie.value()) v = str(QtCookie.path()) path_specified = bool( v != "" ) path = v if path_specified else None v = str(QtCookie.domain()) domain_specified = bool( v != "" ) domain = v domain_initial_dot = v.startswith('.') if domain_specified else None v = long(QtCookie.expirationDate().toTime_t()) # Long type boundary on 32bit platfroms; avoid ValueError expires = 2147483647 if v > 2147483647 else v rest = {} discard = False return Cookie(0, name, value, port, port_specified, domain , domain_specified, domain_initial_dot, path, path_specified , secure, expires, discard, None, None, rest) if cookie_storage.__class__.__name__ == 'str': cj = LWPCookieJar(cookie_storage) toPyCookieJar(self.cookie_jar,cj) cj.save() elif cookie_storage.__class__.__name__.endswith('CookieJar') : toPyCookieJar(self.cookie_jar,cookie_storage) else: raise ValueError, 'unsupported cookie_storage type.' @can_load_page def set_field_value(self, selector, value, blur=True): """Sets the value of the field matched by given selector. :param selector: A CSS selector that target the field. :param value: The value to fill in. :param blur: An optional boolean that force blur when filled in. """ def _set_checkbox_value(el, value): el.setFocus() if value is True: el.setAttribute('checked', 'checked') else: el.removeAttribute('checked') def _set_checkboxes_value(els, value): for el in els: if el.attribute('value') == value: _set_checkbox_value(el, True) else: _set_checkbox_value(el, False) def _set_radio_value(els, value): for el in els: if el.attribute('value') == value: el.setFocus() el.setAttribute('checked', 'checked') def _set_text_value(el, value): el.setFocus() el.setAttribute('value', value) def _set_select_value(el, value): el.setFocus() self.evaluate('document.querySelector("%s").value = "%s";' % (selector.replace('"', '\"'), value.replace('"', '\"'))) def _set_textarea_value(el, value): el.setFocus() el.setPlainText(value) res, ressources = None, [] element = self.main_frame.findFirstElement(selector) if element.isNull(): raise Error('can\'t find element for %s"' % selector) if element.tagName() == "SELECT": _set_select_value(element, value) elif element.tagName() == "TEXTAREA": _set_textarea_value(element, value) elif element.tagName() == "INPUT": if element.attribute('type') in ["color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week"]: _set_text_value(element, value) elif element.attribute('type') == "checkbox": els = self.main_frame.findAllElements(selector) if els.count() > 1: _set_checkboxes_value(els, value) else: _set_checkbox_value(element, value) elif element.attribute('type') == "radio": _set_radio_value(self.main_frame.findAllElements(selector), value) elif element.attribute('type') == "file": Ghost._upload_file = value res, resources = self.click(selector) Ghost._upload_file = None else: raise Error('unsuported field tag') if blur: self.fire_on(selector, 'blur') return res, ressources def set_proxy(self, type, host='localhost', port=8888, user='', password=''): """Set up proxy for FURTHER connections. :param type: proxy type to use: \ none/default/socks5/https/http. :param host: proxy server ip or host name. :param port: proxy port. """ _types = {'default': QNetworkProxy.DefaultProxy, 'none': QNetworkProxy.NoProxy, 'socks5': QNetworkProxy.Socks5Proxy, 'https': QNetworkProxy.HttpProxy, 'http': QNetworkProxy.HttpCachingProxy } if type is None: type='none' type = type.lower() if type in ['none','default'] : self.manager.setProxy(QNetworkProxy(_types[type])) return elif type in _types: proxy = QNetworkProxy(_types[type], hostName=host, port=port , user=user, password=password ) self.manager.setProxy(proxy) else: raise ValueError, 'Unsupported proxy type:' + type \ + '\nsupported types are: none/socks5/http/https/default' def set_viewport_size(self, width, height): """Sets the page viewport size. :param width: An integer that sets width pixel count. :param height: An integer that sets height pixel count. """ self.page.setViewportSize(QSize(width, height)) def append_popup_message(self, message): self.popup_messages.append(str(message)) def show(self): """Show current page inside a QWebView. """ self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def sleep(self, value): started_at = time.time() while True: if time.time() > (started_at + value): break time.sleep(0.01) Ghost._app.processEvents() def wait_for(self, condition, timeout_message): """Waits until condition is True. :param condition: A callable that returns the condition. :param timeout_message: The exception message on timeout. """ started_at = time.time() while not condition(): if time.time() > (started_at + self.wait_timeout): raise TimeoutError(timeout_message) time.sleep(0.01) Ghost._app.processEvents() if self.wait_callback is not None: self.wait_callback() def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: Ghost._alert is not None, 'User has not been alerted.') msg = Ghost._alert Ghost._alert = None return msg, self._release_last_resources() def wait_for_page_loaded(self): """Waits until page is loaded, assumed that a page as been requested. """ self.wait_for(lambda: self.loaded, 'Unable to load requested page') resources = self._release_last_resources() page = None url = self.main_frame.url().toString() url_without_hash = url.split("#")[0] for resource in resources: if url == resource.url or url_without_hash == resource.url: page = resource return page, resources def wait_for_selector(self, selector): """Waits until selector match an element on the frame. :param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) return True, self._release_last_resources() def wait_for_text(self, text): """Waits until given text appear on main frame. :param text: The text to wait for. """ self.wait_for(lambda: text in self.content, 'Can\'t find "%s" in current frame' % text) return True, self._release_last_resources() def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. :param mix: The QNetworkReply or QNetworkProxy object. :param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _page_loaded(self): """Called back when page is loaded. """ self.loaded = True self.cache.clear() def _page_load_started(self): """Called back when page load started. """ self.loaded = False def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources = self.http_resources self.http_resources = [] return last_resources def _request_ended(self, reply): """Adds an HttpResource object to http_resources. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): Logger.log("[%s] bytesAvailable()= %s" %(str(reply.url()), reply.bytesAvailable()), level="debug") # Some web pages return cache headers that mandates not to cache the # reply, which means we won't find this QNetworkReply in the cache # object. In this case bytesAvailable will return > 0. # Such pages are www.etsy.com # This is a bit of a hack and due to the async nature of QT, might # not work at times. We should move to using some proxied implementation # of QNetworkManager and QNetworkReply in order to get the contents # of the requests properly rather than relying on the cache. if reply.bytesAvailable() > 0: content = reply.peek(reply.bytesAvailable()) else: content = None self.http_resources.append(HttpResource(reply, self.cache, content=content)) def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. :param reply: The QNetworkReply object. """ self.wait_for(lambda: reply.isFinished(), 'Download timeout.') if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache, reply.readAll())) def _on_manager_ssl_errors(self, reply, errors): url = unicode(reply.url().toString()) if self.ignore_ssl_errors: reply.ignoreSslErrors() else: Logger.log('SSL certificate error: %s' % url, level='warning')
class Ghost(object): """Ghost manages a QWebPage. :param user_agent: The default User-Agent header. :param wait_timeout: Maximum step duration in second. :param wait_callback: An optional callable that is periodically executed until Ghost stops waiting. :param log_level: The optional logging level. :param display: A boolean that tells ghost to displays UI. :param viewport_size: A tupple that sets initial viewport size. """ _alert = None _confirm_expected = None _prompt_expected = None _upload_file = None _app = None def __init__(self, user_agent=default_user_agent, wait_timeout=8, wait_callback=None, log_level=logging.WARNING, display=False, viewport_size=(800, 600), cache_dir='/tmp/ghost.py'): self.http_resources = [] self.user_agent = user_agent self.wait_timeout = wait_timeout self.wait_callback = wait_callback self.loaded = True if not sys.platform.startswith('win') and not 'DISPLAY' in os.environ\ and not hasattr(Ghost, 'xvfb'): try: os.environ['DISPLAY'] = ':99' Ghost.xvfb = subprocess.Popen(['Xvfb', ':99']) except OSError: raise Exception('Xvfb is required to a ghost run oustside ' +\ 'an X instance') self.display = display if not Ghost._app: Ghost._app = QApplication.instance() or QApplication(['ghost']) self.page = GhostWebPage(Ghost._app) QtWebKit.QWebSettings.setMaximumPagesInCache(0) QtWebKit.QWebSettings.setObjectCacheCapacities(0, 0, 0) self.page.setForwardUnsupportedContent(True) self.set_viewport_size(*viewport_size) # Page signals self.page.loadFinished.connect(self._page_loaded) self.page.loadStarted.connect(self._page_load_started) self.page.unsupportedContent.connect(self._unsupported_content) self.manager = self.page.networkAccessManager() self.manager.finished.connect(self._request_ended) # Cache self.cache = QNetworkDiskCache() self.cache.setCacheDirectory(cache_dir) self.manager.setCache(self.cache) # Cookie jar self.cookie_jar = QNetworkCookieJar() self.manager.setCookieJar(self.cookie_jar) # User Agent self.page.setUserAgent(self.user_agent) self.page.networkAccessManager().authenticationRequired\ .connect(self._authenticate) self.page.networkAccessManager().proxyAuthenticationRequired\ .connect(self._authenticate) self.main_frame = self.page.mainFrame() logger.setLevel(log_level) if self.display: self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def __del__(self): self.exit() def capture(self, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Returns snapshot as QImage. :param region: An optional tupple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ if region is None and selector is not None: region = self.region_for_selector(selector) if region: x1, y1, x2, y2 = region w, h = (x2 - x1), (y2 - y1) image = QImage(QSize(x2, y2), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() image = image.copy(x1, y1, w, h) else: image = QImage(self.page.viewportSize(), format) painter = QPainter(image) self.main_frame.render(painter) painter.end() return image def capture_to(self, path, region=None, selector=None, format=QImage.Format_ARGB32_Premultiplied): """Saves snapshot as image. :param path: The destination path. :param region: An optional tupple containing region as pixel coodinates. :param selector: A selector targeted the element to crop on. :param format: The output image format. """ self.capture(region=region, format=format, selector=selector).save(path) @can_load_page def click(self, selector): """Click the targeted element. :param selector: A CSS3 selector to targeted element. """ if not self.exists(selector): raise Exception("Can't find element to click") return self.evaluate(""" var element = document.querySelector("%s"); var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click", true, true, window, 1, 1, 1, 1, 1, false, false, false, false, 0, element); element.dispatchEvent(evt) """ % selector) class confirm: """Statement that tells Ghost how to deal with javascript confirm(). :param confirm: A bollean that confirm. :param callable: A callable that returns a boolean for confirmation. """ def __init__(self, confirm=True, callback=None): self.confirm = confirm self.callback = callback def __enter__(self): Ghost._confirm_expected = (self.confirm, self.callback) def __exit__(self, type, value, traceback): Ghost._confirm_expected = None @property def content(self): """Returns current frame HTML as a string.""" return unicode(self.main_frame.toHtml()) @property def cookies(self): """Returns all cookies.""" return self.cookie_jar.allCookies() def delete_cookies(self): """Deletes all cookies.""" self.cookie_jar.setAllCookies([]) @can_load_page def evaluate(self, script): """Evaluates script in page frame. :param script: The script to evaluate. """ return (self.main_frame.evaluateJavaScript("%s" % script), self._release_last_resources()) def evaluate_js_file(self, path, encoding='utf-8'): """Evaluates javascript file at given path in current frame. Raises native IOException in case of invalid file. :param path: The path of the file. :param encoding: The file's encoding. """ self.evaluate(codecs.open(path, encoding=encoding).read()) def exists(self, selector): """Checks if element exists for given selector. :param string: The element selector. """ return not self.main_frame.findFirstElement(selector).isNull() def exit(self): """Exits application and relateds.""" if self.display: self.webview.close() Ghost._app.quit() del self.manager del self.page del self.main_frame if hasattr(self, 'xvfb'): self.xvfb.terminate() @can_load_page def fill(self, selector, values): """Fills a form with provided values. :param selector: A CSS selector to the target form to fill. :param values: A dict containing the values. """ if not self.exists(selector): raise Exception("Can't find form") resources = [] for field in values: r, res = self.set_field_value("%s [name=%s]" % (selector, field), values[field]) resources.extend(res) return True, resources @can_load_page def fire_on(self, selector, method): """Call method on element matching given selector. :param selector: A CSS selector to the target element. :param method: The name of the method to fire. :param expect_loading: Specifies if a page loading is expected. """ return self.evaluate('document.querySelector("%s").%s();' % \ (selector, method)) def global_exists(self, global_name): """Checks if javascript global exists. :param global_name: The name of the global. """ return self.evaluate('!(typeof %s === "undefined");' % global_name)[0] def hide(self): """Close the webview.""" try: self.webview.close() except: raise Exception("no webview to close") def open(self, address, method='get', headers={}, auth=None): """Opens a web page. :param address: The resource URL. :param method: The Http method. :param headers: An optional dict of extra request hearders. :param auth: An optional tupple of HTTP auth (username, password). :return: Page resource, All loaded resources. """ body = QByteArray() try: method = getattr(QNetworkAccessManager, "%sOperation" % method.capitalize()) except AttributeError: raise Exception("Invalid http method %s" % method) request = QNetworkRequest(QUrl(address)) request.CacheLoadControl(0) for header in headers: request.setRawHeader(header, headers[header]) self._auth = auth self._auth_attempt = 0 # Avoids reccursion self.main_frame.load(request, method, body) self.loaded = False return self.wait_for_page_loaded() class prompt: """Statement that tells Ghost how to deal with javascript prompt(). :param value: A string value to fill in prompt. :param callback: A callable that returns the value to fill in. """ def __init__(self, value='', callback=None): self.value = value self.callback = callback def __enter__(self): Ghost._prompt_expected = (self.value, self.callback) def __exit__(self, type, value, traceback): Ghost._prompt_expected = None def region_for_selector(self, selector): """Returns frame region for given selector as tupple. :param selector: The targeted element. """ geo = self.main_frame.findFirstElement(selector).geometry() try: region = (geo.left(), geo.top(), geo.right(), geo.bottom()) except: raise Exception("can't get region for selector '%s'" % selector) return region @can_load_page def set_field_value(self, selector, value, blur=True): """Sets the value of the field matched by given selector. :param selector: A CSS selector that target the field. :param value: The value to fill in. :param blur: An optional boolean that force blur when filled in. """ def _set_checkbox_value(el, value): el.setFocus() if value is True: el.setAttribute('checked', 'checked') else: el.removeAttribute('checked') def _set_checkboxes_value(els, value): for el in els: if el.attribute('value') == value: _set_checkbox_value(el, True) else: _set_checkbox_value(el, False) def _set_radio_value(els, value): for el in els: if el.attribute('value') == value: el.setFocus() el.setAttribute('checked', 'checked') def _set_text_value(el, value): el.setFocus() el.setAttribute('value', value) def _set_textarea_value(el, value): el.setFocus() el.setPlainText(value) res, ressources = None, [] element = self.main_frame.findFirstElement(selector) if element.isNull(): raise Exception('can\'t find element for %s"' % selector) if element.tagName() == "SELECT": _set_text_value(element, value) elif element.tagName() == "TEXTAREA": _set_textarea_value(element, value) elif element.tagName() == "INPUT": if element.attribute('type') in ["color", "date", "datetime", "datetime-local", "email", "hidden", "month", "number", "password", "range", "search", "tel", "text", "time", "url", "week"]: _set_text_value(element, value) elif element.attribute('type') == "checkbox": els = self.main_frame.findAllElements(selector) if els.count() > 1: _set_checkboxes_value(els, value) else: _set_checkbox_value(element, value) elif element.attribute('type') == "radio": _set_radio_value(self.main_frame.findAllElements(selector), value) elif element.attribute('type') == "file": Ghost._upload_file = value res, resources = self.click(selector) Ghost._upload_file = None else: raise Exception('unsuported field tag') if blur: self.fire_on(selector, 'blur') return res, ressources def set_viewport_size(self, width, height): """Sets the page viewport size. :param width: An integer that sets width pixel count. :param height: An integer that sets height pixel count. """ self.page.setViewportSize(QSize(width, height)) def show(self): """Show current page inside a QWebView. """ self.webview = QtWebKit.QWebView() self.webview.setPage(self.page) self.webview.show() def wait_for(self, condition, timeout_message): """Waits until condition is True. :param condition: A callable that returns the condition. :param timeout_message: The exception message on timeout. """ started_at = time.time() while not condition(): if time.time() > (started_at + self.wait_timeout): raise Exception(timeout_message) time.sleep(0.01) Ghost._app.processEvents() if self.wait_callback is not None: self.wait_callback() def wait_for_alert(self): """Waits for main frame alert(). """ self.wait_for(lambda: Ghost._alert is not None, 'User has not been alerted.') msg = Ghost._alert Ghost._alert = None return msg, self._release_last_resources() def wait_for_page_loaded(self): """Waits until page is loaded, assumed that a page as been requested. """ self.wait_for(lambda: self.loaded, 'Unable to load requested page') resources = self._release_last_resources() page = None url = self.main_frame.url().toString() for resource in resources: if url == resource.url: page = resource return page, resources def wait_for_selector(self, selector): """Waits until selector match an element on the frame. :param selector: The selector to wait for. """ self.wait_for(lambda: self.exists(selector), 'Can\'t find element matching "%s"' % selector) return True, self._release_last_resources() def wait_for_text(self, text): """Waits until given text appear on main frame. :param text: The text to wait for. """ self.wait_for(lambda: text in self.content, 'Can\'t find "%s" in current frame' % text) return True, self._release_last_resources() def _authenticate(self, mix, authenticator): """Called back on basic / proxy http auth. :param mix: The QNetworkReply or QNetworkProxy object. :param authenticator: The QAuthenticator object. """ if self._auth_attempt == 0: username, password = self._auth authenticator.setUser(username) authenticator.setPassword(password) self._auth_attempt += 1 def _page_loaded(self): """Called back when page is loaded. """ self.loaded = True self.cache.clear() def _page_load_started(self): """Called back when page load started. """ self.loaded = False def _release_last_resources(self): """Releases last loaded resources. :return: The released resources. """ last_resources = self.http_resources self.http_resources = [] return last_resources def _request_ended(self, reply): """Adds an HttpResource object to http_resources. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache)) def _unsupported_content(self, reply): """Adds an HttpResource object to http_resources with unsupported content. :param reply: The QNetworkReply object. """ if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): self.http_resources.append(HttpResource(reply, self.cache, reply.readAll()))
def construct(path=defaults.CACHE_PATH, size=defaults.CACHE_SIZE): cache = QNetworkDiskCache() cache.setCacheDirectory(path) cache.setMaximumCacheSize(size * 1024**2) return cache
class ScudCloud(QtGui.QMainWindow): plugins = True debug = False forceClose = False messages = 0 speller = Speller() def __init__(self, parent=None, settings_path=""): super(ScudCloud, self).__init__(parent) self.setWindowTitle('ScudCloud') self.settings_path = settings_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud.cfg', QSettings.IniFormat) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtGui.QStackedWidget() centralWidget = QtGui.QWidget(self) layout = QtGui.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: self.startURL = self.domain() self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(ScudCloud.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') def addWrapper(self, url): webView = Wrapper(self) webView.page().networkAccessManager().setCookieJar(self.cookiesjar) webView.page().networkAccessManager().setCache(self.diskCache) webView.load(QtCore.QUrl(url)) webView.show() self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # Required by Youtube videos (HTML5 video support only on Qt5) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, self.plugins) # We don't want Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute( QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.settings_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "False") else: self.settings.setValue("Menu", "True") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addMenu(self): self.menus = { "file": { "preferences": self.createAction("Preferences", lambda: self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda: self.switchTo(Resources.SIGNIN_URL)), "signout": self.createAction("Signout", lambda: self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": {}, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12) }, "help": { "help": self.createAction("Help and Feedback", lambda: self.current().help(), QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", lambda: self.current().helpCenter()), "about": self.createAction("About", lambda: self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) self.editMenu = menu.addMenu("&Edit") self.updateEditMenu() viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) if Unity is None: viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def updateEditMenu(self): self.editMenu.clear() self.menus["edit"] = { "undo": self.current().pageAction(QtWebKit.QWebPage.Undo), "redo": self.current().pageAction(QtWebKit.QWebPage.Redo), "cut": self.current().pageAction(QtWebKit.QWebPage.Cut), "copy": self.current().pageAction(QtWebKit.QWebPage.Copy), "paste": self.current().pageAction(QtWebKit.QWebPage.Paste), "back": self.current().pageAction(QtWebKit.QWebPage.Back), "forward": self.current().pageAction(QtWebKit.QWebPage.Forward), "reload": self.current().pageAction(QtWebKit.QWebPage.Reload) } self.editMenu.addAction(self.menus["edit"]["undo"]) self.editMenu.addAction(self.menus["edit"]["redo"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["cut"]) self.editMenu.addAction(self.menus["edit"]["copy"]) self.editMenu.addAction(self.menus["edit"]["paste"]) self.editMenu.addSeparator() self.editMenu.addAction(self.menus["edit"]["back"]) self.editMenu.addAction(self.menus["edit"]["forward"]) self.editMenu.addAction(self.menus["edit"]["reload"]) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtGui.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def domain(self): if self.identifier.endswith(".slack.com"): return self.identifier else: return "https://" + self.identifier + ".slack.com" def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): for t in teams: # If team_icon is not present, it's because team is already connected if 'team_icon' in t: self.leftPane.addTeam(t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0]) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) exists = True break if not exists: self.addWrapper(url) self.updateEditMenu() def eventFilter(self, obj, event): if event.type( ) == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtGui.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtGui.QMainWindow.eventFilter(self, obj, event) def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() def titleChanged(self): self.setWindowTitle(self.current().title()) def setForceClose(self): self.forceClose = True def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) # Let's save the first team registered as default qUrl = self.stackedWidget.widget(0).url() if self.identifier is None and Resources.MESSAGES_URL_RE.match( qUrl.toString()): self.settings.setValue("Domain", 'https://' + qUrl.host()) self.forceClose = False def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): self.setForceClose() self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if c['is_member']: item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c['name']) item.property_set("id", c['name']) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon=None): self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() def count(self): total = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) messages = widget.highlights if messages == 0: self.leftPane.stopAlert(widget.team()) else: self.leftPane.alert(widget.team()) if messages is not None: total += messages if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.messages = total