remove not ported plugins

This commit is contained in:
Yann Leboulanger
2017-09-27 22:21:20 +02:00
parent 3e7571db91
commit 72db56fda8
55 changed files with 0 additions and 8256 deletions

View File

@@ -1 +0,0 @@
from fshare import FileSharePlugin

View File

@@ -1,43 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVBox" id="hbox111">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Incoming folder:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="dl_folder">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,199 +0,0 @@
import sqlite3
from common import app
import sys
import os
class FilesharingDatabase:
def __init__(self, plugin):
self.plugin = plugin
path_l = os.path.split(plugin.config.FILE_PATH)
path = os.path.join(path_l[0], 'shared_files.db')
db_exist = os.path.exists(path)
self.conn = sqlite3.connect(path)
# Enable foreign keys contraints
self.conn.cursor().execute("pragma foreign_keys = on")
if not db_exist:
self.create_database()
# NOTE: Make sure we are getting and setting the requester without its
# resource
def create_database(self):
c = self.conn.cursor()
# Create tables
c.execute("CREATE TABLE permissions" +
"(fid integer REFERENCES files(fid) ON DELETE CASCADE, " +
"account text, requester text)")
c.execute("CREATE TABLE files" +
"(fid INTEGER PRIMARY KEY AUTOINCREMENT," +
" file_path text, relative_path text, hash_sha1 text," +
"size numeric, description text, mod_date text, is_dir boolean)")
# Save (commit) the changes
self.conn.commit()
c.close()
def get_toplevel_files(self, account, requester):
c = self.conn.cursor()
data = (account, requester)
c.execute("SELECT relative_path, hash_sha1, size, description, " +
"mod_date, is_dir FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND relative_path NOT LIKE '%/%'", data)
result = c.fetchall()
c.close()
return result
def get_files_from_dir(self, account, requester, dir_):
c = self.conn.cursor()
data = (account, requester, dir_ + '/%')
c.execute("SELECT relative_path, hash_sha1, size, description, " +
"mod_date, is_dir FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND relative_path LIKE ?", data)
result = c.fetchall()
c.close()
fresult = []
for r in result:
name = r[0][len(dir_) + 1:]
if '/' not in name:
fresult.append(r)
return fresult
def get_files(self, account, requester):
"""
>>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \
'date', False)
>>> foo = add_file('account@gajim', 'requester@jabber', file_)
>>> result = get_files('account@gajim', 'requester@jabber')
>>> len(result)
1
>>> _delete_file(1)
"""
c = self.conn.cursor()
data = (account, requester)
c.execute("SELECT relative_path, hash_sha1, size, description, " +
"mod_date, is_dir FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?", data)
result = c.fetchall()
c.close()
return result
def get_file(self, account, requester, hash_, name):
c = self.conn.cursor()
if hash_:
data = (account, requester, hash_)
sql = "SELECT relative_path, hash_sha1, size, description, " + \
"mod_date, file_path FROM (files JOIN permissions ON" + \
" files.fid=permissions.fid) WHERE account=? AND requester=?" +\
" AND hash_sha1=?"
else:
data = (account, requester, name)
sql = "SELECT relative_path, hash_sha1, size, description, " + \
"mod_date, file_path FROM (files JOIN permissions ON" + \
" files.fid=permissions.fid) WHERE account=? AND requester=?" +\
" AND relative_path=?"
c.execute(sql, data)
result = c.fetchall()
c.close()
if result == []:
return None
else:
return result[0]
def get_files_name(self, account, requester):
result = self.get_files(account, requester)
flist = []
for r in result:
flist.append(r[0])
return flist
def add_file(self, account, requester, file_):
"""
>>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \
'date', False)
>>> add_file('account@gajim', 'requester@jabber', file_)
1
>>> _delete_file(1)
"""
self._check_duplicate(account, requester, file_)
requester = app.get_jid_without_resource(requester)
c = self.conn.cursor()
c.execute("INSERT INTO files (file_path, " +
"relative_path, hash_sha1, size, description, mod_date, " +
" is_dir) VALUES (?,?,?,?,?,?,?)", file_)
fid = c.lastrowid
permission_data = (fid, account, requester)
c.execute("INSERT INTO permissions VALUES (?,?,?)", permission_data)
self.conn.commit()
c.close()
return fid
def _check_duplicate(self, account, requester, file_):
c = self.conn.cursor()
data = (account, requester, file_[1])
c.execute("SELECT * FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND relative_path=? ", data)
result = c.fetchall()
if file_[2] != '':
data = (account, requester, file_[2])
c.execute("SELECT * FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND hash_sha1=?)", data)
result.extend(c.fetchall())
if len(result) > 0:
raise Exception('Duplicated entry')
c.close()
def _delete_file(self, fid):
c = self.conn.cursor()
data = (fid, )
c.execute("DELETE FROM files WHERE fid=?", data)
self.conn.commit()
c.close()
def _delete_dir(self, dir_, account, requester):
c = self.conn.cursor()
data = (account, requester, dir_, dir_ + '/%')
sql = "DELETE FROM files WHERE fid IN " + \
" (SELECT files.fid FROM files, permissions WHERE" + \
" files.fid=permissions.fid AND account=?"+ \
" AND requester=? AND (relative_path=? OR relative_path LIKE ?))"
c.execute(sql, data)
self.conn.commit()
c.close()
def delete(self, account, requester, relative_path):
c = self.conn.cursor()
data = (account, requester, relative_path)
c.execute("SELECT files.fid, is_dir FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=? AND " +
"relative_path=? ", data)
result = c.fetchone()
c.close()
if result[1] == 0:
self._delete_file(result[0])
else:
self._delete_dir(relative_path, account, requester)
def delete_all(self, account, requester):
c = self.conn.cursor()
data = (account, requester)
sql = "DELETE FROM files WHERE fid IN (SELECT fid FROM permissions" + \
" WHERE account=? AND requester=?)"
c.execute(sql, data)
self.conn.commit()
c.close()
if __name__ == "__main__":
"""
DELETE DATABASE FILE BEFORE RUNNING TESTS
"""
import doctest
path = sys.path[0]
path = path + '/' + 'shared_files.db'
conn = sqlite3.connect(path)
# Enable foreign keys contraints
conn.cursor().execute("pragma foreign_keys = on")
FilesharingDatabase.create_database()
doctest.testmod()

View File

@@ -1,399 +0,0 @@
import gtk
import gobject
from common import gajim
from common import helpers
from common.file_props import FilesProp
import fshare
import os
import fshare_protocol
class FileShareWindow(gtk.Window):
def __init__(self, plugin):
self.plugin = plugin
gtk.Window.__init__(self)
self.set_title('File Share')
self.connect('delete_event', self.delete_event)
self.set_position(gtk.WIN_POS_CENTER)
self.set_default_size(400, 400)
# Children
self.notebook = gtk.Notebook()
# Browse page
self.bt_search = gtk.Button('Search')
self.bt_search.connect('clicked', self.on_bt_search_clicked)
self.entry_search = gtk.Entry(max=0)
self.entry_search.set_size_request(300, -1)
self.exp_advance = gtk.Expander('Advance')
self.ts_search = gtk.TreeStore(gobject.TYPE_STRING)
self.tv_search = gtk.TreeView(self.ts_search)
self.tv_search.connect('row-expanded', self.row_expanded)
self.tv_search.connect('button-press-event',
self.on_treeview_button_press_event)
self.browse_popup = gtk.Menu()
mi_download = gtk.MenuItem('Download')
mi_property = gtk.MenuItem('Property')
mi_download.show()
mi_property.show()
mi_download.connect('activate', self.on_download_clicked)
self.browse_popup.append(mi_download)
self.browse_popup.append(mi_property)
self.browse_sw = gtk.ScrolledWindow()
self.browse_sw.add(self.tv_search)
self.tvcolumn_browse = gtk.TreeViewColumn('')
self.tv_search.append_column(self.tvcolumn_browse)
self.cell_browse = gtk.CellRendererText()
self.tvcolumn_browse.pack_start(self.cell_browse, True)
self.tvcolumn_browse.add_attribute(self.cell_browse, 'text', 0)
self.lbl_browse = gtk.Label('Browse')
self.browse_hbox = gtk.HBox()
self.browse_hbox2 = gtk.HBox()
self.browse_vbox = gtk.VBox()
self.browse_hbox.pack_start(self.entry_search, True, True, 10)
self.browse_hbox.pack_start(self.bt_search, False, False, 10)
self.browse_vbox.pack_start(self.browse_hbox, False, False, 10)
self.browse_vbox.pack_start(self.exp_advance, False, False, 10)
self.browse_vbox.pack_start(self.browse_sw, True, True, 10)
self.notebook.append_page(self.browse_vbox, self.lbl_browse)
# file references for tv_search
self.browse_fref = {}
# contact references for tv_search
self.browse_jid = {}
# Information of the files inserted in the treeview: name, size, etc
self.brw_file_info = {}
# dummy row children so that we can get expanders
self.empty_row_child = {}
# Manage page
self.bt_add_file = gtk.Button('Add file')
self.bt_add_file.connect('clicked', self.add_file)
self.bt_add_dir = gtk.Button('Add directory')
self.bt_add_dir.connect('clicked', self.add_directory)
self.bt_remove = gtk.Button('Remove')
self.bt_remove.connect('clicked', self.remove_file_clicked)
self.bt_remove_all = gtk.Button('Remove all')
self.bt_remove_all.connect('clicked', self.remove_all_clicked)
self.ts_contacts = gtk.TreeStore(gobject.TYPE_STRING)
self.cbb_contacts = gtk.ComboBoxEntry(self.ts_contacts)
self.cbb_contacts.connect('changed', self.__check_combo_edit)
cbb_entry = self.cbb_contacts.child
self.cbb_completion = gtk.EntryCompletion()
cbb_entry.set_completion(self.cbb_completion)
self.cbb_completion.set_model(self.ts_contacts)
self.cbb_completion.set_text_column(0)
self.bt_stophash = gtk.Button('Calculating hash')
self.lbl_manage = gtk.Label('Manage Shared Files')
self.ts_files = gtk.TreeStore(gobject.TYPE_STRING)
self.tv_files = gtk.TreeView(self.ts_files)
self.treeSelection_files = self.tv_files.get_selection()
self.treeSelection_files.connect('changed', self.row_selected)
self.manage_sw = gtk.ScrolledWindow()
self.manage_sw.add(self.tv_files)
self.tvcolumn = gtk.TreeViewColumn('')
self.tv_files.append_column(self.tvcolumn)
self.cell = gtk.CellRendererText()
self.tvcolumn.pack_start(self.cell, True)
self.tvcolumn.add_attribute(self.cell, 'text', 0)
self.pb_filehash = gtk.ProgressBar()
self.manage_hbox = gtk.HBox()
self.manage_hbox2 = gtk.HBox()
self.manage_vbox = gtk.VBox()
self.manage_vbox2 = gtk.VBox()
self.manage_hbox.pack_start(self.bt_stophash , False, False, 10)
self.manage_hbox.pack_start(self.pb_filehash, True, True, 10)
self.manage_hbox.set_sensitive(False)
self.manage_vbox.pack_start(self.cbb_contacts, False, False, 10)
self.manage_vbox.pack_start(self.manage_hbox, False, False, 10)
self.manage_hbox2.pack_start(self.manage_sw, True, True, 10)
self.manage_hbox2.pack_start(self.manage_vbox2, False, False, 10)
self.manage_vbox2.pack_start(self.bt_add_file, False, False, 10)
self.manage_vbox2.pack_start(self.bt_add_dir, False, False, 10)
self.manage_vbox2.pack_start(self.bt_remove, False, False, 10)
self.manage_vbox2.pack_start(self.bt_remove_all, False, False, 10)
self.manage_vbox2.set_sensitive(False)
self.manage_vbox.pack_start(self.manage_hbox2, True, True, 10)
self.notebook.append_page(self.manage_vbox, self.lbl_manage)
# Preferences page
self.lbl_pref = gtk.Label('Preferences')
self.entry_dir_pref = gtk.Entry(max=0)
self.entry_dir_pref.set_text(self.plugin.config['incoming_dir'])
self.bt_sel_dir_pref = gtk.Button('Select dir', gtk.STOCK_OPEN)
self.bt_sel_dir_pref.connect('clicked', self.on_bt_sel_dir_pref_clicked)
self.frm_dir_pref = gtk.Frame('Incoming files directory')
self.frm_dir_pref.set_shadow_type(gtk.SHADOW_IN)
self.pref_hbox = gtk.HBox()
self.pref_vbox = gtk.VBox()
self.pref_hbox.pack_start(self.entry_dir_pref, True, True, 10)
self.pref_hbox.pack_start(self.bt_sel_dir_pref, False, False, 10)
self.frm_dir_pref.add(self.pref_hbox)
self.pref_vbox.pack_start(self.frm_dir_pref, False, False, 10)
self.notebook.append_page(self.pref_vbox , self.lbl_pref)
self.add(self.notebook)
self.show_all()
def set_account(self, account):
self.account = account
# connect window to protocol
pro = fshare.FileSharePlugin.prohandler[self.account]
pro.set_window(self)
def add_file(self, widget):
dialog = gtk.FileChooserDialog('Add file to be shared', self,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
gtk.RESPONSE_OK)
)
dialog.set_select_multiple(True)
response = dialog.run()
if response == gtk.RESPONSE_OK:
file_list = dialog.get_filenames()
self.add_items_tvcontacts(file_list)
dialog.destroy()
def add_directory(self, widget):
dialog = gtk.FileChooserDialog('Add directory to be shared', self,
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
gtk.RESPONSE_ACCEPT)
)
response = dialog.run()
if response == gtk.RESPONSE_ACCEPT:
file_list = []
file_list.append(dialog.get_filename())
self.add_items_tvcontacts(file_list)
dialog.destroy()
def add_file_list(self, flist, treestore, fref = {}, root = None):
# file references to their in the treeStore
for f in flist:
# keeps track of the relative dir path
tail = ''
# keeps track of the parent row
parent = None
dirpath = f.split('/')
if len(dirpath) == 1:
# Top level file, it doesnt have parent, add it right away
fref[dirpath[0]] = treestore.insert(root, 0, (dirpath[0],))
else:
for dir_ in dirpath:
if tail + dir_ not in fref:
fref[tail + dir_] = treestore.append(parent, (dir_,))
parent = fref[tail + dir_]
tail = tail + dir_ + '/'
return fref
def __convert_date(self, epoch):
# Converts date-time from seconds from epoch to iso 8601
import time, datetime
ts = time.gmtime(epoch)
dt = datetime.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour,
ts.tm_min, ts.tm_sec)
return dt.isoformat()
def add_items_tvcontacts(self, file_list, parentdir = None, parent = None):
# TODO: execute this method inside of a thread
for f in file_list:
# Relative name to be used internally in the shared folders
relative_name = f.split('/')[-1]
if parentdir:
relative_name = parentdir + '/' + relative_name
short_name = relative_name.split('/')[-1]
if short_name[0] == '.':
# Return if it is a hidden file
return
if parent:
row = self.ts_files.append(parent, (short_name,))
else:
row = self.ts_files.insert(None, 0, (short_name,))
# File info
size = os.path.getsize(f)
is_dir = os.path.isdir(f)
mod_date = os.path.getmtime(f)
mod_date = self.__convert_date(mod_date)
# TODO: add hash
file_ = (f, relative_name, '', size, '', mod_date, is_dir)
requester = self.cbb_contacts.get_active_text()
try:
fid = self.plugin.database.add_file(self.account, requester,
file_)
except Exception, e:
if e == 'Duplicated entry':
print 'Error: ' + e
continue
else:
raise Exception(e)
if is_dir:
tmpfl = os.listdir(f)
fl = []
for item in tmpfl:
fl.append(f + '/' + item)
self.add_items_tvcontacts(fl, relative_name, row)
def add_contact_browse(self, contact):
fjid = contact.get_full_jid()
jid = gajim.get_jid_without_resource(fjid)
contacts = gajim.contacts.get_contacts(self.account, jid)
for con in contacts:
if con.show in ('offline', 'error') and not \
con.supports(fshare_protocol.NS_FILE_SHARING):
break
cjid = con.get_full_jid()
r = self.ts_search.insert(None, 0, (cjid, ))
self.browse_jid[cjid] = r
pro = fshare.FileSharePlugin.prohandler[self.account]
# Request list of files from peer
stanza = pro.request(cjid)
if pro.conn.connection:
pro.conn.connection.send(stanza)
def add_contact_manage(self, contact):
self.contacts_rows = []
self.cbb_contacts.grab_focus()
for c in gajim.contacts.iter_contacts(self.account):
jid = gajim.get_jid_without_resource(c.get_full_jid())
r = self.ts_contacts.insert(None, len(self.ts_contacts), (jid, ))
if c.get_full_jid() == contact.get_full_jid():
self.cbb_contacts.set_active_iter(r)
self.contacts_rows.append(r)
self.manage_vbox2.set_sensitive(True)
self.bt_remove.set_sensitive(False)
self.add_file_list(self.plugin.database.get_files_name(self.account,
gajim.get_jid_without_resource(contact.get_full_jid())),
self.ts_files)
def delete_event(self, widget, data=None):
fshare.FileSharePlugin.filesharewindow = {}
return False
def __check_combo_edit(self, widget, data=None):
self.ts_files.clear()
entry = self.cbb_contacts.child
contact = entry.get_text()
self.manage_vbox2.set_sensitive(False)
for i in self.contacts_rows:
# If the contact in the comboboxentry is include inside of the
# combobox
if contact == self.ts_contacts.get_value(i, 0):
self.add_file_list(self.plugin.database.get_files_name(
self.account, contact), self.ts_files)
self.manage_vbox2.set_sensitive(True)
self.bt_remove.set_sensitive(False)
break
def remove_file_clicked(self, widget, data=None):
entry = self.cbb_contacts.child
contact = entry.get_text()
sel = self.treeSelection_files.get_selected()
relative_name = self.ts_files.get_value(sel[1], 0)
self.ts_files.remove(sel[1])
self.plugin.database.delete(self.account, contact, relative_name)
widget.set_sensitive(False)
def remove_all_clicked(self, widget, data=None):
entry = self.cbb_contacts.child
contact = entry.get_text()
self.plugin.database.delete_all(self.account, contact)
self.ts_files.clear()
def row_selected(self, widget, data=None):
# When row is selected in tv_files
sel = self.treeSelection_files.get_selected()
if not sel[1]:
return
depth = self.ts_files.iter_depth(sel[1])
# Don't remove file and dirs that aren't at the root level
if depth == 0:
self.bt_remove.set_sensitive(True)
else:
self.bt_remove.set_sensitive(False)
def row_expanded(self, widget, iter_, path, data=None):
name = None
for key in self.empty_row_child:
parent = self.ts_search.iter_parent(self.empty_row_child[key])
p = self.ts_search.get_path(parent)
if p == path:
name = key
break
if name:
# if we found that the expanded row is the parent of the empty row
# remove it from the treestore and empty_row dictionary. Then ask
# peer for list of files of that directory
i = self.empty_row_child[name]
pro = fshare.FileSharePlugin.prohandler[self.account]
contact = self.get_contact_from_iter(self.ts_search, i)
contact = gajim.contacts.get_contact_with_highest_priority(
self.account, contact)
stanza = pro.request(contact.get_full_jid(), name, isFile=False)
if pro.conn.connection:
pro.conn.connection.send(stanza)
self.ts_search.remove(i)
del self.empty_row_child[name]
def get_contact_from_iter(self, treestore, iter_):
toplevel = treestore.get_iter_root()
while toplevel:
if treestore.is_ancestor(toplevel, iter_):
return gajim.get_jid_without_resource(treestore.get_value(
toplevel, 0))
toplevel = treestore.iter_next(toplevel)
def on_treeview_button_press_event(self, treeview, event):
if event.button == 3:
x = int(event.x)
y = int(event.y)
time = event.time
pthinfo = treeview.get_path_at_pos(x, y)
if pthinfo is not None:
path, col, cellx, celly = pthinfo
treeview.grab_focus()
treeview.set_cursor( path, col, 0)
treestore = treeview.get_model()
it = treestore.get_iter(path)
if treestore.iter_depth(it) != 0:
self.browse_popup.popup(None, None, None, event.button, time)
return True
def on_bt_search_clicked(self, widget, data=None):
pass
def on_download_clicked(self, widget, data=None):
tree, row = self.tv_search.get_selection().get_selected()
path = tree.get_path(row)
file_info = self.brw_file_info[path]
fjid = self.get_contact_from_iter(tree, row)
# Request the file
file_path = os.path.join(self.plugin.config['incoming_dir'],
file_info[0])
sid = helpers.get_random_string_16()
new_file_props = FilesProp.getNewFileProp(self.account, sid)
new_file_props.file_name = file_path
print file_path
new_file_props.name = file_info[0]
new_file_props.desc = file_info[4]
new_file_props.size = file_info[2]
new_file_props.date = file_info[1]
new_file_props.hash_ = None if file_info[3] == '' else file_info[3]
new_file_props.type_ = 'r'
tsid = gajim.connections[self.account].start_file_transfer(fjid,
new_file_props, True)
new_file_props.transport_sid = tsid
ft_window = gajim.interface.instances['file_transfers']
contact = gajim.contacts.get_contact_from_full_jid(self.account, fjid)
ft_window .add_transfer(self.account, contact, new_file_props)
def on_bt_sel_dir_pref_clicked(self, widget, data=None):
chooser = gtk.FileChooserDialog(title='Incoming files directory',
action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
gtk.RESPONSE_OK))
response = chooser.run()
if response == gtk.RESPONSE_OK:
file_name = chooser.get_filename()
self.entry_dir_pref.set_text(file_name)
self.plugin.config['incoming_dir'] = file_name
chooser.destroy()
if __name__ == "__main__":
f = FileShareWindow(None)
f.show()
gtk.main()

View File

@@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
##
import database
import gtk
import os
import base64
import urllib2
from plugins import GajimPlugin
from plugins.helpers import log_calls
import gui_menu_builder
import gtkgui_helpers
from common import gajim
from fileshare_window import FileShareWindow
import fshare_protocol
from common import ged
from common import caps_cache
from common import xmpp
from plugins.gui import GajimPluginConfigDialog
class FileSharePlugin(GajimPlugin):
filesharewindow = {}
prohandler = {}
@log_calls('FileSharePlugin')
def init(self):
self.activated = False
self.description = _('This plugin allows you to share folders'
' with a peer using jingle file transfer.')
self.config_dialog = FileSharePluginConfigDialog(self)
home_path = os.path.expanduser('~/')
self.config_default_values = {'incoming_dir': (home_path, '')}
self.database = database.FilesharingDatabase(self)
# Create one protocol handler per account
accounts = gajim.contacts.get_accounts()
for account in gajim.contacts.get_accounts():
FileSharePlugin.prohandler[account] = \
fshare_protocol.Protocol(account, self)
self.events_handlers = {
'raw-iq-received': (ged.CORE, self._nec_raw_iq)
}
def activate(self):
self.activated = True
# Add fs feature
if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features:
gajim.gajim_common_features.append(fshare_protocol.NS_FILE_SHARING)
self._compute_caps_hash()
# Replace the contact menu
self.__get_contact_menu = gui_menu_builder.get_contact_menu
gui_menu_builder.get_contact_menu = self.contact_menu
# Replace get_file_info
for account in gajim.contacts.get_accounts():
conn = gajim.connections[account]
self._get_file_info = conn.get_file_info
conn.get_file_info = self.get_file_info
def deactivate(self):
self.activated = False
# Remove fs feature
if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features:
gajim.gajim_common_features.remove(fshare_protocol.NS_FILE_SHARING)
self._compute_caps_hash()
# Restore the contact menu
gui_menu_builder.get_contact_menu = self.__get_contact_menu
# Restore get_file_info
for account in gajim.contacts.get_accounts():
conn = gajim.connections[account]
conn.get_file_info = self._get_file_info
def _compute_caps_hash(self):
for a in gajim.connections:
gajim.caps_hash[a] = caps_cache.compute_caps_hash([
gajim.gajim_identity], gajim.gajim_common_features + \
gajim.gajim_optional_features[a])
# re-send presence with new hash
connected = gajim.connections[a].connected
if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
gajim.connections[a].status)
def _nec_raw_iq(self, obj):
if obj.stanza.getTag('match',
namespace=fshare_protocol.NS_FILE_SHARING) and self.activated:
account = obj.conn.name
pro = FileSharePlugin.prohandler[account]
pro.handler(obj.stanza)
raise xmpp.NodeProcessed
def __get_contact_menu(self, contact, account):
raise NotImplementedError
def contact_menu(self, contact, account):
menu = self.__get_contact_menu(contact, account)
fs = gtk.MenuItem('File sharing')
submenu = gtk.Menu()
fs.set_submenu(submenu)
bf = gtk.MenuItem('Browse files')
bf.connect('activate', self.browse_menu_clicked, account, contact)
msf = gtk.MenuItem('Manage shared files')
msf.connect('activate', self.manage_menu_clicked, account, contact)
enable_fs = gtk.CheckMenuItem('Enable file sharing')
enable_fs.set_active(True)
submenu.attach(bf, 0, 1, 0, 1)
submenu.attach(msf, 0, 1, 1, 2)
submenu.attach(enable_fs, 0, 1, 2, 3)
if gajim.account_is_disconnected(account) or \
contact.show in ('offline', 'error') or not \
contact.supports(fshare_protocol.NS_FILE_SHARING):
bf.set_sensitive(False)
submenu.show()
bf.show()
msf.show()
enable_fs.show()
fs.show()
menu.attach(fs, 0, 1, 3, 4)
return menu
def _get_file_info(self, peerjid, hash_=None, name=None, account=None):
raise NotImplementedError
def get_file_info(self, peerjid, hash_=None, name=None, account=None):
file_info = self._get_file_info(hash_, name)
if file_info:
return file_info
raw_info = self.database.get_file(account, peerjid, hash_, name)
file_info = {'name': raw_info[0],
'file-name' : raw_info[5],
'hash' : raw_info[1],
'size' : raw_info[2],
'date' : raw_info[4],
'peerjid' : peerjid
}
return file_info
def __get_fsw_instance(self, account):
# Makes sure we only have one instance of the window per account
if account not in FileSharePlugin.filesharewindow:
FileSharePlugin.filesharewindow[account] = fsw = FileShareWindow(
self)
FileSharePlugin.prohandler[account].set_window(fsw)
return FileSharePlugin.filesharewindow[account]
def __init_window(self, account, contact):
fsw = self.__get_fsw_instance(account)
fsw.set_account(account)
fsw.contacts_rows = []
fsw.ts_contacts.clear()
fsw.ts_search.clear()
# Add information to widgets
fsw.add_contact_manage(contact)
fsw.add_contact_browse(contact)
return fsw
def manage_menu_clicked(self, widget, account, contact):
fsw = self.__init_window(account, contact)
fsw.notebook.set_current_page(1)
def browse_menu_clicked(self, widget, account, contact):
fsw = self.__init_window(account, contact)
fsw.notebook.set_current_page(0)
class FileSharePluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['hbox111'])
hbox = self.xml.get_object('hbox111')
self.child.pack_start(hbox)
self.connect('hide', self.on_hide)
def on_run(self):
widget = self.xml.get_object('dl_folder')
widget.set_text(str(self.plugin.config['incoming_dir']))
def on_hide(self, widget):
widget = self.xml.get_object('dl_folder')
self.plugin.config['incoming_dir'] = widget.get_text()

