diff options
Diffstat (limited to 'lang/py3-pyme/examples/PyGtkGpgKeys.py')
| -rwxr-xr-x | lang/py3-pyme/examples/PyGtkGpgKeys.py | 663 | 
1 files changed, 663 insertions, 0 deletions
| diff --git a/lang/py3-pyme/examples/PyGtkGpgKeys.py b/lang/py3-pyme/examples/PyGtkGpgKeys.py new file mode 100755 index 00000000..0221b05c --- /dev/null +++ b/lang/py3-pyme/examples/PyGtkGpgKeys.py @@ -0,0 +1,663 @@ +#!/usr/bin/env python3 +# $Id$ +# Copyright (C) 2005,2008 Igor Belyi <[email protected]> +# +#    This program is free software; you can redistribute it and/or modify +#    it under the terms of the GNU General Public License as published by +#    the Free Software Foundation; either version 2 of the License, or +#    (at your option) any later version. +# +#    This program is distributed in the hope that it will be useful, +#    but WITHOUT ANY WARRANTY; without even the implied warranty of +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +#    GNU General Public License for more details. +# +#    You should have received a copy of the GNU General Public License +#    along with this program; if not, write to the Free Software +#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA + +import gtk, gobject, gtk.glade +import time, sys, os +from pyme import callbacks, core, errors +from pyme.core import Data, Context, pubkey_algo_name +from pyme import constants +from pyme.constants import validity +from pyme.constants.keylist import mode + +# Thanks to Bernhard Reiter for pointing out the following: +# gpgme_check_version() necessary for initialisation according to  +# gpgme 1.1.6 and this is not done automatically in pyme-0.7.0 +print("gpgme version:", core.check_version(None)) + +# Convert trust constant into a string +trusts = {validity.UNKNOWN: "", +          validity.UNDEFINED: "Undefined", +          validity.NEVER: "Never", +          validity.MARGINAL: "Marginal", +          validity.FULL: "Full", +          validity.ULTIMATE: "Ultimate"} + +# Convert seconds into a date +def sec2str(secs): +    if secs > 0:    return time.strftime("%Y-%m-%d", time.gmtime(secs)) +    elif secs == 0: return "Unlimited" +    else:           return "" + +index = 0 +class KeyColumn: +    "Helper class for data columns." +    def __init__(self, name, gtype, vattr=None, tcols=None, +                 func=lambda x:x, view=None): +        """new(name, qtype, vattr, column, ocolumn, func): +        name  - column title +        qtype - gobject type to use in TreeStore for this column +        vattr - column data is visible if method vattr present in the object +        tcols - list of type specific columns to append its name to. +        func  - function converting object data into viewable presentation +        view  - to put or not the column in the view menu""" +        global index +        self.name = name +        self.type = gtype +        self.vattr = vattr +        self.func = func +        self.view = view +        self.index = index +        self.attrs = {} +        if tcols != None: tcols.append(name) +        index += 1 + +# List column names specific to an object type +key_columns = []                        # names only in key +uid_columns = []                        # names only in uids +sub_columns = []                        # names only in subkeys +sign_columns = []                       # names only in signatures +sub_sign_columns = []                   # names in subkeys and signatures + +# Explicite columns +visible_columns = [ +    KeyColumn("Secret", gobject.TYPE_BOOLEAN, "subkeys"), +    KeyColumn("Name", gobject.TYPE_STRING, "name", uid_columns, +              lambda x: x.name+(x.comment and " (%s)"%x.comment)), +    KeyColumn("Email", gobject.TYPE_STRING, "email", uid_columns, +              lambda x: x.email), +    KeyColumn("Owner Trust", gobject.TYPE_STRING, "owner_trust", key_columns, +              lambda x: trusts[x.owner_trust], True), +    KeyColumn("Type", gobject.TYPE_STRING, "pubkey_algo", sub_sign_columns, +              lambda x: pubkey_algo_name(x.pubkey_algo)), +    KeyColumn("Length", gobject.TYPE_INT, "length", sub_columns, +              lambda x: x.length), +    KeyColumn("Can Auth", gobject.TYPE_BOOLEAN,"can_authenticate", sub_columns, +              lambda x: x.can_authenticate, False), +    KeyColumn("Can Cert", gobject.TYPE_BOOLEAN, "can_certify", sub_columns, +              lambda x: x.can_certify, False), +    KeyColumn("Can Encr", gobject.TYPE_BOOLEAN, "can_encrypt", sub_columns, +              lambda x: x.can_encrypt, False), +    KeyColumn("Can Sign", gobject.TYPE_BOOLEAN, "can_sign", sub_columns, +              lambda x: x.can_sign, False), +    KeyColumn("Created", gobject.TYPE_STRING, "timestamp", sub_sign_columns, +              lambda x: sec2str(x.timestamp), True), +    KeyColumn("Expires", gobject.TYPE_STRING, "expires", sub_sign_columns, +              lambda x: sec2str(x.expires), True), +    KeyColumn("Id", gobject.TYPE_STRING, "keyid", sub_sign_columns, +              lambda x: x.keyid) +    ] + +helper_columns = [ +    KeyColumn("Name Invalid", gobject.TYPE_BOOLEAN, None, uid_columns, +              lambda x: x.revoked or x.invalid), +    KeyColumn("Subkey Invalid", gobject.TYPE_BOOLEAN, None, sub_sign_columns, +              lambda x: x.revoked or x.invalid or x.expired), +    KeyColumn("FPR", gobject.TYPE_STRING, None, sub_columns, +              lambda x: x.fpr) +    ] + +# Calculate implicite columns - defining visibility of the data in a column. +# In the same loop calculate tuple for rows having only name in them. +name_only = () +for item in visible_columns: +    vis_item = KeyColumn("Show"+item.name, gobject.TYPE_BOOLEAN) +    helper_columns.append(vis_item) +    item.attrs["visible"] = vis_item.index +    name_only += (vis_item.index, item.name == "Name") + +columns = {} +for item in visible_columns + helper_columns: +    columns[item.name] = item + +# Use strikethrough to indicate revoked or invalid keys and uids +columns["Name"].attrs["strikethrough"] = columns["Name Invalid"].index +columns["Id"].attrs["strikethrough"] = columns["Subkey Invalid"].index + +def pair(name, value): +    "pair(name, value) creates (index, func(value)) tuple based on column name" +    item = columns[name] +    if item.index < len(visible_columns): +        return (item.index, item.func(value), columns["Show"+name].index, True) +    else: +        return (item.index, item.func(value)) + +class PyGtkGpgKeys: +    "Main class representing PyGtkGpgKeys application" +    def error_message(self, text, parent=None): +        dialog = gtk.MessageDialog(parent or self.mainwin, +                                   gtk.DIALOG_MODAL | +                                   gtk.DIALOG_DESTROY_WITH_PARENT, +                                   gtk.MESSAGE_ERROR, +                                   gtk.BUTTONS_OK, +                                   text) +        dialog.run() +        dialog.destroy()         + +    def yesno_message(self, text, parent=None): +        dialog = gtk.MessageDialog(parent or self.mainwin, +                                   gtk.DIALOG_MODAL | +                                   gtk.DIALOG_DESTROY_WITH_PARENT, +                                   gtk.MESSAGE_QUESTION, +                                   gtk.BUTTONS_YES_NO, +                                   text) +        result = dialog.run() == gtk.RESPONSE_YES +        dialog.destroy() +        return result +     +    def load_keys(self, first_time=False): +        if not first_time: self.model.clear() +        secret_keys = {} +        for key in self.context.op_keylist_all(None, 1): +            secret_keys[key.subkeys[0].fpr] = 1 +        for key in self.context.op_keylist_all(None, 0): +            self.add_key(key, key.subkeys[0].fpr in secret_keys) +     +    def add_key(self, key, secret): +        "self.add_key(key) - add key to the TreeStore model" +        iter = self.model.append(None) +        # Can delete only the whole key +        param = (iter,) + pair("Secret", secret) +        # Key information is a combination of the key and first uid and subkey +        for col in key_columns: param += pair(col, key) +        for col in uid_columns: param += pair(col, key.uids[0]) +        for col in sub_columns: param += pair(col, key.subkeys[0]) +        for col in sub_sign_columns: param += pair(col, key.subkeys[0]) +        self.model.set(*param) +        if key.uids: +            self.add_signatures(key.uids[0].signatures, iter) +            self.add_uids(key.uids[1:], iter) +        self.add_subkeys(key.subkeys[1:], iter) + +    def add_subkeys(self, subkeys, iter): +        "self.add_subkeys(subkey, iter) - add subkey as child to key's iter" +        if not subkeys: +            return +        key_iter = self.model.append(iter) +        self.model.set(key_iter, columns["Name"].index, "Subkeys", *name_only) +        for subkey in subkeys: +            child_iter = self.model.append(key_iter) +            param = (child_iter,) +            for col in sub_columns: param += pair(col, subkey) +            for col in sub_sign_columns: param += pair(col, subkey) +            self.model.set(*param) + +    def add_uids(self, uids, iter): +        "self.add_uids(uid, iter) - add uid as a child to key's iter" +        if not uids: +            return +        uid_iter = self.model.append(iter) +        self.model.set(uid_iter,columns["Name"].index,"Other UIDs",*name_only) +        for uid in uids: +            child_iter = self.model.append(uid_iter) +            param = (child_iter,) +            for col in uid_columns: param += pair(col, uid) +            self.model.set(*param) +            self.add_signatures(uid.signatures, child_iter) + +    def add_signatures(self, signs, iter): +        "self.add_signatures(sign, iter) - add signature as a child to iter" +        if not signs: +            return +        sign_iter = self.model.append(iter) +        self.model.set(sign_iter,columns["Name"].index,"Signatures",*name_only) +        for sign in signs: +            child_iter = self.model.append(sign_iter) +            param = (child_iter,) +            for col in uid_columns: param += pair(col, sign) +            for col in sign_columns: param += pair(col, sign) +            for col in sub_sign_columns: param += pair(col, sign) +            self.model.set(*param) + +    def add_columns(self): +        "Add viewable columns for the data in TreeStore model" +        view_menu = gtk.Menu() +        for item in visible_columns: +            if item.type == gobject.TYPE_BOOLEAN: +                renderer = gtk.CellRendererToggle() +                item.attrs["active"] = item.index +            else: +                renderer = gtk.CellRendererText() +                item.attrs["text"] = item.index +            column = self.treeview.insert_column_with_attributes( +                item.index, item.name, renderer, **item.attrs) +            column.set_sort_column_id(item.index) +            # Create callback for a View menu item +            if item.view != None: +                check = gtk.CheckMenuItem(item.name) +                check.set_active(item.view) +                check.connect("activate", +                              lambda x, y: y.set_visible(x.get_active()), +                              column) +                view_menu.append(check) +                column.set_visible(check.get_active()) +                 +        view_menu.show_all() +        self.wtree.get_widget("view_menu").set_submenu(view_menu) + +    def on_GPGKeysView_button_press_event(self, obj, event): +        if event.button != 3: +            return False + +        menu = gtk.Menu() +        for title, callback in [ +            ("Reload", self.on_reload_activate), +            (None, None), +            ("Delete", self.on_delete_activate), +            ("Export (txt)", self.on_export_keys_text_activate), +            ("Export (bin)", self.on_export_keys_activate) +            ]: +            if title: +                item = gtk.MenuItem(title) +                item.connect("activate", callback) +            else: +                item = gtk.SeparatorMenuItem() +            menu.append(item) +        menu.show_all() +         +        menu.popup(None, None, None, event.button, event.time) +        return True + +    def editor_func(self, status, args, val_dict): +        state = val_dict["state"] +        prompt = "%s %s" % (state, args) +        if prompt in val_dict: +            val_dict["state"] = val_dict[prompt][0] +            return val_dict[prompt][1] +        elif args: +            sys.stderr.write("Unexpected prompt in editor_func: %s\n" % prompt) +            raise EOFError() +        return "" + +    def change_key_trust(self, key, new_trust): +        val_dict = { +            "state": "start", +            "start keyedit.prompt": ("trust", "trust"), +            "trust edit_ownertrust.value": ("prompt", "%d" % new_trust), +            "prompt edit_ownertrust.set_ultimate.okay": ("prompt", "Y"), +            "prompt keyedit.prompt": ("finish", "quit") +            } +        out = Data() +        self.context.op_edit(key, self.editor_func, val_dict, out) + +    def on_change_trust(self, new_trust): +        selection = self.treeview.get_selection() +        if selection.count_selected_rows() <= 0: +            return +         +        key_list = [] +        selection.selected_foreach(self.collect_keys, key_list) + +        message = "Change trust to %s on the following keys?\n" % \ +                  trusts[new_trust] +        for key, row in key_list: +            message += "\n%s\t" % key.subkeys[0].keyid +            if key.uids: message += key.uids[0].uid +            else:        message += "<undefined>"                 +        if self.yesno_message(message): +            for key, row in key_list: +                if key.owner_trust != new_trust: +                    self.change_key_trust(key, new_trust) +                    row[columns["Owner Trust"].index] = trusts[new_trust] + +    def on_undefined_trust_activate(self, obj): +        self.on_change_trust(1) + +    def on_never_trust_activate(self, obj): +        self.on_change_trust(2) + +    def on_marginal_trust_activate(self, obj): +        self.on_change_trust(3) + +    def on_full_trust_activate(self, obj): +        self.on_change_trust(4) + +    def on_ultimate_trust_activate(self, obj): +        self.on_change_trust(5) + +    def collect_keys(self, model, path, iter, key_list): +        row = model[path[:1]] +        keyid = row[columns["FPR"].index] +        key = self.context.get_key(keyid, 0) +        key_list.append((key, row)) + +    def export_keys(self): +        selection = self.treeview.get_selection() +        if selection.count_selected_rows() <= 0: +            return +         +        export_file = None +        dialog = gtk.FileChooserDialog("Export Keys (Public only) into a File", +                                       self.mainwin, +                                       gtk.FILE_CHOOSER_ACTION_SAVE, +                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, +                                        gtk.STOCK_OK, gtk.RESPONSE_OK)) +        while dialog.run() == gtk.RESPONSE_OK: +            filename = dialog.get_filename() +            if os.path.exists(filename): +                if os.path.isdir(filename): +                    self.error_message("%s is a directory!" % filename, +                                       dialog) +                    continue +                elif not self.yesno_message("%s exists. Override?" % filename, +                                            dialog): +                    continue + +            # FIXME. Verify that file can be written to +            export_file = open(filename, "wb") +            break +        dialog.destroy() +        if export_file == None: +            return + +        key_list = [] +        selection.selected_foreach(self.collect_keys, key_list) +        expkeys = Data() +        for key, row in key_list: +            self.context.op_export(key.subkeys[0].fpr, 0, expkeys) +        expkeys.seek(0,0) +        export_file.write(expkeys.read()) +        export_file.close() +             +    def on_export_keys_activate(self, obj): +        self.context.set_armor(0) +        self.export_keys() + +    def on_export_keys_text_activate(self, obj): +        self.context.set_armor(1) +        self.export_keys() + +    def on_import_keys_activate(self, obj): +        import_file = None +        dialog = gtk.FileChooserDialog("Import Keys from a File", +                                       self.mainwin, +                                       gtk.FILE_CHOOSER_ACTION_OPEN, +                                       (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, +                                        gtk.STOCK_OK, gtk.RESPONSE_OK)) +        while dialog.run() == gtk.RESPONSE_OK: +            filename = dialog.get_filename() +            if os.path.exists(filename): +                if os.path.isdir(filename): +                    self.error_message("%s is a directory!" % filename, +                                       dialog) +                else: +                    # FIXME. Verify that file can be open. +                    import_file = filename +                    break +            else: +                self.error_message("%s does not exist." % filename, +                                   dialog) +        dialog.destroy() +        if import_file == None: +            return + +        impkeys = Data(file=import_file) +        status = self.context.op_import(impkeys) +        if status: +            self.error_message("Import return an error message %d" % status) +        result = self.context.op_import_result() +        if result.considered == 0: +            self.error_message("There's no keys in the file.") +        # FIXME. Instead of rereading everything we could find out what's new +        # from the result based on the ORed value of impkey: +        # constants.import.NEW    - The key was new. +        # constants.import.UID    - The key contained new user IDs. +        # constants.import.SIG    - The key contained new signatures. +        # constants.import.SUBKEY - The key contained new sub keys. +        # constants.import.SECRET - The key contained a secret key. +        # It would be nice to highlight new things as well. +        self.load_keys() +        #if result: +        #    impkey = result.imports +        #    while impkey: +        #        if impkey.status & constants.import.NEW: +        #            self.add_key(self.context.get_key(impkey.fpr, 0)) +        #        impkey = impkey.next + +    def on_delete_activate(self, obj): +        "self.on_delete_activate(obj) - callback for key deletion request" +        selection = self.treeview.get_selection() +        if selection.count_selected_rows() > 0: +            key_list = [] +            selection.selected_foreach(self.collect_keys, key_list) +             +            message = "Delete selected keys?\n" +            for key, row in key_list: +                message += "\n%s\t" % key.subkeys[0].keyid +                if key.uids: message += key.uids[0].uid +                else:        message += "<undefined>"                 +            if self.yesno_message(message): +                for key, row in key_list: +                    self.context.op_delete(key, 1) +                    row.model.remove(row.iter) + +    def get_widget_values(self, widgets): +        "Create an array of values from widgets' getter methods" +        return [getattr(self.wtree.get_widget(w),"get_"+f)() for w,f in widgets] + +    def set_widget_values(self, widgets, values): +        "Set values using widgets' setter methods" +        for (w,f), v in zip(widgets, values): +            # ComboBox.set_active_iter(None) does not reset active. Fixing. +            if f == "active_iter" and v == None: +                f, v = "active", -1 +            getattr(self.wtree.get_widget(w), "set_"+f)(v) + +    def key_type_changed(self, which): +        """self.key_type_changed([\"key\"|\"subkey\"]) - helper function to +        adjust allowed key length based on the Algorithm selected""" +        (key_type,) = self.get_widget_values([(which+"_type", "active_iter")]) +        if key_type: +            key_type = self.wtree.get_widget(which+"_type").get_model( +                ).get_value(key_type,0) +            length_widget = self.wtree.get_widget(which+"_length") +            if key_type == "DSA": +                length_widget.set_range(1024, 1024) +                length_widget.set_value(1024) +            elif key_type == "RSA" or key_type == "ELG-E": +                length_widget.set_range(1024, 4096) + +    def on_key_type_changed(self, obj): +        self.key_type_changed("key") + +    def on_subkey_type_changed(self, obj): +        self.key_type_changed("subkey") + +    def on_expire_calendar_day_selected(self, obj): +        "Callback for selecting a day on the calendar" +        (year, month, day)=self.wtree.get_widget("expire_calendar").get_date() +        expander = self.wtree.get_widget("expire_date") +        # Past dates means no expiration date +        if time.localtime() < (year, month+1, day): +            expander.set_label("%04d-%02d-%02d" % (year, month+1, day)) +        else: +            expander.set_label("Unlimited") +        expander.set_expanded(False) + +    def on_generate_activate(self, obj): +        "Callback to generate new key" +         +        # Set of (widget, common suffix of getter/setter function) tuples +        # from the GenerateDialog prompt for new key properties. +        widgets = [ +            ("key_type", "active_iter"), +            ("key_length", "value"), +            ("key_encrypt", "active"), +            ("key_sign", "active"), +            ("subkey_type", "active_iter"), +            ("subkey_length", "value"), +            ("subkey_encrypt", "active"), +            ("subkey_sign", "active"), +            ("name_real", "text"), +            ("name_comment", "text"), +            ("name_email", "text"), +            ("expire_date", "label"), +            ("passphrase", "text"), +            ("passphrase_repeat", "text") +            ] + +        saved_values = self.get_widget_values(widgets) +        result = None +        dialog = self.wtree.get_widget("GenerateDialog") +        if dialog.run() == gtk.RESPONSE_OK: +            (key_type, key_length, key_encrypt, key_sign, +             subkey_type, subkey_length, subkey_encrypt, subkey_sign, +             name_real, name_comment, name_email, expire_date, +             passphrase, passphrase2) = self.get_widget_values(widgets) +            if key_type and passphrase == passphrase2: +                key_type = self.wtree.get_widget("key_type").get_model( +                    ).get_value(key_type,0) +                result = "<GnupgKeyParms format=\"internal\">\n" +                result += "Key-Type: %s\n" % key_type +                result += "Key-Length: %d\n" % int(key_length) +                if key_encrypt or key_sign: +                    result += "Key-Usage:" + \ +                              ((key_encrypt and " encrypt") or "") + \ +                              ((key_sign and " sign") or "") + "\n" +                if subkey_type: +                    subkey_type=self.wtree.get_widget("subkey_type").get_model( +                        ).get_value(subkey_type,0) +                    result += "Subkey-Type: %s\n" % subkey_type +                    result += "Subkey-Length: %d\n" % int(subkey_length) +                    if subkey_encrypt or subkey_sign: +                        result += "Subkey-Usage:" + \ +                                  ((subkey_encrypt and " encrypt") or "") + \ +                                  ((subkey_sign and " sign") or "") + "\n" +                if name_real: +                    result += "Name-Real: %s\n" % name_real +                if name_comment: +                    result += "Name-Comment: %s\n" % name_comment +                if name_email: +                    result += "Name-Email: %s\n" % name_email +                if passphrase: +                    result += "Passphrase: %s\n" % passphrase +                if expire_date != "Unlimited": +                    result += "Expire-Date: %s\n" % expire_date +                else: +                    result += "Expire-Date: 0\n" +                result += "</GnupgKeyParms>\n" +            else: +                if not key_type: +                    message = "Type of the primary key is not specified." +                elif passphrase != passphrase2: +                    message = "Passphrases do not match." +                else: +                    message = "Unknown error." +                self.error_message(message, dialog) +        else: +            self.set_widget_values(widgets, saved_values) + +        dialog.hide() +        if result: +            # Setup and show progress Dialog +            self.progress = "" +            self.progress_entry = self.wtree.get_widget( +                "progress_entry").get_buffer() +            self.progress_entry.set_text("") +            gobject.timeout_add(500, self.update_progress) +            self.wtree.get_widget("GenerateProgress").show_all() +            # Start asynchronous key generation +            self.context.op_genkey_start(result, None, None) + +    def gen_progress(self, what=None, type=None, current=None, +                     total=None, hook=None): +        "Gpg's progress_cb" +        if self.progress != None: +            self.progress += "%c" % type +        else: +            sys.stderr.write("%c" % type) + +    def update_progress(self): +        "Timeout callback to yeild to gpg and update progress Dialog view" +        status = self.context.wait(False) +        if status == None: +            self.progress_entry.set_text(self.progress) +            return True +        elif status == 0: +            fpr = self.context.op_genkey_result().fpr +            self.add_key(self.context.get_key(fpr, 0), True) +        self.wtree.get_widget("GenerateProgress").hide() +        self.progress = None + +        if status: +            self.error_message("Got an error during key generation:\n%s" % +                               errors.GPGMEError(status).getstring()) + +        # Let callback to be removed. +        return False + +    def on_generating_close_clicked(self, obj): +        # Request cancelation of the outstanding asynchronous call +        self.context.cancel() + +    def get_password(self, hint, desc, hook): +        "Gpg's password_cb" +        dialog = self.wtree.get_widget("PasswordDialog") +        label = self.wtree.get_widget("pwd_prompt") +        entry = self.wtree.get_widget("password") +        label.set_text("Please supply %s's password%s:" % +                       (hint, (hook and (' '+hook)) or '')) +        if dialog.run() == gtk.RESPONSE_OK: +            result = entry.get_text() +        else: +            result = "" +        entry.set_text("") +        dialog.hide() +        return result + +    def on_reload_activate(self, obj): +        self.load_keys() + +    def on_about_activate(self, obj): +        about = self.wtree.get_widget("AboutDialog") +        about.run() +        about.hide() + +    def __init__(self, path): +        "new(path) path - location of the glade file" +        gladefile = os.path.join(path, "PyGtkGpgKeys.glade") +        self.wtree = gtk.glade.XML(gladefile) +        self.wtree.signal_autoconnect(self) + +        self.mainwin = self.wtree.get_widget("GPGAdminWindow") +        self.treeview = self.wtree.get_widget("GPGKeysView") + +        self.model = gtk.TreeStore(*[x.type for x in visible_columns + +                                     helper_columns])         + +        self.context = Context() +        self.context.set_passphrase_cb(self.get_password, "") +        self.progress = None +        self.context.set_progress_cb(self.gen_progress, None) +        # Use mode.SIGS to include signatures in the list. +        self.context.set_keylist_mode(mode.SIGS) +        self.load_keys(True) + +        self.treeview.set_model(self.model) +        self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) +        self.add_columns() + +        gtk.main() + +    def on_Exit(self, obj): +        gtk.main_quit() + +try: +    # Glade file is expected to be in the same location as this script +    PyGtkGpgKeys(os.path.dirname(sys.argv[0])) +except IOError as message: +    print("%s:%s" %(sys.argv[0], message)) | 
