diff --git a/lib/ext/wocky b/lib/ext/wocky
index 97faaaf..352247c 160000
--- a/lib/ext/wocky
+++ b/lib/ext/wocky
@@ -1 +1 @@
-Subproject commit 97faaafa088f496815ca7ca833a52da2ab8143ee
+Subproject commit 352247c52b89b5a58c02f8675a95ea64cc3724d2
diff --git a/plugins/telepathy-gabble-xmpp-console b/plugins/telepathy-gabble-xmpp-console
index cc25a9d..b8bffed 100755
--- a/plugins/telepathy-gabble-xmpp-console
+++ b/plugins/telepathy-gabble-xmpp-console
@@ -3,10 +3,10 @@
"""
The world's worst XMPP console user interface.
-Pass it a Gabble account name; type some words; get minimalistic
+Pass it the bus name of a Gabble connection; type some words; get minimalistic
error reporting.
-Copyright © 2011–2013 Collabora Ltd.
+Copyright © 2011 Collabora Ltd.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -22,17 +22,49 @@ You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
+from __future__ import print_function
+import collections
+import os
import sys
+import re
from xml.dom import minidom
+from xml.etree import ElementTree
-from gi.repository import Gtk, GLib, Gio, GtkSource
-from gi.repository import TelepathyGLib as Tp
+import gi
+gi.require_version("Gtk", "3.0")
+gi.require_version("GtkSource", "3.0")
+
+from gi.repository import Gtk
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GtkSource
+from gi.repository import GObject
PADDING = 6
+from pprint import pprint,pformat
+
+def pathify(name):
+ return '/' + name.replace('.', '/')
+
+def nameify(path):
+ return (path[1:]).replace('/', '.')
+
+CONN_FUTURE_IFACE = "org.freedesktop.Telepathy.Connection.FUTURE"
CONSOLE_IFACE = "org.freedesktop.Telepathy.Gabble.Plugin.Console"
+DISCO_INFO_NS = 'http://jabber.org/protocol/disco#info'
+DISCO_ITEMS_NS = 'http://jabber.org/protocol/disco#items'
+COMMANDS_NS = 'http://jabber.org/protocol/commands'
+JABBER_X_DATA_NS = 'jabber:x:data'
+MUC_NS = 'http://jabber.org/protocol/muc'
+
+def NS(namespace, term):
+ """Format namespace+terms to match element tree tags
+ """
+ return '{' + namespace + '}' + term
+
class StanzaViewer(Gtk.ScrolledWindow):
def __init__(self):
Gtk.ScrolledWindow.__init__(self)
@@ -89,6 +121,7 @@ class SpinWrapper(Gtk.Notebook):
self.spinner.stop()
self.set_current_page(self.PRIMARY_PAGE)
+
class Page(Gtk.Grid):
def __init__(self, console_proxy):
Gtk.Grid.__init__(self)
@@ -287,38 +320,700 @@ class SnoopyPage(Page):
self.stanza_viewer.append_comment('sent' if outgoing else 'received')
self.stanza_viewer.append_stanza(xml)
+class AdHocPage(Page):
+ __gsignals__ = {
+ 'receive-iq': (GObject.SignalFlags.RUN_FIRST, None, (object, object, object))
+ }
+
+ def __init__(self, console_proxy):
+ Page.__init__(self, console_proxy)
+
+ self._jid_heading = {}
+ self.session_id = None
+ self.form = JabberXForm()
+ #self.note = CommandNote()
+ self.node = None
+ self.status = None
+ self.sessionid = None
+ self.commands_model = Gtk.TreeStore(str, str)
+ self.commands_model.append(None, ["", ""])
+
+ # Title
+ request_label = self.add_title("Ad-Hoc Commands")
+
+ # Jid to Query
+ recipient_label, recipient_entry = self.add_label_entry_pair(
+ 'To:', below=request_label)
+ recipient_entry.set_icon_from_stock(
+ Gtk.EntryIconPosition.SECONDARY, Gtk.STOCK_GO_FORWARD)
+ recipient_entry.set_icon_tooltip_text(
+ Gtk.EntryIconPosition.SECONDARY, "Request Ad-Hoc Commands")
+ recipient_entry.set_text('ghic.org')
+ self.recipient_label = recipient_label
+ self.recipient_entry = recipient_entry
+ #recipient_entry.connect('activate', self.request_commands)
+ #recipient_entry.connect('icon-release', self.request_commands)
+ recipient_entry.connect('activate', self.start_browsing)
+ recipient_entry.connect('icon-release', self.start_browsing)
+
+ # List commands
+ self.commands_list = Gtk.TreeView(self.commands_model)
+ self.commands_list.set_property('activate-on-single-click', False)
+ self.commands_list.connect('row-activated', self.send_command)
+ renderer = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn("Command", renderer, text=0)
+ self.commands_list.append_column(column)
+ self.commands_stack = Gtk.Stack()
+ self.commands_stack.add_named(self.commands_list, 'browser')
+ self.commands_stack.add_named(self.form, 'form')
+ #self.commands_stack.add_named(self.notes, 'note')
+
+ self.commands_window = Gtk.ScrolledWindow()
+ self.commands_window.set_property('expand', True)
+ self.commands_window.add(self.commands_stack)
+# self.attach_next_to(self.commands_window, recipient_label, Gtk.PositionType.BOTTOM, 2, 1)
+
+ self.result_nb = SpinWrapper(self.commands_window)
+
+ self.attach_next_to(self.result_nb, self.recipient_label, Gtk.PositionType.BOTTOM, 2, 1)
+
+ box = Gtk.HBox()
+ self.back = Gtk.Button.new_from_stock(Gtk.STOCK_GO_BACK)
+ self.back.set_sensitive(False)
+ self.cancel = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL)
+ self.forward = Gtk.Button.new_from_stock(Gtk.STOCK_GO_FORWARD)
+ #self.forward.set_sensitive(False)
+ box.pack_start(self.back, True, True, 0)
+ box.pack_start(self.cancel, True, True, 0)
+ box.pack_start(self.forward, True, True, 0)
+ self.attach_next_to(box, self.result_nb, Gtk.PositionType.BOTTOM, 2, 1)
+
+ self.cancel.connect('clicked', self.cancel_command)
+ self.forward.connect('clicked', self.forward_clicked)
+
+ self.start_browsing()
+ #self.request_commands()
+
+ def add_label_entry_pair(self, title, below):
+ label = self.add_label(title, below)
+
+ entry = Gtk.Entry()
+ entry.set_property('margin-right', PADDING)
+ entry.set_property('hexpand', True)
+
+ self.attach_next_to(entry, label, Gtk.PositionType.RIGHT, 1, 1)
+
+ return label, entry
+
+ def start_browsing(self, *misc):
+ self.commands_stack.set_visible_child_name('browser')
+ self.commands_model.clear()
+ to = self.recipient_entry.get_text()
+ print('hi', to)
+ self._jid_heading[to] = self.commands_model.append(None, [to, None])
+ self.start_disco_info()
+
+ def forward_clicked(self, button):
+ visible = self.commands_stack.get_visible_child_name()
+ print('visible', visible)
+ if visible == 'browser':
+ selection = self.commands_list.get_selection()
+ if selection:
+ model, selections = selection.get_selected_rows()
+ print (selections)
+ if len(selections) == 1:
+ self.send_command(self.commands_list, selections[0], None)
+ else:
+ print("Can't select multiple commands")
+ elif visible == 'form':
+ self.send_form()
+
+ def start_disco_info(self, to=None):
+ if to is None:
+ to = self.recipient_entry.get_text()
+ body = "".format(DISCO_INFO_NS)
+ self.console_proxy.SendIQ('(sss)', 'get', to, body,
+ result_handler=self.disco_info_cb,
+ user_data=to)
+ self.result_nb.start_spinning()
+
+ def disco_info_cb(self, proxy, result, user_data):
+ to = user_data
+ print('disco_info_cb', to)
+ self.emit('receive-iq', proxy, result, user_data)
+ if isinstance(result, Exception):
+ return
+
+ iq = self.parse_iq(result)
+ for child in iq.getchildren():
+ if child.tag == NS(DISCO_INFO_NS, 'query'):
+ for item in child.getchildren():
+ if item.tag == NS(DISCO_INFO_NS, 'feature'):
+ var = item.attrib['var']
+ if var == COMMANDS_NS:
+ self.request_commands(to=to)
+ if var == DISCO_ITEMS_NS:
+ self.start_disco_items(to=to)
+ if item.tag == NS(DISCO_INFO_NS, 'identity'):
+ print(item.attrib)
+
+ self.result_nb.stop_spinning()
+
+ def start_disco_items(self, to=None):
+ print('start_disco_items', to)
+ if to is None:
+ to = self.recipient_entry.get_text()
+ body = "".format(DISCO_ITEMS_NS)
+ self.console_proxy.SendIQ('(sss)', 'get', to, body,
+ result_handler=self.disco_items_cb,
+ user_data=to)
+
+ def disco_items_cb(self, proxy, results, user_data):
+ to = user_data
+ print('disco_items_cb', to)
+ iq = self.parse_iq(results)
+ for query in iq.getchildren():
+ if query.tag == NS(DISCO_ITEMS_NS, 'query'):
+ for child in query.getchildren():
+ if child.tag == NS(DISCO_ITEMS_NS, 'item'):
+ jid = child.attrib['jid']
+ #self.start_disco_info(to=jid)
+ i = self.commands_model.append(None, [jid, None])
+ self._jid_heading[jid] = i
+
+ def request_commands(self, to=None, *misc):
+ print('request')
+ body = "".format(DISCO_ITEMS_NS, COMMANDS_NS)
+ print(body)
+ if to is None:
+ to = self.recipient_entry.get_text()
+
+ self.console_proxy.SendIQ('(sss)', 'get', to, body,
+ result_handler=self.receive_commands_cb,
+ user_data=to)
+ #self.result_nb.start_spinning()
+
+ def receive_commands_cb(self, proxy, result, user_data):
+ to = user_data
+ print('recieve commands', to)
+ self.emit('receive-iq', proxy, result, user_data)
+ if isinstance(result, Exception):
+ return
+
+ reply_type, reply = result
+ iq = ElementTree.fromstring(reply)
+ print(reply)
+ query = iq.getchildren()
+ if (query[0].tag == NS(DISCO_ITEMS_NS, 'query') and
+ query[0].attrib['node'] == COMMANDS_NS):
+ # we have command list?
+ for item in query[0].getchildren():
+ if item.tag == NS(DISCO_ITEMS_NS, 'item'):
+ name = item.attrib['name']
+ command = item.attrib['node']
+ print(name, command)
+ i = self._jid_heading[to]
+ self.commands_model.append(i, [name, command])
+
+ self.emit('receive-iq', proxy, result, user_data)
+ self.result_nb.stop_spinning()
+
+ def send_command(self, tree_view, path, column):
+ row_iter = self.commands_model.get_iter(path)
+ self.node = self.commands_model.get_value(row_iter, 1)
+ body = "".format(
+ COMMANDS_NS,
+ self.node)
+ to = self.recipient_entry.get_text()
+ self.console_proxy.SendIQ('(sss)', 'set', to, body,
+ result_handler=self.sent_command_cb)
+
+ def sent_command_cb(self, proxy, result, user_data):
+ if not isinstance(result, Exception):
+ reply_type, reply = result
+ self.parse_command_reply(reply)
+
+ self.emit('receive-iq', proxy, result, user_data)
+
+ def send_form(self):
+ if self.form:
+ to = self.recipient_entry.get_text()
+ x = self.form.get_submit_form()
+ command = ElementTree.Element('command')
+ command.attrib['sessionid'] = self.sessionid
+ command.attrib['node'] = self.node
+ command.attrib['xmlns'] = COMMANDS_NS
+ command.append(x)
+ body = ElementTree.tostring(command)
+ self.console_proxy.SendIQ('(sss)', 'set', to, body,
+ result_handler=self.send_form_cb)
+
+ def send_form_cb(self, proxy, result, user_data):
+ if not isinstance(result, Exception):
+ reply_type, reply = result
+ self.parse_command_reply(reply)
+
+ self.emit('receive-iq', proxy, result, user_data)
+
+ def cancel_command(self, button):
+ if self.sessionid is None:
+ return
+
+ body = "".format(
+ COMMANDS_NS,
+ self.sessionid)
+ to = self.recipient_entry.get_text()
+ self.console_proxy.SendIQ('(sss)', 'set', to, body,
+ result_handler=self.cancel_command_cb)
+
+ def cancel_command_cb(self, proxy, result, user_data):
+ if not isinstance(result, Exception):
+ reply_type, reply = result
+
+ iq = ElementTree.fromstring(reply)
+ commands = iq.getchildren()
+
+ self.sessionid = None
+ self.node = None
+ self.commands_stack.set_visible_child_name('browser')
+
+ self.emit('receive-iq', proxy, result, user_data)
+
+ def parse_command_reply(self, stanza):
+ iq = ElementTree.fromstring(stanza)
+ query = iq.getchildren()
+ notes = []
+ if query[0].tag == NS(COMMANDS_NS, 'command'):
+ self.sessionid = query[0].attrib['sessionid']
+ self.status = query[0].attrib.get('status')
+ for child in query[0].getchildren():
+ if child.tag == NS(JABBER_X_DATA_NS, 'x'):
+ # data
+ self.form.clear()
+ self.form.parse_form(child)
+ self.form.show_all()
+ self.commands_stack.set_visible_child_name('form')
+
+ elif child.tag == '{http://jabber.org/protocol/commands}actions':
+ # next steps
+ self.parse_actions(child)
+ elif child.tag == '{http://jabber.org/protocol/commands}note':
+ print('note', child.text)
+
+ def parse_actions(self, actions):
+ for allowed_action in actions.getchildren():
+ if allowed_action.tag == '{http://jabber.org/protocol/commands}prev':
+ self.back.set_sensitive(True)
+ elif allowed_action.tag == '{http://jabber.org/protocol/commands}next':
+ self.forward.set_sensitive(True)
+ elif allowed_action.tag == '{http://jabber.org/protocol/commands}complete':
+ self.forward.set_sensitive(True)
+
+ def parse_iq(self, result):
+ reply_type, reply = result
+ return ElementTree.fromstring(reply)
+
+class JabberXForm(Gtk.ListBox):
+ def __init__(self, form=None):
+ super(JabberXForm, self).__init__()
+
+ self.title = None
+ self.reported = None
+ self.notes = []
+ self.form_xml= form
+ self.fields = {}
+
+ if form is not None:
+ self.parse_form(form)
+
+ def clear(self):
+ self.foreach(self.remove)
+ self.fields = {}
+
+ def parse_form(self, form):
+ self.form_xml = form
+ for child in form.getchildren():
+ if child.tag == '{jabber:x:data}instructions':
+ print('instructions', child.text)
+ elif child.tag == '{jabber:x:data}title':
+ print('title', child.text)
+ self.title = self.add_title(child.text)
+ elif child.tag == '{jabber:x:data}field':
+ self.parse_form_field(child)
+ elif child.tag == '{jabber:x:data}reported':
+ # column heading
+ print('reported', child.text)
+ self.reported = self.append_title(child.text)
+ elif child.tag == '{jabber:x:data}item':
+ self.parse_form_item(child)
+ else:
+ print("Unrecognized tag %s" % (child.tag,))
+
+ def parse_form_item(self, item):
+ for field in item.getchildren():
+ self.parse_form_field(field)
+
+ def parse_form_field(self, field):
+ if field.tag != '{jabber:x:data}field':
+ raise ValueError("Unrecognized tag %s, expected field" % (field.tag,))
+
+ data_field = DataFormField(field)
+ print(data_field._repr_pretty_())
+
+ self.fields[data_field.var] = data_field
+ type_to_widgets = {
+ 'boolean': XFormBoolean,
+ 'fixed': XFormFixed,
+ 'hidden': None,
+ 'jid-multi': XFormJidMulti,
+ 'jid-single': XFormJidSingle,
+ 'list-multi': XFormListMulti,
+ 'list-single': XFormListSingle,
+ 'text-multi': XFormTextMulti,
+ 'text-private': XFormTextPrivate,
+ 'text-single': XFormTextSingle,
+ }
+ print(data_field.type)
+ widget = type_to_widgets.get(data_field.type, XFormTextSingle)
+ if widget is not None:
+ print(widget)
+ self.add(widget(data_field))
+
+ def add_title(self, title):
+ label = Gtk.Label()
+ label.set_markup("%s" % title)
+ label.set_property('xalign', 0)
+ self.add(label)
+ return label
+
+ def get_submit_form(self):
+ self.form_xml.attrib['type'] = 'submit'
+ return self.form_xml
+
+class XFormBoolean(Gtk.HBox):
+ def __init__(self, data_field):
+ super(XFormBoolean, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.switch = Gtk.Switch()
+ self.value = self.data_field.value
+ # force setting default value
+ #self.state_set(self.switch, self.value)
+
+ self.switch.connect('state-set', self.state_set)
+ self.pack_start(self.label, True, True, 0)
+ self.pack_start(self.switch, True, True, 0)
+
+ @property
+ def value(self):
+ return self.switch.get_state()
+
+ @value.setter
+ def value(self, value):
+ self.switch.set_state(value)
+
+ def state_set(self, switch, value):
+ self.data_field.value = '1' if value else '0'
+
+class XFormFixed(Gtk.Label):
+ def __init__(self, data_field):
+ super(XFormFixed, self).__init__()
+ self.data_field = data_field
+
+ self.set_markup("%s" % self.data_field.label)
+ self.set_property('xalign', 0)
+
+class XFormJidMulti(Gtk.VBox):
+ def __init__(self, data_field):
+ super(XFormJidMulti, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.text = Gtk.TextView()
+ self.value = self.data_field.value
+
+ self.text.connect('insert-at-cursor', self.insert_at_cursor)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.text, True, True, 0)
+
+ @property
+ def value(self):
+ start, end = self.text.get_buffer().get_bounds()
+ data = self.text.get_buffer().get_text(start, end, False)
+ return data.split('\n')
+
+ @value.setter
+ def value(self, value):
+ if value != self.value:
+ self.text.get_buffer().set_text('\n'.join(value))
+ self.data_field.value = value
+
+ def insert_at_cursor(self, entry, string):
+ self.data_field.value = self.value
+
+
+class XFormJidSingle(Gtk.HBox):
+ def __init__(self, data_field):
+ super(XFormJidSingle, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.entry = Gtk.Entry()
+ self.entry.set_property('margin-right', PADDING)
+ self.entry.set_property('hexpand', True)
+
+ self.entry.connect('changed', self.changed)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.entry, True, True, 0)
+
+ @property
+ def value(self):
+ return self.entry.get_buffer().get_text()
+
+ @value.setter
+ def value(self, value):
+ if value != self.value:
+ self.entry.get_buffer().set_text(value)
+ self.data_field.value = value
+
+ def changed(self, entry):
+ self.data_field.value = self.value
+
+class XFormListMulti(Gtk.VBox):
+ def __init__(self, data_field):
+ super(XFormListSingle, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.model = Gtk.ListStore((str, str))
+ self.entry = Gtk.TreeView(self.model)
+
+ for o in self.data_field.options:
+ # FIXME: would be a great use for a named tuple
+ self.model.append(o[0], o[1])
+
+ if self.data_field.value:
+ self.value = self.data_field.value
+
+ self.entry.connect('changed', self.changed)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.entry, True, True, 0)
+
+ @property
+ def value(self):
+ return self.entry.get_active_id()
+
+ @value.setter
+ def value(self, value):
+ self.entry.set_active_id(value)
+
+ def changed(self, entry):
+ self.data_field.value = entry.get_active_id()
+
+class XFormListSingle(Gtk.HBox):
+ def __init__(self, data_field):
+ super(XFormListSingle, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.entry = Gtk.ComboBoxText()
+
+ for o in self.data_field.options:
+ # FIXME: would be a great use for a named tuple
+ self.entry.append(o[0], o[1])
+ if self.data_field.value:
+ self.value = self.data_field.value
+
+ self.entry.connect('changed', self.changed)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.entry, True, True, 0)
+
+ @property
+ def value(self):
+ return self.entry.get_active_id()
+
+ @value.setter
+ def value(self, value):
+ self.entry.set_active_id(value)
+
+ def changed(self, entry):
+ self.data_field.value = entry.get_active_id()
+
+class XFormTextMulti(Gtk.VBox):
+ def __init__(self, data_field):
+ super(XFormTextMulti, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.text = Gtk.TextView()
+ self.value = self.data_field.value
+
+ self.text.connect('insert-at-cursor', self.insert_at_cursor)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.text, True, True, 0)
+
+ @property
+ def value(self):
+ start, end = self.text.get_buffer().get_bounds()
+ data = self.text.get_buffer().get_text(start, end, False)
+ return data.split('\n')
+
+ @value.setter
+ def value(self, value):
+ if value != self.value:
+ self.text.get_buffer().set_text('\n'.join(value))
+ self.data_field.value = value
+
+ def insert_at_cursor(self, entry, string):
+ self.data_field.value = self.value
+
+class XFormTextSingle(Gtk.HBox):
+ def __init__(self, data_field):
+ super(XFormTextSingle, self).__init__()
+ self.data_field = data_field
+ self.label = Gtk.Label(self.data_field.label)
+ self.entry = Gtk.Entry()
+ self.entry.set_property('margin-right', PADDING)
+ self.entry.set_property('hexpand', True)
+
+ self.entry.connect('changed', self.changed)
+ self.pack_start(self.label, False, False, 0)
+ self.pack_start(self.entry, True, True, 0)
+
+ @property
+ def value(self):
+ return self.entry.get_buffer().get_text()
+
+ @value.setter
+ def value(self, value):
+ if value != self.value:
+ self.entry.get_buffer().set_text(value)
+ self.data_field.value = value
+
+ def changed(self, entry):
+ self.data_field.value = self.value
+
+class XFormTextPrivate(XFormTextSingle):
+ def __init__(self, data_field):
+ super(XFormTextPrivate, self).__init__()
+ self.entry.set_input_purpose(Gtk.InputPurpose.PASSWORD)
+
+class DataFormField(collections.Mapping):
+ PUBLIC_FIELDS = ['type', 'var', 'label', 'description', 'required', 'default_value', 'value', 'options']
+ def __init__(self, node):
+ if node.tag != '{jabber:x:data}field':
+ raise ValueError("Unrecognized tag %s, expected field" % (field.tag,))
+
+ self.type = node.attrib.get('type')
+ self.var = node.attrib.get('var')
+ self.label = node.attrib.get('label')
+ self.raw_value_contents = node
+ self.description = None
+ self.required = False
+ self.default_value = []
+ self.options = []
+ for child in node.getchildren():
+ if child.tag == '{jabber:x:data}desc':
+ self.description = node.text
+ elif child.tag == '{jabber:x:data}required':
+ self.required = True
+ elif child.tag == '{jabber:x:data}value':
+ if self.type in ('list-multi', 'jid-multi', 'text-multi'):
+ self.default_value.append(child.text)
+ else:
+ self.default_value = node.text
+ elif child.tag == '{jabber:x:data}option':
+ option_label = child.attrib.get('label')
+ option_value = child.getchildren()[0].text
+ self.options.append((option_value, option_label))
+ self._value = self.default_value
+
+ def __iter__(self):
+ for k in DataFormField.PUBLIC_FIELDS:
+ yield k
+
+ def __getitem__(self, key):
+ if key in DataFormField.PUBLIC_FIELDS:
+ return getattr(self, key)
+ else:
+ raise KeyError("Invalid key")
+
+ def __len__(self):
+ return len(DataFormField.PUBLIC_FIELDS)
+
+ @property
+ def value(self):
+ return self._value
+
+ @value.setter
+ def value(self, value):
+ self._value = value
+
+ node = self.raw_value_contents.find('value')
+ if node is None:
+ node = ElementTree.Element('value')
+ node.attrib['xmlns'] = 'jabber:x:data'
+ self.raw_value_contents.append(node)
+
+ node.text = self._value
+ print('set {var}: {value}'.format(**self))
+
+ def _repr_pretty_(self):
+ for key in self:
+ print(key, self[key])
+ r = ['Field {type}: {var}: {value}'.format(**self)]
+ if self.label:
+ r.append('label: {label}'.format(**self))
+ if self.description:
+ r.append('description: {description}'.format(**self))
+ if self.options:
+ r.append(', '.join(('{}: {}'.format(x[0], x[1]) for x in self.options)))
+ return os.linesep.join(r)
+
+ def _repr_etree_(self):
+ return self.raw_value_contents
+
+ def _repr_xml_(self):
+ return ElementTree.tostring(self._repr_etree_)
+
class Window(Gtk.Window):
IQ_PAGE = 0
STANZA_PAGE = 1
SNOOPY_PAGE = 2
+ AD_HOC_PAGE = 3
- def __init__(self, account):
+ def __init__(self, bus, connection_bus_name):
Gtk.Window.__init__(self)
self.set_title('XMPP Console')
self.set_default_size(600, 371)
- request = Tp.AccountChannelRequest.new(
- account,
- { Tp.PROP_CHANNEL_CHANNEL_TYPE: CONSOLE_IFACE },
- 0)
- request.create_and_handle_channel_async(None, self.__create_cb, None)
+ conn_future_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ connection_bus_name, pathify(connection_bus_name),
+ CONN_FUTURE_IFACE, None)
+ try:
+ sidecar_path, _ = conn_future_proxy.EnsureSidecar('(s)', CONSOLE_IFACE)
+ except Exception as e:
+ print("""
+Couldn't connect to the XMPP console interface on '%(connection_bus_name)s':
+ %(e)s
+Check that it's a running Jabber connection, and that you have the console
+plugin installed.""" % locals())
- self.connect('destroy', Window.__destroy_cb)
+ raise SystemExit(2)
- def __build_ui(self):
- # Build up the UI
- self.grid = Gtk.Grid()
- self.add(self.grid)
+ self.console_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ connection_bus_name, sidecar_path, CONSOLE_IFACE, None)
+ # Build up the UI
self.nb = Gtk.Notebook()
- self.grid.attach(self.nb, 0, 0, 1, 1)
+ self.add(self.nb)
self.iq = IQPage(self.console_proxy)
self.nb.insert_page(self.iq,
Gtk.Label.new_with_mnemonic("_IQ console"),
self.IQ_PAGE)
+ self.adhoc = AdHocPage(self.console_proxy)
+ self.nb.insert_page(self.adhoc,
+ Gtk.Label.new_with_mnemonic("_AdHoc console"),
+ self.AD_HOC_PAGE)
+
self.stanza = StanzaPage(self.console_proxy)
self.nb.insert_page(self.stanza,
Gtk.Label.new_with_mnemonic("Send a s_tanza"),
@@ -329,103 +1024,61 @@ class Window(Gtk.Window):
Gtk.Label.new_with_mnemonic("_Monitor network traffic"),
self.SNOOPY_PAGE)
- self.infobar = Gtk.InfoBar()
- self.infobar.set_message_type(Gtk.MessageType.WARNING)
- self.infobar.set_no_show_all(True)
- label = Gtk.Label("The connection went away! Time to leave.")
- label.show()
- self.infobar.get_content_area().add(label)
- self.infobar_close_button = self.infobar.add_button("Close", Gtk.ResponseType.CLOSE)
- self.infobar.connect('response', lambda infobar, response: Gtk.main_quit())
- self.infobar.connect('close', lambda infobar: Gtk.main_quit())
-
- self.grid.attach_next_to(self.infobar, self.nb,
- Gtk.PositionType.BOTTOM, 1, 1)
-
- def __create_cb(self, request, result, _):
- try:
- channel, context = request.create_and_handle_channel_finish(result)
- channel.connect('invalidated', self.__channel_invalidated_cb)
-
- bus_name = channel.get_bus_name()
- sidecar_path = channel.get_object_path()
-
- bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
- self.console_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
- bus_name, sidecar_path, CONSOLE_IFACE, None)
-
- except GLib.GError as e:
- print("""
-Couldn't connect to the XMPP console interface on '%(name)s':
-%(e)s
-Check that you have the console plugin installed.""" % {
- 'name': request.get_account().get_path_suffix(),
- 'e': e,
- })
- raise SystemExit(2)
-
- self.__build_ui()
- self.show_all()
-
- def __channel_invalidated_cb(self, channel, domain, code, message):
- self.infobar.show()
- self.infobar_close_button.grab_focus()
- self.nb.set_sensitive(False)
- # TODO: try to reconnect?
+ self.connect('destroy', Window.__destroy_cb)
+ self.adhoc.connect(
+ 'receive-iq',
+ lambda _, proxy, result, user_data:
+ IQPage.send_iq_cb(self.iq, proxy, result, user_data))
def __destroy_cb(self):
- try:
- self.snoopy.teardown()
- except GLib.GError as e:
- print("Couldn't turn off the monitor (maybe the connection went away?)")
- print(e)
+ self.snoopy.teardown()
Gtk.main_quit()
-def usage(am):
- xmpp_accounts = sorted(
- account.get_path_suffix()
- for account in am.dup_valid_accounts()
- if account.get_cm_name() == 'gabble')
+GABBLE_PREFIX = 'org.freedesktop.Telepathy.Connection.gabble.jabber.'
+AM_BUS_NAME = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_PREFIX = '/org/freedesktop/Telepathy/Account'
+ACCOUNT_IFACE = 'org.freedesktop.Telepathy.Account'
+
+def usage():
print("""
Usage:
%(arg0)s gabble/jabber/blahblah
+ %(arg0)s %(prefix)sblahblah
-Here are some account identifiers:
-
- %(accounts)s
+List account identifiers using `mc-tool list | grep gabble`.
+List connection bus names using `qdbus | grep gabble`.
""" % { 'arg0': sys.argv[0],
- 'accounts': '\n '.join(xmpp_accounts),
+ 'prefix': GABBLE_PREFIX,
})
raise SystemExit(1)
-def am_prepared_cb(am, result, account_suffix):
- try:
- am.prepare_finish(result)
- except GLib.GError as e:
- print(e)
- raise SystemExit(2)
-
- if account_suffix is None:
- usage(am)
-
- for account in am.dup_valid_accounts():
- if account.get_path_suffix() == account_suffix:
- if account.get_connection() is None:
- print("%s is not online." % account_suffix)
- raise SystemExit(2)
- else:
- win = Window(account)
- return
-
- usage(am)
-
if __name__ == '__main__':
- account_suffix = sys.argv[1] if len(sys.argv) == 2 else None
+ bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+
+ if len(sys.argv) != 2:
+ usage()
+
+ thing = sys.argv[1]
+
+ if re.match('^gabble/jabber/[a-zA-Z0-9_]+$', thing):
+ # Looks like an account path to me.
+ account_proxy = Gio.DBusProxy.new_sync(bus, 0, None,
+ AM_BUS_NAME, '%s/%s' % (ACCOUNT_PREFIX, thing),
+ ACCOUNT_IFACE, None)
+ path = account_proxy.get_cached_property('Connection').get_string()
+ if path == '/':
+ print("%s is not online" % thing)
+ raise SystemExit(1)
+ else:
+ thing = nameify(path)
+
+ if not re.match('^%s[a-zA-Z0-9_]+$' % GABBLE_PREFIX, thing):
+ usage()
- am = Tp.AccountManager.dup()
- am.prepare_async([], am_prepared_cb, account_suffix)
+ win = Window(bus, thing)
+ win.show_all()
Gtk.main()