View File

@@ -1,163 +0,0 @@
from common import xmpp
from common import helpers
from common import gajim
from common import XMPPDispatcher
from common.xmpp import Hashes
# Namespace for file sharing
NS_FILE_SHARING = 'http://gajim.org/protocol/filesharing'
class Protocol():
def __init__(self, account, plugin):
self.account = account
self.plugin = plugin
self.conn = gajim.connections[self.account]
# get our jid with resource
self.ourjid = gajim.get_jid_from_account(self.account)
self.fsw = None
def set_window(self, window):
self.fsw = window
def request(self, contact, name=None, isFile=False):
iq = xmpp.Iq(typ='get', to=contact, frm=self.ourjid)
match = iq.addChild(name='match', namespace=NS_FILE_SHARING)
request = match.addChild(name='request')
if not isFile and name is None:
request.addChild(name='directory')
elif not isFile and name is not None:
dir_ = request.addChild(name='directory')
dir_.addChild(name='name').addData('/' + name)
elif isFile:
pass
return iq
def __buildReply(self, typ, stanza):
iq = xmpp.Iq(typ, to=stanza.getFrom(), frm=stanza.getTo(),
attrs={'id': stanza.getID()})
iq.addChild(name='match', namespace=NS_FILE_SHARING)
return iq
def on_request(self, stanza):
try:
fjid = helpers.get_full_jid_from_iq(stanza)
except helpers.InvalidFormat:
# A message from a non-valid JID arrived, it has been ignored.
return
if stanza.getTag('error'):
# TODO: better handle this
return
jid = gajim.get_jid_without_resource(fjid)
req = stanza.getTag('match').getTag('request')
if req.getTag('directory') and not \
req.getTag('directory').getChildren():
# We just received a toplevel directory request
files = self.plugin.database.get_toplevel_files(self.account, jid)
response = self.offer(stanza.getID(), fjid, files)
self.conn.connection.send(response)
elif req.getTag('directory') and req.getTag('directory').getTag('name'):
dir_ = req.getTag('directory').getTag('name').getData()[1:]
files = self.plugin.database.get_files_from_dir(self.account, jid, dir_)
response = self.offer(stanza.getID(), fjid, files)
self.conn.connection.send(response)
def on_offer(self, stanza):
# We just got a stanza offering files
fjid = helpers.get_full_jid_from_iq(stanza)
info = get_files_info(stanza)
if fjid not in self.fsw.browse_jid or not info:
# We weren't expecting anything from this contact, do nothing
# Or we didn't receive any offering files
return
flist = []
for f in info[0]:
flist.append(f['name'])
flist.extend(info[1])
self.fsw.browse_fref = self.fsw.add_file_list(flist, self.fsw.ts_search,
self.fsw.browse_fref,
self.fsw.browse_jid[fjid]
)
for f in info[0]:
iter_ = self.fsw.browse_fref[f['name']]
path = self.fsw.ts_search.get_path(iter_)
self.fsw.brw_file_info[path] = (f['name'], f['date'], f['size'],
f['hash'], f['desc'])
# TODO: add tooltip
'''
for f in info[0]:
r = self.fsw.browse_fref[f['name']]
path = self.fsw.ts_search.get_path(r)
# AM HERE WORKING ON THE TOOLTIP
tooltip.set_text('noooo')
self.fsw.tv_search.set_tooltip_row(tooltip, path)
'''
for dir_ in info[1]:
if dir_ not in self.fsw.empty_row_child:
parent = self.fsw.browse_fref[dir_]
row = self.fsw.ts_search.append(parent, ('',))
self.fsw.empty_row_child[dir_] = row
def handler(self, stanza):
# handles incoming match stanza
if stanza.getTag('match').getTag('offer'):
self.on_offer(stanza)
elif stanza.getTag('match').getTag('request'):
self.on_request(stanza)
else:
# TODO: reply with malformed stanza error
pass
def offer(self, id_, contact, items):
iq = xmpp.Iq(typ='result', to=contact, frm=self.ourjid,
attrs={'id': id_})
match = iq.addChild(name='match', namespace=NS_FILE_SHARING)
offer = match.addChild(name='offer')
if len(items) == 0:
offer.addChild(name='directory')
else:
for i in items:
# if it is a directory
if i[5] == True:
item = offer.addChild(name='directory')
name = item.addChild('name')
name.setData('/' + i[0])
else:
item = offer.addChild(name='file')
item.addChild('name').setData('/' + i[0])
if i[1] != '':
h = Hashes()
h.addHash(i[1], 'sha-1')
item.addChild(node=h)
item.addChild('size').setData(i[2])
item.addChild('desc').setData(i[3])
item.addChild('date').setData(i[4])
return iq
def set_window(self, fsw):
self.fsw = fsw
def get_files_info(stanza):
# Crawls the stanza in search for file and dir structure.
files = []
dirs = []
children = stanza.getTag('match').getTag('offer').getChildren()
for c in children:
if c.getName() == 'file':
f = {'name' : \
c.getTag('name').getData()[1:] if c.getTag('name') else '',
'size' : c.getTag('size').getData() if c.getTag('size') else '',
'date' : c.getTag('date').getData() if c.getTag('date') else '',
'desc' : c.getTag('desc').getData() if c.getTag('desc') else '',
# TODO: handle different hash algo
'hash' : c.getTag('hash').getData() if c.getTag('hash') else '',
}
files.append(f)
else:
dirname = c.getTag('name')
if dirname is None:
return None
dirs.append(dirname.getData()[1:])
return (files, dirs)

View File

@@ -1,9 +0,0 @@
[info]
name: File Sharing
short_name: fshare
#version: 0.1.1
description: This plugin allows you to share folders with your peers using jingle file transfer.
authors: Jefry Lagrange <jefry.reyes@gmail.com>
homepage: www.google.com
max_gajim_version: 0.15.9

View File

@@ -1 +0,0 @@
from plugin import GnomeSessionManagerPlugin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

View File

@@ -1,8 +0,0 @@
[info]
name: GNOME SessionManager
short_name: gnome_session_manager
version: 0.1.3
description: Set and react on GNOME Session presence settings
authors: Philippe Normand <phil@base-art.net>
homepage: http://base-art.net
max_gajim_version: 0.15.9

View File

@@ -1,90 +0,0 @@
# -*- coding: utf-8 -*-
## Copyright (C) 2010 Philippe Normand <phil@base-art.net>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import dbus
from common import app
from common import ged
from common import dbus_support
import gui_interface
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
GNOME_STATUS = [u'online', u'invisible', u'dnd', u'idle']
PRESENCE_INTERFACE = "org.gnome.SessionManager.Presence"
class GnomeSessionManagerPlugin(GajimPlugin):
@log_calls('GnomeSessionManagerPlugin')
def init(self):
self.description = _('Set and react on GNOME Session presence settings')
self.config_dialog = None
self.events_handlers = {}
@log_calls('GnomeSessionManagerPlugin')
def activate(self):
if not dbus_support.supported:
return
self.bus = dbus_support.session_bus.SessionBus()
try:
self.session_presence = self.bus.get_object("org.gnome.SessionManager",
"/org/gnome/SessionManager/Presence")
except:
app.log.debug("GNOME SessionManager D-Bus service not found")
return
self.active = True
app.ged.register_event_handler('our-show', ged.POSTGUI,
self.on_our_status)
self.bus.add_signal_receiver(self.gnome_presence_changed,
"StatusChanged", PRESENCE_INTERFACE)
@log_calls('GnomeSessionManagerPlugin')
def deactivate(self):
if not dbus_support.supported or not self.active:
return
self.active = False
self.bus.remove_signal_receiver(self.gnome_presence_changed, "StatusChanged",
dbus_interface=PRESENCE_INTERFACE)
app.ged.remove_event_handler('our-show', ged.POSTGUI, self.on_our_status)
def gnome_presence_changed(self, status, *args, **kw):
if not app.interface.remote_ctrl:
try:
import remote_control
app.interface.remote_ctrl = remote_control.Remote()
except:
return
remote_gajim = app.interface.remote_ctrl.signal_object
gajim_status = GNOME_STATUS[status]
accounts = remote_gajim.list_accounts()
for account in accounts:
message = remote_gajim.get_status_message(account)
remote_gajim.change_status(gajim_status, message, account)
def on_our_status(self, network_event):
try:
gnome_status = GNOME_STATUS.index(network_event.show)
except ValueError:
print "GNOME SessionManager doesn't support %r status" % network_event.show
else:
self.session_presence.SetStatus(dbus.UInt32(gnome_status),
dbus_interface=PRESENCE_INTERFACE)

View File

@@ -1 +0,0 @@
from otrmodule import OtrPlugin

View File

@@ -1,382 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="account_store">
<columns>
<!-- column-name accountname -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="fingerprint_store">
<columns>
<!-- column-name screenname -->
<column type="gchararray"/>
<!-- column-name status -->
<column type="gchararray"/>
<!-- column-name verified -->
<column type="gboolean"/>
<!-- column-name fingerprint -->
<column type="gchararray"/>
<!-- column-name account -->
<column type="gchararray"/>
<!-- column-name tooltip -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkMenu" id="fprclipboard_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ubuntu_local">True</property>
<child>
<object class="GtkMenuItem" id="copyfprclipboard_item">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="label" translatable="yes" comments="Context menu item">Copy to clipboard</property>
<property name="use_underline">True</property>
<signal name="activate" handler="clipboard_button_cb"/>
</object>
</child>
</object>
<object class="GtkNotebook" id="notebook1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="fingerprint_label_desc">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="Descriptive label">Fingerprint:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="fingerprint_label">
<property name="visible">True</property>
<property name="label">&lt;tt&gt;-------- -------- -------- -------- -------- &lt;/tt&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="regenerate_button">
<property name="label" translatable="yes" comments="Generate Fingerprint button">(Re-)generate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="regenerate_button_clicked_cb"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton" id="enable_check">
<property name="label" translatable="yes" comments="checkbox">Enable private (Off-the-Record) messaging</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="flags_toggled_cb" after="yes"/>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="advertise_check">
<property name="label" translatable="yes" comments="checkbox">Advertise Off-the-Record messaging support</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="flags_toggled_cb" after="yes"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="autoinitiate_check">
<property name="label" translatable="yes" comments="checkbox">Automatically start private messaging</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="flags_toggled_cb" after="yes"/>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="require_check">
<property name="label" translatable="yes" comments="checkbox">Require private messaging</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="flags_toggled_cb" after="yes"/>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="title above options">&lt;b&gt;Default OTR Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="label for account selector">&lt;b&gt;Off-the-Record settings for:&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="account_combobox">
<property name="visible">True</property>
<property name="model">account_store</property>
<signal name="changed" handler="account_combobox_changed_cb"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="tab label">OTR Settings</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="fingerprint_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">fingerprint_store</property>
<property name="search_column">0</property>
<property name="tooltip_column">5</property>
<signal name="button-press-event" handler="fpr_button_pressed_cb" swapped="no"/>
<child>
<object class="GtkTreeViewColumn" id="name_column">
<property name="resizable">True</property>
<property name="title">Name</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="status_column">
<property name="resizable">True</property>
<property name="title">Status</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="validated_column">
<property name="resizable">True</property>
<property name="title">Validated</property>
<child>
<object class="GtkCellRendererToggle" id="cellrenderertoggle1"/>
<attributes>
<attribute name="active">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="fingerprint_column">
<property name="resizable">True</property>
<property name="title">Fingerprint</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext4"/>
<attributes>
<attribute name="markup">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="account_column">
<property name="resizable">True</property>
<property name="title">Account</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext5"/>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="verify_button">
<property name="label" translatable="yes" comments="button">Verify Fingerprint</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="verify_button_clicked_cb"/>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="forget_button">
<property name="label" translatable="yes" comments="button">Forget Fingerprint</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="forget_button_clicked_cb"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="tab label">Known Fingerprints</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
</interface>

View File

@@ -1,355 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkNotebook" id="otr_settings_notebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkVBox" id="otr_fp_vbox">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="our_fp_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label">Your fingerprint:
&lt;span weight="bold" face="monospace"&gt;01234567 89ABCDEF 01234567 89ABCDEF 01234567&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="their_fp_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label">Purported fingerprint for asdfasdf@xyzxyzxyz.de:
&lt;span weight="bold" face="monospace"&gt;01234567 89ABCDEF 01234567 89ABCDEF 01234567&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="verified_combobox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">verifiedmodel</property>
<property name="active">0</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="tab label">Authentication</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkVBox" id="otr_settings_vbox">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkCheckButton" id="otr_policy_allow_v2_checkbutton">
<property name="label" translatable="yes" comments="checkbox">OTR version 2 allowed</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="otr_policy_require_checkbutton">
<property name="label" translatable="yes" comments="checkbox">Encryption required</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="otr_policy_send_tag_checkbutton">
<property name="label" translatable="yes" comments="checkbox">Show others we understand OTR</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="otr_policy_start_on_tag_checkbutton">
<property name="label" translatable="yes" comments="checkbox">Automatically initiate encryption if partner understands OTR</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkCheckButton" id="otr_default_checkbutton">
<property name="label" translatable="yes" comments="checkbox">Use the default settings</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="tab label">OTR Settings</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<object class="GtkListStore" id="verifiedmodel">
<columns>
<!-- column-name verified -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes" comments="unverified option (dropdown label)">I have NOT verified that the purported fingerprint is in fact the correct fingerprint for that contact.</col>
</row>
<row>
<col id="0" translatable="yes" comments="verified option (dropdown label)">I have verified that the purported fingerprint is in fact the correct fingerprint for that contact.</col>
</row>
</data>
</object>
<object class="GtkWindow" id="otr_smp_window">
<property name="resizable">False</property>
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="desclabel1">
<property name="visible">True</property>
<property name="label">label</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<object class="GtkCheckButton" id="qcheckbutton">
<property name="label" translatable="yes" comments="checkbox for socialist millionaire protocol with question support">Use question: </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="qentry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="desclabel2">
<property name="visible">True</property>
<property name="label">label</property>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="secret_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<property name="border_width">5</property>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="smp_cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="smp_ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkMenuItem" id="otr_submenu">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes" comments="contact's submenu entry">Off-the-Record Encryption</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="otr_submenu_menu">
<child>
<object class="GtkMenuItem" id="otr_settings_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="menu entry for contact's otr settings">OTR settings / fingerprint</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_otr_settings_menuitem_activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="smp_otr_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="menu entry for SMP authentication">Authenticate contact</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_smp_otr_menuitem_activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="start_otr_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes" comments="menu entry for starting OTR session">Start / Refresh OTR</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_start_otr_menuitem_activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="end_otr_menuitem">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes" comments="menu entry for killing an OTR session">End OTR</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_end_otr_menuitem_activate"/>
</object>
</child>
</object>
</child>
</object>
</interface>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,8 +0,0 @@
[info]
name: Off-The-Record Encryption
short_name: gotr
version: 1.7.2
description: Provide OTR encryption
authors: Kjell Braden <afflux.gajim@pentabarf.de>
homepage: http://gajim-otr.pentabarf.de
max_gajim_version: 0.15.9

View File

