Showing posts with label gmonitor. Show all posts
Showing posts with label gmonitor. Show all posts

Wednesday, January 20, 2010

More fun with QWebKit

In the previous post I wrote about calling python methods and accessing properties from JavaScript executed in QWebKit. In this post I'll show you how to do the other way around, calling a JavaScript function from Python.

I would like to thank Rich Moore for commenting on the previous post and pointing out that you should re-add your python object  every time the javaScriptWindowObjectCleared() signal is emitted.

Actually, I think this is almost to simple to do a post about so I'll try doing something fun/useful with it.

QWebFrame contains a public slot:
QVariant evaluateJavaScript(const QString & scriptSource)
You simply pass the JavaScript-snippet to the function. The snippet will be evaluated using the frame as context and returns the result of the last executed statement. This makes it possible to 'click' submit buttons, fill form fields and other interesting stuff in the currently loaded frame. I'll give you an example on how you can create an auto-login GMail widget. The widget will automatically login the user by populating the login form with the user's credentials and 'click' the login button.
#!/usr/bin/env python
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtWebKit

javaScriptLogin = """
document.getElementsByName('Email').item(0).value='{email}';
document.getElementsByName('Passwd').item(0).value='{password}';
document.getElementsByName('signIn').item(0).click(); void(0);
"""

class GmailWebView(QtWebKit.QWebView):
    def __init__(self, parent=None):
        super(GmailWebView, self).__init__(parent)
        self.loggedIn = False

    def login(self, url, email, password):
        """Login to gmail."""
        self.url = QtCore.QUrl(url)
        self.email = email
        self.password = password  
        self.loadFinished.connect(self._loadFinished)
        self.load(self.url)

    def createWindow(self, windowType):
        """Load links in the same web-view."""
        return self

    def _loadFinished(self):
        if self.loggedIn:
            self.loadFinished.disconnect(self._loadFinished)

        self.loggedIn = True
        jscript = javaScriptLogin.format(email=self.email, password=self.password)
        self.page().mainFrame().evaluateJavaScript(jscript)

    def contextMenuEvent(self, event):
        """Add a 'Back to GMail' entry."""
        menu = self.page().createStandardContextMenu()
        menu.addSeparator()
        action = menu.addAction('Back to GMail')
        @action.triggered.connect
        def backToGMail():
            self.load(self.url)
        menu.exec_(QtGui.QCursor.pos())

def main():
    import sys
    qApp = QtGui.QApplication(sys.argv)

    # Prevents me from posting my password on the blog :)
    password, ok = QtGui.QInputDialog.getText(None, "Password request", "Enter password", QtGui.QLineEdit.Password)
    if not ok:
        return

    gmailWebView = GmailWebView()
    gmailWebView.login('https://mail.google.com/mail',
                       'mario.boikov',
                       password)
    gmailWebView.show()

    sys.exit(qApp.exec_())

if __name__ == "__main__":
    main()
This is just a quick hack, it lacks a bunch of checkings...

Maybe it's time to wipe the dust off my GMonitor-plasmoid and add support for opening my account in a new window based on the GMail widget above.

Thursday, November 5, 2009

GMonitor

Ah, I've finally managed to make my new fresh Kubuntu 9.10 installation usable. Now it's time to do some KDE stuff and I'll show you how easy it's to create a plasmoid (applet/widget) for KDE4.

I've created a simple applet which shows the number of unread mail in a Gmail inbox. We already know how to fetch the list with unread mail, which I have explained in a previous blog post. Now I'll create a nice applet showing the count. I'm going to keep it as simple as possible, writing hard-coded values, skip error handling and so on to avoid making things more complicated than necessary.

There are several good tutorials at techbase.kde.org which explains plasma programming. They cover the basics and explains things in detail so I'll only make references to the pages instead of repeating what's already been written.

The first tutorial you should read (you don't have to read it right now, you can read it after you tried doing the applet) is Python Plasma Getting Started. The tutorial covers setting up a simple plasmoid, packaging, installing and running.

I'll name the plasmoid GMonitor because it only monitors the mailbox and shows the number of unread mail, it will not actually try to do some kind of notification (yet?!).

I've put the code on github, you can clone it with:
$ git clone git://github.com/mariob/gmonitor.git
You'll find a Makefile in the git which can be useful when doing plasmoids. The Makefile supports installing, un-installing, viewing, packaging and updating the plasmoid.

The directory tree looks like this:
gmonitor/
|-- Makefile
|-- README
|-- contents
|   `-- code
|       `-- main.py
`-- metadata.desktop
The gmonitor directory is the project home and contents/code contains the python source. The metadata.desktop file will be explained below.

To be able to install and run your applet you need to provide a metadata.desktop file to plasma. The metadata.desktop file contains important information about the applet and you can read more about the file in the Plasma Getting Started tutorial.

Feel free to change the file. The Name specifies the applet name and the Icon field gives the name to the icon to associated with this applet. These two fields are typically shown when listing applets in the 'Add Widget' dialog. There are two important fields which must be present for plasma to run your applet, the X-Plasma-API field specifies which script-engine to be used and the X-Plasma-MainScript field which script to be executed. It's also good to know that the X-KDE-PluginInfo-Name fields is used as a plasmoid identification, so it should be unique.

Some notes about the implementation. There are two classes, GMonitor and MailFrame (I know, poor name). The MaiFrame class has an icon and a label. When the mail count is set to zero (by calling setCount(cnt)), the icon is disabled, grayed and the label is set to 'No new mail'. When the mail count is set to a value greater than zero, the icon is enabled, colored and the label is set to 'Count: x', where x is the number of mail in the inbox. See the images below.



Sample of what the applet looks like

When the icon is enabled and clicked, a 'clicked()' signal is emitted. The GMonitor class connects the openBrowser() method to the 'clicked()' signal. The openBrowser() method opens a default browser in KDE to load the google mail url. The GMonitor also sets up a timer to fetch the feed in 60 second interval. The fetchFeed() method uses the KIO framework in KDE to download the content from a URL and parseFeed() parses the downloaded feed and emits a 'mailcount' signal.

The implementation depends on feedparser which can be installed on Ubuntu by running:
$ sudo aptitude install python-feedparser
You can also download and put the feedparser.py file inside the code directory.

Not that bad. It's actually possible to create a very simple 'good looking' Gmail monitor in less than 100 lines of code (excluding comments) with Python and KDE.

To view this plasmoid without installing it run (while standing in the gmonitor folder):
$ make view
This might only work on KDE4.3, not sure if the plasmoidviewer in 4.2 supports running plasmoids without first installing them. If it fails you should install the plasmoid and run plasmoidviewer yourself (or modify the makefile):
$ make install
$ plasmoidviewer pysnippet-gmonitor
If you never heard of Qt signals and slots you should read this introduction on the topic. For example, a button can 'emit' a signal when it's clicked. A slot (function/method) can be connected to a signal and each time the signal is emitted the function/method will be called.

You can find the Python KDE4.3 API docs here. Unfortunately, I don't find them as good as the Qt docs.

When running the applet, KDE will ask for the username/password when it tries to connect to Google. You can check the 'remember the password' checkbox which will make KDE cache the password as long as the desktop session is alive. If you choose not to, you'll have to enter your username/password each time the code tries to fetch the feed. A better solution is to use KWallet.

Ok, hope you enjoyed it and start doing cool plasmoids!