@@ -1,665 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
## otrmodule.py
##
## Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Off-The-Record encryption plugin.
:author: Kjell self.Braden <kb.otr@pentabarf.de>
:since: 2008
:copyright: Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
:license: GPL
'''
MINVERSION = (1,0,0,'beta5')
MINCRYPTOVERSION = (2,1,0,'final',0)
IGNORE = True
PASS = False
DEFAULTFLAGS = {
'ALLOW_V1':False,
'ALLOW_V2':True,
'REQUIRE_ENCRYPTION':False,
'SEND_TAG':True,
'WHITESPACE_START_AKE':True,
'ERROR_START_AKE':True,
}
MMS = 1024
PROTOCOL = 'xmpp'
enc_tip = 'A private chat session <i>is established</i> to this contact ' \
'with this fingerprint'
unused_tip = 'A private chat session is established to this contact using ' \
'<i>another</i> fingerprint'
ended_tip = 'The private chat session to this contact has <i>ended</i>'
inactive_tip = 'Communication to this contact is currently ' \
'<i>unencrypted</i>'
import logging
import os
import pickle
import time
import sys
import common.xmpp
from common import gajim
from common import ged
from common.connection_handlers_events import MessageOutgoingEvent
from plugins import GajimPlugin
from message_control import TYPE_CHAT, MessageControl
from plugins.helpers import log_calls, log
from plugins.plugin import GajimPluginException
import ui
sys.path.insert(0, os.path.dirname(ui.__file__))
from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
HAS_CRYPTO = True
try:
import Crypto
if not hasattr(Crypto, 'version_info') \
or Crypto.version_info < MINCRYPTOVERSION:
raise ImportError('PyCrypto not found or too old')
except ImportError:
HAS_CRYPTO = False
HAS_POTR = True
try:
import potr
import potr.crypt
import potr.context
if not hasattr(potr, 'VERSION') or potr.VERSION < MINVERSION:
raise ImportError('old / unsupported python-otr version')
potrrootlog = logging.getLogger('potr')
potrrootlog.handlers = []
potrrootlog.propagate = False
gajimrootlog = logging.getLogger('gajim')
for h in gajimrootlog.handlers:
potrrootlog.addHandler(h)
def get_jid_from_fjid(fjid):
return gajim.get_room_and_nick_from_fjid(fjid)[0]
class GajimContext(potr.context.Context):
# self.peer is fjid
# self.jid does not contain resource
__slots__ = ['smpWindow', 'jid']
def __init__(self, account, peer):
super(GajimContext, self).__init__(account, peer)
self.jid = get_jid_from_fjid(peer)
self.trustName = self.jid
self.smpWindow = ui.ContactOtrSmpWindow(self)
def inject(self, msg, appdata=None):
log.debug('inject(appdata=%s)', appdata)
msg = unicode(msg)
account = self.user.accountname
stanza = common.xmpp.Message(to=self.peer, body=msg, typ='chat')
if appdata is not None:
session = appdata.get('session', None)
if session is not None:
stanza.setThread(session.thread_id)
gajim.connections[account].connection.send(stanza, now=True)
def setState(self, newstate):
if self.state == potr.context.STATE_ENCRYPTED:
# we were encrypted
if newstate == potr.context.STATE_ENCRYPTED:
# and are still -> it's just a refresh
OtrPlugin.gajim_log(
_('Private conversation with %s refreshed.') % self.peer,
self.user.accountname, self.peer)
elif newstate == potr.context.STATE_FINISHED:
# and aren't anymore -> other side disconnected
OtrPlugin.gajim_log(_('%s has ended his/her private '
'conversation with you. You should do the same.')
% self.peer, self.user.accountname, self.peer)
else:
if newstate == potr.context.STATE_ENCRYPTED:
# we are now encrypted
trust = self.getCurrentTrust()
if trust is None:
fpr = str(self.getCurrentKey())
OtrPlugin.gajim_log(_('New fingerprint for %(peer)s: %(fpr)s')
% {'peer': self.peer, 'fpr': fpr},
self.user.accountname, self.peer)
self.setCurrentTrust('')
trustStr = 'authenticated' if bool(trust) else '*unauthenticated*'
OtrPlugin.gajim_log(
_('%(trustStr)s secured OTR conversation with %(peer)s started')
% {'trustStr': trustStr, 'peer': self.peer},
self.user.accountname, self.peer)
if self.state != potr.context.STATE_PLAINTEXT and \
newstate == potr.context.STATE_PLAINTEXT:
# we are now plaintext
OtrPlugin.gajim_log(
_('Private conversation with %s lost.') % self.peer,
self.user.accountname, self.peer)
super(GajimContext, self).setState(newstate)
OtrPlugin.update_otr(self.peer, self.user.accountname)
self.user.plugin.update_context_list()
def getPolicy(self, key):
ret = self.user.plugin.get_flags(self.user.accountname, self.jid)[key]
log.debug('getPolicy(key=%s) = %s', key, ret)
return ret
class GajimOtrAccount(potr.context.Account):
contextclass = GajimContext
def __init__(self, plugin, accountname):
global PROTOCOL, MMS
self.plugin = plugin
self.accountname = accountname
name = gajim.get_jid_from_account(accountname)
super(GajimOtrAccount, self).__init__(name, PROTOCOL, MMS)
self.keyFilePath = os.path.join(gajim.gajimpaths.data_root, accountname)
def dropPrivkey(self):
try:
os.remove(self.keyFilePath + '.key3')
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when removing key file for %s',
self.name)
self.privkey = None
def loadPrivkey(self):
try:
with open(self.keyFilePath + '.key3', 'rb') as keyFile:
return potr.crypt.PK.parsePrivateKey(keyFile.read())[0]
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when loading key file for %s',
self.name)
return None
def savePrivkey(self):
try:
with open(self.keyFilePath + '.key3', 'wb') as keyFile:
keyFile.write(self.getPrivkey().serializePrivateKey())
except IOError, e:
log.exception('IOError occurred when loading key file for %s',
self.name)
def loadTrusts(self, newCtxCb=None):
''' load the fingerprint trustdb '''
# it has the same format as libotr, therefore the
# redundant account / proto field
try:
with open(self.keyFilePath + '.fpr', 'r') as fprFile:
for line in fprFile:
ctx, acc, proto, fpr, trust = line[:-1].split('\t')
if acc != self.name or proto != PROTOCOL:
continue
jid = get_jid_from_fjid(ctx)
self.setTrust(jid, fpr, trust)
except IOError, e:
if e.errno != 2:
log.exception('IOError occurred when loading fpr file for %s',
self.name)
def saveTrusts(self):
try:
with open(self.keyFilePath + '.fpr', 'w') as fprFile:
for uid, trusts in self.trusts.iteritems():
for fpr, trustVal in trusts.iteritems():
fprFile.write('\t'.join(
(uid, self.name, PROTOCOL, fpr, trustVal)))
fprFile.write('\n')
except IOError, e:
log.exception('IOError occurred when loading fpr file for %s',
self.name)
except ImportError:
HAS_POTR = False
def otr_dialog_destroy(widget, *args, **kwargs):
widget.destroy()
class OtrPlugin(GajimPlugin):
otr = None
def init(self):
self.description = _('See http://www.cypherpunks.ca/otr/')
self.us = {}
if not HAS_POTR:
self.activatable = False
self.available_text = _('Can\'t find potr. Verify this ' \
'plugin\'s integrity.')
return
if not HAS_CRYPTO:
self.activatable = False
self.available_text = _('PyCrypto not installed or too old.')
return
self.config_dialog = ui.OtrPluginConfigDialog(self)
self.events_handlers = {}
self.events_handlers['message-received'] = (ged.PRECORE,
self.handle_incoming_msg)
self.events_handlers['message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg)
self.gui_extension_points = {
'chat_control' : (self.cc_connect, self.cc_disconnect)
}
for acc in gajim.contacts.get_accounts():
self.us[acc] = GajimOtrAccount(self, acc)
self.us[acc].loadTrusts()
acc = str(acc)
if acc not in self.config or None not in self.config[acc]:
self.config[acc] = {None:DEFAULTFLAGS.copy()}
self.update_context_list()
@log_calls('OtrPlugin')
def activate(self):
if not HAS_CRYPTO or not HAS_POTR or not hasattr(potr, 'VERSION') \
or potr.VERSION < MINVERSION:
raise GajimPluginException(self.available_text)
def get_otr_status(self, account, contact):
ctx = self.us[account].getContext(contact.get_full_jid())
finished = ctx.state == potr.context.STATE_FINISHED
encrypted = finished or ctx.state == potr.context.STATE_ENCRYPTED
trusted = encrypted and bool(ctx.getCurrentTrust())
return (encrypted, trusted, finished)
def cc_connect(self, cc):
def update_otr(print_status=False):
enc_status, authenticated, finished = \
self.get_otr_status(cc.account, cc.contact)
otr_status_text = ''
if finished:
otr_status_text = u'finished OTR connection'
elif authenticated:
otr_status_text = u'authenticated secure OTR connection'
elif enc_status:
otr_status_text = u'*unauthenticated* secure OTR connection'
cc._show_lock_image(enc_status, u'OTR', enc_status, True,
authenticated)
if print_status and otr_status_text:
cc.print_conversation_line(u'[OTR] %s' % otr_status_text,
'status', '', None)
cc.update_otr = update_otr
cc.update_otr(True)
# hijack authentication button with our submenu
def authbutton_cb(widget):
if not cc.gpg_is_active and not (cc.session and
cc.session.enable_encryption):
ui.get_otr_submenu(self, cc).get_submenu().popup(None,
None, None, 0, 0)
else:
cc._on_authentication_button_clicked(widget)
self.overwrite_handler(cc, cc.authentication_button, authbutton_cb)
# hijack context menu
cc.orig_prepare_context_menu = cc.prepare_context_menu
def inject_menu(hide_buttonbar_items=False):
menu = cc.orig_prepare_context_menu(hide_buttonbar_items)
menu.insert(ui.get_otr_submenu(self, cc), 8)
return menu
cc.prepare_context_menu = inject_menu
def cc_disconnect(self, cc):
try:
self.overwrite_handler(cc, cc.authentication_button,
cc._on_authentication_button_clicked)
cc.prepare_context_menu = cc.orig_prepare_context_menu
del cc.update_otr
except AttributeError:
pass
def menu_settings_cb(self, item, control):
ctx = self.us[control.account].getContext(control.contact.get_full_jid())
dlg = ui.ContactOtrWindow(self, ctx)
dlg.run()
dlg.destroy()
def menu_start_cb(self, item, control):
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=control.account, jid=control.contact.jid,
message=u'?OTRv?', type_='chat',
resource=control.contact.resource, is_loggable=False))
def menu_end_cb(self, item, control):
fjid = control.contact.get_full_jid()
thread_id = control.session.thread_id if control.session else None
self.us[control.account].getContext(fjid).disconnect(
appdata={'session':control.session})
def menu_smp_cb(self, item, control):
ctx = self.us[control.account].getContext(control.contact.get_full_jid())
ctx.smpWindow.show(False)
@staticmethod
def overwrite_handler(window, control, handler):
for id_, v in window.handlers.iteritems():
if v == control:
break
else:
raise LookupError
del window.handlers[id_]
control.disconnect(id_)
id_ = control.connect('clicked', handler)
window.handlers[id_] = control
def set_flags(self, value, account=None, contact=None):
if isinstance(account, unicode):
account = account.encode()
if account not in self.config:
self.config[account] = {None:DEFAULTFLAGS.copy()}
if account is None and contact is not None:
# don't set per-contact options without account
raise Exception("can't set contact flags without account")
config = self.config[account]
config[contact] = value
self.config[account] = config
def get_flags(self, account=None, contact=None, fallback=True):
if isinstance(account, unicode):
account = account.encode()
setting = DEFAULTFLAGS.copy()
if account in self.config:
setting.update(self.config[account][None])
if contact in self.config[account] \
and self.config[account][contact] is not None:
setting.update(self.config[account][contact])
elif not fallback:
return None
return setting
def update_context_list(self):
self.config_dialog.fpr_model.clear()
for us in self.us.itervalues():
usedFpr = set()
for fjid, ctx in us.ctxs.iteritems():
# get active contexts first
key = ctx.getCurrentKey()
if not key:
continue
fpr = key.cfingerprint()
usedFpr.add(fpr)
human_hash = potr.human_hash(fpr)
trust = bool(us.getTrust(ctx.trustName, fpr))
if ctx.state == potr.context.STATE_ENCRYPTED:
state = "encrypted"
tip = enc_tip
elif ctx.state == potr.context.STATE_FINISHED:
state = "finished"
tip = ended_tip
else:
state = 'inactive'
tip = inactive_tip
self.config_dialog.fpr_model.append((fjid, state, trust,
'<tt>%s</tt>' % human_hash, us.name, tip, fpr))
for uid, trusts in us.trusts.iteritems():
for fpr, trust in trusts.iteritems():
if fpr in usedFpr:
continue
state = 'inactive'
tip = inactive_tip
human_hash = potr.human_hash(fpr)
self.config_dialog.fpr_model.append((uid, state, bool(trust),
'<tt>%s</tt>' % human_hash, us.name, tip, fpr))
@classmethod
def gajim_log(cls, msg, account, fjid, no_print=False,
is_status_message=True, thread_id=None):
if not isinstance(fjid, unicode):
fjid = unicode(fjid)
if not isinstance(account, unicode):
account = unicode(account)
resource = gajim.get_resource_from_jid(fjid)
jid = gajim.get_jid_without_resource(fjid)
tim = time.localtime()
if is_status_message is True:
if not no_print:
ctrl = cls.get_control(fjid, account)
if ctrl:
ctrl.print_conversation_line(u'[OTR] %s' % msg, 'status',
'', None)
id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
# gajim.logger.write() only marks a message as unread (and so
# only returns an id) when fjid is a real contact (NOT if it's a
# GC private chat)
if id:
gajim.logger.set_read_messages([id])
else:
session = gajim.connections[account].get_or_create_session(fjid,
thread_id)
session.received_thread_id |= bool(thread_id)
session.last_receive = time.time()
if not session.control:
# look for an existing chat control without a session
ctrl = cls.get_control(fjid, account)
if ctrl:
session.control = ctrl
session.control.set_session(session)
msg_id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
session.roster_message(jid, msg, tim=tim, msg_id=msg_id,
msg_type='chat', resource=resource)
@classmethod
def update_otr(cls, user, acc, print_status=False):
ctrl = cls.get_control(user, acc)
if ctrl:
ctrl.update_otr(print_status)
@staticmethod
def get_control(fjid, account):
# first try to get the window with the full jid
ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
if ctrl:
# got one, be happy
return ctrl
# otherwise try without the resource
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(fjid), account)
# but only use it when it's not a GC window
if ctrl and ctrl.TYPE_ID == TYPE_CHAT:
return ctrl
def handle_incoming_msg(self, event):
ctx = None
account = event.conn.name
accjid = gajim.get_jid_from_account(account)
if event.encrypted is not False or not event.stanza.getTag('body') \
or not isinstance(event.stanza.getBody(), unicode):
return PASS
try:
ctx = self.us[account].getContext(event.fjid)
msgtxt, tlvs = ctx.receiveMessage(event.msgtxt,
appdata={'session':event.session})
except potr.context.NotOTRMessage, e:
# received message was not OTR - pass it on
return PASS
except potr.context.UnencryptedMessage, e:
# we are encrypted but got some plaintext
# display it with a warning
tlvs = []
msgtxt = _('The following message received from %(jid)s was '
'*not encrypted*: [%(error)s]') % {'jid': event.fjid,
'error': e.args[0]}
except potr.context.NotEncryptedError, e:
# we got some encrypted data
# but we don't have an encrypted session
self.gajim_log(_('The encrypted message received from %s is '
'unreadable, as you are not currently communicating '
'privately') % event.fjid, account, event.fjid)
return IGNORE
except potr.context.ErrorReceived, e:
# got a protocol error
self.gajim_log(_('We received the following OTR error '
'message from %(jid)s: [%(error)s]') % {'jid': event.fjid,
'error': e.args[0].error},
account, event.fjid)
return IGNORE
except potr.crypt.InvalidParameterError, e:
# received a packet we cannot process (probably tampered or
# sent to wrong session)
self.gajim_log(_('We received an unreadable OTR message '
'from %(jid)s. It has probably been tampered with, '
'or was sent from an older OTR session.')
% {'jid':event.fjid}, account, event.fjid)
return IGNORE
except RuntimeError, e:
# generic library bug?
self.gajim_log(_('The following error occurred when trying to '
'decrypt a message from %(jid)s: [%(error)s]') % {
'jid': event.fjid, 'error': e},
account, event.fjid)
return IGNORE
if ctx is not None:
ctx.smpWindow.handle_tlv(tlvs)
stripper = HTMLStripper()
stripper.feed(unicode(msgtxt or ''))
event.msgtxt = stripper.stripped_data
event.stanza.setBody(event.msgtxt)
event.stanza.setXHTML(msgtxt)
return PASS
def handle_outgoing_msg(self, event):
if hasattr(event, 'otrmessage'):
return PASS
xep_200 = bool(event.session) and event.session.enable_encryption
if xep_200 or not event.message:
return PASS
if event.session:
fjid = event.session.get_to()
else:
fjid = event.jid
if event.resource:
fjid += '/' + event.resource
message = event.xhtml or escape(event.message)
try:
newmsg = self.us[event.account].getContext(fjid).sendMessage(
potr.context.FRAGMENT_SEND_ALL_BUT_LAST, message,
appdata={'session':event.session})
except potr.context.NotEncryptedError, e:
if e.args[0] == potr.context.EXC_FINISHED:
self.gajim_log(_('Your message was not send. Either end '
'your private conversation, or restart it'), event.account,
fjid)
return IGNORE
else:
raise e
if event.xhtml: # if we had html before, replace with new content
event.xhtml = newmsg
stripper = HTMLStripper()
stripper.feed(unicode(newmsg or ''))
event.message = stripper.stripped_data
return PASS
class HTMLStripper(HTMLParser):
def reset(self):
self.stripped_data = ''
HTMLParser.reset(self)
def handle_data(self, data):
self.stripped_data += data
def handle_starttag(self, tag, attrs):
if tag == 'br':
self.stripped_data += '\n'
def handle_entityref(self, name):
c = unichr(name2codepoint[name])
self.stripped_data += c
def handle_charref(self, name):
if name.startswith('x'):
c = unichr(int(name[1:], 16))
else:
c = unichr(int(name))
self.stripped_data += c
def unknown_decl(self, data):
if data.startswith('CDATA['):
self.stripped_data += data[6:]
def feed(self, data):
data = data.replace('\n', '')
HTMLParser.feed(self, data)
def escape(s):
'''Replace special characters "&", "<" and ">" to HTML-safe sequences.
If the optional flag quote is true, the quotation mark character (")
is also translated.'''
s = s.replace("&", "&amp;") # Must be done first!
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
s = s.replace("\n", "<br/>")
return s
## TODO:
## - disconnect ctxs on disconnect

View File

@@ -1,27 +0,0 @@
# Copyright 2011-2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
from potr import context
from potr import proto
from potr.utils import human_hash
''' version is: (major, minor, patch, sub) with sub being one of 'alpha',
'beta', 'final' '''
VERSION = (1, 0, 0, 'beta7')

View File

@@ -1,21 +0,0 @@
# Copyright 2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from potr.compatcrypto.common import *
from potr.compatcrypto.pycrypto import *

View File

@@ -1,108 +0,0 @@
# Copyright 2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
import logging
import struct
from potr.utils import human_hash, bytes_to_long, unpack, pack_mpi
DEFAULT_KEYTYPE = 0x0000
pkTypes = {}
def registerkeytype(cls):
if cls.keyType is None:
raise TypeError('registered key class needs a type value')
pkTypes[cls.keyType] = cls
return cls
def generateDefaultKey():
return pkTypes[DEFAULT_KEYTYPE].generate()
class PK(object):
keyType = None
@classmethod
def generate(cls):
raise NotImplementedError
@classmethod
def parsePayload(cls, data, private=False):
raise NotImplementedError
def sign(self, data):
raise NotImplementedError
def verify(self, data):
raise NotImplementedError
def fingerprint(self):
raise NotImplementedError
def serializePublicKey(self):
return struct.pack(b'!H', self.keyType) \
+ self.getSerializedPublicPayload()
def getSerializedPublicPayload(self):
buf = b''
for x in self.getPublicPayload():
buf += pack_mpi(x)
return buf
def getPublicPayload(self):
raise NotImplementedError
def serializePrivateKey(self):
return struct.pack(b'!H', self.keyType) \
+ self.getSerializedPrivatePayload()
def getSerializedPrivatePayload(self):
buf = b''
for x in self.getPrivatePayload():
buf += pack_mpi(x)
return buf
def getPrivatePayload(self):
raise NotImplementedError
def cfingerprint(self):
return '{0:040x}'.format(bytes_to_long(self.fingerprint()))
@classmethod
def parsePrivateKey(cls, data):
implCls, data = cls.getImplementation(data)
logging.debug('Got privkey of type %r', implCls)
return implCls.parsePayload(data, private=True)
@classmethod
def parsePublicKey(cls, data):
implCls, data = cls.getImplementation(data)
logging.debug('Got pubkey of type %r', implCls)
return implCls.parsePayload(data)
def __str__(self):
return human_hash(self.cfingerprint())
def __repr__(self):
return '<{cls}(fpr=\'{fpr}\')>'.format(
cls=self.__class__.__name__, fpr=str(self))
@staticmethod
def getImplementation(data):
typeid, data = unpack(b'!H', data)
cls = pkTypes.get(typeid, None)
if cls is None:
raise NotImplementedError('unknown typeid %r' % typeid)
return cls, data

View File

@@ -1,144 +0,0 @@
# Copyright 2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from Crypto import Cipher
from Crypto.Hash import SHA256 as _SHA256
from Crypto.Hash import SHA as _SHA1
from Crypto.Hash import HMAC as _HMAC
from Crypto.PublicKey import DSA
from Crypto.Random import random
from numbers import Number
from potr.compatcrypto import common
from potr.utils import read_mpi, bytes_to_long, long_to_bytes
def SHA256(data):
return _SHA256.new(data).digest()
def SHA1(data):
return _SHA1.new(data).digest()
def HMAC(key, data, mod):
return _HMAC.new(key, msg=data, digestmod=mod).digest()
def SHA1HMAC(key, data):
return HMAC(key, data, _SHA1)
def SHA256HMAC(key, data):
return HMAC(key, data, _SHA256)
def SHA256HMAC160(key, data):
return SHA256HMAC(key, data)[:20]
def AESCTR(key, counter=0):
if isinstance(counter, Number):
counter = Counter(counter)
if not isinstance(counter, Counter):
raise TypeError
return Cipher.AES.new(key, Cipher.AES.MODE_CTR, counter=counter)
class Counter(object):
def __init__(self, prefix):
self.prefix = prefix
self.val = 0
def inc(self):
self.prefix += 1
self.val = 0
def __setattr__(self, attr, val):
if attr == 'prefix':
self.val = 0
super(Counter, self).__setattr__(attr, val)
def __repr__(self):
return '<Counter(p={p!r},v={v!r})>'.format(p=self.prefix, v=self.val)
def byteprefix(self):
return long_to_bytes(self.prefix, 8)
def __call__(self):
bytesuffix = long_to_bytes(self.val, 8)
self.val += 1
return self.byteprefix() + bytesuffix
@common.registerkeytype
class DSAKey(common.PK):
keyType = 0x0000
def __init__(self, key=None, private=False):
self.priv = self.pub = None
if not isinstance(key, tuple):
raise TypeError('4/5-tuple required for key')
if len(key) == 5 and private:
self.priv = DSA.construct(key)
self.pub = self.priv.publickey()
elif len(key) == 4 and not private:
self.pub = DSA.construct(key)
else:
raise TypeError('wrong number of arguments for ' \
'private={0!r}: got {1} '
.format(private, len(key)))
def getPublicPayload(self):
return (self.pub.p, self.pub.q, self.pub.g, self.pub.y)
def getPrivatePayload(self):
return (self.priv.p, self.priv.q, self.priv.g, self.priv.y, self.priv.x)
def fingerprint(self):
return SHA1(self.getSerializedPublicPayload())
def sign(self, data):
# 2 <= K <= q
K = random.randrange(2, self.priv.q)
r, s = self.priv.sign(data, K)
return long_to_bytes(r, 20) + long_to_bytes(s, 20)
def verify(self, data, sig):
r, s = bytes_to_long(sig[:20]), bytes_to_long(sig[20:])
return self.pub.verify(data, (r, s))
def __hash__(self):
return bytes_to_long(self.fingerprint())
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
return self.fingerprint() == other.fingerprint()
def __ne__(self, other):
return not (self == other)
@classmethod
def generate(cls):
privkey = DSA.generate(1024)
return cls((privkey.key.y, privkey.key.g, privkey.key.p, privkey.key.q,
privkey.key.x), private=True)
@classmethod
def parsePayload(cls, data, private=False):
p, data = read_mpi(data)
q, data = read_mpi(data)
g, data = read_mpi(data)
y, data = read_mpi(data)
if private:
x, data = read_mpi(data)
return cls((y, g, p, q, x), private=True), data
return cls((y, g, p, q), private=False), data

View File

@@ -1,568 +0,0 @@
# Copyright 2011-2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
try:
type(basestring)
except NameError:
# all strings are unicode in python3k
basestring = str
unicode = str
# callable is not available in python 3.0 and 3.1
try:
type(callable)
except NameError:
from collections import Callable
def callable(x):
return isinstance(x, Callable)
import base64
import logging
import struct
logger = logging.getLogger(__name__)
from potr import crypt
from potr import proto
from potr import compatcrypto
from time import time
EXC_UNREADABLE_MESSAGE = 1
EXC_FINISHED = 2
HEARTBEAT_INTERVAL = 60
STATE_PLAINTEXT = 0
STATE_ENCRYPTED = 1
STATE_FINISHED = 2
FRAGMENT_SEND_ALL = 0
FRAGMENT_SEND_ALL_BUT_FIRST = 1
FRAGMENT_SEND_ALL_BUT_LAST = 2
OFFER_NOTSENT = 0
OFFER_SENT = 1
OFFER_REJECTED = 2
OFFER_ACCEPTED = 3
class Context(object):
def __init__(self, account, peername):
self.user = account
self.peer = peername
self.policy = {}
self.crypto = crypt.CryptEngine(self)
self.tagOffer = OFFER_NOTSENT
self.mayRetransmit = 0
self.lastSend = 0
self.lastMessage = None
self.state = STATE_PLAINTEXT
self.trustName = self.peer
self.fragmentInfo = None
self.fragment = None
self.discardFragment()
def getPolicy(self, key):
raise NotImplementedError
def inject(self, msg, appdata=None):
raise NotImplementedError
def policyOtrEnabled(self):
return self.getPolicy('ALLOW_V2') or self.getPolicy('ALLOW_V1')
def discardFragment(self):
self.fragmentInfo = (0, 0)
self.fragment = []
def fragmentAccumulate(self, message):
'''Accumulate a fragmented message. Returns None if the fragment is
to be ignored, returns a string if the message is ready for further
processing'''
params = message.split(b',')
if len(params) < 5 or not params[1].isdigit() or not params[2].isdigit():
logger.warning('invalid formed fragmented message: %r', params)
self.discardFragment()
return message
K, N = self.fragmentInfo
try:
k = int(params[1])
n = int(params[2])
except ValueError:
logger.warning('invalid formed fragmented message: %r', params)
self.discardFragment()
return message
fragData = params[3]
logger.debug(params)
if n >= k == 1:
# first fragment
self.discardFragment()
self.fragmentInfo = (k, n)
self.fragment.append(fragData)
elif N == n >= k > 1 and k == K+1:
# accumulate
self.fragmentInfo = (k, n)
self.fragment.append(fragData)
else:
# bad, discard
self.discardFragment()
logger.warning('invalid fragmented message: %r', params)
return message
if n == k > 0:
assembled = b''.join(self.fragment)
self.discardFragment()
return assembled
return None
def removeFingerprint(self, fingerprint):
self.user.removeFingerprint(self.trustName, fingerprint)
def setTrust(self, fingerprint, trustLevel):
''' sets the trust level for the given fingerprint.
trust is usually:
- the empty string for known but untrusted keys
- 'verified' for manually verified keys
- 'smp' for smp-style verified keys '''
self.user.setTrust(self.trustName, fingerprint, trustLevel)
def getTrust(self, fingerprint, default=None):
return self.user.getTrust(self.trustName, fingerprint, default)
def setCurrentTrust(self, trustLevel):
self.setTrust(self.crypto.theirPubkey.cfingerprint(), trustLevel)
def getCurrentKey(self):
return self.crypto.theirPubkey
def getCurrentTrust(self):
''' returns a 2-tuple: first element is the current fingerprint,
second is:
- None if the key is unknown yet
- a non-empty string if the key is trusted
- an empty string if the key is untrusted '''
if self.crypto.theirPubkey is None:
return None
return self.getTrust(self.crypto.theirPubkey.cfingerprint(), None)
def receiveMessage(self, messageData, appdata=None):
IGN = None, []
if not self.policyOtrEnabled():
raise NotOTRMessage(messageData)
message = self.parse(messageData)
if message is None:
# nothing to see. move along.
return IGN
logger.debug(repr(message))
if self.getPolicy('SEND_TAG'):
if isinstance(message, basestring):
# received a plaintext message without tag
# we should not tag anymore
self.tagOffer = OFFER_REJECTED
else:
# got something OTR-ish, cool!
self.tagOffer = OFFER_ACCEPTED
if isinstance(message, proto.Query):
self.handleQuery(message, appdata=appdata)
if isinstance(message, proto.TaggedPlaintext):
# it's actually a plaintext message
if self.state != STATE_PLAINTEXT or \
self.getPolicy('REQUIRE_ENCRYPTION'):
# but we don't want plaintexts
raise UnencryptedMessage(message.msg)
raise NotOTRMessage(message.msg)
return IGN
if isinstance(message, proto.AKEMessage):
self.crypto.handleAKE(message, appdata=appdata)
return IGN
if isinstance(message, proto.DataMessage):
ignore = message.flags & proto.MSGFLAGS_IGNORE_UNREADABLE
if self.state != STATE_ENCRYPTED:
self.sendInternal(proto.Error(
'You sent encrypted to {user}, who wasn\'t expecting it.'
.format(user=self.user.name).encode('utf-8')), appdata=appdata)
if ignore:
return IGN
raise NotEncryptedError(EXC_UNREADABLE_MESSAGE)
try:
plaintext, tlvs = self.crypto.handleDataMessage(message)
self.processTLVs(tlvs, appdata=appdata)
if plaintext and self.lastSend < time() - HEARTBEAT_INTERVAL:
self.sendInternal(b'', appdata=appdata)
return plaintext or None, tlvs
except crypt.InvalidParameterError:
if ignore:
return IGN
logger.exception('decryption failed')
raise
if isinstance(message, basestring):
if self.state != STATE_PLAINTEXT or \
self.getPolicy('REQUIRE_ENCRYPTION'):
raise UnencryptedMessage(message)
if isinstance(message, proto.Error):
raise ErrorReceived(message)
raise NotOTRMessage(messageData)
def sendInternal(self, msg, tlvs=[], appdata=None):
self.sendMessage(FRAGMENT_SEND_ALL, msg, tlvs=tlvs, appdata=appdata,
flags=proto.MSGFLAGS_IGNORE_UNREADABLE)
def sendMessage(self, sendPolicy, msg, flags=0, tlvs=[], appdata=None):
if self.policyOtrEnabled():
self.lastSend = time()
if isinstance(msg, proto.OTRMessage):
# we want to send a protocol message (probably internal)
# so we don't need further protocol encryption
# also we can't add TLVs to arbitrary protocol messages
if tlvs:
raise TypeError('can\'t add tlvs to protocol message')
else:
# we got plaintext to send. encrypt it
msg = self.processOutgoingMessage(msg, flags, tlvs)
if isinstance(msg, proto.OTRMessage) \
and not isinstance(msg, proto.Query):
# if it's a query message, it must not get fragmented
return self.sendFragmented(bytes(msg), policy=sendPolicy, appdata=appdata)
else:
msg = bytes(msg)
return msg
def processOutgoingMessage(self, msg, flags, tlvs=[]):
isQuery = self.parseExplicitQuery(msg) is not None
if isQuery:
return self.user.getDefaultQueryMessage(self.getPolicy)
if self.state == STATE_PLAINTEXT:
if self.getPolicy('REQUIRE_ENCRYPTION'):
if not isQuery:
self.lastMessage = msg
self.lastSend = time()
self.mayRetransmit = 2
# TODO notify
msg = self.user.getDefaultQueryMessage(self.getPolicy)
return msg
if self.getPolicy('SEND_TAG') and self.tagOffer != OFFER_REJECTED:
self.tagOffer = OFFER_SENT
versions = set()
if self.getPolicy('ALLOW_V1'):
versions.add(1)
if self.getPolicy('ALLOW_V2'):
versions.add(2)
return proto.TaggedPlaintext(msg, versions)
return msg
if self.state == STATE_ENCRYPTED:
msg = self.crypto.createDataMessage(msg, flags, tlvs)
self.lastSend = time()
return msg
if self.state == STATE_FINISHED:
raise NotEncryptedError(EXC_FINISHED)
def disconnect(self, appdata=None):
if self.state != STATE_FINISHED:
self.sendInternal(b'', tlvs=[proto.DisconnectTLV()], appdata=appdata)
self.setState(STATE_PLAINTEXT)
self.crypto.finished()
else:
self.setState(STATE_PLAINTEXT)
def setState(self, newstate):
self.state = newstate
def _wentEncrypted(self):
self.setState(STATE_ENCRYPTED)
def sendFragmented(self, msg, policy=FRAGMENT_SEND_ALL, appdata=None):
mms = self.maxMessageSize(appdata)
msgLen = len(msg)
if mms != 0 and msgLen > mms:
fms = mms - 19
fragments = [ msg[i:i+fms] for i in range(0, msgLen, fms) ]
fc = len(fragments)
if fc > 65535:
raise OverflowError('too many fragments')
for fi in range(len(fragments)):
ctr = unicode(fi+1) + ',' + unicode(fc) + ','
fragments[fi] = b'?OTR,' + ctr.encode('ascii') \
+ fragments[fi] + b','
if policy == FRAGMENT_SEND_ALL:
for f in fragments:
self.inject(f, appdata=appdata)
return None
elif policy == FRAGMENT_SEND_ALL_BUT_FIRST:
for f in fragments[1:]:
self.inject(f, appdata=appdata)
return fragments[0]
elif policy == FRAGMENT_SEND_ALL_BUT_LAST:
for f in fragments[:-1]:
self.inject(f, appdata=appdata)
return fragments[-1]
else:
if policy == FRAGMENT_SEND_ALL:
self.inject(msg, appdata=appdata)
return None
else:
return msg
def processTLVs(self, tlvs, appdata=None):
for tlv in tlvs:
if isinstance(tlv, proto.DisconnectTLV):
logger.info('got disconnect tlv, forcing finished state')
self.setState(STATE_FINISHED)
self.crypto.finished()
# TODO cleanup
continue
if isinstance(tlv, proto.SMPTLV):
self.crypto.smpHandle(tlv, appdata=appdata)
continue
logger.info('got unhandled tlv: {0!r}'.format(tlv))
def smpAbort(self, appdata=None):
if self.state != STATE_ENCRYPTED:
raise NotEncryptedError
self.crypto.smpAbort(appdata=appdata)
def smpIsValid(self):
return self.crypto.smp and self.crypto.smp.prog != crypt.SMPPROG_CHEATED
def smpIsSuccess(self):
return self.crypto.smp.prog == crypt.SMPPROG_SUCCEEDED \
if self.crypto.smp else None
def smpGotSecret(self, secret, question=None, appdata=None):
if self.state != STATE_ENCRYPTED:
raise NotEncryptedError
self.crypto.smpSecret(secret, question=question, appdata=appdata)
def smpInit(self, secret, question=None, appdata=None):
if self.state != STATE_ENCRYPTED:
raise NotEncryptedError
self.crypto.smp = None
self.crypto.smpSecret(secret, question=question, appdata=appdata)
def handleQuery(self, message, appdata=None):
if 2 in message.versions and self.getPolicy('ALLOW_V2'):
self.authStartV2(appdata=appdata)
elif 1 in message.versions and self.getPolicy('ALLOW_V1'):
self.authStartV1(appdata=appdata)
def authStartV1(self, appdata=None):
raise NotImplementedError()
def authStartV2(self, appdata=None):
self.crypto.startAKE(appdata=appdata)
def parseExplicitQuery(self, message):
otrTagPos = message.find(proto.OTRTAG)
if otrTagPos == -1:
return None
indexBase = otrTagPos + len(proto.OTRTAG)
if len(message) <= indexBase:
return None
compare = message[indexBase]
hasq = compare == b'?'[0]
hasv = compare == b'v'[0]
if not hasq and not hasv:
return None
hasv |= len(message) > indexBase+1 and message[indexBase+1] == b'v'[0]
if hasv:
end = message.find(b'?', indexBase+1)
else:
end = indexBase+1
return message[indexBase:end]
def parse(self, message, nofragment=False):
otrTagPos = message.find(proto.OTRTAG)
if otrTagPos == -1:
if proto.MESSAGE_TAG_BASE in message:
return proto.TaggedPlaintext.parse(message)
else:
return message
indexBase = otrTagPos + len(proto.OTRTAG)
if len(message) <= indexBase:
return message
compare = message[indexBase]
if nofragment is False and compare == b','[0]:
message = self.fragmentAccumulate(message[indexBase:])
if message is None:
return None
else:
return self.parse(message, nofragment=True)
else:
self.discardFragment()
queryPayload = self.parseExplicitQuery(message)
if queryPayload is not None:
return proto.Query.parse(queryPayload)
if compare == b':'[0] and len(message) > indexBase + 4:
try:
infoTag = base64.b64decode(message[indexBase+1:indexBase+5])
classInfo = struct.unpack(b'!HB', infoTag)
cls = proto.messageClasses.get(classInfo, None)
if cls is None:
return message
logger.debug('{user} got msg {typ!r}' \
.format(user=self.user.name, typ=cls))
return cls.parsePayload(message[indexBase+5:])
except (TypeError, struct.error):
logger.exception('could not parse OTR message %s', message)
return message
if message[indexBase:indexBase+7] == b' Error:':
return proto.Error(message[indexBase+7:])
return message
def maxMessageSize(self, appdata=None):
"""Return the max message size for this context."""
return self.user.maxMessageSize
def getExtraKey(self, extraKeyAppId=None, extraKeyAppData=None, appdata=None):
""" retrieves the generated extra symmetric key.
if extraKeyAppId is set, notifies the chat partner about intended
usage (additional application specific information can be supplied in
extraKeyAppData).
returns the 256 bit symmetric key """
if self.state != STATE_ENCRYPTED:
raise NotEncryptedError
if extraKeyAppId is not None:
tlvs = [proto.ExtraKeyTLV(extraKeyAppId, extraKeyAppData)]
self.sendInternal(b'', tlvs=tlvs, appdata=appdata)
return self.crypto.extraKey
class Account(object):
contextclass = Context
def __init__(self, name, protocol, maxMessageSize, privkey=None):
self.name = name
self.privkey = privkey
self.policy = {}
self.protocol = protocol
self.ctxs = {}
self.trusts = {}
self.maxMessageSize = maxMessageSize
self.defaultQuery = '?OTRv{versions}?\n{accountname} has requested ' \
'an Off-the-Record private conversation. However, you ' \
'do not have a plugin to support that.\nSee '\
'http://otr.cypherpunks.ca/ for more information.'
def __repr__(self):
return '<{cls}(name={name!r})>'.format(cls=self.__class__.__name__,
name=self.name)
def getPrivkey(self, autogen=True):
if self.privkey is None:
self.privkey = self.loadPrivkey()
if self.privkey is None:
if autogen is True:
self.privkey = compatcrypto.generateDefaultKey()
self.savePrivkey()
else:
raise LookupError
return self.privkey
def loadPrivkey(self):
raise NotImplementedError
def savePrivkey(self):
raise NotImplementedError
def saveTrusts(self):
raise NotImplementedError
def getContext(self, uid, newCtxCb=None):
if uid not in self.ctxs:
self.ctxs[uid] = self.contextclass(self, uid)
if callable(newCtxCb):
newCtxCb(self.ctxs[uid])
return self.ctxs[uid]
def getDefaultQueryMessage(self, policy):
v = '2' if policy('ALLOW_V2') else ''
msg = self.defaultQuery.format(accountname=self.name, versions=v)
return msg.encode('ascii')
def setTrust(self, key, fingerprint, trustLevel):
if key not in self.trusts:
self.trusts[key] = {}
self.trusts[key][fingerprint] = trustLevel
self.saveTrusts()
def getTrust(self, key, fingerprint, default=None):
if key not in self.trusts:
return default
return self.trusts[key].get(fingerprint, default)
def removeFingerprint(self, key, fingerprint):
if key in self.trusts and fingerprint in self.trusts[key]:
del self.trusts[key][fingerprint]
class NotEncryptedError(RuntimeError):
pass
class UnencryptedMessage(RuntimeError):
pass
class ErrorReceived(RuntimeError):
pass
class NotOTRMessage(RuntimeError):
pass

View File

@@ -1,795 +0,0 @@
# Copyright 2011-2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
import logging
import struct
from potr.compatcrypto import SHA256, SHA1, SHA1HMAC, SHA256HMAC, \
SHA256HMAC160, Counter, AESCTR, PK, random
from potr.utils import bytes_to_long, long_to_bytes, pack_mpi, read_mpi
from potr import proto
logger = logging.getLogger(__name__)
STATE_NONE = 0
STATE_AWAITING_DHKEY = 1
STATE_AWAITING_REVEALSIG = 2
STATE_AWAITING_SIG = 4
STATE_V1_SETUP = 5
DH_MODULUS = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919
DH_MODULUS_2 = DH_MODULUS-2
DH_GENERATOR = 2
DH_BITS = 1536
DH_MAX = 2**DH_BITS
SM_ORDER = (DH_MODULUS - 1) // 2
def check_group(n):
return 2 <= n <= DH_MODULUS_2
def check_exp(n):
return 1 <= n < SM_ORDER
class DH(object):
@classmethod
def set_params(cls, prime, gen):
cls.prime = prime
cls.gen = gen
def __init__(self):
self.priv = random.randrange(2, 2**320)
self.pub = pow(self.gen, self.priv, self.prime)
DH.set_params(DH_MODULUS, DH_GENERATOR)
class DHSession(object):
def __init__(self, sendenc, sendmac, rcvenc, rcvmac):
self.sendenc = sendenc
self.sendmac = sendmac
self.rcvenc = rcvenc
self.rcvmac = rcvmac
self.sendctr = Counter(0)
self.rcvctr = Counter(0)
self.sendmacused = False
self.rcvmacused = False
def __repr__(self):
return '<{cls}(send={s!r},rcv={r!r})>' \
.format(cls=self.__class__.__name__,
s=self.sendmac, r=self.rcvmac)
@classmethod
def create(cls, dh, y):
s = pow(y, dh.priv, DH_MODULUS)
sb = pack_mpi(s)
if dh.pub > y:
sendbyte = b'\1'
rcvbyte = b'\2'
else:
sendbyte = b'\2'
rcvbyte = b'\1'
sendenc = SHA1(sendbyte + sb)[:16]
sendmac = SHA1(sendenc)
rcvenc = SHA1(rcvbyte + sb)[:16]
rcvmac = SHA1(rcvenc)
return cls(sendenc, sendmac, rcvenc, rcvmac)
class CryptEngine(object):
def __init__(self, ctx):
self.ctx = ctx
self.ake = None
self.sessionId = None
self.sessionIdHalf = False
self.theirKeyid = 0
self.theirY = None
self.theirOldY = None
self.ourOldDHKey = None
self.ourDHKey = None
self.ourKeyid = 0
self.sessionkeys = {0:{0:None, 1:None}, 1:{0:None, 1:None}}
self.theirPubkey = None
self.savedMacKeys = []
self.smp = None
self.extraKey = None
def revealMacs(self, ours=True):
if ours:
dhs = self.sessionkeys[1].values()
else:
dhs = ( v[1] for v in self.sessionkeys.values() )
for v in dhs:
if v is not None:
if v.rcvmacused:
self.savedMacKeys.append(v.rcvmac)
if v.sendmacused:
self.savedMacKeys.append(v.sendmac)
def rotateDHKeys(self):
self.revealMacs(ours=True)
self.ourOldDHKey = self.ourDHKey
self.sessionkeys[1] = self.sessionkeys[0].copy()
self.ourDHKey = DH()
self.ourKeyid += 1
self.sessionkeys[0][0] = None if self.theirY is None else \
DHSession.create(self.ourDHKey, self.theirY)
self.sessionkeys[0][1] = None if self.theirOldY is None else \
DHSession.create(self.ourDHKey, self.theirOldY)
logger.debug('{0}: Refreshing ourkey to {1} {2}'.format(
self.ctx.user.name, self.ourKeyid, self.sessionkeys))
def rotateYKeys(self, new_y):
self.theirOldY = self.theirY
self.revealMacs(ours=False)
self.sessionkeys[0][1] = self.sessionkeys[0][0]
self.sessionkeys[1][1] = self.sessionkeys[1][0]
self.theirY = new_y
self.theirKeyid += 1
self.sessionkeys[0][0] = DHSession.create(self.ourDHKey, self.theirY)
self.sessionkeys[1][0] = DHSession.create(self.ourOldDHKey, self.theirY)
logger.debug('{0}: Refreshing theirkey to {1} {2}'.format(
self.ctx.user.name, self.theirKeyid, self.sessionkeys))
def handleDataMessage(self, msg):
if self.saneKeyIds(msg) is False:
raise InvalidParameterError
sesskey = self.sessionkeys[self.ourKeyid - msg.rkeyid] \
[self.theirKeyid - msg.skeyid]
logger.debug('sesskeys: {0!r}, our={1}, r={2}, their={3}, s={4}' \
.format(self.sessionkeys, self.ourKeyid, msg.rkeyid,
self.theirKeyid, msg.skeyid))
if msg.mac != SHA1HMAC(sesskey.rcvmac, msg.getMacedData()):
logger.error('HMACs don\'t match')
raise InvalidParameterError
sesskey.rcvmacused = True
newCtrPrefix = bytes_to_long(msg.ctr)
if newCtrPrefix <= sesskey.rcvctr.prefix:
logger.error('CTR must increase (old %r, new %r)',
sesskey.rcvctr.prefix, newCtrPrefix)
raise InvalidParameterError
sesskey.rcvctr.prefix = newCtrPrefix
logger.debug('handle: enc={0!r} mac={1!r} ctr={2!r}' \
.format(sesskey.rcvenc, sesskey.rcvmac, sesskey.rcvctr))
plaintextData = AESCTR(sesskey.rcvenc, sesskey.rcvctr) \
.decrypt(msg.encmsg)
if b'\0' in plaintextData:
plaintext, tlvData = plaintextData.split(b'\0', 1)
tlvs = proto.TLV.parse(tlvData)
else:
plaintext = plaintextData
tlvs = []
if msg.rkeyid == self.ourKeyid:
self.rotateDHKeys()
if msg.skeyid == self.theirKeyid:
self.rotateYKeys(bytes_to_long(msg.dhy))
return plaintext, tlvs
def smpSecret(self, secret, question=None, appdata=None):
if self.smp is None:
logger.debug('Creating SMPHandler')
self.smp = SMPHandler(self)
self.smp.gotSecret(secret, question=question, appdata=appdata)
def smpHandle(self, tlv, appdata=None):
if self.smp is None:
logger.debug('Creating SMPHandler')
self.smp = SMPHandler(self)
self.smp.handle(tlv, appdata=appdata)
def smpAbort(self, appdata=None):
if self.smp is None:
logger.debug('Creating SMPHandler')
self.smp = SMPHandler(self)
self.smp.abort(appdata=appdata)
def createDataMessage(self, message, flags=0, tlvs=None):
# check MSGSTATE
if self.theirKeyid == 0:
raise InvalidParameterError
if tlvs is None:
tlvs = []
sess = self.sessionkeys[1][0]
sess.sendctr.inc()
logger.debug('create: enc={0!r} mac={1!r} ctr={2!r}' \
.format(sess.sendenc, sess.sendmac, sess.sendctr))
# plaintext + TLVS
plainBuf = message + b'\0' + b''.join([ bytes(t) for t in tlvs])
encmsg = AESCTR(sess.sendenc, sess.sendctr).encrypt(plainBuf)
msg = proto.DataMessage(flags, self.ourKeyid-1, self.theirKeyid,
long_to_bytes(self.ourDHKey.pub), sess.sendctr.byteprefix(),
encmsg, b'', b''.join(self.savedMacKeys))
msg.mac = SHA1HMAC(sess.sendmac, msg.getMacedData())
return msg
def saneKeyIds(self, msg):
anyzero = self.theirKeyid == 0 or msg.skeyid == 0 or msg.rkeyid == 0
if anyzero or (msg.skeyid != self.theirKeyid and \
msg.skeyid != self.theirKeyid - 1) or \
(msg.rkeyid != self.ourKeyid and msg.rkeyid != self.ourKeyid - 1):
return False
if self.theirOldY is None and msg.skeyid == self.theirKeyid - 1:
return False
return True
def startAKE(self, appdata=None):
self.ake = AuthKeyExchange(self.ctx.user.getPrivkey(), self.goEncrypted)
outMsg = self.ake.startAKE()
self.ctx.sendInternal(outMsg, appdata=appdata)
def handleAKE(self, inMsg, appdata=None):
outMsg = None
if not self.ctx.getPolicy('ALLOW_V2'):
return
if isinstance(inMsg, proto.DHCommit):
if self.ake is None or self.ake.state != STATE_AWAITING_REVEALSIG:
self.ake = AuthKeyExchange(self.ctx.user.getPrivkey(),
self.goEncrypted)
outMsg = self.ake.handleDHCommit(inMsg)
elif isinstance(inMsg, proto.DHKey):
if self.ake is None:
return # ignore
outMsg = self.ake.handleDHKey(inMsg)
elif isinstance(inMsg, proto.RevealSig):
if self.ake is None:
return # ignore
outMsg = self.ake.handleRevealSig(inMsg)
elif isinstance(inMsg, proto.Signature):
if self.ake is None:
return # ignore
self.ake.handleSignature(inMsg)
if outMsg is not None:
self.ctx.sendInternal(outMsg, appdata=appdata)
def goEncrypted(self, ake):
if ake.dh.pub == ake.gy:
logger.warning('We are receiving our own messages')
raise InvalidParameterError
# TODO handle new fingerprint
self.theirPubkey = ake.theirPubkey
self.sessionId = ake.sessionId
self.sessionIdHalf = ake.sessionIdHalf
self.theirKeyid = ake.theirKeyid
self.ourKeyid = ake.ourKeyid
self.theirY = ake.gy
self.theirOldY = None
self.extraKey = ake.extraKey
if self.ourKeyid != ake.ourKeyid + 1 or self.ourOldDHKey != ake.dh.pub:
self.ourDHKey = ake.dh
self.sessionkeys[0][0] = DHSession.create(self.ourDHKey, self.theirY)
self.rotateDHKeys()
# we don't need the AKE anymore, free the reference
self.ake = None
self.ctx._wentEncrypted()
logger.info('went encrypted with {0}'.format(self.theirPubkey))
def finished(self):
self.smp = None
class AuthKeyExchange(object):
def __init__(self, privkey, onSuccess):
self.privkey = privkey
self.state = STATE_NONE
self.r = None
self.encgx = None
self.hashgx = None
self.ourKeyid = 1
self.theirPubkey = None
self.theirKeyid = 1
self.enc_c = None
self.enc_cp = None
self.mac_m1 = None
self.mac_m1p = None
self.mac_m2 = None
self.mac_m2p = None
self.sessionId = None
self.sessionIdHalf = False
self.dh = DH()
self.onSuccess = onSuccess
self.gy = None
self.extraKey = None
self.lastmsg = None
def startAKE(self):
self.r = long_to_bytes(random.getrandbits(128))
gxmpi = pack_mpi(self.dh.pub)
self.hashgx = SHA256(gxmpi)
self.encgx = AESCTR(self.r).encrypt(gxmpi)
self.state = STATE_AWAITING_DHKEY
return proto.DHCommit(self.encgx, self.hashgx)
def handleDHCommit(self, msg):
self.encgx = msg.encgx
self.hashgx = msg.hashgx
self.state = STATE_AWAITING_REVEALSIG
return proto.DHKey(long_to_bytes(self.dh.pub))
def handleDHKey(self, msg):
if self.state == STATE_AWAITING_DHKEY:
self.gy = bytes_to_long(msg.gy)
# check 2 <= g**y <= p-2
if not check_group(self.gy):
logger.error('Invalid g**y received: %r', self.gy)
return
self.createAuthKeys()
aesxb = self.calculatePubkeyAuth(self.enc_c, self.mac_m1)
self.state = STATE_AWAITING_SIG
self.lastmsg = proto.RevealSig(self.r, aesxb, b'')
self.lastmsg.mac = SHA256HMAC160(self.mac_m2,
self.lastmsg.getMacedData())
return self.lastmsg
elif self.state == STATE_AWAITING_SIG:
logger.info('received DHKey while not awaiting DHKEY')
if msg.gy == self.gy:
logger.info('resending revealsig')
return self.lastmsg
else:
logger.info('bad state for DHKey')
def handleRevealSig(self, msg):
if self.state != STATE_AWAITING_REVEALSIG:
logger.error('bad state for RevealSig')
raise InvalidParameterError
self.r = msg.rkey
gxmpi = AESCTR(self.r).decrypt(self.encgx)
if SHA256(gxmpi) != self.hashgx:
logger.error('Hashes don\'t match')
logger.info('r=%r, hashgx=%r, computed hash=%r, gxmpi=%r',
self.r, self.hashgx, SHA256(gxmpi), gxmpi)
raise InvalidParameterError
self.gy = read_mpi(gxmpi)[0]
self.createAuthKeys()
if msg.mac != SHA256HMAC160(self.mac_m2, msg.getMacedData()):
logger.error('HMACs don\'t match')
logger.info('mac=%r, mac_m2=%r, data=%r', msg.mac, self.mac_m2,
msg.getMacedData())
raise InvalidParameterError
self.checkPubkeyAuth(self.enc_c, self.mac_m1, msg.encsig)
aesxb = self.calculatePubkeyAuth(self.enc_cp, self.mac_m1p)
self.sessionIdHalf = True
self.onSuccess(self)
self.ourKeyid = 0
self.state = STATE_NONE
cmpmac = struct.pack(b'!I', len(aesxb)) + aesxb
return proto.Signature(aesxb, SHA256HMAC160(self.mac_m2p, cmpmac))
def handleSignature(self, msg):
if self.state != STATE_AWAITING_SIG:
logger.error('bad state (%d) for Signature', self.state)
raise InvalidParameterError
if msg.mac != SHA256HMAC160(self.mac_m2p, msg.getMacedData()):
logger.error('HMACs don\'t match')
raise InvalidParameterError
self.checkPubkeyAuth(self.enc_cp, self.mac_m1p, msg.encsig)
self.sessionIdHalf = False
self.onSuccess(self)
self.ourKeyid = 0
self.state = STATE_NONE
def createAuthKeys(self):
s = pow(self.gy, self.dh.priv, DH_MODULUS)
sbyte = pack_mpi(s)
self.sessionId = SHA256(b'\x00' + sbyte)[:8]
enc = SHA256(b'\x01' + sbyte)
self.enc_c = enc[:16]
self.enc_cp = enc[16:]
self.mac_m1 = SHA256(b'\x02' + sbyte)
self.mac_m2 = SHA256(b'\x03' + sbyte)
self.mac_m1p = SHA256(b'\x04' + sbyte)
self.mac_m2p = SHA256(b'\x05' + sbyte)
self.extraKey = SHA256(b'\xff' + sbyte)
def calculatePubkeyAuth(self, key, mackey):
pubkey = self.privkey.serializePublicKey()
buf = pack_mpi(self.dh.pub)
buf += pack_mpi(self.gy)
buf += pubkey
buf += struct.pack(b'!I', self.ourKeyid)
MB = self.privkey.sign(SHA256HMAC(mackey, buf))
buf = pubkey
buf += struct.pack(b'!I', self.ourKeyid)
buf += MB
return AESCTR(key).encrypt(buf)
def checkPubkeyAuth(self, key, mackey, encsig):
auth = AESCTR(key).decrypt(encsig)
self.theirPubkey, auth = PK.parsePublicKey(auth)
receivedKeyid, auth = proto.unpack(b'!I', auth)
if receivedKeyid == 0:
raise InvalidParameterError
authbuf = pack_mpi(self.gy)
authbuf += pack_mpi(self.dh.pub)
authbuf += self.theirPubkey.serializePublicKey()
authbuf += struct.pack(b'!I', receivedKeyid)
if self.theirPubkey.verify(SHA256HMAC(mackey, authbuf), auth) is False:
raise InvalidParameterError
self.theirKeyid = receivedKeyid
SMPPROG_OK = 0
SMPPROG_CHEATED = -2
SMPPROG_FAILED = -1
SMPPROG_SUCCEEDED = 1
class SMPHandler:
def __init__(self, crypto):
self.crypto = crypto
self.state = 1
self.g1 = DH_GENERATOR
self.g2 = None
self.g3 = None
self.g3o = None
self.x2 = None
self.x3 = None
self.prog = SMPPROG_OK
self.pab = None
self.qab = None
self.questionReceived = False
self.secret = None
self.p = None
self.q = None
def abort(self, appdata=None):
self.state = 1
self.sendTLV(proto.SMPABORTTLV(), appdata=appdata)
def sendTLV(self, tlv, appdata=None):
self.crypto.ctx.sendInternal(b'', tlvs=[tlv], appdata=appdata)
def handle(self, tlv, appdata=None):
logger.debug('handling TLV {0.__class__.__name__}'.format(tlv))
self.prog = SMPPROG_CHEATED
if isinstance(tlv, proto.SMPABORTTLV):
self.state = 1
return
is1qTlv = isinstance(tlv, proto.SMP1QTLV)
if isinstance(tlv, proto.SMP1TLV) or is1qTlv:
if self.state != 1:
self.abort(appdata=appdata)
return
msg = tlv.mpis
if not check_group(msg[0]) or not check_group(msg[3]) \
or not check_exp(msg[2]) or not check_exp(msg[5]) \
or not check_known_log(msg[1], msg[2], self.g1, msg[0], 1) \
or not check_known_log(msg[4], msg[5], self.g1, msg[3], 2):
logger.error('invalid SMP1TLV received')
self.abort(appdata=appdata)
return
self.questionReceived = is1qTlv
self.g3o = msg[3]
self.x2 = random.randrange(2, DH_MAX)
self.x3 = random.randrange(2, DH_MAX)
self.g2 = pow(msg[0], self.x2, DH_MODULUS)
self.g3 = pow(msg[3], self.x3, DH_MODULUS)
self.prog = SMPPROG_OK
self.state = 0
return
if isinstance(tlv, proto.SMP2TLV):
if self.state != 2:
self.abort(appdata=appdata)
return
msg = tlv.mpis
mp = msg[6]
mq = msg[7]
if not check_group(msg[0]) or not check_group(msg[3]) \
or not check_group(msg[6]) or not check_group(msg[7]) \
or not check_exp(msg[2]) or not check_exp(msg[5]) \
or not check_exp(msg[9]) or not check_exp(msg[10]) \
or not check_known_log(msg[1], msg[2], self.g1, msg[0], 3) \
or not check_known_log(msg[4], msg[5], self.g1, msg[3], 4):
logger.error('invalid SMP2TLV received')
self.abort(appdata=appdata)
return
self.g3o = msg[3]
self.g2 = pow(msg[0], self.x2, DH_MODULUS)
self.g3 = pow(msg[3], self.x3, DH_MODULUS)
if not self.check_equal_coords(msg[6:11], 5):
logger.error('invalid SMP2TLV received')
self.abort(appdata=appdata)
return
r = random.randrange(2, DH_MAX)
self.p = pow(self.g3, r, DH_MODULUS)
msg = [self.p]
qa1 = pow(self.g1, r, DH_MODULUS)
qa2 = pow(self.g2, self.secret, DH_MODULUS)
self.q = qa1*qa2 % DH_MODULUS
msg.append(self.q)
msg += self.proof_equal_coords(r, 6)
inv = invMod(mp)
self.pab = self.p * inv % DH_MODULUS
inv = invMod(mq)
self.qab = self.q * inv % DH_MODULUS
msg.append(pow(self.qab, self.x3, DH_MODULUS))
msg += self.proof_equal_logs(7)
self.state = 4
self.prog = SMPPROG_OK
self.sendTLV(proto.SMP3TLV(msg), appdata=appdata)
return
if isinstance(tlv, proto.SMP3TLV):
if self.state != 3:
self.abort(appdata=appdata)
return
msg = tlv.mpis
if not check_group(msg[0]) or not check_group(msg[1]) \
or not check_group(msg[5]) or not check_exp(msg[3]) \
or not check_exp(msg[4]) or not check_exp(msg[7]) \
or not self.check_equal_coords(msg[:5], 6):
logger.error('invalid SMP3TLV received')
self.abort(appdata=appdata)
return
inv = invMod(self.p)
self.pab = msg[0] * inv % DH_MODULUS
inv = invMod(self.q)
self.qab = msg[1] * inv % DH_MODULUS
if not self.check_equal_logs(msg[5:8], 7):
logger.error('invalid SMP3TLV received')
self.abort(appdata=appdata)
return
md = msg[5]
msg = [pow(self.qab, self.x3, DH_MODULUS)]
msg += self.proof_equal_logs(8)
rab = pow(md, self.x3, DH_MODULUS)
self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED
if self.prog != SMPPROG_SUCCEEDED:
logger.error('secrets don\'t match')
self.abort(appdata=appdata)
self.crypto.ctx.setCurrentTrust('')
return
logger.info('secrets matched')
if not self.questionReceived:
self.crypto.ctx.setCurrentTrust('smp')
self.state = 1
self.sendTLV(proto.SMP4TLV(msg), appdata=appdata)
return
if isinstance(tlv, proto.SMP4TLV):
if self.state != 4:
self.abort(appdata=appdata)
return
msg = tlv.mpis
if not check_group(msg[0]) or not check_exp(msg[2]) \
or not self.check_equal_logs(msg[:3], 8):
logger.error('invalid SMP4TLV received')
self.abort(appdata=appdata)
return
rab = pow(msg[0], self.x3, DH_MODULUS)
self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED
if self.prog != SMPPROG_SUCCEEDED:
logger.error('secrets don\'t match')
self.abort(appdata=appdata)
self.crypto.ctx.setCurrentTrust('')
return
logger.info('secrets matched')
self.crypto.ctx.setCurrentTrust('smp')
self.state = 1
return
def gotSecret(self, secret, question=None, appdata=None):
ourFP = self.crypto.ctx.user.getPrivkey().fingerprint()
if self.state == 1:
# first secret -> SMP1TLV
combSecret = SHA256(b'\1' + ourFP +
self.crypto.theirPubkey.fingerprint() +
self.crypto.sessionId + secret)
self.secret = bytes_to_long(combSecret)
self.x2 = random.randrange(2, DH_MAX)
self.x3 = random.randrange(2, DH_MAX)
msg = [pow(self.g1, self.x2, DH_MODULUS)]
msg += proof_known_log(self.g1, self.x2, 1)
msg.append(pow(self.g1, self.x3, DH_MODULUS))
msg += proof_known_log(self.g1, self.x3, 2)
self.prog = SMPPROG_OK
self.state = 2
if question is None:
self.sendTLV(proto.SMP1TLV(msg), appdata=appdata)
else:
self.sendTLV(proto.SMP1QTLV(question, msg), appdata=appdata)
if self.state == 0:
# response secret -> SMP2TLV
combSecret = SHA256(b'\1' + self.crypto.theirPubkey.fingerprint() +
ourFP + self.crypto.sessionId + secret)
self.secret = bytes_to_long(combSecret)
msg = [pow(self.g1, self.x2, DH_MODULUS)]
msg += proof_known_log(self.g1, self.x2, 3)
msg.append(pow(self.g1, self.x3, DH_MODULUS))
msg += proof_known_log(self.g1, self.x3, 4)
r = random.randrange(2, DH_MAX)
self.p = pow(self.g3, r, DH_MODULUS)
msg.append(self.p)
qb1 = pow(self.g1, r, DH_MODULUS)
qb2 = pow(self.g2, self.secret, DH_MODULUS)
self.q = qb1 * qb2 % DH_MODULUS
msg.append(self.q)
msg += self.proof_equal_coords(r, 5)
self.state = 3
self.sendTLV(proto.SMP2TLV(msg), appdata=appdata)
def proof_equal_coords(self, r, v):
r1 = random.randrange(2, DH_MAX)
r2 = random.randrange(2, DH_MAX)
temp2 = pow(self.g1, r1, DH_MODULUS) \
* pow(self.g2, r2, DH_MODULUS) % DH_MODULUS
temp1 = pow(self.g3, r1, DH_MODULUS)
cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
c = bytes_to_long(cb)
temp1 = r * c % SM_ORDER
d1 = (r1-temp1) % SM_ORDER
temp1 = self.secret * c % SM_ORDER
d2 = (r2 - temp1) % SM_ORDER
return c, d1, d2
def check_equal_coords(self, coords, v):
(p, q, c, d1, d2) = coords
temp1 = pow(self.g3, d1, DH_MODULUS) * pow(p, c, DH_MODULUS) \
% DH_MODULUS
temp2 = pow(self.g1, d1, DH_MODULUS) \
* pow(self.g2, d2, DH_MODULUS) \
* pow(q, c, DH_MODULUS) % DH_MODULUS
cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
return long_to_bytes(c, 32) == cprime
def proof_equal_logs(self, v):
r = random.randrange(2, DH_MAX)
temp1 = pow(self.g1, r, DH_MODULUS)
temp2 = pow(self.qab, r, DH_MODULUS)
cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
c = bytes_to_long(cb)
temp1 = self.x3 * c % SM_ORDER
d = (r - temp1) % SM_ORDER
return c, d
def check_equal_logs(self, logs, v):
(r, c, d) = logs
temp1 = pow(self.g1, d, DH_MODULUS) \
* pow(self.g3o, c, DH_MODULUS) % DH_MODULUS
temp2 = pow(self.qab, d, DH_MODULUS) \
* pow(r, c, DH_MODULUS) % DH_MODULUS
cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2))
return long_to_bytes(c, 32) == cprime
def proof_known_log(g, x, v):
r = random.randrange(2, DH_MAX)
c = bytes_to_long(SHA256(struct.pack(b'B', v) + pack_mpi(pow(g, r, DH_MODULUS))))
temp = x * c % SM_ORDER
return c, (r-temp) % SM_ORDER
def check_known_log(c, d, g, x, v):
gd = pow(g, d, DH_MODULUS)
xc = pow(x, c, DH_MODULUS)
gdxc = gd * xc % DH_MODULUS
return SHA256(struct.pack(b'B', v) + pack_mpi(gdxc)) == long_to_bytes(c, 32)
def invMod(n):
return pow(n, DH_MODULUS_2, DH_MODULUS)
class InvalidParameterError(RuntimeError):
pass

View File

@@ -1,465 +0,0 @@
# Copyright 2011-2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
import base64
import struct
from potr.utils import pack_mpi, read_mpi, pack_data, read_data, unpack
OTRTAG = b'?OTR'
MESSAGE_TAG_BASE = b' \t \t\t\t\t \t \t \t '
MESSAGE_TAGS = {
1:b' \t \t \t ',
2:b' \t\t \t ',
3:b' \t\t \t\t',
}
MSGTYPE_NOTOTR = 0
MSGTYPE_TAGGEDPLAINTEXT = 1
MSGTYPE_QUERY = 2
MSGTYPE_DH_COMMIT = 3
MSGTYPE_DH_KEY = 4
MSGTYPE_REVEALSIG = 5
MSGTYPE_SIGNATURE = 6
MSGTYPE_V1_KEYEXCH = 7
MSGTYPE_DATA = 8
MSGTYPE_ERROR = 9
MSGTYPE_UNKNOWN = -1
MSGFLAGS_IGNORE_UNREADABLE = 1
tlvClasses = {}
messageClasses = {}
hasByteStr = bytes == str
def bytesAndStrings(cls):
if hasByteStr:
cls.__str__ = lambda self: self.__bytes__()
else:
cls.__str__ = lambda self: str(self.__bytes__(), encoding='ascii')
return cls
def registermessage(cls):
if not hasattr(cls, 'parsePayload'):
raise TypeError('registered message types need parsePayload()')
messageClasses[cls.version, cls.msgtype] = cls
return cls
def registertlv(cls):
if not hasattr(cls, 'parsePayload'):
raise TypeError('registered tlv types need parsePayload()')
if cls.typ is None:
raise TypeError('registered tlv type needs type ID')
tlvClasses[cls.typ] = cls
return cls
def getslots(cls, base):
''' helper to collect all the message slots from ancestors '''
clss = [cls]
for cls in clss:
if cls == base:
continue
clss.extend(cls.__bases__)
for slot in cls.__slots__:
yield slot
@bytesAndStrings
class OTRMessage(object):
__slots__ = ['payload']
version = 0x0002
msgtype = 0
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
for slot in getslots(self.__class__, OTRMessage):
if getattr(self, slot) != getattr(other, slot):
return False
return True
def __neq__(self, other):
return not self.__eq__(other)
class Error(OTRMessage):
__slots__ = ['error']
def __init__(self, error):
super(Error, self).__init__()
self.error = error
def __repr__(self):
return '<proto.Error(%r)>' % self.error
def __bytes__(self):
return b'?OTR Error:' + self.error
class Query(OTRMessage):
__slots__ = ['versions']
def __init__(self, versions=set()):
super(Query, self).__init__()
self.versions = versions
@classmethod
def parse(cls, data):
if not isinstance(data, bytes):
raise TypeError('can only parse bytes')
udata = data.decode('ascii', errors='replace')
versions = set()
if len(udata) > 0 and udata[0] == '?':
udata = udata[1:]
versions.add(1)
if len(udata) > 0 and udata[0] == 'v':
versions.update(( int(c) for c in udata if c.isdigit() ))
return cls(versions)
def __repr__(self):
return '<proto.Query(versions=%r)>' % (self.versions)
def __bytes__(self):
d = b'?OTR'
if 1 in self.versions:
d += b'?'
d += b'v'
# in python3 there is only int->unicode conversion
# so I convert to unicode and encode it to a byte string
versions = [ '%d' % v for v in self.versions if v != 1 ]
d += ''.join(versions).encode('ascii')
d += b'?'
return d
class TaggedPlaintext(Query):
__slots__ = ['msg']
def __init__(self, msg, versions):
super(TaggedPlaintext, self).__init__(versions)
self.msg = msg
def __bytes__(self):
data = self.msg + MESSAGE_TAG_BASE
for v in self.versions:
data += MESSAGE_TAGS[v]
return data
def __repr__(self):
return '<proto.TaggedPlaintext(versions={versions!r},msg={msg!r})>' \
.format(versions=self.versions, msg=self.msg)
@classmethod
def parse(cls, data):
tagPos = data.find(MESSAGE_TAG_BASE)
if tagPos < 0:
raise TypeError(
'this is not a tagged plaintext ({0!r:.20})'.format(data))
tags = [ data[i:i+8] for i in range(tagPos, len(data), 8) ]
versions = set([ version for version, tag in MESSAGE_TAGS.items() if tag
in tags ])
return TaggedPlaintext(data[:tagPos], versions)
class GenericOTRMessage(OTRMessage):
__slots__ = ['data']
fields = []
def __init__(self, *args):
super(GenericOTRMessage, self).__init__()
if len(args) != len(self.fields):
raise TypeError('%s needs %d arguments, got %d' %
(self.__class__.__name__, len(self.fields), len(args)))
super(GenericOTRMessage, self).__setattr__('data',
dict(zip((f[0] for f in self.fields), args)))
def __getattr__(self, attr):
if attr in self.data:
return self.data[attr]
raise AttributeError(
"'{t!r}' object has no attribute '{attr!r}'".format(attr=attr,
t=self.__class__.__name__))
def __setattr__(self, attr, val):
if attr in self.__slots__:
super(GenericOTRMessage, self).__setattr__(attr, val)
else:
self.__getattr__(attr) # existence check
self.data[attr] = val
def __bytes__(self):
data = struct.pack(b'!HB', self.version, self.msgtype) \
+ self.getPayload()
return b'?OTR:' + base64.b64encode(data) + b'.'
def __repr__(self):
name = self.__class__.__name__
data = ''
for k, _ in self.fields:
data += '%s=%r,' % (k, self.data[k])
return '<proto.%s(%s)>' % (name, data)
@classmethod
def parsePayload(cls, data):
data = base64.b64decode(data)
args = []
for _, ftype in cls.fields:
if ftype == 'data':
value, data = read_data(data)
elif isinstance(ftype, bytes):
value, data = unpack(ftype, data)
elif isinstance(ftype, int):
value, data = data[:ftype], data[ftype:]
args.append(value)
return cls(*args)
def getPayload(self, *ffilter):
payload = b''
for k, ftype in self.fields:
if k in ffilter:
continue
if ftype == 'data':
payload += pack_data(self.data[k])
elif isinstance(ftype, bytes):
payload += struct.pack(ftype, self.data[k])
else:
payload += self.data[k]
return payload
class AKEMessage(GenericOTRMessage):
__slots__ = []
@registermessage
class DHCommit(AKEMessage):
__slots__ = []
msgtype = 0x02
fields = [('encgx', 'data'), ('hashgx', 'data'), ]
@registermessage
class DHKey(AKEMessage):
__slots__ = []
msgtype = 0x0a
fields = [('gy', 'data'), ]
@registermessage
class RevealSig(AKEMessage):
__slots__ = []
msgtype = 0x11
fields = [('rkey', 'data'), ('encsig', 'data'), ('mac', 20),]
def getMacedData(self):
p = self.encsig
return struct.pack(b'!I', len(p)) + p
@registermessage
class Signature(AKEMessage):
__slots__ = []
msgtype = 0x12
fields = [('encsig', 'data'), ('mac', 20)]
def getMacedData(self):
p = self.encsig
return struct.pack(b'!I', len(p)) + p
@registermessage
class DataMessage(GenericOTRMessage):
__slots__ = []
msgtype = 0x03
fields = [('flags', b'!B'), ('skeyid', b'!I'), ('rkeyid', b'!I'),
('dhy', 'data'), ('ctr', 8), ('encmsg', 'data'), ('mac', 20),
('oldmacs', 'data'), ]
def getMacedData(self):
return struct.pack(b'!HB', self.version, self.msgtype) + \
self.getPayload('mac', 'oldmacs')
@bytesAndStrings
class TLV(object):
__slots__ = []
typ = None
def getPayload(self):
raise NotImplementedError
def __repr__(self):
val = self.getPayload()
return '<{cls}(typ={t},len={l},val={v!r})>'.format(t=self.typ,
l=len(val), v=val, cls=self.__class__.__name__)
def __bytes__(self):
val = self.getPayload()
return struct.pack(b'!HH', self.typ, len(val)) + val
@classmethod
def parse(cls, data):
if not data:
return []
typ, length, data = unpack(b'!HH', data)
return [tlvClasses[typ].parsePayload(data[:length])] \
+ cls.parse(data[length:])
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
for slot in getslots(self.__class__, TLV):
if getattr(self, slot) != getattr(other, slot):
return False
return True
def __neq__(self, other):
return not self.__eq__(other)
@registertlv
class PaddingTLV(TLV):
typ = 0
__slots__ = ['padding']
def __init__(self, padding):
super(PaddingTLV, self).__init__()
self.padding = padding
def getPayload(self):
return self.padding
@classmethod
def parsePayload(cls, data):
return cls(data)
@registertlv
class DisconnectTLV(TLV):
typ = 1
def __init__(self):
super(DisconnectTLV, self).__init__()
def getPayload(self):
return b''
@classmethod
def parsePayload(cls, data):
if len(data) > 0:
raise TypeError('DisconnectTLV must not contain data. got {0!r}'
.format(data))
return cls()
class SMPTLV(TLV):
__slots__ = ['mpis']
dlen = None
def __init__(self, mpis=None):
super(SMPTLV, self).__init__()
if mpis is None:
mpis = []
if self.dlen is None:
raise TypeError('no amount of mpis specified in dlen')
if len(mpis) != self.dlen:
raise TypeError('expected {0} mpis, got {1}'
.format(self.dlen, len(mpis)))
self.mpis = mpis
def getPayload(self):
d = struct.pack(b'!I', len(self.mpis))
for n in self.mpis:
d += pack_mpi(n)
return d
@classmethod
def parsePayload(cls, data):
mpis = []
if cls.dlen > 0:
count, data = unpack(b'!I', data)
for _ in range(count):
n, data = read_mpi(data)
mpis.append(n)
if len(data) > 0:
raise TypeError('too much data for {0} mpis'.format(cls.dlen))
return cls(mpis)
@registertlv
class SMP1TLV(SMPTLV):
typ = 2
dlen = 6
@registertlv
class SMP1QTLV(SMPTLV):
typ = 7
dlen = 6
__slots__ = ['msg']
def __init__(self, msg, mpis):
self.msg = msg
super(SMP1QTLV, self).__init__(mpis)
def getPayload(self):
return self.msg + b'\0' + super(SMP1QTLV, self).getPayload()
@classmethod
def parsePayload(cls, data):
msg, data = data.split(b'\0', 1)
mpis = SMP1TLV.parsePayload(data).mpis
return cls(msg, mpis)
@registertlv
class SMP2TLV(SMPTLV):
typ = 3
dlen = 11
@registertlv
class SMP3TLV(SMPTLV):
typ = 4
dlen = 8
@registertlv
class SMP4TLV(SMPTLV):
typ = 5
dlen = 3
@registertlv
class SMPABORTTLV(SMPTLV):
typ = 6
dlen = 0
def getPayload(self):
return b''
@registertlv
class ExtraKeyTLV(TLV):
typ = 8
__slots__ = ['appid', 'appdata']
def __init__(self, appid, appdata):
super(ExtraKeyTLV, self).__init__()
self.appid = appid
self.appdata = appdata
if appdata is None:
self.appdata = b''
def getPayload(self):
return self.appid + self.appdata
@classmethod
def parsePayload(cls, data):
return cls(data[:4], data[4:])

View File

@@ -1,66 +0,0 @@
# Copyright 2012 Kjell Braden <afflux@pentabarf.de>
#
# This file is part of the python-potr library.
#
# python-potr is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# python-potr 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
# some python3 compatibilty
from __future__ import unicode_literals
import struct
def pack_mpi(n):
return pack_data(long_to_bytes(n))
def read_mpi(data):
n, data = read_data(data)
return bytes_to_long(n), data
def pack_data(data):
return struct.pack(b'!I', len(data)) + data
def read_data(data):
datalen, data = unpack(b'!I', data)
return data[:datalen], data[datalen:]
def unpack(fmt, buf):
s = struct.Struct(fmt)
return s.unpack(buf[:s.size]) + (buf[s.size:],)
def bytes_to_long(b):
l = len(b)
s = 0
for i in range(l):
s += byte_to_long(b[i:i+1]) << 8*(l-i-1)
return s
def long_to_bytes(l, n=0):
b = b''
while l != 0 or n > 0:
b = long_to_byte(l & 0xff) + b
l >>= 8
n -= 1
return b
def byte_to_long(b):
return struct.unpack(b'B', b)[0]
def long_to_byte(l):
return struct.pack(b'B', l)
def human_hash(fp):
fp = fp.upper()
fplen = len(fp)
wordsize = fplen//5
buf = ''
for w in range(0, fplen, wordsize):
buf += '{0} '.format(fp[w:w+wordsize])
return buf.rstrip()

View File

@@ -1,581 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
## ui.py
##
## Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import gobject
import gtk
from common import gajim
from plugins.gui import GajimPluginConfigDialog
import otrmodule
try:
import potr
import potr.proto
except ImportError:
pass
class OtrPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = \
self.plugin.local_file_path('config_dialog.ui')
self.B = gtk.Builder()
self.B.set_translation_domain('gajim_plugins')
self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)
self.fpr_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_STRING, gobject.TYPE_STRING)
self.otr_account_store = self.B.get_object('account_store')
for account in sorted(gajim.contacts.get_accounts()):
self.otr_account_store.append(row=(account,))
self.fpr_view = self.B.get_object('fingerprint_view')
self.fpr_view.set_model(self.fpr_model)
self.fpr_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
if len(self.otr_account_store) > 0:
self.B.get_object('account_combobox').set_active(0)
self.child.pack_start(self.B.get_object('notebook1'))
self.flags = dict()
flagList = (
('ALLOW_V2', 'enable_check'),
('SEND_TAG', 'advertise_check'),
('WHITESPACE_START_AKE', 'autoinitiate_check'),
('REQUIRE_ENCRYPTION', 'require_check')
)
for flagName, checkBoxName in flagList:
self.flags[flagName] = self.B.get_object(checkBoxName)
self.B.connect_signals(self)
def on_run(self):
self.plugin.update_context_list()
self.account_combobox_changed_cb(self.B.get_object('account_combobox'))
def fpr_button_pressed_cb(self, tw, event):
if event.button == 3:
pthinfo = tw.get_path_at_pos(int(event.x), int(event.y))
if pthinfo is None:
# only show the popup when we right clicked on list content
# ie. don't show it when we click at empty rows
return False
# if the row under the mouse is already selected, we keep the
# selection, otherwise we only select the new item
keep_selection = tw.get_selection().path_is_selected(pthinfo[0])
pop = self.B.get_object('fprclipboard_menu')
pop.popup(None, None, None, event.button, event.time)
# keep_selection=True -> no further processing of click event
# keep_selection=False-> further processing -> GTK usually selects
# the item below the cursor
return keep_selection
def clipboard_button_cb(self, menuitem):
mod, paths = self.fpr_view.get_selection().get_selected_rows()
fprs = []
for path in paths:
it = mod.get_iter(path)
jid, fpr = mod.get(it, 0, 6)
fprs.append('%s: %s' % (jid, potr.human_hash(fpr)))
gtk.Clipboard().set_text('\n'.join(fprs))
gtk.Clipboard(selection='PRIMARY').set_text('\n'.join(fprs))
def flags_toggled_cb(self, button):
if button == self.B.get_object('enable_check'):
new_status = button.get_active()
self.B.get_object('advertise_check').set_sensitive(new_status)
self.B.get_object('autoinitiate_check').set_sensitive(new_status)
self.B.get_object('require_check').set_sensitive(new_status)
if new_status is False:
self.B.get_object('advertise_check').set_active(False)
self.B.get_object('autoinitiate_check').set_active(False)
self.B.get_object('require_check').set_active(False)
box = self.B.get_object('account_combobox')
active = box.get_active()
if active > -1:
account = self.otr_account_store[active][0]
flagValues = {}
for key, box in self.flags.iteritems():
flagValues[key] = box.get_active()
self.plugin.set_flags(flagValues, account)
def account_combobox_changed_cb(self, box, *args):
fpr_label = self.B.get_object('fingerprint_label')
regen_button = self.B.get_object('regenerate_button')
active = box.get_active()
fpr = '-------- -------- -------- -------- --------'
try:
if active > -1:
regen_button.set_sensitive(True)
account = self.otr_account_store[active][0]
otr_flags = self.plugin.get_flags(account)
for key, box in self.flags.iteritems():
box.set_active(otr_flags[key])
fpr = str(self.plugin.us[account].getPrivkey(autogen=False))
regen_button.set_label('Regenerate')
else:
regen_button.set_sensitive(False)
except LookupError, e:
# Account not found, no private key available - display the
# empty one
regen_button.set_label('Generate')
finally:
self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'%fpr)
def forget_button_clicked_cb(self, button, *args):
accounts = {}
for acc in gajim.connections.iterkeys():
accounts[gajim.get_jid_from_account(acc)] = acc
mod, paths = self.fpr_view.get_selection().get_selected_rows()
for path in paths:
it = mod.get_iter(path)
user, human_fpr, a, fpr = mod.get(it, 0, 3, 4, 6)
dlg = gtk.Dialog('Confirm removal of fingerprint', self,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_YES, gtk.RESPONSE_YES,
gtk.STOCK_NO, gtk.RESPONSE_NO)
)
l = gtk.Label()
l.set_markup('Are you sure you want remove the following '
'fingerprint for the contact %s on the account %s?'
'\n\n%s' % (user, a, human_fpr))
l.set_line_wrap(True)
dlg.vbox.pack_start(l)
dlg.show_all()
if dlg.run() == gtk.RESPONSE_YES:
ctx = self.plugin.us[accounts[a]].getContext(user)
ctx.removeFingerprint(fpr)
dlg.destroy()
self.plugin.us[accounts[a]].saveTrusts()
self.plugin.update_context_list()
def verify_button_clicked_cb(self, button, *args):
accounts = {}
for acc in gajim.connections.iterkeys():
accounts[gajim.get_jid_from_account(acc)] = acc
mod, paths = self.fpr_view.get_selection().get_selected_rows()
# open the window for the first selected row
for path in paths[0:1]:
it = mod.get_iter(path)
fjid, fpr, a = mod.get(it, 0, 6, 4)
ctx = self.plugin.us[accounts[a]].getContext(fjid)
dlg = ContactOtrWindow(self.plugin, ctx, fpr=fpr, parent=self)
dlg.run()
dlg.destroy()
break
def regenerate_button_clicked_cb(self, button, *args):
box = self.B.get_object('account_combobox')
active = box.get_active()
if active > -1:
account = self.otr_account_store[active][0]
button.set_sensitive(False)
try:
self.plugin.us[account].getPrivkey(autogen=False)
self.plugin.us[account].dropPrivkey()
except LookupError:
pass
self.plugin.us[account].getPrivkey(autogen=True)
self.account_combobox_changed_cb(box, *args)
button.set_sensitive(True)
import gtkgui_helpers
from common import gajim
our_fp_text = _('Your fingerprint:\n' \
'<span weight="bold" face="monospace">%s</span>')
their_fp_text = _('Purported fingerprint for %(jid)s:\n' \
'<span weight="bold" face="monospace">%(fp)s</span>')
another_q = _('You may want to authenticate your buddy as well by asking'\
'your own question.')
smp_query = _('<b>%s is trying to authenticate you using a secret only known '\
'to him/her and you.</b>')
smp_q_query = _('<b>%s has chosen a question for you to answer to '\
'authenticate yourself:</b>')
enter_secret = _('Please enter your secret below.')
smp_init = _('<b>You are trying to authenticate %s using a secret only known ' \
'to him/her and yourself.</b>')
choose_q = _('You can choose a question as a hint for your buddy below.')
class ContactOtrSmpWindow:
def gw(self, n):
return self.xml.get_object(n)
def __init__(self, ctx):
self.question = None
self.smp_running = False
self.ctx = ctx
self.account = ctx.user.accountname
self.plugin = ctx.user.plugin
self.GTK_BUILDER_FILE_PATH = \
self.plugin.local_file_path('contact_otr_window.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)
self.window = self.gw('otr_smp_window')
self.window.set_title(_('OTR settings for %s') % ctx.peer)
# the lambda thing is an anonymous helper that just discards the
# parameters and calls hide_on_delete on clicking the window's
# close button
self.window.connect('delete-event', lambda d,o:
self.window.hide_on_delete())
self.gw('smp_cancel_button').connect('clicked', self._on_destroy)
self.gw('smp_ok_button').connect('clicked', self._apply)
self.gw('qcheckbutton').connect('toggled', self._toggle)
self.gw('qcheckbutton').set_no_show_all(False)
self.gw('qentry').set_no_show_all(False)
self.gw('desclabel2').set_no_show_all(False)
def _toggle(self, w, *args):
self.gw('qentry').set_sensitive(w.get_active())
def show(self, response):
self.smp_running = False
self.finished = False
self.gw('smp_cancel_button').set_sensitive(True)
self.gw('smp_ok_button').set_sensitive(True)
self.gw('progressbar').set_fraction(0)
self.gw('secret_entry').set_text('')
self.response = response
self.window.show_all()
if response:
self.gw('qcheckbutton').set_sensitive(False)
if self.question is None:
self.gw('qcheckbutton').set_active(False)
self.gw('qcheckbutton').hide()
self.gw('qentry').hide()
self.gw('desclabel2').hide()
self.gw('qcheckbutton').set_sensitive(False)
self.gw('desclabel1').set_markup((smp_query % self.ctx.peer)
+ ' ' + enter_secret)
else:
self.gw('qcheckbutton').set_active(True)
self.gw('qcheckbutton').show()
self.gw('qentry').show()
self.gw('qentry').set_sensitive(True)
self.gw('qentry').set_editable(False)
self.gw('desclabel2').show()
self.gw('qentry').set_text(self.question)
self.gw('desclabel1').set_markup(smp_q_query % self.ctx.peer)
self.gw('desclabel2').set_markup(enter_secret)
else:
self.gw('qcheckbutton').show()
self.gw('qcheckbutton').set_active(True)
self.gw('qcheckbutton').set_mode(True)
self.gw('qcheckbutton').set_sensitive(True)
self.gw('qentry').set_sensitive(True)
self.gw('qentry').show()
self.gw('qentry').set_text("")
self.gw('qentry').set_editable(True)
self.gw('qentry').set_sensitive(True)
self.gw('desclabel2').show()
self.gw('desclabel1').set_markup((smp_init % self.ctx.peer) + ' '
+ choose_q)
self.gw('desclabel2').set_markup(enter_secret)
def _abort(self, text=None, appdata=None):
self.smp_running = False
self.ctx.smpAbort(appdata=appdata)
if text:
self.plugin.gajim_log(text, self.account, self.ctx.peer)
def _finish(self, text):
self.smp_running = False
self.finished = True
self.gw('qcheckbutton').set_active(False)
self.gw('qcheckbutton').hide()
self.gw('qentry').hide()
self.gw('desclabel2').hide()
self.gw('qcheckbutton').set_sensitive(False)
self.gw('smp_cancel_button').set_sensitive(False)
self.gw('smp_ok_button').set_sensitive(True)
self.gw('progressbar').set_fraction(1)
self.plugin.gajim_log(text, self.account, self.ctx.peer)
self.gw('desclabel1').set_markup(text)
self.plugin.update_otr(self.ctx.peer, self.account, True)
self.ctx.user.saveTrusts()
self.plugin.update_context_list()
def get_tlv(self, tlvs, check):
for tlv in tlvs:
if isinstance(tlv, check):
return tlv
return None
def handle_tlv(self, tlvs):
if tlvs:
is1qtlv = self.get_tlv(tlvs, potr.proto.SMP1QTLV)
# check for TLV_SMP_ABORT or state = CHEATED
if self.smp_running and not self.ctx.smpIsValid():
self._finish(_('SMP verifying aborted'))
# check for TLV_SMP1
elif self.get_tlv(tlvs, potr.proto.SMP1TLV):
self.smp_running = True
self.question = None
self.show(True)
self.gw('progressbar').set_fraction(0.3)
# check for TLV_SMP1Q
elif is1qtlv:
self.smp_running = True
self.question = is1qtlv.msg
self.show(True)
self.gw('progressbar').set_fraction(0.3)
# check for TLV_SMP2
elif self.get_tlv(tlvs, potr.proto.SMP2TLV):
self.gw('progressbar').set_fraction(0.6)
# check for TLV_SMP3
elif self.get_tlv(tlvs, potr.proto.SMP3TLV):
if self.ctx.smpIsSuccess():
text = _('SMP verifying succeeded')
if self.question is not None:
text += ' '+another_q
self._finish(text)
else:
self._finish(_('SMP verifying failed'))
# check for TLV_SMP4
elif self.get_tlv(tlvs, potr.proto.SMP4TLV):
if self.ctx.smpIsSuccess():
text = _('SMP verifying succeeded')
if self.question is not None:
text += ' '+another_q
self._finish(text)
else:
self._finish(_('SMP verifying failed'))
def _on_destroy(self, widget):
if self.smp_running:
self._abort(_('user aborted SMP authentication'))
self.window.hide_all()
def _apply(self, widget, appdata=None):
if self.finished:
self.window.hide_all()
return
secret = self.gw('secret_entry').get_text()
if self.response:
self.ctx.smpGotSecret(secret, appdata=appdata)
else:
if self.gw('qcheckbutton').get_active():
qtext = self.gw('qentry').get_text()
self.ctx.smpInit(secret, question=qtext, appdata=appdata)
else:
self.ctx.smpInit(secret, appdata=appdata)
self.gw('progressbar').set_fraction(0.3)
self.smp_running = True
widget.set_sensitive(False)
class ContactOtrWindow(gtk.Dialog):
def gw(self, n):
return self.xml.get_object(n)
def __init__(self, plugin, ctx, fpr=None, parent=None):
fjid = ctx.peer
gtk.Dialog.__init__(self, title=_('OTR settings for %s') % fjid,
parent=parent,
flags=gtk.DIALOG_DESTROY_WITH_PARENT,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
self.ctx = ctx
self.fjid = fjid
self.jid = gajim.get_room_and_nick_from_fjid(self.fjid)[0]
self.account = ctx.user.accountname
self.fpr = fpr
self.plugin = plugin
if self.fpr is None:
key = self.ctx.getCurrentKey()
if key is not None:
self.fpr = key.cfingerprint()
self.GTK_BUILDER_FILE_PATH = \
self.plugin.local_file_path('contact_otr_window.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)
self.notebook = self.gw('otr_settings_notebook')
self.child.pack_start(self.notebook)
self.connect('response', self.on_response)
self.gw('otr_default_checkbutton').connect('toggled',
self._otr_default_checkbutton_toggled)
# always set the label containing our fingerprint
self.gw('our_fp_label').set_markup(our_fp_text % ctx.user.getPrivkey())
if self.fpr is None:
# make the fingerprint widgets insensitive
# when not encrypted
for widget in self.gw('otr_fp_vbox').get_children():
widget.set_sensitive(False)
# show that the fingerprint is unknown
self.gw('their_fp_label').set_markup(their_fp_text % {
'jid': self.fjid, 'fp': _('unknown')})
self.gw('verified_combobox').set_active(-1)
else:
# make the fingerprint widgets sensitive when encrypted
for widget in self.gw('otr_fp_vbox').get_children():
widget.set_sensitive(True)
# show their fingerprint
fp = potr.human_hash(self.fpr)
self.gw('their_fp_label').set_markup(their_fp_text % {
'jid': self.fjid, 'fp': fp})
# set the trust combobox
if ctx.getCurrentTrust():
self.gw('verified_combobox').set_active(1)
else:
self.gw('verified_combobox').set_active(0)
otr_flags = self.plugin.get_flags(self.account, self.jid, fallback=False)
if otr_flags is not None:
self.gw('otr_default_checkbutton').set_active(0)
for w in self.gw('otr_settings_vbox').get_children():
w.set_sensitive(True)
else:
# per-user settings not available,
# using default settings
otr_flags = self.plugin.get_flags(self.account)
self.gw('otr_default_checkbutton').set_active(1)
for w in self.gw('otr_settings_vbox').get_children():
w.set_sensitive(False)
self.gw('otr_policy_allow_v2_checkbutton').set_active(
otr_flags['ALLOW_V2'])
self.gw('otr_policy_require_checkbutton').set_active(
otr_flags['REQUIRE_ENCRYPTION'])
self.gw('otr_policy_send_tag_checkbutton').set_active(
otr_flags['SEND_TAG'])
self.gw('otr_policy_start_on_tag_checkbutton').set_active(
otr_flags['WHITESPACE_START_AKE'])
self.child.show_all()
def on_response(self, dlg, response, *args):
if response != gtk.RESPONSE_ACCEPT:
return
# -1 when nothing is selected
# (ie. the connection is not encrypted)
trust_state = self.gw('verified_combobox').get_active()
if trust_state == 1 and not self.ctx.getTrust(self.fpr):
self.ctx.setTrust(self.fpr, 'verified')
self.ctx.user.saveTrusts()
self.plugin.update_context_list()
elif trust_state == 0:
self.ctx.setTrust(self.fpr, '')
self.ctx.user.saveTrusts()
self.plugin.update_context_list()
self.plugin.update_otr(self.ctx.peer, self.ctx.user.accountname, True)
if self.gw('otr_default_checkbutton').get_active():
# default is enabled, so remove any user-specific
# settings if available
self.plugin.set_flags(None, self.account, self.jid)
else:
# build the flags using the checkboxes
flags = {}
flags['ALLOW_V2'] = \
self.gw('otr_policy_allow_v2_checkbutton').get_active()
flags['REQUIRE_ENCRYPTION'] = \
self.gw('otr_policy_require_checkbutton').get_active()
flags['SEND_TAG'] = \
self.gw('otr_policy_send_tag_checkbutton').get_active()
flags['WHITESPACE_START_AKE'] = \
self.gw('otr_policy_start_on_tag_checkbutton').get_active()
self.plugin.set_flags(flags, self.account, self.jid)
def _otr_default_checkbutton_toggled(self, widget):
for w in self.gw('otr_settings_vbox').get_children():
w.set_sensitive(not widget.get_active())
def get_otr_submenu(plugin, control):
GTK_BUILDER_FILE_PATH = \
plugin.local_file_path('contact_otr_window.ui')
xml = gtk.Builder()
xml.set_translation_domain('gajim_plugins')
xml.add_from_file(GTK_BUILDER_FILE_PATH)
otr_submenu = xml.get_object('otr_submenu')
otr_settings_menuitem, smp_otr_menuitem, start_otr_menuitem, \
end_otr_menuitem = otr_submenu.get_submenu().get_children()
otr_submenu.set_sensitive(True)
otr_settings_menuitem.connect('activate', plugin.menu_settings_cb, control)
start_otr_menuitem.connect('activate', plugin.menu_start_cb, control)
end_otr_menuitem.connect('activate', plugin.menu_end_cb, control)
smp_otr_menuitem.connect('activate', plugin.menu_smp_cb, control)
enc, _, fin = plugin.get_otr_status(control.account, control.contact)
# can end only when not in PLAINTEXT
end_otr_menuitem.set_sensitive(enc)
# can SMP only when ENCRYPTED
smp_otr_menuitem.set_sensitive(enc and not fin)
return otr_submenu

View File

@@ -1,2 +0,0 @@
from offline_bookmarks import OfflineBookmarksPlugin

View File

@@ -1,458 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="manage_bookmarks_window">
<property name="border_width">12</property>
<property name="title" translatable="yes">Manage Bookmarks</property>
<property name="default_width">550</property>
<property name="default_height">300</property>
<property name="type_hint">dialog</property>
<child>
<object class="GtkVBox" id="vbox86">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkHBox" id="hbox2965">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox94">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow37">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="bookmarks_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox25">
<property name="visible">True</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="add_bookmark_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_add_bookmark_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_bookmark_button">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_remove_bookmark_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table33">
<property name="n_rows">11</property>
<property name="n_columns">2</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label318">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Password:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="pass_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="server_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label317">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Server:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label316">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Roo_m:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="room_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="nick_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label315">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Nickname:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label325">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Title:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="title_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkLabel" id="label326">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Pr_int status:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkComboBox" id="print_status_combobox">
<property name="visible">True</property>
<property name="sensitive">False</property>
<signal name="changed" handler="on_print_status_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child>
<object class="GtkCheckButton" id="autojoin_checkbutton">
<property name="label" translatable="yes">A_uto join</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will join this group chat on startup</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_autojoin_checkbutton_toggled"/>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="minimize_checkbutton">
<property name="label" translatable="yes">Minimi_ze on Auto Join</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_minimize_checkbutton_toggled"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">1</property>
<property name="ypad">4</property>
<property name="label" translatable="yes">Import bookmarks:</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Import from:</property>
</object>
<packing>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
</packing>
</child>
<child>
<object class="GtkButton" id="import_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="focus_on_click">False</property>
<signal name="clicked" handler="on_import_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="xalign">1</property>
<property name="stock">gtk-add</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Import</property>
</object>
<packing>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="import_from">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<signal name="changed" handler="on_import_from_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Import to:</property>
</object>
<packing>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"/>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkComboBox" id="import_to">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<signal name="changed" handler="on_import_to_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,9 +0,0 @@
[info]
name: Offline Bookmarks
short_name: offline_bookmarks
version: 0.2.1
description: Saving bookmarks inside the plugin configuration file. Allows the use of locally stored bookmarks if the server does not support the storage of bookmarks (eg talk.google.com).
Support to import bookmarks from one account to another.
authors = Denis Fomin <fominde@gmail.com>
homepage = http://trac-plugins.gajim.org/wiki/OfflineBookmarksPlugin
max_gajim_version: 0.15.9

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

View File

@@ -1,371 +0,0 @@
# -*- coding: utf-8 -*-
##
import gtk
import gtkgui_helpers
from plugins.gui import GajimPluginConfigDialog
from plugins import GajimPlugin
from plugins.helpers import log_calls
from common import ged
from common import app
from common.i18n import Q_
from config import ManageBookmarksWindow
class OfflineBookmarksPlugin(GajimPlugin):
@log_calls('OfflineBookmarksPlugin')
def init(self):
self.description = _('Saving bookmarks inside the plugin configuration '
'file. Allows the use of locally stored bookmarks if the server '
'does not support the storage of bookmarks (eg talk.google.com).\n'
'Support to import bookmarks from one account to another.')
self.events_handlers = {
'bookmarks-received': (ged.POSTGUI, self.bookmarks_received),
'signed-in': (ged.POSTGUI, self.handle_event_signed_in),}
self.gui_extension_points = {
'groupchat_control': (self.connect_with_gc_control,
self.disconnect_from_gc_control),}
self.controls = []
self.config_dialog = OfflineBookmarksPluginConfigDialog(self)
@log_calls('OfflineBookmarksPlugin')
def activate(self):
pass
@log_calls('OfflineBookmarksPlugin')
def deactivate(self):
pass
def save_bookmarks(self, account, bookmarks):
jid = app.get_jid_from_account(account)
if jid not in self.config:
self.config[jid] = {}
self.config[jid] = bookmarks
def bookmarks_received(self, obj):
self.save_bookmarks(obj.conn.name, obj.bookmarks)
def handle_event_signed_in(self, obj):
account = obj.conn.name
connection = app.connections[account]
jid = app.get_jid_from_account(obj.conn.name)
bm_jids = [b['jid'] for b in connection.bookmarks]
if jid in self.config:
for bm in self.config[jid]:
if bm['jid'] not in bm_jids:
connection.bookmarks.append(bm)
invisible_show = app.SHOW_LIST.index('invisible')
# do not autojoin if we are invisible
if connection.connected == invisible_show:
return
# do not autojoin if bookmarks supported
bookmarks_supported = self.is_bookmark_supported(
app.connections[account])
if not bookmarks_supported:
app.interface.auto_join_bookmarks(connection.name)
def connect_with_gc_control(self, gc_control):
control = Base(self, gc_control)
self.controls.append(control)
def disconnect_from_gc_control(self, gc_control):
for control in self.controls:
control.disconnect_from_gc_control()
self.controls = []
def is_bookmark_supported(self, account):
if account.is_zeroconf:
return False
return (account.private_storage_supported or (
account.pubsub_supported and account.pubsub_publish_options_supported))
class Base(object):
def __init__(self, plugin, gc_control):
self.plugin = plugin
self.gc_control = gc_control
self.create_buttons()
def create_buttons(self):
# create button
actions_hbox = self.gc_control.xml.get_object('actions_hbox')
self.button = gtk.Button(label=None, stock=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False)
img = gtk.Image()
if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU)
else:
img.set_from_stock('gtk-add', gtk.ICON_SIZE_MENU)
self.button.set_image(img)
self.button.set_tooltip_text(_('Bookmark this room(local)'))
send_button = self.gc_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button,
'position')
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 1, 'expand', False)
self.button.set_no_show_all(True)
id_ = self.button.connect('clicked', self.add_bookmark_button_clicked)
self.gc_control.handlers[id_] = self.button
for bm in app.connections[self.gc_control.account].bookmarks:
if bm['jid'] == self.gc_control.contact.jid:
self.button.hide()
break
else:
account = self.gc_control.account
bookmarks_supported = self.plugin.is_bookmark_supported(
app.connections[account])
self.button.set_sensitive(not bookmarks_supported)
self.button.set_visible(not bookmarks_supported)
def add_bookmark_button_clicked(self, widget):
"""
Bookmark the room, without autojoin and not minimized
"""
from dialogs import ErrorDialog, InformationDialog
password = app.gc_passwords.get(self.gc_control.room_jid, '')
account = self.gc_control.account
bm = {'name': self.gc_control.name,
'jid': self.gc_control.room_jid,
'autojoin': 0,
'minimize': 0,
'password': password,
'nick': self.gc_control.nick}
place_found = False
index = 0
# check for duplicate entry and respect alpha order
for bookmark in app.connections[account].bookmarks:
if bookmark['jid'] == bm['jid']:
ErrorDialog(
_('Bookmark already set'),
_('Group Chat "%s" is already in your bookmarks.') % \
bm['jid'])
return
if bookmark['name'] > bm['name']:
place_found = True
break
index += 1
if place_found:
app.connections[account].bookmarks.insert(index, bm)
else:
app.connections[account].bookmarks.append(bm)
self.plugin.save_bookmarks(account, app.connections[account].bookmarks)
app.interface.roster.set_actions_menu_needs_rebuild()
InformationDialog(
_('Bookmark has been added successfully'),
_('You can manage your bookmarks via Actions menu in your roster.'))
def disconnect_from_gc_control(self):
actions_hbox = self.gc_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button)
class OfflineBookmarksPluginConfigDialog(GajimPluginConfigDialog,
ManageBookmarksWindow):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['vbox86'])
vbox = self.xml.get_object('vbox86')
self.child.pack_start(vbox)
self.import_from_combo = self.xml.get_object('import_from')
self.import_to_combo = self.xml.get_object('import_to')
def on_run(self):
self.fill_treeview()
#Prepare comboboxes
self.print_status_combobox = self.xml.get_object('print_status_combobox')
model = gtk.ListStore(str, str)
self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'),
'in_and_out': _('Enter and leave only'),
'none': Q_('?print_status:None')}
opts = sorted(self.option_list.keys())
for opt in opts:
model.append([self.option_list[opt], opt])
self.print_status_combobox.set_model(model)
self.print_status_combobox.set_active(1)
#Prepare import_from combobox
model = gtk.ListStore(str)
for account in self.accounts:
model.append([account,])
for account_jid in self.plugin.config:
if account_jid not in self.plugin.config_default_values and \
account_jid not in self.jids:
model.append([account_jid,])
self.import_from_combo.set_model(model)
#Prepare import_to combobox
model = gtk.ListStore(str)
for account in self.accounts:
model.append([account,])
self.import_to_combo.set_model(model)
self.selection = self.view.get_selection()
self.selection.connect('changed', self.bookmark_selected)
#Prepare input fields
self.title_entry = self.xml.get_object('title_entry')
self.title_entry.connect('changed', self.on_title_entry_changed)
self.nick_entry = self.xml.get_object('nick_entry')
self.nick_entry.connect('changed', self.on_nick_entry_changed)
self.server_entry = self.xml.get_object('server_entry')
self.server_entry.connect('changed', self.on_server_entry_changed)
self.room_entry = self.xml.get_object('room_entry')
self.room_entry.connect('changed', self.on_room_entry_changed)
self.pass_entry = self.xml.get_object('pass_entry')
self.pass_entry.connect('changed', self.on_pass_entry_changed)
self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton')
self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton')
self.xml.connect_signals(self)
self.connect('hide', self.on_hide)
self.show_all()
self.view.set_cursor((0,))
def fill_treeview(self):
# Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick,
# Show_Status
self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str)
self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.accounts = []
self.jids = []
# Store bookmarks in treeview.
for account in app.connections:
if app.connections[account].connected <= 1:
continue
if app.connections[account].is_zeroconf:
continue
self.accounts.append(account)
self.jids.append(app.get_jid_from_account(account))
iter_ = self.treestore.append(None, [None, account, None, None,
None, None, None, None])
for bookmark in app.connections[account].bookmarks:
if bookmark['name'] == '':
# No name was given for this bookmark.
# Use the first part of JID instead...
name = bookmark['jid'].split("@")[0]
bookmark['name'] = name
from common import helpers
# make '1', '0', 'true', 'false' (or other) to True/False
autojoin = helpers.from_xs_boolean_to_python_boolean(
bookmark['autojoin'])
minimize = helpers.from_xs_boolean_to_python_boolean(
bookmark['minimize'])
print_status = bookmark.get('print_status', '')
if print_status not in ('', 'all', 'in_and_out', 'none'):
print_status = ''
self.treestore.append(iter_, [
account,
bookmark['name'],
bookmark['jid'],
autojoin,
minimize,
bookmark['password'],
bookmark['nick'],
print_status ])
self.view = self.xml.get_object('bookmarks_treeview')
self.view.set_model(self.treestore)
self.view.expand_all()
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn('Bookmarks', renderer, text=1)
if self.view.get_column(0):
self.view.remove_column(self.view.get_column(0))
self.view.append_column(column)
def on_hide(self, widget):
"""
Parse the treestore data into our new bookmarks array, then send the new
bookmarks to the server.
"""
(model, iter_) = self.selection.get_selected()
if iter_ and model.iter_parent(iter_):
#bookmark selected, check it
if not self.check_valid_bookmark():
return
for account in self.treestore:
account_unicode = account[1].decode('utf-8')
app.connections[account_unicode].bookmarks = []
for bm in account.iterchildren():
# Convert True/False/None to '1' or '0'
autojoin = unicode(int(bm[3]))
minimize = unicode(int(bm[4]))
name = bm[1]
if name:
name = name.decode('utf-8')
jid = bm[2]
if jid:
jid = jid.decode('utf-8')
pw = bm[5]
if pw:
pw = pw.decode('utf-8')
nick = bm[6]
if nick:
nick = nick.decode('utf-8')
# create the bookmark-dict
bmdict = { 'name': name, 'jid': jid, 'autojoin': autojoin,
'minimize': minimize, 'password': pw, 'nick': nick,
'print_status': bm[7]}
app.connections[account_unicode].bookmarks.append(bmdict)
bookmarks_supported = self.plugin.is_bookmark_supported(
app.connections[account_unicode])
if bookmarks_supported:
app.connections[account_unicode].store_bookmarks()
self.plugin.save_bookmarks(account_unicode,
app.connections[account_unicode].bookmarks)
app.interface.roster.set_actions_menu_needs_rebuild()
def on_import_to_changed(self, treeview):
self.on_import_from_changed(self.import_from_combo)
def on_import_from_changed(self, widget):
if widget.get_active() == -1 or self.import_to_combo.get_active() == -1:
self.xml.get_object('import_button').set_sensitive(False)
else:
if widget.get_active_text() != self.import_to_combo.get_active_text():
self.xml.get_object('import_button').set_sensitive(True)
else:
self.xml.get_object('import_button').set_sensitive(False)
def on_import_button_clicked(self, widget):
from_ = self.import_from_combo.get_active_text()
to_connection = app.connections[self.import_to_combo.get_active_text()]
to_bookmarks = to_connection.bookmarks
if from_ in self.accounts:
from_bookmarks = app.connections[from_].bookmarks
else:
from_bookmarks = self.plugin.config[from_]
for bm in from_bookmarks:
for bookmark in to_bookmarks:
if bookmark['jid'] == bm['jid']:
break
else:
to_bookmarks.append(bm)
self.fill_treeview()
self.view.set_cursor((0,))
self.import_from_combo.set_active(-1)
self.import_to_combo.set_active(-1)

View File

@@ -1 +0,0 @@
from plugin import SnarlNotificationsPlugin

View File

@@ -1,9 +0,0 @@
[info]
name: Snarl Notifications
short_name: snarl_notifications
version: 0.1.2
description: Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system.
PySnarl bindings are used (http://sourceforge.net/projects/pysnarl/).
authors = Yann Leboulanger <asterix@lagaule.org>
homepage = http://trac-plugins.gajim.org/wiki/SnarlNotificationsPlugin
max_gajim_version: 0.15.9

View File

@@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Events notifications using Snarl
Fancy events notifications under Windows using Snarl infrastructure.
:note: plugin is at proof-of-concept state.
:author: Yann Leboulanger <asterix@lagaule.org>
:since: 08 April 2012
:copyright: Copyright (2012) Yann Leboulanger <asterix@lagaule.org>
:license: GPL
'''
import pySnarl
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
import os.path
class SnarlActionHandler(pySnarl.EventHandler):
def OnNotificationInvoked(self, uid):
account, jid, msg_type = uid.split()
gajim.interface.handle_event(account, jid, msg_type)
class SnarlNotificationsPlugin(GajimPlugin):
@log_calls('SnarlNotificationsPlugin')
def init(self):
self.description = _('Shows events notification using Snarl '
'(http://www.fullphat.net/) under Windows. '
'Snarl needs to be installed in system.\n'
'PySnarl bindings are used (http://code.google.com/p/pysnarl/).')
self.config_dialog = None
self.h = SnarlActionHandler
self.snarl_win = pySnarl.SnarlApp(
"pySnarl/Gajim", # app signature
"Gajim", # app title
os.path.abspath("..\data\pixmaps\gajim.ico"), # icon
"", # config Tool
"Gajim will use Snarl to display notifications", # hint
False, # IsDaemon
self.h, # event handler
[] # classes
)
self.events_handlers = {'notification' : (ged.PRECORE, self.notif)}
@log_calls('SnarlNotificationsPlugin')
def notif(self, obj):
if obj.do_popup:
uid = obj.conn.name + " " + obj.jid + " " + obj.popup_msg_type
self.snarl_win.notify(
[], # actions
"", # callbackScript
"", # callbackScriptType
"", # class
"", # defaultCallback
5, # duration
os.path.abspath(obj.popup_image),#r"C:\Documents and Settings\Administrateur\Mes documents\gajim\data\pixmaps\gajim.ico", # icon
"", # mergeUID
0, # priority
"", # replaceUID
obj.popup_text,
obj.popup_title,
uid, # UID
"", # sound
-1, # percent
0, # log
64, # sensitivity
)
obj.do_popup = False

View File

@@ -1,365 +0,0 @@
# -*- coding: utf-8 -*-
# ActiveX/COM Snarl python library
# For proper functioning requires Snarl 2.5.1 or later !
# Version 0.0.2
# Changelog (in reverse chronological order):
# -------------------------------------------
# 0.0.2 by Pako 2012-01-30 14:12 UTC+1
# - base64 icon format is now supported
# 0.0.1 by Pako 2012-01-06 15:30 UTC+1
# - initial version
#-------------------------------------------------------
from win32com.client import constants, gencache, Dispatch, DispatchWithEvents
#===============================================================================
def IsRunning():
from win32gui import FindWindow
return FindWindow("w>Snarl", "Snarl")
#===============================================================================
class EventHandler:
def OnActivated(self):
self.fn("DaemonActivated")
def OnNotificationActionSelected(self, uid, command):
self.fn("ActionSelected.%s" % command, payload = uid)
def OnNotificationClosed(self, uid):
self.fn("Closed", payload = uid)
def OnNotificationExpired(self, uid):
self.fn("Expired", payload = uid)
def OnNotificationInvoked(self, uid):
self.fn("Invoked", payload = uid)
def OnQuit(self):
self.fn("AppQuit")
def OnShowAbout(self):
self.fn("ShowAbout")
def OnShowConfig(self):
self.fn("ShowConfig")
def OnSnarlLaunched(self):
self.fn("Launched")
def OnSnarlQuit(self):
self.fn("Quit")
def OnSnarlStarted(self):
self.fn("Started")
def OnSnarlStopped(self):
self.fn("Stopped")
def OnUserAway(self):
self.fn("UserAway")
def OnUserReturned(self):
self.fn("UserReturned")
#===============================================================================
class SnarlApp(object):
"""Creates an SnarlApp object"""
def __init__(
self,
signature,
title,
icon = "",
configTool = "",
hint = "",
isDaemon = False,
eventHandler = None,
classes = []
):
self.app = gencache.EnsureDispatch("libsnarl25.SnarlApp")
for d in constants.__dicts__: # we must find the right dictionary ...
if 'ERROR_NOTIFICATION_NOT_FOUND' in d: # this is it !
break
codes = d.items()
codes.sort(reverse = True)
self.statCodes = dict([[v,k] for k,v in codes[6:]])
self.statCodes[codes[2][1]] = codes[2][0] # added SUCCESS
self.classes = NotifClasses(classes)
if eventHandler:
self.SetEventHandler(eventHandler)
self.SetTo(
configTool,
hint,
icon,
isDaemon,
signature,
title
)
def SetTo(
self,
configTool,
hint,
icon,
isDaemon,
signature,
title
):
self.app.Classes = self.classes.Classes()
self.app.ConfigTool = configTool
self.app.Hint = hint
self.app.Icon = icon
self.app.IsDaemon = isDaemon
self.app.Signature = signature
self.app.Title = title
def GetStatusCode(self, code):
return self.statCodes[code]
def SetEventHandler(self, handler):
self.events = DispatchWithEvents(self.app, handler)
def register(self):
return self.GetStatusCode(self.app.Register())
def unregister(self):
return self.GetStatusCode(self.app.Unregister())
def tidyUp(self):
self.app.TidyUp()
def addClass(
self,
classId,
name,
enabled = True,
title = "",
text = "",
icon = "",
callback = "",
duration = -1,
sound = "",
):
cntOld = self.classes.count()
cntNew = self.classes.add(classId, name, enabled, title, text, icon, callback, duration, sound)
return int(not cntNew == cntOld + 1)
def remClass(self, id):
cntOld = self.classes.count()
cntNew = self.classes.remove(id)
return int(not cntNew == cntOld - 1)
def clearClasses(self):
return self.classes.makeEmpty()
def classesCount(self):
return self.classes.count()
def notify(
self,
actions,
callbackScript,
callbackScriptType,
cls,
defaultCallback,
duration,
icon,
mergeUID,
priority,
replaceUID,
text,
title,
uid,
sound = None,
percent = None,
log = None,
sensitivity = None
):
note = Notification(
actions,
callbackScript,
callbackScriptType,
cls,
defaultCallback,
duration,
"",
mergeUID,
priority,
replaceUID,
text,
title,
uid,
)
if icon:
if icon[1] == ":":
note.Add("icon", icon, True)
else:
note.Add("icon-base64", icon.replace("=","%"), True)
if sound:
note.Add("sound", sound, True)
if percent is not None and percent > -1:
note.Add("value-percent", str(percent), True)
if log is not None:
note.Add("log", str(log), True)
if sensitivity is not None:
note.Add("sensitivity", str(sensitivity), True)
return self.GetStatusCode(self.app.Show(note.Note())[0])
def hideNotification(self, uid):
return self.GetStatusCode(self.app.Hide(uid))
def isVisible(self, uid):
return self.GetStatusCode(self.app.IsVisible(uid))
def getEtcPath(self):
return self.app.GetEtcPath()
def makePath(self, pth):
return self.app.GetEtcPath(pth)
def isInstalled(self):
return self.app.IsSnarlInstalled()
def isRunning(self):
return self.app.IsSnarlRunning()
def version(self):
ver = self.app.SnarlVersion()
return ver if ver > 0 else self.GetStatusCode(-ver)
def isConnected(self):
return self.app.IsConnected
def getLibVersion(self):
return self.app.LibVersion
def getLibRevision(self):
return self.app.LibRevision
def Destroy(self):
self.app.TidyUp()
if self.events:
del self.events
del self.app
#===============================================================================
class NotifClasses(object):
def __init__(self, classes = []):
self.clss = Dispatch("libsnarl25.Classes")
for cls in classes:
self.add(*cls)
def count(self):
return self.clss.Count()
def add(self, *cls):
self.clss.Add(*cls)
return self.clss.Count()
def remove(self, cls):
self.clss.Remove(cls)
return self.clss.Count()
def makeEmpty(self):
self.clss.MakeEmpty()
return self.clss.Count()
def Classes(self):
return self.clss
#===============================================================================
class Notification(object):
def __init__(
self,
actions,
callbackScript,
callbackScriptType,
cls,
defaultCallback,
duration,
icon,
mergeUID,
priority,
replaceUID,
text,
title,
uid,
):
nt = Dispatch("libsnarl25.Notification")
nt.Actions = NotifActions(actions).Actions()
nt.CallbackScript = callbackScript
nt.CallbackScriptType = callbackScriptType
nt.Class = cls
nt.DefaultCallback = defaultCallback
nt.Duration = duration
if icon:
nt.Icon = icon
nt.MergeUID = mergeUID
nt.Priority = priority
nt.ReplaceUID = replaceUID
nt.Text = text
nt.Title = title
nt.UID = uid
self.nt = nt
def Add(self, name, value, update):
self.nt.Add(name, value, True)
def Note(self):
return self.nt
#===============================================================================
class NotifActions(object):
def __init__(self, actions=[]):
self.actns = Dispatch("libsnarl25.Actions")
self.actions = actions
for action in actions:
self.actns.Add(*action)
def add(self, actn):
self.actns.Add(*actn)
self.actions.append(actn)
return self.actns.Count()
def remove(self, actn):
if actn in self.actions:
ix = self.actions.index(actn)
self.actns.Remove(ix + 1)
self.actions.pop(ix)
return self.actns.Count()
def makeEmpty(self):
self.actns.MakeEmpty()
self.actions = []
return self.actns.Count()
def Actions(self):
return self.actns
#===============================================================================

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1 +0,0 @@
from plugin import UbuntuIntegrationPlugin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,12 +0,0 @@
[info]
name: Ubuntu Ayatana Integration
short_name: ubuntu_integration
version: 0.1.4
description: This plugin integrates Gajim with the Ubuntu Messaging Menu.
You must have python-indicate and python-xdg (and Gajim obviously) installed to enable this plugin.
Many thanks to the guys from gajim@conference.gajim.org for answering my questions :)
authors: Michael Kainer <kaini@linuxlovers.at>
homepage: http://trac-plugins.gajim.org/wiki/UbuntuIntegrationPlugin
max_gajim_version: 0.15.9

View File

@@ -1,196 +0,0 @@
# -*- coding: utf-8 -*-
"""
Ubuntu Ayatana Integration plugin.
TODO:
* handle gc-invitation, subscription_request: it looks like they don't fire
* nice error if plugin can't load
* me menu
* permanent integration into the messaging menu after quitting gajim
* show/hide gajim on root menu entry
* switch workspace on click on events
* corrent group chat handling
* hide gajim if the plugin is disabled
:author: Michael Kainer <kaini@linuxlovers.at>
:since: 21st October 2010
:copyright: Copyright (2010) Michael Kainer <kaini1123@gmail.com>
:license: GPLv3
"""
# Python
import time
# Gajim
from plugins import GajimPlugin
from plugins.plugin import GajimPluginException
from plugins.helpers import log_calls
from common import gajim
import gtkgui_helpers
try:
from xdg.BaseDirectory import load_data_paths
import indicate
except ImportError:
pass
class UbuntuIntegrationPlugin(GajimPlugin):
"""
Class for Messaging Menu and Me Menu.
"""
@log_calls("UbuntuIntegrationPlugin")
def init(self):
"""
Does nothing.
"""
self.description = _('This plugin integrates Gajim '
'with the Ubuntu Messaging Menu.\n\n'
'You must have python-indicate and python-xdg (and Gajim obviously)'
' installed to enable this plugin.\n\n'
'Many thanks to the guys from gajim@conference.gajim.org for '
'answering my questions :)')
self.config_dialog = None
self.test_activatable()
def test_activatable(self):
self.available_text = ''
try:
from xdg.BaseDirectory import load_data_paths
except ImportError:
self.activatable = False
self.available_text += _('python-xdg is missing! '
'Install python-xdg.\n')
try:
import indicate
except ImportError:
self.activatable = False
self.available_text += _('python-indicate is missing! '
'Install python-indicate.')
@log_calls("UbuntuIntegrationPlugin")
def activate(self):
"""
Displays gajim in the Messaging Menu.
"""
# {(account, jid): (indicator, [event, ...]), ...}
self.events = {}
version = gajim.version.split('-')[0]
if version == '0.15' and self.available_text:
raise GajimPluginException(self.available_text)
self.server = indicate.indicate_server_ref_default()
self.server.set_type("message.im")
dfile = ""
for file in load_data_paths("applications/gajim.desktop"):
dfile = file
break
if not dfile:
raise GajimPluginException("Can't locate gajim.desktop!")
self.server.set_desktop_file(dfile)
self.server.show()
gajim.events.event_added_subscribe(self.on_event_added)
gajim.events.event_removed_subscribe(self.on_event_removed)
@log_calls("UbuntuIntegrationPlugin")
def deactivate(self):
"""
Cleaning up.
"""
gajim.events.event_added_unsubscribe(self.on_event_added)
gajim.events.event_removed_unsubscribe(self.on_event_removed)
if hasattr(self, 'server'):
self.server.hide()
del self.server
if hasattr(self, 'events'):
for (_, event) in self.events:
event[0].hide()
del self.events
def on_indicator_activate(self, indicator, _):
"""
Forwards the action to gajims event handler.
"""
key = indicator.key
event = self.events[key][1][0]
gajim.interface.handle_event(event.account, event.jid, event.type_)
def on_event_added(self, event):
"""
Adds "Nickname Time" to the Messaging menu.
"""
print "----", event.type_
# Basic variables
account = event.account
jid = event.jid
when = time.time()
contact = ""
key = (account, jid)
# Check if the event is valid and modify the variables
if event.type_ == "chat" or \
event.type_ == "printed_chat" or \
event.type_ == "normal" or \
event.type_ == "printed_normal" or \
event.type_ == "file-request" or \
event.type_ == "jingle-incoming":
contact = gajim.contacts.get_contact_from_full_jid(account, jid)
if contact:
contact = contact.get_shown_name()
else:
contact = jid
elif event.type_ == "pm" or event.type_ == "printed_pm":
contact = gajim.get_nick_from_jid(gajim.get_room_from_fjid(jid)) +\
"/" + gajim.get_room_and_nick_from_fjid(jid)[1]
elif event.type_ == "printed_marked_gc_msg":
contact = gajim.get_nick_from_jid(gajim.get_room_from_fjid(jid))
else:
print "ignored"
return
print account, jid, when, contact
# Add a new indicator if necessary
if key not in self.events:
indicator = indicate.Indicator()
indicator.set_property("name", contact)
indicator.set_property_time("time", when)
indicator.set_property_bool("draw-attention", True)
if gajim.config.get("show_avatars_in_roster"):
pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
if pixbuf not in (None, "ask"):
indicator.set_property_icon("icon", pixbuf)
indicator.connect("user-display", self.on_indicator_activate)
indicator.show()
indicator.key = key
self.events[key] = (indicator, [])
# Prepare the event and save it
event.time = when
self.events[key][1].append(event)
def on_event_removed(self, events):
"""
Goes through the events and removes them from the array and
the indicator if there are no longer any events pending.
"""
for event in events:
print "====", event.type_
key = (event.account, event.jid)
if key not in self.events and \
event in self.events[key][1]:
self.events[key][1].remove(event)
if len(self.events[key][1]) == 0: # remove indicator
self.events[key][0].hide()
del self.events[key]
else: # set the indicator time to the text event
self.events[key][0].set_property_time("time",
self.events[key][1][0].time)
else:
print "ignored"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 B

View File

@@ -1 +0,0 @@
from url_shortener import UrlShortenerPlugin

View File

@@ -1,112 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<child>
<object class="GtkSpinButton" id="max_chars">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">6</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="avatar_size_value_changed"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="in_max_chars">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="width_chars">6</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="on_in_max_chars_value_changed"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options">GTK_EXPAND</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="incoming message">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">13</property>
<property name="label" translatable="yes">incoming message</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options">GTK_EXPAND</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="avatar_size_lebel">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">12</property>
<property name="label" translatable="yes">outgoing message</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="y_options">GTK_EXPAND</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;The maximum length not be shortened links(chars):&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="padding">6</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="shorten_outgoing">
<property name="label" translatable="yes">shorten links in outgoing messages</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="shorten_outgoing_toggled"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,12 +0,0 @@
[info]
name: Url Shortener
short_name: url_shortener
version: 0.3.5
description: Plugin that allows users to shorten a long URL in messages.
For example, you can turn this link:
https://trac.gajim.org/timeline
Into this link:
http://bit.ly/THy6ZK
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/UrlShortenerPlugin
max_gajim_version: 0.15.9

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

View File

@@ -1,232 +0,0 @@
# -*- coding: utf-8 -*-
import gtk
import json
import urllib
import urllib2
from common import app
from common import ged
from plugins import GajimPlugin
from plugins.helpers import log_calls
from plugins.gui import GajimPluginConfigDialog
APIKEY = 'R_fcba926fc7978bd19acbca73ec82b2be'
USER = 'dicson'
class UrlShortenerPlugin(GajimPlugin):
@log_calls('UrlShortenerPlugin')
def init(self):
self.description = _('Plugin that allows users to shorten a long URL '
'in received messages.\n'
'For example, you can turn this link:\n'
'https://trac.gajim.org/timeline\n'
'Into this link:\n'
'http://bit.ly/THy6ZK')
self.config_dialog = UrlShortenerPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'print_special_text': (self.print_special_text,
self.print_special_text1),}
self.config_default_values = {
'MAX_CHARS': (50, ('MAX_CHARS(30-...)')),
'IN_MAX_CHARS': (50, ('MAX_CHARS(30-...)')),
'SHORTEN_OUTGOING': (False, ''),}
self.events_handlers = {'message-outgoing': (ged.OUT_PRECORE,
self.handle_outgoing_msg),
'gc-message-outgoing': (ged.OUT_PRECORE,
self.handle_outgoing_msg)}
self.chat_control = None
self.controls = []
def handle_outgoing_msg(self, event):
if not self.active:
return
if not event.message:
return
if not self.config['SHORTEN_OUTGOING']:
return
if hasattr(event, 'shortened'):
return
iterator = app.interface.basic_pattern_re.finditer(event.message)
for match in iterator:
start, end = match.span()
link = event.message[start:end]
if len(link) < self.config['MAX_CHARS']:
continue
short_link = None
try:
params = urllib.urlencode({'longUrl': link,
'login': USER,
'apiKey': APIKEY,
'format': 'json'})
req = urllib2.Request('http://api.bit.ly/v3/shorten?%s' % params)
response = urllib2.urlopen(req)
j = json.load(response)
if j['status_code'] == 200:
short_link = j['data']['url']
except urllib2.HTTPError, e:
pass
if short_link:
event.message = event.message.replace(link, short_link)
event.callback_args[1] = event.message
event.shortened = True
@log_calls('UrlShortenerPlugin')
def connect_with_chat_control(self, chat_control):
self.chat_control = chat_control
control = Base(self, self.chat_control)
self.controls.append(control)
@log_calls('UrlShortenerPlugin')
def disconnect_from_chat_control(self, chat_control):
for control in self.controls:
control.disconnect_from_chat_control()
self.controls = []
def print_special_text(self, tv, special_text, other_tags, graphics=True,
additional_data={}):
for control in self.controls:
if control.chat_control.conv_textview != tv:
continue
control.print_special_text(special_text, other_tags, graphics=True)
def print_special_text1(self, chat_control, special_text, other_tags=None,
graphics=True, additional_data={}):
for control in self.controls:
if control.chat_control == chat_control:
control.disconnect_from_chat_control()
self.controls.remove(control)
class Base(object):
def __init__(self, plugin, chat_control):
self.plugin = plugin
self.chat_control = chat_control
self.textview = self.chat_control.conv_textview
self.id_ = self.textview.tv.connect('motion_notify_event',
self.on_textview_motion_notify_event)
self.chat_control.handlers[self.id_] = self.textview.tv
def print_special_text(self, special_text, other_tags, graphics=True):
if not self.plugin.active:
return
is_xhtml_link = None
text_is_valid_uri = False
buffer_ = self.textview.tv.get_buffer()
# Detect XHTML-IM link
ttt = buffer_.get_tag_table()
tags_ = [(ttt.lookup(t) if isinstance(t, str) else t) for t in other_tags]
for t in tags_:
is_xhtml_link = getattr(t, 'href', None)
if is_xhtml_link:
break
# Check if we accept this as an uri
schemes = app.config.get('uri_schemes').split()
for scheme in schemes:
if special_text.startswith(scheme):
text_is_valid_uri = True
if special_text.startswith('www.') or special_text.startswith('ftp.') \
or text_is_valid_uri and not is_xhtml_link:
if len(special_text) < self.plugin.config['IN_MAX_CHARS']:
return
end_iter = buffer_.get_end_iter()
mark = buffer_.create_mark(None, end_iter, True)
app.thread_interface(self.insert_hyperlink, [mark, special_text,
ttt])
self.textview.plugin_modified = True
def insert_hyperlink(self, mark, special_text, ttt):
try:
params = urllib.urlencode({'longUrl': special_text,
'login': USER,
'apiKey': APIKEY,
'format': 'json'})
req = urllib2.Request('http://api.bit.ly/v3/shorten?%s' % params)
response = urllib2.urlopen(req)
j = json.load(response)
if j['status_code'] == 200:
special_text = j['data']['url']
except urllib2.HTTPError, e:
pass
buffer_ = mark.get_buffer()
end_iter = buffer_.get_iter_at_mark(mark)
buffer_.insert_with_tags(end_iter, special_text, ttt.lookup('url'))
def on_textview_motion_notify_event(self, widget, event):
pointer_x, pointer_y = self.textview.tv.window.get_pointer()[0:2]
x, y = self.textview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
pointer_x, pointer_y)
tags = self.textview.tv.get_iter_at_location(x, y).get_tags()
tag_table = self.textview.tv.get_buffer().get_tag_table()
buffer_ = self.textview.tv.get_buffer()
for tag in tags:
if tag != tag_table.lookup('url'):
continue
it = self.textview.tv.get_iter_at_location(x, y)
st = it.copy()
st.backward_to_tag_toggle(tag_table.lookup('url'))
it.forward_to_tag_toggle(tag_table.lookup('url'))
text = buffer_.get_text(st, it, include_hidden_chars=True)
if text.startswith('http://bit.ly/'):
try:
params = urllib.urlencode({'shortUrl': text,
'login': USER,
'apiKey': APIKEY,
'format': 'json'})
req = urllib2.Request('http://api.bit.ly/v3/expand?%s' \
% params)
response = urllib2.urlopen(req)
j = json.load(response)
if j['status_code'] != 200:
raise Exception('%s'%j['status_txt'])
txt = j['data']['expand'][0]['long_url']
self.textview.tv.set_tooltip_text(txt)
self.textview.on_textview_motion_notify_event(widget, event)
return
except Exception, e:
break
self.textview.tv.set_tooltip_text('')
self.textview.on_textview_motion_notify_event(widget, event)
def disconnect_from_chat_control(self):
pass
class UrlShortenerPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['vbox1'])
self.max_chars_spinbutton = self.xml.get_object('max_chars')
self.max_chars_spinbutton.get_adjustment().set_all(30, 30, 99999, 1,
10, 0)
self.in_max_chars_spinbutton = self.xml.get_object('in_max_chars')
self.in_max_chars_spinbutton.get_adjustment().set_all(30, 30, 99999, 1,
10, 0)
self.shorten_outgoing = self.xml.get_object('shorten_outgoing')
hbox = self.xml.get_object('vbox1')
self.child.pack_start(hbox)
self.xml.connect_signals(self)
def on_run(self):
self.max_chars_spinbutton.set_value(self.plugin.config['MAX_CHARS'])
self.in_max_chars_spinbutton.set_value(self.plugin.config['IN_MAX_CHARS'])
self.shorten_outgoing.set_active(self.plugin.config['SHORTEN_OUTGOING'])
def avatar_size_value_changed(self, spinbutton):
self.plugin.config['MAX_CHARS'] = spinbutton.get_value()
def on_in_max_chars_value_changed(self, spinbutton):
self.plugin.config['IN_MAX_CHARS'] = spinbutton.get_value()
def shorten_outgoing_toggled(self, checkbutton):
self.plugin.config['SHORTEN_OUTGOING'] = checkbutton.get_active()

View File

@@ -1 +0,0 @@
from plugin import WhiteboardPlugin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,8 +0,0 @@
[info]
name: Whiteboard
short_name: whiteboard
version: 0.2.2
description: Shows a whiteboard in chat. python-pygoocanvas is required.
authors = Yann Leboulanger <asterix@lagaule.org>
homepage = http://trac-plugins.gajim.org/wiki/WhiteboardPlugin
max_gajim_version: 0.15.9

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

View File

@@ -1,486 +0,0 @@
## plugins/whiteboard/plugin.py
##
## Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
## Copyright (C) 2010 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Whiteboard plugin.
:author: Yann Leboulanger <asterix@lagaule.org>
:since: 1st November 2010
:copyright: Copyright (2010) Yann Leboulanger <asterix@lagaule.org>
:license: GPL
'''
from common import helpers
from common import gajim
from plugins import GajimPlugin
from plugins.plugin import GajimPluginException
from plugins.helpers import log_calls, log
import common.xmpp
import gtk
import chat_control
from common import ged
from common.jingle_session import JingleSession
from common.jingle_content import JingleContent
from common.jingle_transport import JingleTransport, TransportType
import dialogs
from whiteboard_widget import Whiteboard, HAS_GOOCANVAS
from common import xmpp
from common import caps_cache
NS_JINGLE_XHTML = 'urn:xmpp:tmp:jingle:apps:xhtml'
NS_JINGLE_SXE = 'urn:xmpp:tmp:jingle:transports:sxe'
NS_SXE = 'urn:xmpp:sxe:0'
class WhiteboardPlugin(GajimPlugin):
@log_calls('WhiteboardPlugin')
def init(self):
self.description = _('Shows a whiteboard in chat.'
' python-pygoocanvas is required.')
self.config_dialog = None
self.events_handlers = {
'jingle-request-received': (ged.GUI1, self._nec_jingle_received),
'jingle-connected-received': (ged.GUI1, self._nec_jingle_connected),
'jingle-disconnected-received': (ged.GUI1,
self._nec_jingle_disconnected),
'raw-message-received': (ged.GUI1, self._nec_raw_message),
}
self.gui_extension_points = {
'chat_control_base' : (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'chat_control_base_update_toolbar': (self.update_button_state,
None),
}
self.controls = []
self.sid = None
@log_calls('WhiteboardPlugin')
def _compute_caps_hash(self):
for a in gajim.connections:
gajim.caps_hash[a] = caps_cache.compute_caps_hash([
gajim.gajim_identity], gajim.gajim_common_features + \
gajim.gajim_optional_features[a])
# re-send presence with new hash
connected = gajim.connections[a].connected
if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
gajim.connections[a].status)
@log_calls('WhiteboardPlugin')
def activate(self):
if not HAS_GOOCANVAS:
raise GajimPluginException('python-pygoocanvas is missing!')
if NS_JINGLE_SXE not in gajim.gajim_common_features:
gajim.gajim_common_features.append(NS_JINGLE_SXE)
if NS_SXE not in gajim.gajim_common_features:
gajim.gajim_common_features.append(NS_SXE)
self._compute_caps_hash()
@log_calls('WhiteboardPlugin')
def deactivate(self):
if NS_JINGLE_SXE in gajim.gajim_common_features:
gajim.gajim_common_features.remove(NS_JINGLE_SXE)
if NS_SXE in gajim.gajim_common_features:
gajim.gajim_common_features.remove(NS_SXE)
self._compute_caps_hash()
@log_calls('WhiteboardPlugin')
def connect_with_chat_control(self, control):
if isinstance(control, chat_control.ChatControl):
base = Base(self, control)
self.controls.append(base)
@log_calls('WhiteboardPlugin')
def disconnect_from_chat_control(self, chat_control):
for base in self.controls:
base.disconnect_from_chat_control()
self.controls = []
@log_calls('WhiteboardPlugin')
def update_button_state(self, control):
for base in self.controls:
if base.chat_control == control:
if control.contact.supports(NS_JINGLE_SXE) and \
control.contact.supports(NS_SXE):
base.button.set_sensitive(True)
tooltip_text = _('Show whiteboard')
else:
base.button.set_sensitive(False)
tooltip_text = _('Client on the other side '
'does not support the whiteboard')
base.button.set_tooltip_text(tooltip_text)
@log_calls('WhiteboardPlugin')
def show_request_dialog(self, account, fjid, jid, sid, content_types):
def on_ok():
session = gajim.connections[account].get_jingle_session(fjid, sid)
self.sid = session.sid
if not session.accepted:
session.approve_session()
for content in content_types:
session.approve_content(content)
for _jid in (fjid, jid):
ctrl = gajim.interface.msg_win_mgr.get_control(_jid, account)
if ctrl:
break
if not ctrl:
# create it
gajim.interface.new_chat_from_jid(account, jid)
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
session = session.contents[('initiator', 'xhtml')]
ctrl.draw_whiteboard(session)
def on_cancel():
session = gajim.connections[account].get_jingle_session(fjid, sid)
session.decline_session()
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if contact:
name = contact.get_shown_name()
else:
name = jid
pritext = _('Incoming Whiteboard')
sectext = _('%(name)s (%(jid)s) wants to start a whiteboard with '
'you. Do you want to accept?') % {'name': name, 'jid': jid}
dialog = dialogs.NonModalConfirmationDialog(pritext, sectext=sectext,
on_response_ok=on_ok, on_response_cancel=on_cancel)
dialog.popup()
@log_calls('WhiteboardPlugin')
def _nec_jingle_received(self, obj):
if not HAS_GOOCANVAS:
return
content_types = set(c[0] for c in obj.contents)
if 'xhtml' not in content_types:
return
self.show_request_dialog(obj.conn.name, obj.fjid, obj.jid, obj.sid,
content_types)
@log_calls('WhiteboardPlugin')
def _nec_jingle_connected(self, obj):
if not HAS_GOOCANVAS:
return
account = obj.conn.name
ctrl = (gajim.interface.msg_win_mgr.get_control(obj.fjid, account)
or gajim.interface.msg_win_mgr.get_control(obj.jid, account))
if not ctrl:
return
session = gajim.connections[obj.conn.name].get_jingle_session(obj.fjid,
obj.sid)
if ('initiator', 'xhtml') not in session.contents:
return
session = session.contents[('initiator', 'xhtml')]
ctrl.draw_whiteboard(session)
@log_calls('WhiteboardPlugin')
def _nec_jingle_disconnected(self, obj):
for base in self.controls:
if base.sid == obj.sid:
base.stop_whiteboard(reason = obj.reason)
@log_calls('WhiteboardPlugin')
def _nec_raw_message(self, obj):
if not HAS_GOOCANVAS:
return
if obj.stanza.getTag('sxe', namespace=NS_SXE):
account = obj.conn.name
try:
fjid = helpers.get_full_jid_from_iq(obj.stanza)
except helpers.InvalidFormat:
obj.conn.dispatch('ERROR', (_('Invalid Jabber ID'),
_('A message from a non-valid JID arrived, it has been '
'ignored.')))
jid = gajim.get_jid_without_resource(fjid)
ctrl = (gajim.interface.msg_win_mgr.get_control(fjid, account)
or gajim.interface.msg_win_mgr.get_control(jid, account))
if not ctrl:
return
sxe = obj.stanza.getTag('sxe')
if not sxe:
return
sid = sxe.getAttr('session')
if (jid, sid) not in obj.conn._sessions:
pass
# newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
# self.addJingle(newjingle)
# we already have such session in dispatcher...
session = obj.conn.get_jingle_session(fjid, sid)
cn = session.contents[('initiator', 'xhtml')]
error = obj.stanza.getTag('error')
if error:
action = 'iq-error'
else:
action = 'edit'
cn.on_stanza(obj.stanza, sxe, error, action)
# def __editCB(self, stanza, content, error, action):
#new_tags = sxe.getTags('new')
#remove_tags = sxe.getTags('remove')
#if new_tags is not None:
## Process new elements
#for tag in new_tags:
#if tag.getAttr('type') == 'element':
#ctrl.whiteboard.recieve_element(tag)
#elif tag.getAttr('type') == 'attr':
#ctrl.whiteboard.recieve_attr(tag)
#ctrl.whiteboard.apply_new()
#if remove_tags is not None:
## Delete rids
#for tag in remove_tags:
#target = tag.getAttr('target')
#ctrl.whiteboard.image.del_rid(target)
# Stop propagating this event, it's handled
return True
class Base(object):
def __init__(self, plugin, chat_control):
self.plugin = plugin
self.chat_control = chat_control
self.chat_control.draw_whiteboard = self.draw_whiteboard
self.contact = self.chat_control.contact
self.account = self.chat_control.account
self.jid = self.contact.get_full_jid()
self.create_buttons()
self.whiteboard = None
self.sid = None
def create_buttons(self):
# create whiteboard button
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
self.button = gtk.ToggleButton(label=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False)
img = gtk.Image()
img_path = self.plugin.local_file_path('whiteboard.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory = gtk.IconFactory()
factory.add('whiteboard', iconset)
img_path = self.plugin.local_file_path('brush_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('brush_tool', iconset)
img_path = self.plugin.local_file_path('line_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('line_tool', iconset)
img_path = self.plugin.local_file_path('oval_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('oval_tool', iconset)
factory.add_default()
img.set_from_stock('whiteboard', gtk.ICON_SIZE_MENU)
self.button.set_image(img)
send_button = self.chat_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button,
'position')
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 1, 'expand', False)
id_ = self.button.connect('toggled', self.on_whiteboard_button_toggled)
self.chat_control.handlers[id_] = self.button
self.button.show()
def draw_whiteboard(self, content):
hbox = self.chat_control.xml.get_object('chat_control_hbox')
if len(hbox.get_children()) == 1:
self.whiteboard = Whiteboard(self.account, self.contact, content,
self.plugin)
# set minimum size
self.whiteboard.hbox.set_size_request(300, 0)
hbox.pack_start(self.whiteboard.hbox, expand=False, fill=False)
self.whiteboard.hbox.show_all()
self.button.set_active(True)
content.control = self
self.sid = content.session.sid
def on_whiteboard_button_toggled(self, widget):
"""
Popup whiteboard
"""
if widget.get_active():
if not self.whiteboard:
self.start_whiteboard()
else:
self.stop_whiteboard()
def start_whiteboard(self):
conn = gajim.connections[self.chat_control.account]
jingle = JingleSession(conn, weinitiate=True, jid=self.jid)
self.sid = jingle.sid
conn._sessions[jingle.sid] = jingle
content = JingleWhiteboard(jingle)
content.control = self
jingle.add_content('xhtml', content)
jingle.start_session()
def stop_whiteboard(self, reason=None):
conn = gajim.connections[self.chat_control.account]
self.sid = None
session = conn.get_jingle_session(self.jid, media='xhtml')
if session:
session.end_session()
self.button.set_active(False)
if reason:
txt = _('Whiteboard stopped: %(reason)s') % {'reason': reason}
self.chat_control.print_conversation(txt, 'info')
if not self.whiteboard:
return
hbox = self.chat_control.xml.get_object('chat_control_hbox')
if self.whiteboard.hbox in hbox.get_children():
if hasattr(self.whiteboard, 'hbox'):
hbox.remove(self.whiteboard.hbox)
self.whiteboard = None
def disconnect_from_chat_control(self):
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button)
class JingleWhiteboard(JingleContent):
''' Jingle Whiteboard sessions consist of xhtml content'''
def __init__(self, session, transport=None):
if not transport:
transport = JingleTransportSXE()
JingleContent.__init__(self, session, transport)
self.media = 'xhtml'
self.negotiated = True # there is nothing to negotiate
self.last_rid = 0
self.callbacks['session-accept'] += [self._sessionAcceptCB]
self.callbacks['session-terminate'] += [self._stop]
self.callbacks['session-terminate-sent'] += [self._stop]
self.callbacks['edit'] = [self._EditCB]
def _EditCB(self, stanza, content, error, action):
new_tags = content.getTags('new')
remove_tags = content.getTags('remove')
if new_tags is not None:
# Process new elements
for tag in new_tags:
if tag.getAttr('type') == 'element':
self.control.whiteboard.recieve_element(tag)
elif tag.getAttr('type') == 'attr':
self.control.whiteboard.recieve_attr(tag)
self.control.whiteboard.apply_new()
if remove_tags is not None:
# Delete rids
for tag in remove_tags:
target = tag.getAttr('target')
self.control.whiteboard.image.del_rid(target)
def _sessionAcceptCB(self, stanza, content, error, action):
log.debug('session accepted')
self.session.connection.dispatch('WHITEBOARD_ACCEPTED',
(self.session.peerjid, self.session.sid))
def generate_rids(self, x):
# generates x number of rids and returns in list
rids = []
for x in range(x):
rids.append(str(self.last_rid))
self.last_rid += 1
return rids
def send_whiteboard_node(self, items, rids):
# takes int rid and dict items and sends it as a node
# sends new item
jid = self.session.peerjid
sid = self.session.sid
message = xmpp.Message(to=jid)
sxe = message.addChild(name='sxe', attrs={'session': sid},
namespace=NS_SXE)
for x in rids:
if items[x]['type'] == 'element':
parent = x
attrs = {'rid': x,
'name': items[x]['data'][0].getName(),
'type': items[x]['type']}
sxe.addChild(name='new', attrs=attrs)
if items[x]['type'] == 'attr':
attr_name = items[x]['data']
chdata = items[parent]['data'][0].getAttr(attr_name)
attrs = {'rid': x,
'name': attr_name,
'type': items[x]['type'],
'chdata': chdata,
'parent': parent}
sxe.addChild(name='new', attrs=attrs)
self.session.connection.connection.send(message)
def delete_whiteboard_node(self, rids):
message = xmpp.Message(to=self.session.peerjid)
sxe = message.addChild(name='sxe', attrs={'session': self.session.sid},
namespace=NS_SXE)
for x in rids:
sxe.addChild(name='remove', attrs = {'target': x})
self.session.connection.connection.send(message)
def send_items(self, items, rids):
# recieves dict items and a list of rids of items to send
# TODO: is there a less clumsy way that doesn't involve passing
# whole list
self.send_whiteboard_node(items, rids)
def del_item(self, rids):
self.delete_whiteboard_node(rids)
def encode(self, xml):
# encodes it sendable string
return 'data:text/xml,' + urllib.quote(xml)
def _fill_content(self, content):
content.addChild(NS_JINGLE_XHTML + ' description')
def _stop(self, *things):
pass
def __del__(self):
pass
def get_content(desc):
return JingleWhiteboard
common.jingle_content.contents[NS_JINGLE_XHTML] = get_content
class JingleTransportSXE(JingleTransport):
def __init__(self, node=None):
if gajim.config.get('version') == '0.15':
JingleTransport.__init__(self, TransportType.streaming)
else:
JingleTransport.__init__(self, TransportType.SOCKS5)
def make_transport(self, candidates=None):
transport = JingleTransport.make_transport(self, candidates)
transport.setNamespace(NS_JINGLE_SXE)
transport.setTagData('host', 'TODO')
return transport
common.jingle_transport.transports[NS_JINGLE_SXE] = JingleTransportSXE

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,418 +0,0 @@
## plugins/whiteboard/whiteboard_widget.py
##
## Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
## Copyright (C) 2010 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim 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; version 3 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import gtk
import gtkgui_helpers
try:
import goocanvas
HAS_GOOCANVAS = True
except:
HAS_GOOCANVAS = False
from common.xmpp import Node
from common import gajim
from dialogs import FileChooserDialog
'''
A whiteboard widget made for Gajim.
- Ummu
'''
class Whiteboard(object):
def __init__(self, account, contact, session, plugin):
self.plugin = plugin
file_path = plugin.local_file_path('whiteboard_widget.ui')
xml = gtk.Builder()
xml.set_translation_domain('gajim_plugins')
xml.add_from_file(file_path)
self.hbox = xml.get_object('whiteboard_hbox')
self.canevas = goocanvas.Canvas()
self.hbox.pack_start(self.canevas)
self.hbox.reorder_child(self.canevas, 0)
self.canevas.set_flags(gtk.CAN_FOCUS)
self.fg_color_select_button = xml.get_object('fg_color_button')
self.root = self.canevas.get_root_item()
self.tool_buttons = []
for tool in ('brush', 'oval', 'line', 'delete'):
self.tool_buttons.append(xml.get_object(tool + '_button'))
xml.get_object('brush_button').set_active(True)
# Events
self.canevas.connect('button-press-event', self.button_press_event)
self.canevas.connect('button-release-event', self.button_release_event)
self.canevas.connect('motion-notify-event', self.motion_notify_event)
self.canevas.connect('item-created', self.item_created)
# Config
self.line_width = 2
xml.get_object('size_scale').set_value(2)
self.color = str(self.fg_color_select_button.get_color())
# SVG Storage
self.image = SVGObject(self.root, session)
xml.connect_signals(self)
# Temporary Variables for items
self.item_temp = None
self.item_temp_coords = (0, 0)
self.item_data = None
# Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance
self.recieving = {}
def on_tool_button_toggled(self, widget):
for btn in self.tool_buttons:
if btn == widget:
continue
btn.set_active(False)
def on_brush_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'brush'
self.on_tool_button_toggled(widget)
def on_oval_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'oval'
self.on_tool_button_toggled(widget)
def on_line_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'line'
self.on_tool_button_toggled(widget)
def on_delete_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'delete'
self.on_tool_button_toggled(widget)
def on_clear_button_clicked(self, widget):
self.image.clear_canvas()
def on_export_button_clicked(self, widget):
SvgChooserDialog(self.image.export_svg)
def on_fg_color_button_color_set(self, widget):
self.color = str(self.fg_color_select_button.get_color())
def item_created(self, canvas, item, model):
print 'item created'
item.connect('button-press-event', self.item_button_press_events)
def item_button_press_events(self, item, target_item, event):
if self.image.draw_tool == 'delete':
self.image.del_item(item)
def on_size_scale_format_value(self, widget):
self.line_width = int(widget.get_value())
def button_press_event(self, widget, event):
x = event.x
y = event.y
state = event.state
self.item_temp_coords = (x, y)
if self.image.draw_tool == 'brush':
self.item_temp = goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color=self.color,
fill_color=self.color,
line_width=self.line_width)
self.item_data = 'M %s,%s L ' % (x, y)
elif self.image.draw_tool == 'oval':
self.item_data = True
if self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % (x, y)
def motion_notify_event(self, widget, event):
x = event.x
y = event.y
state = event.state
if self.item_temp is not None:
self.item_temp.remove()
if self.item_data is not None:
if self.image.draw_tool == 'brush':
self.item_data = self.item_data + '%s,%s ' % (x, y)
self.item_temp = goocanvas.Path(parent=self.root,
data=self.item_data, line_width=self.line_width,
stroke_color=self.color)
elif self.image.draw_tool == 'oval':
self.item_temp = goocanvas.Ellipse(parent=self.root,
center_x=self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2,
center_y=self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2,
radius_x=abs(x - self.item_temp_coords[0]) / 2,
radius_y=abs(y - self.item_temp_coords[1]) / 2,
stroke_color=self.color,
line_width=self.line_width)
elif self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % self.item_temp_coords
self.item_data = self.item_data + ' %s,%s' % (x, y)
self.item_temp = goocanvas.Path(parent=self.root,
data=self.item_data, line_width=self.line_width,
stroke_color=self.color)
def button_release_event(self, widget, event):
x = event.x
y = event.y
state = event.state
if self.image.draw_tool == 'brush':
self.item_data = self.item_data + '%s,%s' % (x, y)
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color=self.color,
fill_color=self.color,
line_width=self.line_width)
self.image.add_path(self.item_data, self.line_width, self.color)
if self.image.draw_tool == 'oval':
cx = self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2
cy = self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2
rx = abs(x - self.item_temp_coords[0]) / 2
ry = abs(y - self.item_temp_coords[1]) / 2
self.image.add_ellipse(cx, cy, rx, ry, self.line_width, self.color)
if self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % self.item_temp_coords
self.item_data = self.item_data + ' %s,%s' % (x, y)
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color='black',
fill_color='black',
line_width=self.line_width)
self.image.add_path(self.item_data, self.line_width, self.color)
if self.image.draw_tool == 'delete':
pass
self.item_data = None
if self.item_temp is not None:
self.item_temp.remove()
self.item_temp = None
def recieve_element(self, element):
node = self.image.g.addChild(name=element.getAttr('name'))
self.image.g.addChild(node=node)
self.recieving[element.getAttr('rid')] = {'type':'element',
'data':[node],
'children':[]}
def recieve_attr(self, element):
node = self.recieving[element.getAttr('parent')]['data'][0]
node.setAttr(element.getAttr('name'), element.getAttr('chdata'))
self.recieving[element.getAttr('rid')] = {'type':'attr',
'data':element.getAttr('name'),
'parent':node}
self.recieving[element.getAttr('parent')]['children'].append(element.getAttr('rid'))
def apply_new(self):
for x in self.recieving.keys():
if self.recieving[x]['type'] == 'element':
self.image.add_recieved(x, self.recieving)
self.recieving = {}
class SvgChooserDialog(FileChooserDialog):
def __init__(self, on_response_ok=None, on_response_cancel=None):
'''
Choose in which SVG file to store the image
'''
def on_ok(widget, callback):
'''
check if file exists and call callback
'''
path_to_file = self.get_filename()
path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
(path_to_file,))[0]
widget.destroy()
callback(path_to_file)
FileChooserDialog.__init__(self,
title_text=_('Save Image as...'),
action=gtk.FILE_CHOOSER_ACTION_SAVE,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE,
gtk.RESPONSE_OK),
current_folder='',
default_response=gtk.RESPONSE_OK,
on_response_ok=(on_ok, on_response_ok),
on_response_cancel=on_response_cancel)
filter_ = gtk.FileFilter()
filter_.set_name(_('All files'))
filter_.add_pattern('*')
self.add_filter(filter_)
filter_ = gtk.FileFilter()
filter_.set_name(_('SVG Files'))
filter_.add_pattern('*.svg')
self.add_filter(filter_)
self.set_filter(filter_)
class SVGObject():
''' A class to store the svg document and make changes to it.'''
def __init__(self, root, session, height=300, width=300):
# Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance
self.items = {}
self.root = root
self.draw_tool = 'brush'
# sxe session
self.session = session
# initialize svg document
self.svg = Node(node='<svg/>')
self.svg.setAttr('version', '1.1')
self.svg.setAttr('height', str(height))
self.svg.setAttr('width', str(width))
self.svg.setAttr('xmlns', 'http://www.w3.org/2000/svg')
# TODO: make this settable
self.g = self.svg.addChild(name='g')
self.g.setAttr('fill', 'none')
self.g.setAttr('stroke-linecap', 'round')
def add_path(self, data, line_width, color):
''' adds the path to the items listing, both minidom node and goocanvas
object in a tuple '''
goocanvas_obj = goocanvas.Path(parent=self.root, data=data,
line_width=line_width, stroke_color=color)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
node = self.g.addChild(name='path')
node.setAttr('d', data)
node.setAttr('stroke-width', str(line_width))
node.setAttr('stroke', color)
self.g.addChild(node=node)
rids = self.session.generate_rids(4)
self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]}
self.items[rids[1]] = {'type':'attr', 'data':'d', 'parent':node}
self.items[rids[2]] = {'type':'attr', 'data':'stroke-width', 'parent':node}
self.items[rids[3]] = {'type':'attr', 'data':'stroke', 'parent':node}
self.session.send_items(self.items, rids)
def add_recieved(self, parent_rid, new_items):
''' adds the path to the items listing, both minidom node and goocanvas
object in a tuple '''
node = new_items[parent_rid]['data'][0]
self.items[parent_rid] = new_items[parent_rid]
for x in new_items[parent_rid]['children']:
self.items[x] = new_items[x]
if node.getName() == 'path':
goocanvas_obj = goocanvas.Path(parent=self.root,
data=node.getAttr('d'),
line_width=int(node.getAttr('stroke-width')),
stroke_color=node.getAttr('stroke'))
if node.getName() == 'ellipse':
goocanvas_obj = goocanvas.Ellipse(parent=self.root,
center_x=float(node.getAttr('cx')),
center_y=float(node.getAttr('cy')),
radius_x=float(node.getAttr('rx')),
radius_y=float(node.getAttr('ry')),
stroke_color=node.getAttr('stroke'),
line_width=float(node.getAttr('stroke-width')))
self.items[parent_rid]['data'].append(goocanvas_obj)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
def add_ellipse(self, cx, cy, rx, ry, line_width, stroke_color):
''' adds the ellipse to the items listing, both minidom node and goocanvas
object in a tuple '''
goocanvas_obj = goocanvas.Ellipse(parent=self.root,
center_x=cx,
center_y=cy,
radius_x=rx,
radius_y=ry,
stroke_color=stroke_color,
line_width=line_width)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
node = self.g.addChild(name='ellipse')
node.setAttr('cx', str(cx))
node.setAttr('cy', str(cy))
node.setAttr('rx', str(rx))
node.setAttr('ry', str(ry))
node.setAttr('stroke-width', str(line_width))
node.setAttr('stroke', stroke_color)
self.g.addChild(node=node)
rids = self.session.generate_rids(7)
self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]}
self.items[rids[1]] = {'type':'attr', 'data':'cx', 'parent':node}
self.items[rids[2]] = {'type':'attr', 'data':'cy', 'parent':node}
self.items[rids[3]] = {'type':'attr', 'data':'rx', 'parent':node}
self.items[rids[4]] = {'type':'attr', 'data':'ry', 'parent':node}
self.items[rids[5]] = {'type':'attr', 'data':'stroke-width', 'parent':node}
self.items[rids[6]] = {'type':'attr', 'data':'stroke', 'parent':node}
self.session.send_items(self.items, rids)
def del_item(self, item):
rids = []
for x in self.items.keys():
if self.items[x]['type'] == 'element':
if self.items[x]['data'][1] == item:
for y in self.items[x]['children']:
rids.append(y)
self.del_rid(y)
rids.append(x)
self.del_rid(x)
break
self.session.del_item(rids)
def clear_canvas(self):
for x in self.items.keys():
if self.items[x]['type'] == 'element':
self.del_rid(x)
def del_rid(self, rid):
if self.items[rid]['type'] == 'element':
self.items[rid]['data'][1].remove()
del self.items[rid]
def export_svg(self, filename):
f = open(filename, 'w')
f.writelines(str(self.svg))
f.close()
def item_button_press_events(self, item, target_item, event):
self.del_item(item)

View File

@@ -1,192 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkHBox" id="whiteboard_hbox">
<property name="visible">True</property>
<property name="border_width">3</property>
<property name="spacing">6</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkVBox" id="vbuttonbox1">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkToggleButton" id="brush_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Brush Tool: Draw freehand lines</property>
<signal name="toggled" handler="on_brush_button_toggled"/>
<child>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="stock">brush_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="oval_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Oval Tool: Draw circles and ellipses</property>
<signal name="toggled" handler="on_oval_button_toggled"/>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="stock">oval_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="line_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Line Tool: Draw straight lines</property>
<signal name="toggled" handler="on_line_button_toggled"/>
<child>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="stock">line_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="delete_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Delete Tool: Remove individual figures</property>
<signal name="toggled" handler="on_delete_button_toggled"/>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Clear Canvas: Cleanup canvas</property>
<signal name="clicked" handler="on_clear_button_clicked"/>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="stock">gtk-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="export_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Export Image: Save image to svg file</property>
<signal name="clicked" handler="on_export_button_clicked"/>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="stock">gtk-save-as</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkVScale" id="size_scale">
<property name="height_request">68</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Line width</property>
<property name="orientation">vertical</property>
<property name="adjustment">adjustment1</property>
<property name="inverted">True</property>
<property name="digits">0</property>
<property name="value_pos">bottom</property>
<signal name="value_changed" handler="on_size_scale_format_value"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkColorButton" id="fg_color_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Foreground color</property>
<property name="color">#000000000000</property>
<signal name="color_set" handler="on_fg_color_button_color_set"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">7</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</object>
<object class="GtkAdjustment" id="adjustment1">
<property name="value">2</property>
<property name="lower">1</property>
<property name="upper">110</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
<property name="page_size">10</property>
</object>
</interface>