Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Bethge <stefan@lanpartei.de>2006-10-11 06:12:56 +0400
committerStefan Bethge <stefan@lanpartei.de>2006-10-11 06:12:56 +0400
commit1dbb2a891f4a3368f04ed9b4206642bc8748abd8 (patch)
treeab453c797cd6803f18355d8658471683f12e516c
parentff8ba1f56e39803129aec45981faa56e8b5f9c70 (diff)
parent6ea11dc7ad65db6624efc99bd54e35e33938c913 (diff)
merge zeroconf branch to trunk
-rw-r--r--data/glade/accounts_window.glade36
-rw-r--r--data/glade/zeroconf_contact_context_menu.glade153
-rw-r--r--data/glade/zeroconf_context_menu.glade49
-rw-r--r--data/glade/zeroconf_information_window.glade666
-rw-r--r--data/glade/zeroconf_properties_window.glade689
-rw-r--r--src/common/config.py9
-rw-r--r--src/common/connection.py1
-rw-r--r--src/common/gajim.py6
-rw-r--r--src/common/xmpp/dispatcher_nb.py8
-rw-r--r--src/common/xmpp/session.py2
-rw-r--r--src/common/zeroconf/__init__.py0
-rw-r--r--src/common/zeroconf/client_zeroconf.py598
-rw-r--r--src/common/zeroconf/connection_handlers_zeroconf.py912
-rw-r--r--src/common/zeroconf/connection_zeroconf.py487
-rw-r--r--src/common/zeroconf/roster_zeroconf.py152
-rwxr-xr-xsrc/common/zeroconf/zeroconf.py373
-rw-r--r--src/config.py330
-rwxr-xr-xsrc/gajim.py28
-rw-r--r--src/roster_window.py297
-rw-r--r--src/tooltips.py35
-rw-r--r--src/vcard.py148
21 files changed, 4905 insertions, 74 deletions
diff --git a/data/glade/accounts_window.glade b/data/glade/accounts_window.glade
index 3ac6f9e7b..39b637bae 100644
--- a/data/glade/accounts_window.glade
+++ b/data/glade/accounts_window.glade
@@ -2,6 +2,7 @@
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
+
<widget class="GtkWindow" id="accounts_window">
<property name="border_width">12</property>
<property name="title" translatable="yes">Accounts</property>
@@ -64,7 +65,7 @@
<property name="visible">True</property>
<property name="tooltip" translatable="yes">If you have 2 or more accounts and it is checked, Gajim will list all contacts as if you had one account</property>
<property name="can_focus">True</property>
- <property name="label" translatable="yes">_Merge accounts</property>
+ <property name="label" translatable="yes">Mer_ge accounts</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
@@ -81,6 +82,38 @@
</child>
<child>
+ <widget class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="enable_zeroconf_checkbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">If checked, all local contacts that use a Bonjour compatible chat client (like iChat, Trillian or Gaim) will be shown in roster. You don't need to be connected to a jabber server for it to work.
+This is only available if python-avahi is installed and avahi-daemon is running.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Enable link-local messaging</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
<widget class="GtkHButtonBox" id="hbuttonbox15">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
@@ -267,4 +300,5 @@
</widget>
</child>
</widget>
+
</glade-interface>
diff --git a/data/glade/zeroconf_contact_context_menu.glade b/data/glade/zeroconf_contact_context_menu.glade
new file mode 100644
index 000000000..edbb0f064
--- /dev/null
+++ b/data/glade/zeroconf_contact_context_menu.glade
@@ -0,0 +1,153 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkMenu" id="zeroconf_contact_context_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="start_chat_menuitem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Start _Chat</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1534">
+ <property name="visible">True</property>
+ <property name="stock">gtk-jump-to</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="rename_menuitem">
+ <property name="label" translatable="yes">_Rename</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1535">
+ <property name="visible">True</property>
+ <property name="stock">gtk-refresh</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="edit_groups_menuitem">
+ <property name="label" translatable="yes">Edit _Groups</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="above_send_file_separator">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="send_file_menuitem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Send _File</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1536">
+ <property name="visible">True</property>
+ <property name="stock">gtk-file</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="assign_openpgp_key_menuitem">
+ <property name="label" translatable="yes">Assign Open_PGP Key</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_assign_openpgp_key_menuitem_activate" last_modification_time="Thu, 30 Jun 2005 22:57:59 GMT"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1537">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-authentication</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="add_special_notification_menuitem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Add Special _Notification</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1538">
+ <property name="visible">True</property>
+ <property name="stock">gtk-info</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="above_information_separator">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="information_menuitem">
+ <property name="label">gtk-info</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="history_menuitem">
+ <property name="label" translatable="yes">_History</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1539">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-fill</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/data/glade/zeroconf_context_menu.glade b/data/glade/zeroconf_context_menu.glade
new file mode 100644
index 000000000..f2928cf04
--- /dev/null
+++ b/data/glade/zeroconf_context_menu.glade
@@ -0,0 +1,49 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkMenu" id="zeroconf_context_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="status_menuitem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Status</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1258">
+ <property name="visible">True</property>
+ <property name="stock">gtk-network</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="zeroconf_properties_menuitem">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Modify Account...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image1259">
+ <property name="visible">True</property>
+ <property name="stock">gtk-preferences</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/data/glade/zeroconf_information_window.glade b/data/glade/zeroconf_information_window.glade
new file mode 100644
index 000000000..8cdf7e891
--- /dev/null
+++ b/data/glade/zeroconf_information_window.glade
@@ -0,0 +1,666 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="zeroconf_information_window">
+ <property name="border_width">12</property>
+ <property name="title">Contact Information</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <signal name="key_press_event" handler="on_zeroconf_information_window_key_press_event" last_modification_time="Fri, 29 Sep 2006 13:23:38 GMT"/>
+ <signal name="destroy" handler="on_zeroconf_information_window_destroy" last_modification_time="Fri, 29 Sep 2006 13:23:44 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="nickname_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkNotebook" id="information_notebook">
+ <property name="visible">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox3">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkTable" id="table7">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label51">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Local jid:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label53">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Resource:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Status:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="local_jid_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEventBox" id="resource_prio_label_eventbox">
+ <property name="visible">True</property>
+ <property name="visible_window">False</property>
+ <property name="above_child">False</property>
+
+ <child>
+ <widget class="GtkLabel" id="resource_prio_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ </child>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEventBox" id="status_label_eventbox">
+ <property name="visible">True</property>
+ <property name="visible_window">True</property>
+ <property name="above_child">False</property>
+
+ <child>
+ <widget class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ </child>
+ </widget>
+ <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="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="log_history_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Log conversation history</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkEventBox" id="PHOTO_eventbox">
+ <property name="visible">True</property>
+ <property name="visible_window">False</property>
+ <property name="above_child">False</property>
+ <signal name="button_press_event" handler="on_PHOTO_eventbox_button_press_event" last_modification_time="Fri, 08 Sep 2006 21:34:18 GMT"/>
+
+ <child>
+ <widget class="GtkImage" id="PHOTO_image">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Contact</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table8">
+ <property name="border_width">16</property>
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label55">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Jabber ID:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="jabber_id_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <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="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label56">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">E-Mail:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="email_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <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="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label58">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Last Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label59">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">First Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="first_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">expand</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="last_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <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="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label57">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Personal</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_close_button_clicked" last_modification_time="Mon, 25 Sep 2006 05:08:55 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/data/glade/zeroconf_properties_window.glade b/data/glade/zeroconf_properties_window.glade
new file mode 100644
index 000000000..c296fe4d5
--- /dev/null
+++ b/data/glade/zeroconf_properties_window.glade
@@ -0,0 +1,689 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="zeroconf_properties_window">
+ <property name="border_width">12</property>
+ <property name="title" translatable="yes">Modify Account</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <signal name="destroy" handler="on_zeroconf_properties_window_destroy" last_modification_time="Tue, 19 Sep 2006 22:21:09 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox23">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox38">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="autoconnect_checkbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">If checked, Gajim, when launched, will automatically connect to jabber using this account</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">C_onnect on Gajim startup</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="log_history_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Save conversation _logs for all contacts</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="sync_with_global_status_checkbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">If checked, any change to the global status (handled by the combobox at the bottom of the roster window) will change the status of this account accordingly</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Synch_ronize account status with global status</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox24">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="custom_port_checkbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">If the default port that is used for incoming messages is unfitting for your setup you can select another one here.
+You might consider to change possible firewall settings.</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Use custom port:</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_custom_port_checkbutton_toggled" last_modification_time="Wed, 20 Sep 2006 18:29:49 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="custom_port_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â—</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">6</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">10</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">General</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">2</property>
+
+ <child>
+ <widget class="GtkEntry" id="jabber_id_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â—</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label363">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Jabber ID:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label364">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">E-Mail:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="email_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â—</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label362">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Last Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="last_name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â—</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label361">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">First Name:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="first_name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">â—</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <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"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label366">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Personal Information&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox96">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkLabel" id="label365">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;OpenPGP&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox25">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">5</property>
+
+ <child>
+ <widget class="GtkLabel" id="gpg_key_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">No key selected</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="gpg_name_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="gpg_choose_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Choose _Key...</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_gpg_choose_button_clicked" last_modification_time="Mon, 02 Oct 2006 00:23:00 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox26">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="gpg_save_password_checkbutton">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">If checked, Gajim will store the password in ~/.gajim/config with 'read' permission only for you</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Save _passphrase (insecure)</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_gpg_save_password_checkbutton_toggled" last_modification_time="Mon, 02 Oct 2006 00:27:36 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="gpg_password_entry">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label360">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Personal Information</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox6">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Sun, 17 Apr 2005 18:17:10 GMT"/>
+ <accelerator key="Escape" modifiers="0" signal="clicked"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="save_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-save</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_save_button_clicked" last_modification_time="Mon, 28 Feb 2005 20:30:56 GMT"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/src/common/config.py b/src/common/config.py
index fd575affa..960b39516 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -6,7 +6,8 @@
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
-##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
@@ -82,6 +83,7 @@ class Config:
'saveposition': [ opt_bool, True ],
'mergeaccounts': [ opt_bool, False, '', True ],
'sort_by_show': [ opt_bool, True, '', True ],
+ 'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
'use_speller': [ opt_bool, False, ],
'speller_language': [ opt_str, '', _('Language used by speller')],
'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
@@ -259,6 +261,11 @@ class Config:
'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
'msgwin-width': [opt_int, 480],
'msgwin-height': [opt_int, 440],
+ 'is_zeroconf': [opt_bool, False],
+ 'zeroconf_first_name': [ opt_str, '', '', True ],
+ 'zeroconf_last_name': [ opt_str, '', '', True ],
+ 'zeroconf_jabber_id': [ opt_str, '', '', True ],
+ 'zeroconf_email': [ opt_str, '', '', True ],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
diff --git a/src/common/connection.py b/src/common/connection.py
index 095cf2d4f..27a7521ea 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -46,6 +46,7 @@ class Connection(ConnectionHandlers):
self.connection = None # xmpppy ClientCommon instance
# this property is used to prevent double connections
self.last_connection = None # last ClientCommon instance
+ self.is_zeroconf = False
self.gpg = None
self.status = ''
self.priority = gajim.get_priority(name, 'offline')
diff --git a/src/common/gajim.py b/src/common/gajim.py
index 2fcc3d0a5..e353f284c 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -120,6 +120,12 @@ status_before_autoaway = {}
SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
+# zeroconf account name
+ZEROCONF_ACC_NAME = 'Local'
+priority_dict = {}
+for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ priority_dict[status] = config.get('autopriority' + status)
+
def get_nick_from_jid(jid):
pos = jid.find('@')
return jid[:pos]
diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py
index ca13af184..3079ca6f2 100644
--- a/src/common/xmpp/dispatcher_nb.py
+++ b/src/common/xmpp/dispatcher_nb.py
@@ -74,7 +74,6 @@ class Dispatcher(PlugIn):
self.RegisterProtocol('presence', Presence)
self.RegisterProtocol('message', Message)
self.RegisterDefaultHandler(self.returnStanzaHandler)
- # Register Gajim's event handler as soon as dispatcher begins
self.RegisterEventHandler(self._owner._caller._event_dispatcher)
self.on_responses = {}
@@ -84,7 +83,10 @@ class Dispatcher(PlugIn):
self._owner.lastErrNode = None
self._owner.lastErr = None
self._owner.lastErrCode = None
- self.StreamInit()
+ if hasattr(self._owner, 'StreamInit'):
+ self._owner.StreamInit()
+ else:
+ self.StreamInit()
def plugout(self):
''' Prepares instance to be destructed. '''
@@ -134,7 +136,7 @@ class Dispatcher(PlugIn):
return 0
except ExpatError:
sys.exc_clear()
- self.DEBUG('Invalid XML received from server. Forcing disconnect.')
+ self.DEBUG('Invalid XML received from server. Forcing disconnect.', 'error')
self._owner.Connection.pollend()
return 0
if len(self._pendingExceptions) > 0:
diff --git a/src/common/xmpp/session.py b/src/common/xmpp/session.py
index 3921937ed..b61e4f6de 100644
--- a/src/common/xmpp/session.py
+++ b/src/common/xmpp/session.py
@@ -183,7 +183,7 @@ class Session:
if self.sendbuffer:
try:
# LOCK_QUEUE
- sent=self._send(self.sendbuffer) # âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ!
+ sent=self._send(self.sendbuffer) # blocking socket
except:
# UNLOCK_QUEUE
self.set_socket_state(SOCKET_DEAD)
diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/common/zeroconf/__init__.py
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
new file mode 100644
index 000000000..6f53b7071
--- /dev/null
+++ b/src/common/zeroconf/client_zeroconf.py
@@ -0,0 +1,598 @@
+## common/zeroconf/client_zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+## 2006 Dimitur Kirov <dkirov@gmail.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+from common import gajim
+import common.xmpp
+from common.xmpp.idlequeue import IdleObject
+from common.xmpp import dispatcher_nb, simplexml
+from common.xmpp.client import *
+from common.xmpp.simplexml import ustr
+from common.zeroconf import zeroconf
+
+from common.xmpp.protocol import *
+import socket
+import errno
+import sys
+
+from common.zeroconf import roster_zeroconf
+
+MAX_BUFF_LEN = 65536
+DATA_RECEIVED='DATA RECEIVED'
+DATA_SENT='DATA SENT'
+TYPE_SERVER, TYPE_CLIENT = range(2)
+
+# wait XX sec to establish a connection
+CONNECT_TIMEOUT_SECONDS = 30
+
+# after XX sec with no activity, close the stream
+ACTIVITY_TIMEOUT_SECONDS = 180
+
+class ZeroconfListener(IdleObject):
+ def __init__(self, port, conn_holder):
+ ''' handle all incomming connections on ('0.0.0.0', port)'''
+ self.port = port
+ self.queue_idx = -1
+ #~ self.queue = None
+ self.started = False
+ self._sock = None
+ self.fd = -1
+ self.caller = conn_holder.caller
+ self.conn_holder = conn_holder
+
+ def bind(self):
+ self._serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ # will fail when port is busy, or we don't have rights to bind
+ try:
+ self._serv.bind(('0.0.0.0', self.port))
+ except Exception, e:
+ # unable to bind, show error dialog
+ return None
+ self._serv.listen(socket.SOMAXCONN)
+ self._serv.setblocking(False)
+ self.fd = self._serv.fileno()
+ gajim.idlequeue.plug_idle(self, False, True)
+ self.started = True
+
+ def pollend(self):
+ ''' called when we stop listening on (host, port) '''
+ self.disconnect()
+
+ def pollin(self):
+ ''' accept a new incomming connection and notify queue'''
+ sock = self.accept_conn()
+ P2PClient(sock[0], sock[1][0], sock[1][1], self.conn_holder)
+
+ def disconnect(self):
+ ''' free all resources, we are not listening anymore '''
+ gajim.idlequeue.remove_timeout(self.fd)
+ gajim.idlequeue.unplug_idle(self.fd)
+ self.fd = -1
+ self.started = False
+ try:
+ self._serv.close()
+ except:
+ pass
+ self.conn_holder.kill_all_connections()
+
+ def accept_conn(self):
+ ''' accepts a new incomming connection '''
+ _sock = self._serv.accept()
+ _sock[0].setblocking(False)
+ return _sock
+
+class P2PClient(IdleObject):
+ def __init__(self, _sock, host, port, conn_holder, messagequeue = [], to = None):
+ self._owner = self
+ self.Namespace = 'jabber:client'
+ self.defaultNamespace = self.Namespace
+ self._component = 0
+ self._registered_name = None
+ self._caller = conn_holder.caller
+ self.conn_holder = conn_holder
+ self.messagequeue = messagequeue
+ self.to = to
+ self.Server = host
+ self.DBG = 'client'
+ self.Connection = None
+ if gajim.verbose:
+ debug = ['always', 'nodebuilder']
+ else:
+ debug = []
+ self._DEBUG = Debug.Debug(debug)
+ self.DEBUG = self._DEBUG.Show
+ self.debug_flags = self._DEBUG.debug_flags
+ self.debug_flags.append(self.DBG)
+ self.sock_hash = None
+ if _sock:
+ self.sock_type = TYPE_SERVER
+ else:
+ self.sock_type = TYPE_CLIENT
+ conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect)
+ self.sock_hash = conn._sock.__hash__
+ self.conn_holder.add_connection(self, self.Server, self.to)
+
+ def add_message(self, message):
+ if self.Connection:
+ if self.Connection.state == -1:
+ return False
+ self.send(message)
+ else:
+ self.messagequeue.append(message)
+ return True
+
+ def on_connect(self, conn):
+ self.Connection = conn
+ self.Connection.PlugIn(self)
+ dispatcher_nb.Dispatcher().PlugIn(self)
+ self._register_handlers()
+ if self.sock_type == TYPE_CLIENT:
+ while self.messagequeue:
+ message = self.messagequeue.pop(0)
+ self.send(message)
+
+ def StreamInit(self):
+ ''' Send an initial stream header. '''
+ self.Dispatcher.Stream = simplexml.NodeBuilder()
+ self.Dispatcher.Stream._dispatch_depth = 2
+ self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
+ self.Dispatcher.Stream.stream_header_received = self._check_stream_start
+ self.debug_flags.append(simplexml.DBG_NODEBUILDER)
+ self.Dispatcher.Stream.DEBUG = self.DEBUG
+ self.Dispatcher.Stream.features = None
+ if self.sock_type == TYPE_CLIENT:
+ self.send_stream_header()
+
+ def send_stream_header(self):
+ self.Dispatcher._metastream = Node('stream:stream')
+ self.Dispatcher._metastream.setNamespace(self.Namespace)
+ # XXX TLS support
+ #~ self._metastream.setAttr('version', '1.0')
+ self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
+ self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(self.Dispatcher._metastream)[:-2])
+
+ def _check_stream_start(self, ns, tag, attrs):
+ if ns<>NS_STREAMS or tag<>'stream':
+ self.Connection.DEBUG('Incorrect stream start: (%s,%s).Terminating! ' % (tag, ns), 'error')
+ self.Connection.disconnect()
+ return
+ if self.sock_type == TYPE_SERVER:
+ self.send_stream_header()
+ while self.messagequeue:
+ message = self.messagequeue.pop(0)
+ self.send(message)
+
+
+ def on_disconnect(self):
+ if self.conn_holder:
+ self.conn_holder.remove_connection(self.sock_hash)
+ if self.__dict__.has_key('Dispatcher'):
+ self.Dispatcher.PlugOut()
+ if self.__dict__.has_key('P2PConnection'):
+ self.P2PConnection.PlugOut()
+ self.Connection = None
+ self._caller = None
+ self.conn_holder = None
+
+ def force_disconnect(self):
+ if self.Connection:
+ self.disconnect()
+ else:
+ self.on_disconnect()
+
+ def _on_receive_document_attrs(self, data):
+ if data:
+ self.Dispatcher.ProcessNonBlocking(data)
+ if not hasattr(self, 'Dispatcher') or \
+ self.Dispatcher.Stream._document_attrs is None:
+ return
+ self.onreceive(None)
+ if self.Dispatcher.Stream._document_attrs.has_key('version') and \
+ self.Dispatcher.Stream._document_attrs['version'] == '1.0':
+ #~ self.onreceive(self._on_receive_stream_features)
+ #XXX continue with TLS
+ return
+ self.onreceive(None)
+ return True
+
+
+
+ def _register_handlers(self):
+ self.RegisterHandler('message', lambda conn, data:self._caller._messageCB(self.Server, conn, data))
+ self.RegisterHandler('iq', self._caller._siSetCB, 'set',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._siResultCB, 'result',
+ common.xmpp.NS_SI)
+ self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
+ common.xmpp.NS_BYTESTREAM)
+ self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
+ common.xmpp.NS_BYTESTREAM)
+ self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
+ common.xmpp.NS_BYTESTREAM)
+
+class P2PConnection(IdleObject, PlugIn):
+ ''' class for sending file to socket over socks5 '''
+ def __init__(self, sock_hash, _sock, host = None, port = None, caller = None, on_connect = None):
+ IdleObject.__init__(self)
+ self._owner = None
+ PlugIn.__init__(self)
+ self.DBG_LINE='socket'
+ self.sendqueue = []
+ self.sendbuff = None
+ self._sock = _sock
+ self.host, self.port = host, port
+ self.on_connect = on_connect
+ self.writable = False
+ self.readable = False
+ self._exported_methods=[self.send, self.disconnect, self.onreceive]
+ self.on_receive = None
+ if _sock:
+ self._sock = _sock
+ self.state = 1
+ self._sock.setblocking(False)
+ self.fd = self._sock.fileno()
+ self.on_connect(self)
+ else:
+ self.state = 0
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.setblocking(False)
+ self.fd = self._sock.fileno()
+ gajim.idlequeue.plug_idle(self, True, False)
+ self.set_timeout(CONNECT_TIMEOUT_SECONDS)
+ self.do_connect()
+
+ def set_timeout(self, timeout):
+ gajim.idlequeue.remove_timeout(self.fd)
+ if self.state >= 0:
+ gajim.idlequeue.set_read_timeout(self.fd, timeout)
+
+ def plugin(self, owner):
+ self.onreceive(owner._on_receive_document_attrs)
+ self._plug_idle()
+ return True
+
+ def plugout(self):
+ ''' Disconnect from the remote server and unregister self.disconnected method from
+ the owner's dispatcher. '''
+ self.disconnect()
+ self._owner = None
+
+ def onreceive(self, recv_handler):
+ if not recv_handler:
+ if hasattr(self._owner, 'Dispatcher'):
+ self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+ else:
+ self.on_receive = None
+ return
+ _tmp = self.on_receive
+ # make sure this cb is not overriden by recursive calls
+ if not recv_handler(None) and _tmp == self.on_receive:
+ self.on_receive = recv_handler
+
+
+
+ def send(self, stanza):
+ '''Append stanza to the queue of messages to be send.
+ If supplied data is unicode string, encode it to utf-8.
+ '''
+ if self.state <= 0:
+ return
+ r = stanza
+ if isinstance(r, unicode):
+ r = r.encode('utf-8')
+ elif not isinstance(r, str):
+ r = ustr(r).encode('utf-8')
+ self.sendqueue.append(r)
+ self._plug_idle()
+
+ def read_timeout(self):
+ self.pollend()
+
+
+ def do_connect(self):
+ errnum = 0
+ try:
+ self._sock.connect((self.host, self.port))
+ self._sock.setblocking(False)
+ except Exception, ee:
+ (errnum, errstr) = ee
+ if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
+ return
+ # win32 needs this
+ elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
+ self.disconnect()
+ return None
+ else: # socket is already connected
+ self._sock.setblocking(False)
+ self.state = 1 # connected
+ self.on_connect(self)
+ return 1 # we are connected
+
+
+ def pollout(self):
+ if self.state == 0:
+ self.do_connect()
+ return
+ gajim.idlequeue.remove_timeout(self.fd)
+ self._do_send()
+
+ def pollend(self):
+ self.state = -1
+ self.disconnect()
+
+ def pollin(self):
+ ''' Reads all pending incoming data. Calls owner's disconnected() method if appropriate.'''
+ received = ''
+ errnum = 0
+ try:
+ # get as many bites, as possible, but not more than RECV_BUFSIZE
+ received = self._sock.recv(MAX_BUFF_LEN)
+ except Exception, e:
+ if len(e.args) > 0 and isinstance(e.args[0], int):
+ errnum = e[0]
+ sys.exc_clear()
+ # "received" will be empty anyhow
+ if errnum == socket.SSL_ERROR_WANT_READ:
+ pass
+ elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
+ self.pollend()
+ # don't proccess result, cas it will raise error
+ return
+ elif not received :
+ if errnum != socket.SSL_ERROR_EOF:
+ # 8 EOF occurred in violation of protocol
+ self.pollend()
+ if self.state >= 0:
+ self.disconnect()
+ return
+
+ if self.state < 0:
+ return
+ if self.on_receive:
+ if self._owner.sock_type == TYPE_CLIENT:
+ self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+ if received.strip():
+ self.DEBUG(received, 'got')
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
+ self.on_receive(received)
+ else:
+ # This should never happed, so we need the debug
+ self.DEBUG('Unhandled data received: %s' % received,'error')
+ self.disconnect()
+ return True
+
+ def onreceive(self, recv_handler):
+ if not recv_handler:
+ if hasattr(self._owner, 'Dispatcher'):
+ self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
+ else:
+ self.on_receive = None
+ return
+ _tmp = self.on_receive
+ # make sure this cb is not overriden by recursive calls
+ if not recv_handler(None) and _tmp == self.on_receive:
+ self.on_receive = recv_handler
+
+ def disconnect(self):
+ ''' Closes the socket. '''
+ gajim.idlequeue.remove_timeout(self.fd)
+ gajim.idlequeue.unplug_idle(self.fd)
+ try:
+ self._sock.shutdown(socket.SHUT_RDWR)
+ self._sock.close()
+ except:
+ # socket is already closed
+ pass
+ self.fd = -1
+ self.state = -1
+ if self._owner:
+ self._owner.on_disconnect()
+
+ def _do_send(self):
+ if not self.sendbuff:
+ if not self.sendqueue:
+ return None # nothing to send
+ self.sendbuff = self.sendqueue.pop(0)
+ self.sent_data = self.sendbuff
+ try:
+ send_count = self._sock.send(self.sendbuff)
+ if send_count:
+ self.sendbuff = self.sendbuff[send_count:]
+ if not self.sendbuff and not self.sendqueue:
+ if self.state < 0:
+ gajim.idlequeue.unplug_idle(self.fd)
+ self._on_send()
+ self.disconnect()
+ return
+ # we are not waiting for write
+ self._plug_idle()
+ self._on_send()
+ except socket.error, e:
+ sys.exc_clear()
+ if e[0] == socket.SSL_ERROR_WANT_WRITE:
+ return True
+ if self.state < 0:
+ self.disconnect()
+ return
+ self._on_send_failure()
+ return
+ if self._owner.sock_type == TYPE_CLIENT:
+ self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
+ return True
+
+ def _plug_idle(self):
+ readable = self.state != 0
+ if self.sendqueue or self.sendbuff:
+ writable = True
+ else:
+ writable = False
+ if self.writable != writable or self.readable != readable:
+ gajim.idlequeue.plug_idle(self, writable, readable)
+
+
+ def _on_send(self):
+ if self.sent_data and self.sent_data.strip():
+ self.DEBUG(self.sent_data,'sent')
+ if hasattr(self._owner, 'Dispatcher'):
+ self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
+ self.sent_data = None
+
+ def _on_send_failure(self):
+ self.DEBUG("Socket error while sending data",'error')
+ self._owner.disconnected()
+ self.sent_data = None
+
+
+class ClientZeroconf:
+ def __init__(self, caller):
+ self.caller = caller
+ self.zeroconf = None
+ self.roster = None
+ self.last_msg = ''
+ self.connections = {}
+ self.recipient_to_hash = {}
+ self.ip_to_hash = {}
+
+ def test_avahi(self):
+ try:
+ import avahi
+ except ImportError:
+ return False
+ return True
+
+ def connect(self, show, msg):
+ self.port = self.start_listener(self.caller.port)
+ if not self.port:
+ return
+ self.zeroconf_init(show, msg)
+ if not self.zeroconf.connect():
+ self.disconnect()
+ return
+ self.roster = roster_zeroconf.Roster(self.zeroconf)
+
+ def remove_announce(self):
+ if self.zeroconf:
+ return self.zeroconf.remove_announce()
+
+ def announce(self):
+ if self.zeroconf:
+ return self.zeroconf.announce()
+
+ def set_show_msg(self, show, msg):
+ if self.zeroconf:
+ self.zeroconf.txt['msg'] = msg
+ self.last_msg = msg
+ return self.zeroconf.update_txt(show)
+
+ def resolve_all(self):
+ if self.zeroconf:
+ self.zeroconf.resolve_all()
+
+ def reannounce(self, txt):
+ self.remove_announce()
+ self.zeroconf.txt = txt
+ self.zeroconf.port = self.port
+ self.zeroconf.username = self.caller.username
+ return self.announce()
+
+
+ def zeroconf_init(self, show, msg):
+ self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
+ self.caller._on_remove_service, self.caller._on_name_conflictCB,
+ self.caller._on_disconnected, self.caller.username, self.caller.host,
+ self.port)
+ self.zeroconf.txt['msg'] = msg
+ self.zeroconf.txt['status'] = show
+ self.zeroconf.txt['1st'] = self.caller.first
+ self.zeroconf.txt['last'] = self.caller.last
+ self.zeroconf.txt['jid'] = self.caller.jabber_id
+ self.zeroconf.txt['email'] = self.caller.email
+ self.zeroconf.username = self.caller.username
+ self.zeroconf.host = self.caller.host
+ self.zeroconf.port = self.port
+ self.last_msg = msg
+
+ def disconnect(self):
+ if self.listener:
+ self.listener.disconnect()
+ self.listener = None
+ if self.zeroconf:
+ self.zeroconf.disconnect()
+ self.zeroconf = None
+ if self.roster:
+ self.roster.zeroconf = None
+ self.roster._data = None
+ self.roster = None
+
+ def kill_all_connections(self):
+ for connection in self.connections.values():
+ connection.force_disconnect()
+
+ def add_connection(self, connection, ip, recipient):
+ sock_hash = connection.sock_hash
+ if sock_hash not in self.connections:
+ self.connections[sock_hash] = connection
+ self.ip_to_hash[ip] = sock_hash
+ if recipient:
+ self.recipient_to_hash[recipient] = sock_hash
+
+ def remove_connection(self, sock_hash):
+ if sock_hash in self.connections:
+ del self.connections[sock_hash]
+ for i in self.recipient_to_hash:
+ if self.recipient_to_hash[i] == sock_hash:
+ del self.recipient_to_hash[i]
+ break
+ for i in self.ip_to_hash:
+ if self.ip_to_hash[i] == sock_hash:
+ del self.ip_to_hash[i]
+ break
+
+ def start_listener(self, port):
+ for p in range(port, port + 5):
+ self.listener = ZeroconfListener(p, self)
+ self.listener.bind()
+ if self.listener.started:
+ return p
+ self.listener = None
+ return False
+
+ def getRoster(self):
+ if self.roster:
+ return self.roster.getRoster()
+ return {}
+
+ def send(self, msg_iq):
+ msg_iq.setFrom(self.roster.zeroconf.name)
+ to = msg_iq.getTo()
+ if to in self.recipient_to_hash:
+ conn = self.connections[self.recipient_to_hash[to]]
+ if conn.add_message(msg_iq):
+ return
+ try:
+ item = self.roster[to]
+ except KeyError:
+ #XXX invalid recipient, show some error maybe ?
+ return
+ if item['address'] in self.ip_to_hash:
+ conn = self.connections[self.ip_to_hash[item['address']]]
+ if conn.add_message(msg_iq):
+ return
+ P2PClient(None, item['address'], item['port'], self, [msg_iq], to)
+
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
new file mode 100644
index 000000000..a698107f6
--- /dev/null
+++ b/src/common/zeroconf/connection_handlers_zeroconf.py
@@ -0,0 +1,912 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## Contributors for this file:
+## - Yann Le Boulanger <asterix@lagaule.org>
+## - Nikos Kouremenos <nkour@jabber.org>
+## - Dimitur Kirov <dkirov@gmail.com>
+## - Travis Shirk <travis@pobox.com>
+## - Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+import os
+import time
+import base64
+import sha
+import socket
+import sys
+
+from calendar import timegm
+
+#import socks5
+import common.xmpp
+
+from common import GnuPG
+from common import helpers
+from common import gajim
+from common.zeroconf import zeroconf
+STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
+ 'invisible']
+# kind of events we can wait for an answer
+VCARD_PUBLISHED = 'vcard_published'
+VCARD_ARRIVED = 'vcard_arrived'
+AGENT_REMOVED = 'agent_removed'
+HAS_IDLE = True
+try:
+ import common.idle as idle # when we launch gajim from sources
+except:
+ try:
+ import idle # when Gajim is installed
+ except:
+ gajim.log.debug(_('Unable to load idle module'))
+ HAS_IDLE = False
+
+
+class ConnectionBytestream:
+ def __init__(self):
+ self.files_props = {}
+
+ def is_transfer_stoped(self, file_props):
+ if file_props.has_key('error') and file_props['error'] != 0:
+ return True
+ if file_props.has_key('completed') and file_props['completed']:
+ return True
+ if file_props.has_key('connected') and file_props['connected'] == False:
+ return True
+ if not file_props.has_key('stopped') or not file_props['stopped']:
+ return False
+ return True
+
+ def send_success_connect_reply(self, streamhost):
+ ''' send reply to the initiator of FT that we
+ made a connection
+ '''
+ if streamhost is None:
+ return None
+ iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result',
+ frm = streamhost['target'])
+ iq.setAttr('id', streamhost['id'])
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_BYTESTREAM)
+ stream_tag = query.setTag('streamhost-used')
+ stream_tag.setAttr('jid', streamhost['jid'])
+ self.connection.send(iq)
+
+ def remove_transfers_for_contact(self, contact):
+ ''' stop all active transfer for contact '''
+ for file_props in self.files_props.values():
+ if self.is_transfer_stoped(file_props):
+ continue
+ receiver_jid = unicode(file_props['receiver']).split('/')[0]
+ if contact.jid == receiver_jid:
+ file_props['error'] = -5
+ self.remove_transfer(file_props)
+ self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
+ sender_jid = unicode(file_props['sender'])
+ if contact.jid == sender_jid:
+ file_props['error'] = -3
+ self.remove_transfer(file_props)
+
+ def remove_all_transfers(self):
+ ''' stops and removes all active connections from the socks5 pool '''
+ for file_props in self.files_props.values():
+ self.remove_transfer(file_props, remove_from_list = False)
+ del(self.files_props)
+ self.files_props = {}
+
+ def remove_transfer(self, file_props, remove_from_list = True):
+ if file_props is None:
+ return
+ self.disconnect_transfer(file_props)
+ sid = file_props['sid']
+ gajim.socks5queue.remove_file_props(self.name, sid)
+
+ if remove_from_list:
+ if self.files_props.has_key('sid'):
+ del(self.files_props['sid'])
+
+ def disconnect_transfer(self, file_props):
+ if file_props is None:
+ return
+ if file_props.has_key('hash'):
+ gajim.socks5queue.remove_sender(file_props['hash'])
+
+ if file_props.has_key('streamhosts'):
+ for host in file_props['streamhosts']:
+ if host.has_key('idx') and host['idx'] > 0:
+ gajim.socks5queue.remove_receiver(host['idx'])
+ gajim.socks5queue.remove_sender(host['idx'])
+
+ def send_socks5_info(self, file_props, fast = True, receiver = None,
+ sender = None):
+ ''' send iq for the present streamhosts and proxies '''
+ if type(self.peerhost) != tuple:
+ return
+ port = gajim.config.get('file_transfers_port')
+ ft_override_host_to_send = gajim.config.get('ft_override_host_to_send')
+ if receiver is None:
+ receiver = file_props['receiver']
+ if sender is None:
+ sender = file_props['sender']
+ proxyhosts = []
+ sha_str = helpers.get_auth_sha(file_props['sid'], sender,
+ receiver)
+ file_props['sha_str'] = sha_str
+ if not ft_override_host_to_send:
+ ft_override_host_to_send = self.peerhost[0]
+ try:
+ ft_override_host_to_send = socket.gethostbyname(
+ ft_override_host_to_send)
+ except socket.gaierror:
+ self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
+ ft_override_host_to_send = self.peerhost[0]
+ listener = gajim.socks5queue.start_listener(port,
+ sha_str, self._result_socks5_sid, file_props['sid'])
+ if listener == None:
+ file_props['error'] = -5
+ self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
+ ''))
+ self._connect_error(unicode(receiver), file_props['sid'],
+ file_props['sid'], code = 406)
+ return
+
+ iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver),
+ typ = 'set')
+ file_props['request-id'] = 'id_' + file_props['sid']
+ iq.setID(file_props['request-id'])
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_BYTESTREAM)
+ query.setAttr('mode', 'tcp')
+ query.setAttr('sid', file_props['sid'])
+ streamhost = query.setTag('streamhost')
+ streamhost.setAttr('port', unicode(port))
+ streamhost.setAttr('host', ft_override_host_to_send)
+ streamhost.setAttr('jid', sender)
+ self.connection.send(iq)
+
+ def send_file_rejection(self, file_props):
+ ''' informs sender that we refuse to download the file '''
+ # user response to ConfirmationDialog may come after we've disconneted
+ if not self.connection or self.connected < 2:
+ return
+ iq = common.xmpp.Protocol(name = 'iq',
+ to = unicode(file_props['sender']), typ = 'error')
+ iq.setAttr('id', file_props['request-id'])
+ err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
+ 'forbidden', text = 'Offer Declined')
+ iq.addChild(node=err)
+ self.connection.send(iq)
+
+ def send_file_approval(self, file_props):
+ ''' send iq, confirming that we want to download the file '''
+ # user response to ConfirmationDialog may come after we've disconneted
+ if not self.connection or self.connected < 2:
+ return
+ iq = common.xmpp.Protocol(name = 'iq',
+ to = unicode(file_props['sender']), typ = 'result')
+ iq.setAttr('id', file_props['request-id'])
+ si = iq.setTag('si')
+ si.setNamespace(common.xmpp.NS_SI)
+ if file_props.has_key('offset') and file_props['offset']:
+ file_tag = si.setTag('file')
+ file_tag.setNamespace(common.xmpp.NS_FILE)
+ range_tag = file_tag.setTag('range')
+ range_tag.setAttr('offset', file_props['offset'])
+ feature = si.setTag('feature')
+ feature.setNamespace(common.xmpp.NS_FEATURE)
+ _feature = common.xmpp.DataForm(typ='submit')
+ feature.addChild(node=_feature)
+ field = _feature.setField('stream-method')
+ field.delAttr('type')
+ field.setValue(common.xmpp.NS_BYTESTREAM)
+ self.connection.send(iq)
+
+ def send_file_request(self, file_props):
+ ''' send iq for new FT request '''
+ if not self.connection or self.connected < 2:
+ return
+ our_jid = gajim.get_jid_from_account(self.name)
+ frm = our_jid
+ file_props['sender'] = frm
+ fjid = file_props['receiver'].jid
+ iq = common.xmpp.Protocol(name = 'iq', to = fjid,
+ typ = 'set')
+ iq.setID(file_props['sid'])
+ self.files_props[file_props['sid']] = file_props
+ si = iq.setTag('si')
+ si.setNamespace(common.xmpp.NS_SI)
+ si.setAttr('profile', common.xmpp.NS_FILE)
+ si.setAttr('id', file_props['sid'])
+ file_tag = si.setTag('file')
+ file_tag.setNamespace(common.xmpp.NS_FILE)
+ file_tag.setAttr('name', file_props['name'])
+ file_tag.setAttr('size', file_props['size'])
+ desc = file_tag.setTag('desc')
+ if file_props.has_key('desc'):
+ desc.setData(file_props['desc'])
+ file_tag.setTag('range')
+ feature = si.setTag('feature')
+ feature.setNamespace(common.xmpp.NS_FEATURE)
+ _feature = common.xmpp.DataForm(typ='form')
+ feature.addChild(node=_feature)
+ field = _feature.setField('stream-method')
+ field.setAttr('type', 'list-single')
+ field.addOption(common.xmpp.NS_BYTESTREAM)
+ self.connection.send(iq)
+
+ def _result_socks5_sid(self, sid, hash_id):
+ ''' store the result of sha message from auth. '''
+ if not self.files_props.has_key(sid):
+ return
+ file_props = self.files_props[sid]
+ file_props['hash'] = hash_id
+ return
+
+ def _connect_error(self, to, _id, sid, code = 404):
+ ''' cb, when there is an error establishing BS connection, or
+ when connection is rejected'''
+ msg_dict = {
+ 404: 'Could not connect to given hosts',
+ 405: 'Cancel',
+ 406: 'Not acceptable',
+ }
+ msg = msg_dict[code]
+ iq = None
+ iq = common.xmpp.Protocol(name = 'iq', to = to,
+ typ = 'error')
+ iq.setAttr('id', _id)
+ err = iq.setTag('error')
+ err.setAttr('code', unicode(code))
+ err.setData(msg)
+ self.connection.send(iq)
+ if code == 404:
+ file_props = gajim.socks5queue.get_file_props(self.name, sid)
+ if file_props is not None:
+ self.disconnect_transfer(file_props)
+ file_props['error'] = -3
+ self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
+
+ def _proxy_auth_ok(self, proxy):
+ '''cb, called after authentication to proxy server '''
+ file_props = self.files_props[proxy['sid']]
+ iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'],
+ typ = 'set')
+ auth_id = "au_" + proxy['sid']
+ iq.setID(auth_id)
+ query = iq.setTag('query')
+ query.setNamespace(common.xmpp.NS_BYTESTREAM)
+ query.setAttr('sid', proxy['sid'])
+ activate = query.setTag('activate')
+ activate.setData(file_props['proxy_receiver'])
+ iq.setID(auth_id)
+ self.connection.send(iq)
+
+ # register xmpppy handlers for bytestream and FT stanzas
+ def _bytestreamErrorCB(self, con, iq_obj):
+ gajim.log.debug('_bytestreamErrorCB')
+ id = unicode(iq_obj.getAttr('id'))
+ frm = unicode(iq_obj.getFrom())
+ query = iq_obj.getTag('query')
+ gajim.proxy65_manager.error_cb(frm, query)
+ jid = unicode(iq_obj.getFrom())
+ id = id[3:]
+ if not self.files_props.has_key(id):
+ return
+ file_props = self.files_props[id]
+ file_props['error'] = -4
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+ raise common.xmpp.NodeProcessed
+
+ def _bytestreamSetCB(self, con, iq_obj):
+ gajim.log.debug('_bytestreamSetCB')
+ target = unicode(iq_obj.getAttr('to'))
+ id = unicode(iq_obj.getAttr('id'))
+ query = iq_obj.getTag('query')
+ sid = unicode(query.getAttr('sid'))
+ file_props = gajim.socks5queue.get_file_props(
+ self.name, sid)
+ streamhosts=[]
+ for item in query.getChildren():
+ if item.getName() == 'streamhost':
+ host_dict={
+ 'state': 0,
+ 'target': target,
+ 'id': id,
+ 'sid': sid,
+ 'initiator': unicode(iq_obj.getFrom())
+ }
+ for attr in item.getAttrs():
+ host_dict[attr] = item.getAttr(attr)
+ streamhosts.append(host_dict)
+ if file_props is None:
+ if self.files_props.has_key(sid):
+ file_props = self.files_props[sid]
+ file_props['fast'] = streamhosts
+ if file_props['type'] == 's':
+ if file_props.has_key('streamhosts'):
+ file_props['streamhosts'].extend(streamhosts)
+ else:
+ file_props['streamhosts'] = streamhosts
+ if not gajim.socks5queue.get_file_props(self.name, sid):
+ gajim.socks5queue.add_file_props(self.name, file_props)
+ gajim.socks5queue.connect_to_hosts(self.name, sid,
+ self.send_success_connect_reply, None)
+ raise common.xmpp.NodeProcessed
+
+ file_props['streamhosts'] = streamhosts
+ if file_props['type'] == 'r':
+ gajim.socks5queue.connect_to_hosts(self.name, sid,
+ self.send_success_connect_reply, self._connect_error)
+ raise common.xmpp.NodeProcessed
+
+ def _ResultCB(self, con, iq_obj):
+ gajim.log.debug('_ResultCB')
+ # if we want to respect jep-0065 we have to check for proxy
+ # activation result in any result iq
+ real_id = unicode(iq_obj.getAttr('id'))
+ if real_id[:3] != 'au_':
+ return
+ frm = unicode(iq_obj.getFrom())
+ id = real_id[3:]
+ if self.files_props.has_key(id):
+ file_props = self.files_props[id]
+ if file_props['streamhost-used']:
+ for host in file_props['proxyhosts']:
+ if host['initiator'] == frm and host.has_key('idx'):
+ gajim.socks5queue.activate_proxy(host['idx'])
+ raise common.xmpp.NodeProcessed
+
+ def _bytestreamResultCB(self, con, iq_obj):
+ gajim.log.debug('_bytestreamResultCB')
+ frm = unicode(iq_obj.getFrom())
+ real_id = unicode(iq_obj.getAttr('id'))
+ query = iq_obj.getTag('query')
+ gajim.proxy65_manager.resolve_result(frm, query)
+
+ try:
+ streamhost = query.getTag('streamhost-used')
+ except: # this bytestream result is not what we need
+ pass
+ id = real_id[3:]
+ if self.files_props.has_key(id):
+ file_props = self.files_props[id]
+ else:
+ raise common.xmpp.NodeProcessed
+ if streamhost is None:
+ # proxy approves the activate query
+ if real_id[:3] == 'au_':
+ id = real_id[3:]
+ if not file_props.has_key('streamhost-used') or \
+ file_props['streamhost-used'] is False:
+ raise common.xmpp.NodeProcessed
+ if not file_props.has_key('proxyhosts'):
+ raise common.xmpp.NodeProcessed
+ for host in file_props['proxyhosts']:
+ if host['initiator'] == frm and \
+ unicode(query.getAttr('sid')) == file_props['sid']:
+ gajim.socks5queue.activate_proxy(host['idx'])
+ break
+ raise common.xmpp.NodeProcessed
+ jid = streamhost.getAttr('jid')
+ if file_props.has_key('streamhost-used') and \
+ file_props['streamhost-used'] is True:
+ raise common.xmpp.NodeProcessed
+
+ if real_id[:3] == 'au_':
+ gajim.socks5queue.send_file(file_props, self.name)
+ raise common.xmpp.NodeProcessed
+
+ proxy = None
+ if file_props.has_key('proxyhosts'):
+ for proxyhost in file_props['proxyhosts']:
+ if proxyhost['jid'] == jid:
+ proxy = proxyhost
+
+ if proxy != None:
+ file_props['streamhost-used'] = True
+ if not file_props.has_key('streamhosts'):
+ file_props['streamhosts'] = []
+ file_props['streamhosts'].append(proxy)
+ file_props['is_a_proxy'] = True
+ receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, file_props['sid'], file_props)
+ gajim.socks5queue.add_receiver(self.name, receiver)
+ proxy['idx'] = receiver.queue_idx
+ gajim.socks5queue.on_success = self._proxy_auth_ok
+ raise common.xmpp.NodeProcessed
+
+ else:
+ gajim.socks5queue.send_file(file_props, self.name)
+ if file_props.has_key('fast'):
+ fasts = file_props['fast']
+ if len(fasts) > 0:
+ self._connect_error(frm, fasts[0]['id'], file_props['sid'],
+ code = 406)
+
+ raise common.xmpp.NodeProcessed
+
+ def _siResultCB(self, con, iq_obj):
+ gajim.log.debug('_siResultCB')
+ self.peerhost = con._owner.Connection._sock.getsockname()
+ id = iq_obj.getAttr('id')
+ if not self.files_props.has_key(id):
+ # no such jid
+ return
+ file_props = self.files_props[id]
+ if file_props is None:
+ # file properties for jid is none
+ return
+ if file_props.has_key('request-id'):
+ # we have already sent streamhosts info
+ return
+ file_props['receiver'] = unicode(iq_obj.getFrom())
+ si = iq_obj.getTag('si')
+ file_tag = si.getTag('file')
+ range_tag = None
+ if file_tag:
+ range_tag = file_tag.getTag('range')
+ if range_tag:
+ offset = range_tag.getAttr('offset')
+ if offset:
+ file_props['offset'] = int(offset)
+ length = range_tag.getAttr('length')
+ if length:
+ file_props['length'] = int(length)
+ feature = si.setTag('feature')
+ if feature.getNamespace() != common.xmpp.NS_FEATURE:
+ return
+ form_tag = feature.getTag('x')
+ form = common.xmpp.DataForm(node=form_tag)
+ field = form.getField('stream-method')
+ if field.getValue() != common.xmpp.NS_BYTESTREAM:
+ return
+ self.send_socks5_info(file_props, fast = True)
+ raise common.xmpp.NodeProcessed
+
+ def _siSetCB(self, con, iq_obj):
+ gajim.log.debug('_siSetCB')
+ jid = unicode(iq_obj.getFrom())
+ si = iq_obj.getTag('si')
+ profile = si.getAttr('profile')
+ mime_type = si.getAttr('mime-type')
+ if profile != common.xmpp.NS_FILE:
+ return
+ file_tag = si.getTag('file')
+ file_props = {'type': 'r'}
+ for attribute in file_tag.getAttrs():
+ if attribute in ('name', 'size', 'hash', 'date'):
+ val = file_tag.getAttr(attribute)
+ if val is None:
+ continue
+ file_props[attribute] = val
+ file_desc_tag = file_tag.getTag('desc')
+ if file_desc_tag is not None:
+ file_props['desc'] = file_desc_tag.getData()
+
+ if mime_type is not None:
+ file_props['mime-type'] = mime_type
+ our_jid = gajim.get_jid_from_account(self.name)
+ file_props['receiver'] = our_jid
+ file_props['sender'] = unicode(iq_obj.getFrom())
+ file_props['request-id'] = unicode(iq_obj.getAttr('id'))
+ file_props['sid'] = unicode(si.getAttr('id'))
+ gajim.socks5queue.add_file_props(self.name, file_props)
+ self.dispatch('FILE_REQUEST', (jid, file_props))
+ raise common.xmpp.NodeProcessed
+
+ def _siErrorCB(self, con, iq_obj):
+ gajim.log.debug('_siErrorCB')
+ si = iq_obj.getTag('si')
+ profile = si.getAttr('profile')
+ if profile != common.xmpp.NS_FILE:
+ return
+ id = iq_obj.getAttr('id')
+ if not self.files_props.has_key(id):
+ # no such jid
+ return
+ file_props = self.files_props[id]
+ if file_props is None:
+ # file properties for jid is none
+ return
+ jid = unicode(iq_obj.getFrom())
+ file_props['error'] = -3
+ self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
+ raise common.xmpp.NodeProcessed
+
+
+
+class ConnectionVcard:
+ def __init__(self):
+ self.vcard_sha = None
+ self.vcard_shas = {} # sha of contacts
+ self.room_jids = [] # list of gc jids so that vcard are saved in a folder
+
+ def add_sha(self, p, send_caps = True):
+ '''
+ c = p.setTag('x', namespace = common.xmpp.NS_VCARD_UPDATE)
+ if self.vcard_sha is not None:
+ c.setTagData('photo', self.vcard_sha)
+ if send_caps:
+ return self.add_caps(p)
+ return p
+ '''
+ pass
+
+ def add_caps(self, p):
+ '''
+ # advertise our capabilities in presence stanza (jep-0115)
+ c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
+ c.setAttr('node', 'http://gajim.org/caps')
+ c.setAttr('ext', 'ftrans')
+ c.setAttr('ver', gajim.version)
+ return p
+ '''
+ pass
+
+ def node_to_dict(self, node):
+ dict = {}
+
+ for info in node.getChildren():
+ name = info.getName()
+ if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
+ if not dict.has_key(name):
+ dict[name] = []
+ entry = {}
+ for c in info.getChildren():
+ entry[c.getName()] = c.getData()
+ dict[name].append(entry)
+ elif info.getChildren() == []:
+ dict[name] = info.getData()
+ else:
+ dict[name] = {}
+ for c in info.getChildren():
+ dict[name][c.getName()] = c.getData()
+
+ return dict
+
+ def save_vcard_to_hd(self, full_jid, card):
+ jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
+ puny_jid = helpers.sanitize_filename(jid)
+ path = os.path.join(gajim.VCARD_PATH, puny_jid)
+ if jid in self.room_jids or os.path.isdir(path):
+ # remove room_jid file if needed
+ if os.path.isfile(path):
+ os.remove(path)
+ # create folder if needed
+ if not os.path.isdir(path):
+ os.mkdir(path, 0700)
+ puny_nick = helpers.sanitize_filename(nick)
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ else:
+ path_to_file = path
+ fil = open(path_to_file, 'w')
+ fil.write(str(card))
+ fil.close()
+
+ def get_cached_vcard(self, fjid, is_fake_jid = False):
+ '''return the vcard as a dict
+ return {} if vcard was too old
+ return None if we don't have cached vcard'''
+ jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
+ puny_jid = helpers.sanitize_filename(jid)
+ if is_fake_jid:
+ puny_nick = helpers.sanitize_filename(nick)
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
+ else:
+ path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
+ if not os.path.isfile(path_to_file):
+ return None
+ # We have the vcard cached
+ f = open(path_to_file)
+ c = f.read()
+ f.close()
+ card = common.xmpp.Node(node = c)
+ vcard = self.node_to_dict(card)
+ if vcard.has_key('PHOTO'):
+ if not isinstance(vcard['PHOTO'], dict):
+ del vcard['PHOTO']
+ elif vcard['PHOTO'].has_key('SHA'):
+ cached_sha = vcard['PHOTO']['SHA']
+ if self.vcard_shas.has_key(jid) and self.vcard_shas[jid] != \
+ cached_sha:
+ # user change his vcard so don't use the cached one
+ return {}
+ vcard['jid'] = jid
+ vcard['resource'] = gajim.get_resource_from_jid(fjid)
+ return vcard
+
+ def request_vcard(self, jid = None, is_fake_jid = False):
+ '''request the VCARD. If is_fake_jid is True, it means we request a vcard
+ to a fake jid, like in private messages in groupchat'''
+ if not self.connection:
+ return
+ '''
+ iq = common.xmpp.Iq(typ = 'get')
+ if jid:
+ iq.setTo(jid)
+ iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+
+ id = self.connection.getAnID()
+ iq.setID(id)
+ self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
+ if is_fake_jid:
+ room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
+ if not room_jid in self.room_jids:
+ self.room_jids.append(room_jid)
+ self.connection.send(iq)
+ #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
+ '''
+ pass
+
+ def send_vcard(self, vcard):
+ if not self.connection:
+ return
+ '''
+ iq = common.xmpp.Iq(typ = 'set')
+ iq2 = iq.setTag(common.xmpp.NS_VCARD + ' vCard')
+ for i in vcard:
+ if i == 'jid':
+ continue
+ if isinstance(vcard[i], dict):
+ iq3 = iq2.addChild(i)
+ for j in vcard[i]:
+ iq3.addChild(j).setData(vcard[i][j])
+ elif type(vcard[i]) == type([]):
+ for j in vcard[i]:
+ iq3 = iq2.addChild(i)
+ for k in j:
+ iq3.addChild(k).setData(j[k])
+ else:
+ iq2.addChild(i).setData(vcard[i])
+
+ id = self.connection.getAnID()
+ iq.setID(id)
+ self.connection.send(iq)
+
+ # Add the sha of the avatar
+ if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \
+ vcard['PHOTO'].has_key('BINVAL'):
+ photo = vcard['PHOTO']['BINVAL']
+ photo_decoded = base64.decodestring(photo)
+ our_jid = gajim.get_jid_from_account(self.name)
+ gajim.interface.save_avatar_files(our_jid, photo_decoded)
+ avatar_sha = sha.sha(photo_decoded).hexdigest()
+ iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
+
+ self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2)
+ '''
+ pass
+
+class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
+ def __init__(self):
+ ConnectionVcard.__init__(self)
+ ConnectionBytestream.__init__(self)
+ # List of IDs we are waiting answers for {id: (type_of_request, data), }
+ self.awaiting_answers = {}
+ # List of IDs that will produce a timeout is answer doesn't arrive
+ # {time_of_the_timeout: (id, message to send to gui), }
+ self.awaiting_timeouts = {}
+ # keep the jids we auto added (transports contacts) to not send the
+ # SUBSCRIBED event to gui
+ self.automatically_added = []
+ try:
+ idle.init()
+ except:
+ HAS_IDLE = False
+
+ def _messageCB(self, ip, con, msg):
+ '''Called when we receive a message'''
+ msgtxt = msg.getBody()
+ msghtml = msg.getXHTML()
+ mtype = msg.getType()
+ subject = msg.getSubject() # if not there, it's None
+ tim = msg.getTimestamp()
+ tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
+ tim = time.localtime(timegm(tim))
+ frm = msg.getFrom()
+ if frm == None:
+ for key in self.connection.zeroconf.contacts:
+ if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
+ frm = key
+ frm = str(frm)
+ jid = frm
+ no_log_for = gajim.config.get_per('accounts', self.name,
+ 'no_log_for').split()
+ encrypted = False
+ chatstate = None
+ encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
+ decmsg = ''
+ # invitations
+ invite = None
+ if not encTag:
+ invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
+ if invite and not invite.getTag('invite'):
+ invite = None
+ delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None
+ msg_id = None
+ composing_jep = None
+ # FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) do NOT RECOMMENDED
+ # invitation
+ # stanza (MUC JEP) remove in 2007, as we do not do NOT RECOMMENDED
+ xtags = msg.getTags('x')
+ # chatstates - look for chatstate tags in a message if not delayed
+ if not delayed:
+ composing_jep = False
+ children = msg.getChildren()
+ for child in children:
+ if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
+ chatstate = child.getName()
+ composing_jep = 'JEP-0085'
+ break
+ # No JEP-0085 support, fallback to JEP-0022
+ if not chatstate:
+ chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
+ if chatstate_child:
+ chatstate = 'active'
+ composing_jep = 'JEP-0022'
+ if not msgtxt and chatstate_child.getTag('composing'):
+ chatstate = 'composing'
+ # JEP-0172 User Nickname
+ user_nick = msg.getTagData('nick')
+ if not user_nick:
+ user_nick = ''
+
+ if encTag and GnuPG.USE_GPG:
+ #decrypt
+ encmsg = encTag.getData()
+
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID:
+ decmsg = self.gpg.decrypt(encmsg, keyID)
+ if decmsg:
+ msgtxt = decmsg
+ encrypted = True
+ if mtype == 'error':
+ error_msg = msg.getError()
+ if not error_msg:
+ error_msg = msgtxt
+ msgtxt = None
+ if self.name not in no_log_for:
+ gajim.logger.write('error', frm, error_msg, tim = tim,
+ subject = subject)
+ self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
+ tim))
+ elif mtype == 'chat': # it's type 'chat'
+ if not msg.getTag('body') and chatstate is None: #no <body>
+ return
+ if msg.getTag('body') and self.name not in no_log_for and jid not in\
+ no_log_for and msgtxt:
+ msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
+ subject = subject)
+ self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
+ chatstate, msg_id, composing_jep, user_nick, msghtml))
+ elif mtype == 'normal': # it's single message
+ if self.name not in no_log_for and jid not in no_log_for and msgtxt:
+ gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
+ subject = subject)
+ if invite:
+ self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
+ subject, chatstate, msg_id, composing_jep, user_nick))
+ # END messageCB
+ '''
+ def build_http_auth_answer(self, iq_obj, answer):
+ if answer == 'yes':
+ iq = iq_obj.buildReply('result')
+ elif answer == 'no':
+ iq = iq_obj.buildReply('error')
+ iq.setError('not-authorized', 401)
+ self.connection.send(iq)
+ '''
+
+ def parse_data_form(self, node):
+ dic = {}
+ tag = node.getTag('title')
+ if tag:
+ dic['title'] = tag.getData()
+ tag = node.getTag('instructions')
+ if tag:
+ dic['instructions'] = tag.getData()
+ i = 0
+ for child in node.getChildren():
+ if child.getName() != 'field':
+ continue
+ var = child.getAttr('var')
+ ctype = child.getAttr('type')
+ label = child.getAttr('label')
+ if not var and ctype != 'fixed': # We must have var if type != fixed
+ continue
+ dic[i] = {}
+ if var:
+ dic[i]['var'] = var
+ if ctype:
+ dic[i]['type'] = ctype
+ if label:
+ dic[i]['label'] = label
+ tags = child.getTags('value')
+ if len(tags):
+ dic[i]['values'] = []
+ for tag in tags:
+ data = tag.getData()
+ if ctype == 'boolean':
+ if data in ('yes', 'true', 'assent', '1'):
+ data = True
+ else:
+ data = False
+ dic[i]['values'].append(data)
+ tag = child.getTag('desc')
+ if tag:
+ dic[i]['desc'] = tag.getData()
+ option_tags = child.getTags('option')
+ if len(option_tags):
+ dic[i]['options'] = {}
+ j = 0
+ for option_tag in option_tags:
+ dic[i]['options'][j] = {}
+ label = option_tag.getAttr('label')
+ tags = option_tag.getTags('value')
+ dic[i]['options'][j]['values'] = []
+ for tag in tags:
+ dic[i]['options'][j]['values'].append(tag.getData())
+ if not label:
+ label = dic[i]['options'][j]['values'][0]
+ dic[i]['options'][j]['label'] = label
+ j += 1
+ if not dic[i].has_key('values'):
+ dic[i]['values'] = [dic[i]['options'][0]['values'][0]]
+ i += 1
+ return dic
+
+ def store_metacontacts(self, tags):
+ ''' fake empty method '''
+ # serverside metacontacts are not supported with zeroconf
+ # (there is no server)
+ pass
+ def remove_transfers_for_contact(self, contact):
+ ''' stop all active transfer for contact '''
+ '''for file_props in self.files_props.values():
+ if self.is_transfer_stoped(file_props):
+ continue
+ receiver_jid = unicode(file_props['receiver']).split('/')[0]
+ if contact.jid == receiver_jid:
+ file_props['error'] = -5
+ self.remove_transfer(file_props)
+ self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
+ sender_jid = unicode(file_props['sender']).split('/')[0]
+ if contact.jid == sender_jid:
+ file_props['error'] = -3
+ self.remove_transfer(file_props)
+ '''
+ pass
+
+ def remove_all_transfers(self):
+ ''' stops and removes all active connections from the socks5 pool '''
+ '''
+ for file_props in self.files_props.values():
+ self.remove_transfer(file_props, remove_from_list = False)
+ del(self.files_props)
+ self.files_props = {}
+ '''
+ pass
+
+ def remove_transfer(self, file_props, remove_from_list = True):
+ '''
+ if file_props is None:
+ return
+ self.disconnect_transfer(file_props)
+ sid = file_props['sid']
+ gajim.socks5queue.remove_file_props(self.name, sid)
+
+ if remove_from_list:
+ if self.files_props.has_key('sid'):
+ del(self.files_props['sid'])
+ '''
+ pass
+
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
new file mode 100644
index 000000000..c75ea4331
--- /dev/null
+++ b/src/common/zeroconf/connection_zeroconf.py
@@ -0,0 +1,487 @@
+## common/zeroconf/connection_zeroconf.py
+##
+## Contributors for this file:
+## - Yann Le Boulanger <asterix@lagaule.org>
+## - Nikos Kouremenos <nkour@jabber.org>
+## - Dimitur Kirov <dkirov@gmail.com>
+## - Travis Shirk <travis@pobox.com>
+## - Stefan Bethge <stefan@lanpartei.de>
+##
+## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
+## Vincent Hanquez <tab@snarc.org>
+## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
+## Vincent Hanquez <tab@snarc.org>
+## Nikos Kouremenos <nkour@jabber.org>
+## Dimitur Kirov <dkirov@gmail.com>
+## Travis Shirk <travis@pobox.com>
+## Norman Rasmussen <norman@rasmussen.co.za>
+## Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+
+import os
+import random
+random.seed()
+
+import signal
+if os.name != 'nt':
+ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+import getpass
+import gobject
+import notify
+
+from common import helpers
+from common import gajim
+from common import GnuPG
+from common.zeroconf import connection_handlers_zeroconf
+from common.zeroconf import client_zeroconf
+from connection_handlers_zeroconf import *
+
+USE_GPG = GnuPG.USE_GPG
+
+class ConnectionZeroconf(ConnectionHandlersZeroconf):
+ '''Connection class'''
+ def __init__(self, name):
+ ConnectionHandlersZeroconf.__init__(self)
+ # system username
+ self.username = None
+ self.name = name
+ self.connected = 0 # offline
+ self.connection = None
+ self.gpg = None
+ self.is_zeroconf = True
+ self.privacy_rules_supported = False
+ self.status = ''
+ self.old_show = ''
+ self.priority = 0
+
+ self.call_resolve_timeout = False
+
+ #self.time_to_reconnect = None
+ #self.new_account_info = None
+ self.bookmarks = []
+
+ #we don't need a password, but must be non-empty
+ self.password = 'zeroconf'
+
+ self.autoconnect = False
+ self.sync_with_global_status = True
+ self.no_log_for = False
+
+ # Do we continue connection when we get roster (send presence,get vcard...)
+ self.continue_connect_info = None
+ if USE_GPG:
+ self.gpg = GnuPG.GnuPG()
+ gajim.config.set('usegpg', True)
+ else:
+ gajim.config.set('usegpg', False)
+
+ self.get_config_values_or_default()
+
+ self.muc_jid = {} # jid of muc server for each transport type
+ self.vcard_supported = False
+
+ def _on_name_conflictCB(self, alt_name):
+ self.disconnect()
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('ZC_NAME_CONFLICT', alt_name)
+
+ def get_config_values_or_default(self):
+ ''' get name, host, port from config, or
+ create zeroconf account with default values'''
+
+ if not self.username:
+ self.username = unicode(getpass.getuser())
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', self.username)
+ else:
+ self.username = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name')
+
+ if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
+ print 'Creating zeroconf account'
+ gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect', True)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', '')
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', 'zeroconf')
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status', True)
+
+ #XXX make sure host is US-ASCII
+ self.host = unicode(socket.gethostname())
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', self.host)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', 5298)
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'is_zeroconf', True)
+ self.host = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname')
+ self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+ self.autoconnect = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect')
+ self.sync_with_global_status = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
+ self.no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for')
+ self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
+ self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
+ self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
+ self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+ # END __init__
+
+ def dispatch(self, event, data):
+ if gajim.handlers.has_key(event):
+ gajim.handlers[event](self.name, data)
+
+ def _reconnect(self):
+ gajim.log.debug('reconnect')
+
+ signed = self.get_signed_msg(self.status)
+ self.reconnect()
+
+ def quit(self, kill_core):
+ if kill_core and self.connected > 1:
+ self.disconnect()
+
+ def disable_account(self):
+ self.disconnect()
+
+ def test_gpg_passphrase(self, password):
+ self.gpg.passphrase = password
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ signed = self.gpg.sign('test', keyID)
+ self.gpg.password = None
+ return signed != 'BAD_PASSPHRASE'
+
+ def get_signed_msg(self, msg):
+ signed = ''
+ keyID = gajim.config.get_per('accounts', self.name, 'keyid')
+ if keyID and USE_GPG:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if self.connected < 2 and self.gpg.passphrase is None and \
+ not use_gpg_agent:
+ # We didn't set a passphrase
+ self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
+ #%s is the account name here
+ _('You will be connected to %s without OpenPGP.') % self.name))
+ elif self.gpg.passphrase is not None or use_gpg_agent:
+ signed = self.gpg.sign(msg, keyID)
+ if signed == 'BAD_PASSPHRASE':
+ signed = ''
+ if self.connected < 2:
+ self.dispatch('BAD_PASSPHRASE', ())
+ return signed
+
+ def _on_resolve_timeout(self):
+ if self.connected:
+ self.connection.resolve_all()
+ diffs = self.roster.getDiffs()
+ for key in diffs:
+ self.roster.setItem(key)
+ self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
+ 'both', 'no', self.roster.getGroups(key)))
+ self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
+ self.roster.getMessage(key), 'local', 0, None, 0))
+ #XXX open chat windows don't get refreshed (full name), add that
+ return self.call_resolve_timeout
+
+ # callbacks called from zeroconf
+ def _on_new_service(self,jid):
+ self.roster.setItem(jid)
+ self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
+ self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
+
+ def _on_remove_service(self, jid):
+ self.roster.delItem(jid)
+ # 'NOTIFY' (account, (jid, status, status message, resource, priority,
+ # keyID, timestamp))
+ self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0))
+
+ def _on_disconnected(self):
+ self.disconnect()
+ self.dispatch('STATUS', 'offline')
+ self.dispatch('CONNECTION_LOST',
+ (_('Connection with account "%s" has been lost') % self.name,
+ _('To continue sending and receiving messages, you will need to reconnect.')))
+ self.status = 'offline'
+ self.disconnect()
+
+ def connect(self, show = 'online', msg = ''):
+ self.get_config_values_or_default()
+ if not self.connection:
+ self.connection = client_zeroconf.ClientZeroconf(self)
+ if not self.connection.test_avahi():
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not connect to "%s"') % self.name,
+ _('Please check if Avahi is installed.')))
+ self.disconnect()
+ return
+ self.connection.connect(show, msg)
+ if not self.connection.listener:
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not start local service'),
+ _('Please check if avahi-daemon is running.')))
+ self.disconnect()
+ return
+ else:
+ self.connection.announce()
+ self.roster = self.connection.getRoster()
+ self.dispatch('ROSTER', self.roster)
+
+ #display contacts already detected and resolved
+ for jid in self.roster.keys():
+ self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
+ self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
+
+ self.connected = STATUS_LIST.index(show)
+
+ # refresh all contacts data every five seconds
+ self.call_resolve_timeout = True
+ gobject.timeout_add(5000, self._on_resolve_timeout)
+ return True
+
+ def disconnect(self, on_purpose = False):
+ self.connected = 0
+ self.time_to_reconnect = None
+ if self.connection:
+ self.connection.disconnect()
+ self.connection = None
+ # stop calling the timeout
+ self.call_resolve_timeout = False
+
+ def reannounce(self):
+ if self.connected:
+ txt = {}
+ txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
+ txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
+ txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
+ txt['email'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+ self.connection.reannounce(txt)
+
+ def update_details(self):
+ if self.connection:
+ port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+ if port != self.port:
+ self.port = port
+ last_msg = self.connection.last_msg
+ self.disconnect()
+ if not self.connect(self.status, last_msg):
+ return
+ if self.status != 'invisible':
+ self.connection.announce()
+ else:
+ self.reannounce()
+
+ def change_status(self, show, msg, sync = False, auto = False):
+ if not show in STATUS_LIST:
+ return -1
+ self.status = show
+
+ check = True #to check for errors from zeroconf
+ # 'connect'
+ if show != 'offline' and not self.connected:
+ if not self.connect(show, msg):
+ return
+ if show != 'invisible':
+ check = self.connection.announce()
+ else:
+ self.connected = STATUS_LIST.index(show)
+
+ # 'disconnect'
+ elif show == 'offline' and self.connected:
+ self.disconnect()
+ self.dispatch('STATUS', 'offline')
+
+ # update status
+ elif show != 'offline' and self.connected:
+ was_invisible = self.connected == STATUS_LIST.index('invisible')
+ self.connected = STATUS_LIST.index(show)
+ if show == 'invisible':
+ check = check and self.connection.remove_announce()
+ elif was_invisible:
+ check = check and self.connection.announce()
+ if self.connection and not show == 'invisible':
+ check = check and self.connection.set_show_msg(show, msg)
+
+ #stay offline when zeroconf does something wrong
+ if check:
+ self.dispatch('STATUS', show)
+ else:
+ # show notification that avahi, or system bus is down
+ self.dispatch('STATUS', 'offline')
+ self.status = 'offline'
+ self.dispatch('CONNECTION_LOST',
+ (_('Could not change status of account "%s"') % self.name,
+ _('Please check if avahi-daemon is running.')))
+
+ def get_status(self):
+ return STATUS_LIST[self.connected]
+
+ def send_message(self, jid, msg, keyID, type = 'chat', subject='',
+ chatstate = None, msg_id = None, composing_jep = None, resource = None,
+ user_nick = None):
+ fjid = jid
+
+ if not self.connection:
+ return
+ if not msg and chatstate is None:
+ return
+
+ msgtxt = msg
+ msgenc = ''
+ if keyID and USE_GPG:
+ #encrypt
+ msgenc = self.gpg.encrypt(msg, [keyID])
+ if msgenc:
+ msgtxt = '[This message is encrypted]'
+ lang = os.getenv('LANG')
+ if lang is not None or lang != 'en': # we're not english
+ msgtxt = _('[This message is encrypted]') +\
+ ' ([This message is encrypted])' # one in locale and one en
+
+
+ if type == 'chat':
+ msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
+
+ else:
+ if subject:
+ msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
+ typ = 'normal', subject = subject)
+ else:
+ msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
+ typ = 'normal')
+
+ if msgenc:
+ msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
+
+ # chatstates - if peer supports jep85 or jep22, send chatstates
+ # please note that the only valid tag inside a message containing a <body>
+ # tag is the active event
+ if chatstate is not None:
+ if composing_jep == 'JEP-0085' or not composing_jep:
+ # JEP-0085
+ msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
+ if composing_jep == 'JEP-0022' or not composing_jep:
+ # JEP-0022
+ chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
+ if not msgtxt: # when no <body>, add <id>
+ if not msg_id: # avoid putting 'None' in <id> tag
+ msg_id = ''
+ chatstate_node.setTagData('id', msg_id)
+ # when msgtxt, requests JEP-0022 composing notification
+ if chatstate is 'composing' or msgtxt:
+ chatstate_node.addChild(name = 'composing')
+
+ self.connection.send(msg_iq)
+ no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
+ ji = gajim.get_jid_without_resource(jid)
+ if self.name not in no_log_for and ji not in no_log_for:
+ log_msg = msg
+ if subject:
+ log_msg = _('Subject: %s\n%s') % (subject, msg)
+ if log_msg:
+ if type == 'chat':
+ kind = 'chat_msg_sent'
+ else:
+ kind = 'single_msg_sent'
+ gajim.logger.write(kind, jid, log_msg)
+
+ self.dispatch('MSGSENT', (jid, msg, keyID))
+
+ def send_stanza(self, stanza):
+ # send a stanza untouched
+ print 'connection_zeroconf.py: send_stanza'
+ if not self.connection:
+ return
+ #self.connection.send(stanza)
+ pass
+
+ def ack_subscribed(self, jid):
+ gajim.log.debug('This should not happen (ack_subscribed)')
+
+ def ack_unsubscribed(self, jid):
+ gajim.log.debug('This should not happen (ack_unsubscribed)')
+
+ def request_subscription(self, jid, msg = '', name = '', groups = [],
+ auto_auth = False):
+ gajim.log.debug('This should not happen (request_subscription)')
+
+ def send_authorization(self, jid):
+ gajim.log.debug('This should not happen (send_authorization)')
+
+ def refuse_authorization(self, jid):
+ gajim.log.debug('This should not happen (refuse_authorization)')
+
+ def unsubscribe(self, jid, remove_auth = True):
+ gajim.log.debug('This should not happen (unsubscribe)')
+
+ def unsubscribe_agent(self, agent):
+ gajim.log.debug('This should not happen (unsubscribe_agent)')
+
+ def update_contact(self, jid, name, groups):
+ if self.connection:
+ self.connection.getRoster().setItem(jid = jid, name = name,
+ groups = groups)
+
+ def new_account(self, name, config, sync = False):
+ gajim.log.debug('This should not happen (new_account)')
+
+ def _on_new_account(self, con = None, con_type = None):
+ gajim.log.debug('This should not happen (_on_new_account)')
+
+ def account_changed(self, new_name):
+ self.name = new_name
+
+ def request_last_status_time(self, jid, resource):
+ gajim.log.debug('This should not happen (request_last_status_time)')
+
+ def request_os_info(self, jid, resource):
+ gajim.log.debug('This should not happen (request_os_info)')
+
+ def get_settings(self):
+ gajim.log.debug('This should not happen (get_settings)')
+
+ def get_bookmarks(self):
+ gajim.log.debug('This should not happen (get_bookmarks)')
+
+ def store_bookmarks(self):
+ gajim.log.debug('This should not happen (store_bookmarks)')
+
+ def get_metacontacts(self):
+ gajim.log.debug('This should not happen (get_metacontacts)')
+
+ def send_agent_status(self, agent, ptype):
+ gajim.log.debug('This should not happen (send_agent_status)')
+
+ def gpg_passphrase(self, passphrase):
+ if USE_GPG:
+ use_gpg_agent = gajim.config.get('use_gpg_agent')
+ if use_gpg_agent:
+ self.gpg.passphrase = None
+ else:
+ self.gpg.passphrase = passphrase
+
+ def ask_gpg_keys(self):
+ if USE_GPG:
+ keys = self.gpg.get_keys()
+ return keys
+ return None
+
+ def ask_gpg_secrete_keys(self):
+ if USE_GPG:
+ keys = self.gpg.get_secret_keys()
+ return keys
+ return None
+
+ def _event_dispatcher(self, realm, event, data):
+ if realm == '':
+ if event == common.xmpp.transports.DATA_RECEIVED:
+ self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
+ elif event == common.xmpp.transports.DATA_SENT:
+ self.dispatch('STANZA_SENT', unicode(data))
+
+# END ConnectionZeroconf
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
new file mode 100644
index 000000000..7924e70f7
--- /dev/null
+++ b/src/common/zeroconf/roster_zeroconf.py
@@ -0,0 +1,152 @@
+## common/zeroconf/roster_zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+
+from common.zeroconf import zeroconf
+
+class Roster:
+ def __init__(self, zeroconf):
+ self._data = None
+ self.zeroconf = zeroconf # our zeroconf instance
+
+ def update_roster(self):
+ for val in self.zeroconf.contacts.values():
+ self.setItem(val[zeroconf.C_NAME])
+
+ def getRoster(self):
+ #print 'roster_zeroconf.py: getRoster'
+ if self._data is None:
+ self._data = {}
+ self.update_roster()
+ return self
+
+ def getDiffs(self):
+ ''' update the roster with new data and return dict with
+ jid -> new status pairs to do notifications and stuff '''
+
+ diffs = {}
+ old_data = self._data.copy()
+ self.update_roster()
+ for key in old_data.keys():
+ if self._data.has_key(key):
+ if old_data[key] != self._data[key]:
+ diffs[key] = self._data[key]['status']
+ #print 'roster_zeroconf.py: diffs:' + str(diffs)
+ return diffs
+
+ def setItem(self, jid, name = '', groups = ''):
+ #print 'roster_zeroconf.py: setItem %s' % jid
+ (service_jid, domain, interface, protocol, host, address, port, bare_jid, txt) \
+ = self.zeroconf.get_contact(jid)
+
+ self._data[jid]={}
+ self._data[jid]['ask'] = 'no' #?
+ self._data[jid]['subscription'] = 'both'
+ self._data[jid]['groups'] = []
+ self._data[jid]['resources'] = {}
+ self._data[jid]['address'] = address
+ self._data[jid]['host'] = host
+ self._data[jid]['port'] = port
+ txt_dict = self.zeroconf.txt_array_to_dict(txt)
+ if txt_dict.has_key('status'):
+ status = txt_dict['status']
+ else:
+ status = ''
+ nm = ''
+ if txt_dict.has_key('1st'):
+ nm = txt_dict['1st']
+ if txt_dict.has_key('last'):
+ if nm != '':
+ nm += ' '
+ nm += txt_dict['last']
+ if nm:
+ self._data[jid]['name'] = nm
+ else:
+ self._data[jid]['name'] = jid
+ if status == 'avail':
+ status = 'online'
+ self._data[jid]['txt_dict'] = txt_dict
+ if not self._data[jid]['txt_dict'].has_key('msg'):
+ self._data[jid]['txt_dict']['msg'] = ''
+ self._data[jid]['status'] = status
+ self._data[jid]['show'] = status
+
+ def delItem(self, jid):
+ #print 'roster_zeroconf.py: delItem %s' % jid
+ if self._data.has_key(jid):
+ del self._data[jid]
+
+ def getItem(self, jid):
+ #print 'roster_zeroconf.py: getItem: %s' % jid
+ if self._data.has_key(jid):
+ return self._data[jid]
+
+ def __getitem__(self,jid):
+ #print 'roster_zeroconf.py: __getitem__'
+ return self._data[jid]
+
+ def getItems(self):
+ #print 'roster_zeroconf.py: getItems'
+ # Return list of all [bare] JIDs that the roster currently tracks.
+ return self._data.keys()
+
+ def keys(self):
+ #print 'roster_zeroconf.py: keys'
+ return self._data.keys()
+
+ def getRaw(self):
+ #print 'roster_zeroconf.py: getRaw'
+ return self._data
+
+ def getResources(self, jid):
+ #print 'roster_zeroconf.py: getResources(%s)' % jid
+ return {}
+
+ def getGroups(self, jid):
+ return self._data[jid]['groups']
+
+ def getName(self, jid):
+ if self._data.has_key(jid):
+ return self._data[jid]['name']
+
+ def getStatus(self, jid):
+ if self._data.has_key(jid):
+ return self._data[jid]['status']
+
+ def getMessage(self, jid):
+ if self._data.has_key(jid):
+ return self._data[jid]['txt_dict']['msg']
+
+ def getShow(self, jid):
+ #print 'roster_zeroconf.py: getShow'
+ return getStatus(jid)
+
+ def getPriority(jid):
+ return 5
+
+ def getSubscription(self,jid):
+ #print 'roster_zeroconf.py: getSubscription'
+ return 'both'
+
+ def Subscribe(self,jid):
+ pass
+
+ def Unsubscribe(self,jid):
+ pass
+
+ def Authorize(self,jid):
+ pass
+
+ def Unauthorize(self,jid):
+ pass
diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py
new file mode 100755
index 000000000..b18fdb28c
--- /dev/null
+++ b/src/common/zeroconf/zeroconf.py
@@ -0,0 +1,373 @@
+## common/zeroconf/zeroconf.py
+##
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 2 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+
+import os
+import sys
+import socket
+from common import gajim
+from common import xmpp
+
+try:
+ import avahi, gobject, dbus
+except ImportError:
+ gajim.log.debug('Error: python-avahi and python-dbus need to be installed. No zeroconf support.')
+
+try:
+ import dbus.glib
+except ImportError, e:
+ pass
+
+
+C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \
+C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9)
+
+class Zeroconf:
+ def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
+ disconnected_CB, name, host, port):
+ self.server = None
+ self.domain = None # specific domain to browse
+ self.stype = '_presence._tcp'
+ self.port = port # listening port that gets announced
+ self.username = name
+ self.host = host
+ self.txt = {} # service data
+
+ #XXX these CBs should be set to None when we destroy the object
+ # (go offline), because they create a circular reference
+ self.new_serviceCB = new_serviceCB
+ self.remove_serviceCB = remove_serviceCB
+ self.name_conflictCB = name_conflictCB
+ self.disconnected_CB = disconnected_CB
+
+ self.service_browser = None
+ self.domain_browser = None
+ self.server = None
+ self.contacts = {} # all current local contacts with data
+ self.entrygroup = None
+ self.connected = False
+ self.announced = False
+ self.invalid_self_contact = {}
+
+
+ ## handlers for dbus callbacks
+ def entrygroup_commit_error_CB(self, err):
+ # left for eventual later use
+ pass
+
+ def error_callback1(self, err):
+ gajim.log.debug('RR' + str(err))
+
+ def error_callback(self, err):
+ gajim.log.debug(str(err))
+ # timeouts are non-critical
+ if str(err) != 'Timeout reached':
+ self.disconnect()
+ self.disconnected_CB()
+
+ def new_service_callback(self, interface, protocol, name, stype, domain, flags):
+ gajim.log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, interface, protocol))
+ # if not self.connected:
+ # return
+
+ # synchronous resolving
+ self.server.ResolveService( int(interface), int(protocol), name, stype, \
+ domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), \
+ reply_handler=self.service_resolved_callback, error_handler=self.error_callback1)
+
+ def remove_service_callback(self, interface, protocol, name, stype, domain, flags):
+ gajim.log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, domain, interface, protocol))
+ # if not self.connected:
+ # return
+ if name != self.name:
+ for key in self.contacts.keys():
+ if self.contacts[key][C_BARE_NAME] == name:
+ del self.contacts[key]
+ self.remove_serviceCB(key)
+ return
+
+ def new_service_type(self, interface, protocol, stype, domain, flags):
+ # Are we already browsing this domain for this type?
+ if self.service_browser:
+ return
+
+ object_path = self.server.ServiceBrowserNew(interface, protocol, \
+ stype, domain, dbus.UInt32(0))
+
+ self.service_browser = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, \
+ object_path) , avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+ self.service_browser.connect_to_signal('ItemNew', self.new_service_callback)
+ self.service_browser.connect_to_signal('ItemRemove', self.remove_service_callback)
+ self.service_browser.connect_to_signal('Failure', self.error_callback)
+
+ def new_domain_callback(self,interface, protocol, domain, flags):
+ if domain != "local":
+ self.browse_domain(interface, protocol, domain)
+
+ def txt_array_to_dict(self,txt_array):
+ items = {}
+
+ for byte_array in txt_array:
+ # 'str' is used for string type in python
+ value = avahi.byte_array_to_string(byte_array)
+ poseq = value.find('=')
+ items[value[:poseq]] = value[poseq+1:]
+ return items
+
+ def service_resolved_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ gajim.log.debug('Service data for service %s in domain %s on %i.%i:' % (name, domain, interface, protocol))
+ gajim.log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, port, avahi.txt_array_to_string_array(txt)))
+ if not self.connected:
+ return
+ bare_name = name
+ if name.find('@') == -1:
+ name = name + '@' + name
+
+ # we don't want to see ourselves in the list
+ if name != self.name:
+ self.contacts[name] = (name, domain, interface, protocol, host, address, port,
+ bare_name, txt)
+ self.new_serviceCB(name)
+ else:
+ # remember data
+ # In case this is not our own record but of another
+ # gajim instance on the same machine,
+ # it will be used when we get a new name.
+ self.invalid_self_contact[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt)
+
+
+ # different handler when resolving all contacts
+ def service_resolved_all_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ if not self.connected:
+ return
+ bare_name = name
+ if name.find('@') == -1:
+ name = name + '@' + name
+ self.contacts[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt)
+
+ def service_added_callback(self):
+ gajim.log.debug('Service successfully added')
+
+ def service_committed_callback(self):
+ gajim.log.debug('Service successfully committed')
+
+ def service_updated_callback(self):
+ gajim.log.debug('Service successfully updated')
+
+ def service_add_fail_callback(self, err):
+ gajim.log.debug('Error while adding service. %s' % str(err))
+ alternative_name = self.server.GetAlternativeServiceName(self.username)
+ self.disconnect()
+ self.name_conflictCB(alternative_name)
+
+ def server_state_changed_callback(self, state, error):
+ print 'server.state %s' % state
+ if state == avahi.SERVER_RUNNING:
+ self.create_service()
+ elif state == avahi.SERVER_COLLISION:
+ self.entrygroup.Reset()
+ elif state == avahi.CLIENT_FAILURE: # TODO: add error handling (avahi daemon dies...?)
+ print 'CLIENT FAILURE'
+
+ def entrygroup_state_changed_callback(self, state, error):
+ # the name is already present, so recreate
+ if state == avahi.ENTRY_GROUP_COLLISION:
+ self.service_add_fail_callback('Local name collision, recreating.')
+ elif state == avahi.ENTRY_GROUP_FAILURE:
+ print 'zeroconf.py: ENTRY_GROUP_FAILURE reached(that should not happen)'
+
+ # make zeroconf-valid names
+ def replace_show(self, show):
+ if show in ['chat', 'online', '']:
+ return 'avail'
+ elif show == 'xa':
+ return 'away'
+ return show
+
+ def create_service(self):
+ try:
+ if not self.entrygroup:
+ # create an EntryGroup for publishing
+ self.entrygroup = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
+ self.entrygroup.connect_to_signal('StateChanged', self.entrygroup_state_changed_callback)
+
+ txt = {}
+
+ #remove empty keys
+ for key,val in self.txt.iteritems():
+ if val:
+ txt[key] = val
+
+ txt['port.p2pj'] = self.port
+ txt['version'] = 1
+ txt['txtvers'] = 1
+
+ # replace gajim's show messages with compatible ones
+ if self.txt.has_key('status'):
+ txt['status'] = self.replace_show(self.txt['status'])
+ else:
+ txt['status'] = 'avail'
+
+ self.txt = txt
+ gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
+ self.entrygroup.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', '', self.port, avahi.dict_to_txt_array(self.txt), reply_handler=self.service_added_callback, error_handler=self.service_add_fail_callback)
+ self.entrygroup.Commit(reply_handler=self.service_committed_callback,
+ error_handler=self.entrygroup_commit_error_CB)
+
+ return True
+
+ except dbus.dbus_bindings.DBusException, e:
+ gajim.log.debug(str(e))
+ return False
+
+ def announce(self):
+ if not self.connected:
+ return False
+
+ state = self.server.GetState()
+ if state == avahi.SERVER_RUNNING:
+ self.create_service()
+ self.announced = True
+ return True
+
+ def remove_announce(self):
+ if self.announced == False:
+ return False
+ try:
+ if self.entrygroup.GetState() != avahi.ENTRY_GROUP_FAILURE:
+ self.entrygroup.Reset()
+ self.entrygroup.Free()
+ self.entrygroup = None
+ self.announced = False
+ return True
+ else:
+ return False
+ except dbus.dbus_bindings.DBusException, e:
+ gajim.log.debug("Can't remove service. That should not happen")
+
+ def browse_domain(self, interface, protocol, domain):
+ self.new_service_type(interface, protocol, self.stype, domain, '')
+
+ def avahi_dbus_connect_cb(self, a, connect, disconnect):
+ if connect != "":
+ gajim.log.debug('Lost connection to avahi-daemon')
+ try:
+ self.connected = False
+ self.disconnect()
+ self.disconnected_CB()
+ except Exception, e:
+ print e
+ else:
+ gajim.log.debug('We are connected to avahi-daemon')
+
+
+
+ # connect to dbus
+ def connect_dbus(self):
+ if self.server:
+ return True
+ try:
+ self.bus = dbus.SystemBus()
+ self.bus.add_signal_receiver(self.avahi_dbus_connect_cb,
+ "NameOwnerChanged", "org.freedesktop.DBus",
+ arg0="org.freedesktop.Avahi")
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, \
+ avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self.server.connect_to_signal('StateChanged',
+ self.server_state_changed_callback)
+ except Exception, e:
+ # Avahi service is not present
+ self.server = None
+ gajim.log.debug(str(e))
+ return False
+ else:
+ return True
+
+ def connect(self):
+ self.name = self.username + '@' + self.host # service name
+ if not self.connect_dbus():
+ return False
+
+ self.connected = True
+ # start browsing
+ if self.domain is None:
+ # Explicitly browse .local
+ self.browse_domain(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local")
+
+ # Browse for other browsable domains
+ self.domain_browser = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, \
+ self.server.DomainBrowserNew(avahi.IF_UNSPEC, \
+ avahi.PROTO_UNSPEC, '', avahi.DOMAIN_BROWSER_BROWSE,\
+ dbus.UInt32(0))), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+ self.domain_browser.connect_to_signal('ItemNew', self.new_domain_callback)
+ self.domain_browser.connect_to_signal('Failure', self.error_callback)
+ else:
+ self.browse_domain(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, domain)
+
+ return True
+
+ def disconnect(self):
+ if self.connected:
+ self.connected = False
+ if self.service_browser:
+ self.service_browser.Free()
+ if self.domain_browser:
+ self.domain_browser.Free()
+ self.remove_announce()
+ self.service_browser = None
+ self.domain_browser = None
+ self.server = None
+
+ # refresh txt data of all contacts manually (no callback available)
+ def resolve_all(self):
+ for val in self.contacts.values():
+ self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), val[C_BARE_NAME], \
+ self.stype, val[C_DOMAIN], avahi.PROTO_UNSPEC, dbus.UInt32(0),\
+ reply_handler=self.service_resolved_all_callback, error_handler=self.error_callback)
+
+ def get_contacts(self):
+ return self.contacts
+
+ def get_contact(self, jid):
+ return self.contacts[jid]
+
+ def update_txt(self, show = None):
+ if show:
+ self.txt['status'] = self.replace_show(show)
+
+ txt = avahi.dict_to_txt_array(self.txt)
+ if self.connected and self.entrygroup:
+ self.entrygroup.UpdateServiceTxt(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype,'', txt, reply_handler=self.service_updated_callback, error_handler=self.error_callback)
+ return True
+ else:
+ return False
+
+
+# END Zeroconf
+
+'''
+# how to use
+
+ zeroconf = Zeroconf()
+ zeroconf.connect()
+ zeroconf.txt['1st'] = 'foo'
+ zeroconf.txt['last'] = 'bar'
+ zeroconf.txt['email'] = foo@bar.org
+ zeroconf.announce()
+
+ # updating after announcing
+ txt = {}
+ txt['status'] = 'avail'
+ txt['msg'] = 'Here I am'
+ zeroconf.update_txt(txt)
+'''
diff --git a/src/config.py b/src/config.py
index 1c8cd49fd..e3aa82d61 100644
--- a/src/config.py
+++ b/src/config.py
@@ -4,6 +4,7 @@
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org>
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
@@ -38,6 +39,7 @@ from common import helpers
from common import gajim
from common import connection
from common import passwords
+from common import zeroconf
from common import dbus_support
from common.exceptions import GajimGeneralException
@@ -1374,6 +1376,9 @@ class AccountModificationWindow:
config['custom_host'] = self.xml.get_widget(
'custom_host_entry').get_text().decode('utf-8')
+ # update in case the name was changed to local account's name
+ config['is_zeroconf'] = False
+
config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
if config['keyname'] == '': #no key selected
config['keyid'] = ''
@@ -1808,6 +1813,22 @@ class AccountsWindow:
st = gajim.config.get('mergeaccounts')
self.xml.get_widget('merge_checkbutton').set_active(st)
+ import os
+
+ avahi_error = False
+ try:
+ import avahi
+ except ImportError:
+ avahi_error = True
+
+ # enable zeroconf
+ st = gajim.config.get('enable_zeroconf')
+ w = self.xml.get_widget('enable_zeroconf_checkbutton')
+ w.set_active(st)
+ if os.name == 'nt' or (avahi_error and not w.get_active()):
+ w.set_sensitive(False)
+ self.zeroconf_toggled_id = w.connect('toggled', self.on_enable_zeroconf_checkbutton_toggled)
+
def on_accounts_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
self.window.destroy()
@@ -1847,6 +1868,17 @@ class AccountsWindow:
dialogs.ErrorDialog(_('Unread events'),
_('Read all pending events before removing this account.'))
return
+
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ w = self.xml.get_widget('enable_zeroconf_checkbutton')
+ w.set_active(False)
+ else:
+ if gajim.interface.instances[account].has_key('remove_account'):
+ gajim.interface.instances[account]['remove_account'].window.present()
+ else:
+ gajim.interface.instances[account]['remove_account'] = \
+ RemoveAccountWindow(account)
+
win_opened = False
if gajim.interface.msg_win_mgr.get_controls(acct = account):
win_opened = True
@@ -1891,21 +1923,107 @@ class AccountsWindow:
self.show_modification_window(account)
def show_modification_window(self, account):
- if gajim.interface.instances[account].has_key('account_modification'):
- gajim.interface.instances[account]['account_modification'].window.present()
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ if gajim.interface.instances.has_key('zeroconf_properties'):
+ gajim.interface.instances['zeroconf_properties'].window.present()
+ else:
+ gajim.interface.instances['zeroconf_properties'] = \
+ ZeroconfPropertiesWindow()
else:
- gajim.interface.instances[account]['account_modification'] = \
- AccountModificationWindow(account)
+ if gajim.interface.instances[account].has_key('account_modification'):
+ gajim.interface.instances[account]['account_modification'].window.present()
+ else:
+ gajim.interface.instances[account]['account_modification'] = \
+ AccountModificationWindow(account)
- def on_merge_checkbutton_toggled(self, widget):
- gajim.config.set('mergeaccounts', widget.get_active())
+ def on_checkbutton_toggled(self, widget, config_name,
+ change_sensitivity_widgets = None):
+ gajim.config.set(config_name, widget.get_active())
+ if change_sensitivity_widgets:
+ for w in change_sensitivity_widgets:
+ w.set_sensitive(widget.get_active())
gajim.interface.save_config()
+
+ def on_merge_checkbutton_toggled(self, widget):
+ self.on_checkbutton_toggled(widget, 'mergeaccounts')
if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
else:
gajim.interface.roster.regroup = False
gajim.interface.roster.draw_roster()
+
+ def on_enable_zeroconf_checkbutton_toggled(self, widget):
+ # don't do anything if there is an account with the local name but is a normal account
+ if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME) and not gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', (_('Account Local already exists.'),_('Please rename or remove it before enabling link-local messaging.')))
+ widget.disconnect(self.zeroconf_toggled_id)
+ widget.set_active(False)
+ self.zeroconf_toggled_id = widget.connect('toggled', self.on_enable_zeroconf_checkbutton_toggled)
+ return
+
+ if gajim.config.get('enable_zeroconf'):
+ #disable
+ gajim.interface.roster.close_all(gajim.ZEROCONF_ACC_NAME)
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].disable_account()
+ del gajim.connections[gajim.ZEROCONF_ACC_NAME]
+ gajim.interface.save_config()
+ del gajim.interface.instances[gajim.ZEROCONF_ACC_NAME]
+ del gajim.nicks[gajim.ZEROCONF_ACC_NAME]
+ del gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME]
+ del gajim.groups[gajim.ZEROCONF_ACC_NAME]
+ gajim.contacts.remove_account(gajim.ZEROCONF_ACC_NAME)
+ del gajim.gc_connected[gajim.ZEROCONF_ACC_NAME]
+ del gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME]
+ del gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME]
+ del gajim.newly_added[gajim.ZEROCONF_ACC_NAME]
+ del gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME]
+ del gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME]
+ del gajim.last_message_time[gajim.ZEROCONF_ACC_NAME]
+ del gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME]
+ if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.draw_roster()
+ gajim.interface.roster.actions_menu_needs_rebuild = True
+ if gajim.interface.instances.has_key('accounts'):
+ gajim.interface.instances['accounts'].init_accounts()
+
+ else:
+ # enable (will create new account if not present)
+ gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
+ # update variables
+ gajim.interface.instances[gajim.ZEROCONF_ACC_NAME] = {'infos': {}, 'disco': {},
+ 'gc_config': {}}
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].connected = 0
+ gajim.groups[gajim.ZEROCONF_ACC_NAME] = {}
+ gajim.contacts.add_account(gajim.ZEROCONF_ACC_NAME)
+ gajim.gc_connected[gajim.ZEROCONF_ACC_NAME] = {}
+ gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME] = {}
+ gajim.newly_added[gajim.ZEROCONF_ACC_NAME] = []
+ gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME] = []
+ gajim.nicks[gajim.ZEROCONF_ACC_NAME] = gajim.ZEROCONF_ACC_NAME
+ gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME] = True
+ gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME] = 'off'
+ gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME] = []
+ gajim.last_message_time[gajim.ZEROCONF_ACC_NAME] = {}
+ gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME] = ''
+ # refresh accounts window
+ if gajim.interface.instances.has_key('accounts'):
+ gajim.interface.instances['accounts'].init_accounts()
+ # refresh roster
+ if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
+ gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
+ else:
+ gajim.interface.roster.regroup = False
+ gajim.interface.roster.draw_roster()
+ gajim.interface.roster.actions_menu_needs_rebuild = True
+ gajim.interface.save_config()
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].change_status('online', '')
+
+ self.on_checkbutton_toggled(widget, 'enable_zeroconf')
+
class DataFormWindow:
def __init__(self, account, config):
self.account = account
@@ -3034,3 +3152,203 @@ _('You can set advanced account options by pressing Advanced button, or later by
gajim.interface.roster.draw_roster()
gajim.interface.roster.actions_menu_needs_rebuild = True
gajim.interface.save_config()
+
+#---------- ZeroconfPropertiesWindow class -------------#
+class ZeroconfPropertiesWindow:
+ def __init__(self):
+ self.xml = gtkgui_helpers.get_glade('zeroconf_properties_window.glade')
+ self.window = self.xml.get_widget('zeroconf_properties_window')
+ self.window.set_transient_for(gajim.interface.roster.window)
+ self.xml.signal_autoconnect(self)
+
+ self.init_account()
+ self.init_account_gpg()
+
+ self.xml.get_widget('save_button').grab_focus()
+ self.window.show_all()
+
+ def init_account(self):
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect')
+ if st:
+ self.xml.get_widget('autoconnect_checkbutton').set_active(st)
+
+ list_no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,'no_log_for').split()
+ if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
+ self.xml.get_widget('log_history_checkbutton').set_active(0)
+ else:
+ self.xml.get_widget('log_history_checkbutton').set_active(1)
+
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
+ if st:
+ self.xml.get_widget('sync_with_global_status_checkbutton').set_active(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
+ if st:
+ self.xml.get_widget('first_name_entry').set_text(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
+ if st:
+ self.xml.get_widget('last_name_entry').set_text(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
+ if st:
+ self.xml.get_widget('jabber_id_entry').set_text(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
+ if st:
+ self.xml.get_widget('email_entry').set_text(st)
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+ if st:
+ self.xml.get_widget('custom_port_entry').set_text(str(st))
+
+ st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'use_custom_host')
+ if st:
+ self.xml.get_widget('custom_port_checkbutton').set_active(st)
+
+ self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
+
+ if not st:
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', '5298')
+
+ def init_account_gpg(self):
+ keyid = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyid')
+ keyname = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyname')
+ savegpgpass = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,'savegpgpass')
+
+ if not keyid or not gajim.config.get('usegpg'):
+ return
+
+ self.xml.get_widget('gpg_key_label').set_text(keyid)
+ self.xml.get_widget('gpg_name_label').set_text(keyname)
+ gpg_save_password_checkbutton = \
+ self.xml.get_widget('gpg_save_password_checkbutton')
+ gpg_save_password_checkbutton.set_sensitive(True)
+ gpg_save_password_checkbutton.set_active(savegpgpass)
+
+ if savegpgpass:
+ entry = self.xml.get_widget('gpg_password_entry')
+ entry.set_sensitive(True)
+ gpgpassword = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'gpgpassword')
+ entry.set_text(gpgpassword)
+
+ def on_zeroconf_properties_window_destroy(self, widget):
+ #close window
+ if gajim.interface.instances.has_key('zeroconf_properties'):
+ del gajim.interface.instances['zeroconf_properties']
+
+ def on_custom_port_checkbutton_toggled(self, widget):
+ st = self.xml.get_widget('custom_port_checkbutton').get_active()
+ self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_save_button_clicked(self, widget):
+ config = {}
+
+ st = self.xml.get_widget('autoconnect_checkbutton').get_active()
+ config['autoconnect'] = st
+ list_no_log_for = gajim.config.get_per('accounts',
+ gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
+ if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
+ list_no_log_for.remove(gajim.ZEROCONF_ACC_NAME)
+ if not self.xml.get_widget('log_history_checkbutton').get_active():
+ list_no_log_for.append(gajim.ZEROCONF_ACC_NAME)
+ config['no_log_for'] = ' '.join(list_no_log_for)
+
+ st = self.xml.get_widget('sync_with_global_status_checkbutton').get_active()
+ config['sync_with_global_status'] = st
+
+ st = self.xml.get_widget('first_name_entry').get_text()
+ config['zeroconf_first_name'] = st.decode('utf-8')
+
+ st = self.xml.get_widget('last_name_entry').get_text()
+ config['zeroconf_last_name'] = st.decode('utf-8')
+
+ st = self.xml.get_widget('jabber_id_entry').get_text()
+ config['zeroconf_jabber_id'] = st.decode('utf-8')
+
+ st = self.xml.get_widget('email_entry').get_text()
+ config['zeroconf_email'] = st.decode('utf-8')
+
+ use_custom_port = self.xml.get_widget('custom_port_checkbutton').get_active()
+ config['use_custom_host'] = use_custom_port
+
+ old_port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
+ if use_custom_port:
+ port = self.xml.get_widget('custom_port_entry').get_text()
+ else:
+ port = 5298
+
+ config['custom_port'] = port
+
+ config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
+ if config['keyname'] == '': #no key selected
+ config['keyid'] = ''
+ config['savegpgpass'] = False
+ config['gpgpassword'] = ''
+ else:
+ config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().decode('utf-8')
+ config['savegpgpass'] = self.xml.get_widget(
+ 'gpg_save_password_checkbutton').get_active()
+ config['gpgpassword'] = self.xml.get_widget('gpg_password_entry'
+ ).get_text().decode('utf-8')
+
+ reconnect = False
+ for opt in ('zeroconf_first_name','zeroconf_last_name', 'zeroconf_jabber_id', 'zeroconf_email', 'custom_port'):
+ if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, opt) != config[opt]:
+ reconnect = True
+
+ for opt in config:
+ gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, opt, config[opt])
+
+ if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
+ if port != old_port or reconnect:
+ gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
+
+ self.window.destroy()
+
+ def on_gpg_choose_button_clicked(self, widget, data = None):
+ if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
+ secret_keys = gajim.connections[gajim.ZEROCONF_ACC_NAME].ask_gpg_secrete_keys()
+
+ # self.account is None and/or gajim.connections is {}
+ else:
+ from common import GnuPG
+ if GnuPG.USE_GPG:
+ secret_keys = GnuPG.GnuPG().get_secret_keys()
+ else:
+ secret_keys = []
+ if not secret_keys:
+ dialogs.ErrorDialog(_('Failed to get secret keys'),
+ _('There was a problem retrieving your OpenPGP secret keys.'))
+ return
+ secret_keys[_('None')] = _('None')
+ instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
+ _('Choose your OpenPGP key'), secret_keys)
+ keyID = instance.run()
+ if keyID is None:
+ return
+ checkbutton = self.xml.get_widget('gpg_save_password_checkbutton')
+ gpg_key_label = self.xml.get_widget('gpg_key_label')
+ gpg_name_label = self.xml.get_widget('gpg_name_label')
+ if keyID[0] == _('None'):
+ gpg_key_label.set_text(_('No key selected'))
+ gpg_name_label.set_text('')
+ checkbutton.set_sensitive(False)
+ self.xml.get_widget('gpg_password_entry').set_sensitive(False)
+ else:
+ gpg_key_label.set_text(keyID[0])
+ gpg_name_label.set_text(keyID[1])
+ checkbutton.set_sensitive(True)
+ checkbutton.set_active(False)
+ self.xml.get_widget('gpg_password_entry').set_text('')
+
+ def on_gpg_save_password_checkbutton_toggled(self, widget):
+ st = widget.get_active()
+ w = self.xml.get_widget('gpg_password_entry')
+ w.set_sensitive(bool(st))
+# w.set_text = ''
diff --git a/src/gajim.py b/src/gajim.py
index 5584b89ca..413f5705c 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -31,6 +31,7 @@ import message_control
from chat_control import ChatControlBase
from common import exceptions
+from common.zeroconf import connection_zeroconf
if os.name == 'posix': # dl module is Unix Only
try: # rename the process name to gajim
@@ -618,7 +619,9 @@ class Interface:
jids = full_jid_with_resource.split('/', 1)
jid = jids[0]
gc_control = self.msg_win_mgr.get_control(jid, account)
- if gc_control and gc_control.type_id == message_control.TYPE_GC:
+ if gc_control and gc_control.type_id != message_control.TYPE_GC:
+ gc_control = None
+ if gc_control:
if len(jids) > 1: # it's a pm
nick = jids[1]
if not self.msg_win_mgr.get_control(full_jid_with_resource,
@@ -1406,6 +1409,20 @@ class Interface:
if win.startswith('privacy_list_'):
self.instances[account][win].check_active_default(data)
+ def handle_event_zc_name_conflict(self, account, data):
+ dlg = dialogs.InputDialog(_('Username Conflict'),
+ _('Please type a new username for your local account'),
+ is_modal = True)
+ dlg.input_entry.set_text(data)
+ response = dlg.get_response()
+ if response == gtk.RESPONSE_OK:
+ new_name = dlg.input_entry.get_text()
+ gajim.config.set_per('accounts', account, 'name', new_name)
+ status = gajim.connections[account].status
+ gajim.connections[account].username = new_name
+ gajim.connections[account].change_status(status, '')
+
+
def read_sleepy(self):
'''Check idle status and change that status if needed'''
if not self.sleeper.poll():
@@ -1711,6 +1728,7 @@ class Interface:
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
self.handle_event_privacy_lists_active_default,
+ 'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
}
gajim.handlers = self.handlers
@@ -1867,9 +1885,13 @@ class Interface:
self.handle_event_file_progress)
gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
self.register_handlers()
+ if gajim.config.get('enable_zeroconf'):
+ gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
for account in gajim.config.get_per('accounts'):
- gajim.connections[account] = common.connection.Connection(account)
-
+ if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ gajim.connections[account] = common.connection.Connection(account)
+
+ # gtk hooks
# gtk hooks
gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
diff --git a/src/roster_window.py b/src/roster_window.py
index 881ce56aa..1edabc878 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -1119,6 +1119,14 @@ class RosterWindow:
else:
info[contact.jid] = vcard.VcardWindow(contact, account)
+ def on_info_zeroconf(self, widget, contact, account):
+ info = gajim.interface.instances[account]['infos']
+ if info.has_key(contact.jid):
+ info[contact.jid].window.present()
+ else:
+ info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
+
+
def show_tooltip(self, contact):
pointer = self.tree.get_pointer()
props = self.tree.get_path_at_pos(pointer[0], pointer[1])
@@ -1363,6 +1371,118 @@ class RosterWindow:
if not contact:
return
+ if gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade')
+ zeroconf_contact_context_menu = xml.get_widget('zeroconf_contact_context_menu')
+
+ start_chat_menuitem = xml.get_widget('start_chat_menuitem')
+ rename_menuitem = xml.get_widget('rename_menuitem')
+ edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
+ # separator has with send file, assign_openpgp_key_menuitem, etc..
+ above_send_file_separator = xml.get_widget('above_send_file_separator')
+ send_file_menuitem = xml.get_widget('send_file_menuitem')
+ assign_openpgp_key_menuitem = xml.get_widget(
+ 'assign_openpgp_key_menuitem')
+ add_special_notification_menuitem = xml.get_widget(
+ 'add_special_notification_menuitem')
+
+ add_special_notification_menuitem.hide()
+ add_special_notification_menuitem.set_no_show_all(True)
+
+ if not our_jid:
+ # add a special img for rename menuitem
+ path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
+ 'kbd_input.png')
+ img = gtk.Image()
+ img.set_from_file(path_to_kbd_input_img)
+ rename_menuitem.set_image(img)
+
+ above_information_separator = xml.get_widget(
+ 'above_information_separator')
+
+ # skip a separator
+ information_menuitem = xml.get_widget('information_menuitem')
+ history_menuitem = xml.get_widget('history_menuitem')
+
+ contacts = gajim.contacts.get_contact(account, jid)
+ if len(contacts) > 1: # sevral resources
+ sub_menu = gtk.Menu()
+ start_chat_menuitem.set_submenu(sub_menu)
+
+ iconset = gajim.config.get('iconset')
+ path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
+ for c in contacts:
+ # icon MUST be different instance for every item
+ state_images = self.load_iconset(path)
+ item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')')
+ icon_name = helpers.get_icon_name_to_show(c, account)
+ icon = state_images[icon_name]
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.on_open_chat_window, c, account,
+ c.resource)
+
+ else: # one resource
+ start_chat_menuitem.connect('activate',
+ self.on_roster_treeview_row_activated, tree_path)
+
+ if contact.resource:
+ send_file_menuitem.connect('activate',
+ self.on_send_file_menuitem_activate, account, contact)
+ else: # if we do not have resource we cannot send file
+ send_file_menuitem.hide()
+ send_file_menuitem.set_no_show_all(True)
+
+ rename_menuitem.connect('activate', self.on_rename, iter, tree_path)
+ information_menuitem.connect('activate', self.on_info_zeroconf, contact,
+ account)
+ history_menuitem.connect('activate', self.on_history, contact,
+ account)
+
+ if _('Not in Roster') not in contact.groups:
+ #contact is in normal group
+ edit_groups_menuitem.set_no_show_all(False)
+ assign_openpgp_key_menuitem.set_no_show_all(False)
+ edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
+ contact,account)])
+
+ if gajim.config.get('usegpg'):
+ assign_openpgp_key_menuitem.connect('activate',
+ self.on_assign_pgp_key, contact, account)
+
+ else: # contact is in group 'Not in Roster'
+ edit_groups_menuitem.hide()
+ edit_groups_menuitem.set_no_show_all(True)
+ # hide first of the two consecutive separators
+ above_send_file_separator.hide()
+ above_send_file_separator.set_no_show_all(True)
+ assign_openpgp_key_menuitem.hide()
+ assign_openpgp_key_menuitem.set_no_show_all(True)
+
+ # Remove many items when it's self contact row
+ if our_jid:
+ for menuitem in (rename_menuitem, edit_groups_menuitem,
+ above_information_separator):
+ menuitem.set_no_show_all(True)
+ menuitem.hide()
+
+ # Unsensitive many items when account is offline
+ if gajim.connections[account].connected < 2:
+ for widget in [start_chat_menuitem, rename_menuitem, edit_groups_menuitem, send_file_menuitem]:
+ widget.set_sensitive(False)
+
+ event_button = gtkgui_helpers.get_possible_button_event(event)
+
+ zeroconf_contact_context_menu.attach_to_widget(self.tree, None)
+ zeroconf_contact_context_menu.connect('selection-done',
+ gtkgui_helpers.destroy_widget)
+ zeroconf_contact_context_menu.show_all()
+ zeroconf_contact_context_menu.popup(None, None, None, event_button,
+ event.time)
+ return
+
+
+ # normal account
xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade')
roster_contact_context_menu = xml.get_widget(
'roster_contact_context_menu')
@@ -1773,6 +1893,14 @@ class RosterWindow:
gajim.interface.instances[account]['account_modification'] = \
config.AccountModificationWindow(account)
+ def on_zeroconf_properties(self, widget, account):
+ if gajim.interface.instances.has_key('zeroconf_properties'):
+ gajim.interface.instances['zeroconf_properties'].\
+ window.present()
+ else:
+ gajim.interface.instances['zeroconf_properties'] = \
+ config.ZeroconfPropertiesWindow()
+
def on_open_gmail_inbox(self, widget, account):
url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
gajim.config.get_per('accounts', account, 'name'))
@@ -1792,71 +1920,123 @@ class RosterWindow:
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
state_images = self.load_iconset(path)
- xml = gtkgui_helpers.get_glade('account_context_menu.glade')
- account_context_menu = xml.get_widget('account_context_menu')
+ if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
+ xml = gtkgui_helpers.get_glade('account_context_menu.glade')
+ account_context_menu = xml.get_widget('account_context_menu')
+
+ status_menuitem = xml.get_widget('status_menuitem')
+ join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
+ open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
+ new_message_menuitem = xml.get_widget('new_message_menuitem')
+ add_contact_menuitem = xml.get_widget('add_contact_menuitem')
+ service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
+ edit_account_menuitem = xml.get_widget('edit_account_menuitem')
+ sub_menu = gtk.Menu()
+ status_menuitem.set_submenu(sub_menu)
- status_menuitem = xml.get_widget('status_menuitem')
- join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
- open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
- new_message_menuitem = xml.get_widget('new_message_menuitem')
- add_contact_menuitem = xml.get_widget('add_contact_menuitem')
- service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
- edit_account_menuitem = xml.get_widget('edit_account_menuitem')
- sub_menu = gtk.Menu()
- status_menuitem.set_submenu(sub_menu)
+ for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images[show]
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, show)
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message'))
+ path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
+ img = gtk.Image()
+ img.set_from_file(path)
+ item.set_image(img)
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate, account)
+ if gajim.connections[account].connected < 2:
+ item.set_sensitive(False)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
item = gtk.ImageMenuItem(uf_show)
- icon = state_images[show]
+ icon = state_images['offline']
item.set_image(icon)
sub_menu.append(item)
- item.connect('activate', self.change_status, account, show)
-
- item = gtk.SeparatorMenuItem()
- sub_menu.append(item)
+ item.connect('activate', self.change_status, account, 'offline')
- item = gtk.ImageMenuItem(_('_Change Status Message'))
- path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
- img = gtk.Image()
- img.set_from_file(path)
- item.set_image(img)
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate, account)
- if gajim.connections[account].connected < 2:
- item.set_sensitive(False)
+ if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
+ open_gmail_inbox_menuitem.set_no_show_all(True)
+ open_gmail_inbox_menuitem.hide()
+ else:
+ open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
+ account)
- uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
- item = gtk.ImageMenuItem(uf_show)
- icon = state_images['offline']
- item.set_image(icon)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, 'offline')
+ edit_account_menuitem.connect('activate', self.on_edit_account, account)
+ add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
+ service_discovery_menuitem.connect('activate',
+ self.on_service_disco_menuitem_activate, account)
+
+ gc_sub_menu = gtk.Menu() # gc is always a submenu
+ join_group_chat_menuitem.set_submenu(gc_sub_menu)
+ self.add_bookmarks_list(gc_sub_menu, account)
+ new_message_menuitem.connect('activate',
+ self.on_new_message_menuitem_activate, account)
- if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
- open_gmail_inbox_menuitem.set_no_show_all(True)
- open_gmail_inbox_menuitem.hide()
+ # make some items insensitive if account is offline
+ if gajim.connections[account].connected < 2:
+ for widget in [add_contact_menuitem, service_discovery_menuitem,
+ join_group_chat_menuitem, new_message_menuitem]:
+ widget.set_sensitive(False)
else:
- open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
- account)
+ xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
+ account_context_menu = xml.get_widget('zeroconf_context_menu')
- edit_account_menuitem.connect('activate', self.on_edit_account, account)
- add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
- service_discovery_menuitem.connect('activate',
- self.on_service_disco_menuitem_activate, account)
-
- gc_sub_menu = gtk.Menu() # gc is always a submenu
- join_group_chat_menuitem.set_submenu(gc_sub_menu)
- self.add_bookmarks_list(gc_sub_menu, account)
- new_message_menuitem.connect('activate',
- self.on_new_message_menuitem_activate, account)
+ status_menuitem = xml.get_widget('status_menuitem')
+ #join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
+ new_message_menuitem = xml.get_widget('new_message_menuitem')
+ zeroconf_properties_menuitem = xml.get_widget('zeroconf_properties_menuitem')
+ sub_menu = gtk.Menu()
+ status_menuitem.set_submenu(sub_menu)
- # make some items insensitive if account is offline
- if gajim.connections[account].connected < 2:
- for widget in [add_contact_menuitem, service_discovery_menuitem,
- join_group_chat_menuitem, new_message_menuitem]:
- widget.set_sensitive(False)
-
+ for show in ('online', 'away', 'dnd', 'invisible'):
+ uf_show = helpers.get_uf_show(show, use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images[show]
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, show)
+
+ item = gtk.SeparatorMenuItem()
+ sub_menu.append(item)
+
+ item = gtk.ImageMenuItem(_('_Change Status Message'))
+ path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
+ img = gtk.Image()
+ img.set_from_file(path)
+ item.set_image(img)
+ sub_menu.append(item)
+ item.connect('activate', self.on_change_status_message_activate, account)
+ if gajim.connections[account].connected < 2:
+ item.set_sensitive(False)
+
+ uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
+ item = gtk.ImageMenuItem(uf_show)
+ icon = state_images['offline']
+ item.set_image(icon)
+ sub_menu.append(item)
+ item.connect('activate', self.change_status, account, 'offline')
+
+ zeroconf_properties_menuitem.connect('activate', self.on_zeroconf_properties, account)
+ #gc_sub_menu = gtk.Menu() # gc is always a submenu
+ #join_group_chat_menuitem.set_submenu(gc_sub_menu)
+ #self.add_bookmarks_list(gc_sub_menu, account)
+ #new_message_menuitem.connect('activate',
+ # self.on_new_message_menuitem_activate, account)
+
+ # make some items insensitive if account is offline
+ #if gajim.connections[account].connected < 2:
+ # for widget in [join_group_chat_menuitem, new_message_menuitem]:
+ # widget.set_sensitive(False)
+ # new_message_menuitem.set_sensitive(False)
+
return account_context_menu
def make_account_menu(self, event, iter):
@@ -1967,6 +2147,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if not len(list_of_paths):
return
type = model[list_of_paths[0]][C_TYPE]
+ account = model[list_of_paths[0]][C_ACCOUNT]
list_ = []
for path in list_of_paths:
if model[path][C_TYPE] != type:
@@ -1976,7 +2157,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
list_.append((contact, account))
- if type in ('account', 'group', 'self_contact'):
+ if type in ('account', 'group', 'self_contact') or account == gajim.ZEROCONF_ACC_NAME:
return
if type == 'contact':
self.on_req_usub(widget, list_)
@@ -3572,6 +3753,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
account_dest, c_dest, path)
return
+ if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
+ # drop on zeroconf account, no contact adds possible
+ return
+
if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
# dropped before a group : we drop it in the previous group
path_dest = (path_dest[0], path_dest[1]-1)
@@ -3583,6 +3768,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return
if type_dest == 'account' and account_source == account_dest:
return
+ if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
+ return
it = iter_source
while model[it][C_TYPE] == 'contact':
it = model.iter_parent(it)
diff --git a/src/tooltips.py b/src/tooltips.py
index 7787c2673..be393c623 100644
--- a/src/tooltips.py
+++ b/src/tooltips.py
@@ -307,7 +307,14 @@ class GCTooltip(BaseTooltip):
properties.append((show, None))
if contact.jid.strip() != '':
- properties.append((_('Jabber ID: '), contact.jid))
+ jid_markup = '<span weight="bold">' + contact.jid + '</span>'
+ else:
+ jid_markup = '<span weight="bold">' + \
+ gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \
+ + '</span>'
+ properties.append((jid_markup, None))
+ properties.append((_('Role: '), helpers.get_uf_role(contact.role)))
+ properties.append((_('Affiliation: '), contact.affiliation.capitalize()))
if hasattr(contact, 'resource') and contact.resource.strip() != '':
properties.append((_('Resource: '),
gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
@@ -408,10 +415,25 @@ class RosterTooltip(NotificationAreaTooltip):
vcard_table.set_homogeneous(False)
vcard_current_row = 1
properties = []
- name_markup = '<b>%s</b>' % gtkgui_helpers.escape_for_pango_markup(
- prim_contact.get_shown_name())
- properties.append((name_markup, None))
+ jid_markup = '<span weight="bold">' + prim_contact.jid + '</span>'
+ properties.append((jid_markup, None))
+
+ properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
+ prim_contact.get_shown_name())))
+ if prim_contact.sub:
+ properties.append(( _('Subscription: '),
+ gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
+ if prim_contact.keyID:
+ keyID = None
+ if len(prim_contact.keyID) == 8:
+ keyID = prim_contact.keyID
+ elif len(prim_contact.keyID) == 16:
+ keyID = prim_contact.keyID[8:]
+ if keyID:
+ properties.append((_('OpenPGP: '),
+ gtkgui_helpers.escape_for_pango_markup(keyID)))
+
num_resources = 0
# put contacts in dict, where key is priority
contacts_dict = {}
@@ -422,6 +444,11 @@ class RosterTooltip(NotificationAreaTooltip):
contacts_dict[contact.priority].append(contact)
else:
contacts_dict[contact.priority] = [contact]
+
+ if num_resources == 1 and contact.resource:
+ properties.append((_('Resource: '),
+ gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
+ unicode(contact.priority) + ')'))
if num_resources > 1:
properties.append((_('Status: '), ' '))
transport = gajim.get_transport_name_from_jid(
diff --git a/src/vcard.py b/src/vcard.py
index c1d86afb1..52b686493 100644
--- a/src/vcard.py
+++ b/src/vcard.py
@@ -2,6 +2,7 @@
##
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
+## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
@@ -336,3 +337,150 @@ class VcardWindow:
def on_close_button_clicked(self, widget):
self.window.destroy()
+
+
+class ZeroconfVcardWindow:
+ def __init__(self, contact, account, is_fake = False):
+ # the contact variable is the jid if vcard is true
+ self.xml = gtkgui_helpers.get_glade('zeroconf_information_window.glade')
+ self.window = self.xml.get_widget('zeroconf_information_window')
+
+ self.contact = contact
+ self.account = account
+ self.is_fake = is_fake
+
+ # self.avatar_mime_type = None
+ # self.avatar_encoded = None
+
+ self.fill_contact_page()
+ self.fill_personal_page()
+
+ self.xml.signal_autoconnect(self)
+ self.window.show_all()
+
+ def on_zeroconf_information_window_destroy(self, widget):
+ del gajim.interface.instances[self.account]['infos'][self.contact.jid]
+
+ def on_zeroconf_information_window_key_press_event(self, widget, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.window.destroy()
+
+ def on_log_history_checkbutton_toggled(self, widget):
+ #log conversation history?
+ oldlog = True
+ no_log_for = gajim.config.get_per('accounts', self.account,
+ 'no_log_for').split()
+ if self.contact.jid in no_log_for:
+ oldlog = False
+ log = widget.get_active()
+ if not log and not self.contact.jid in no_log_for:
+ no_log_for.append(self.contact.jid)
+ if log and self.contact.jid in no_log_for:
+ no_log_for.remove(self.contact.jid)
+ if oldlog != log:
+ gajim.config.set_per('accounts', self.account, 'no_log_for',
+ ' '.join(no_log_for))
+
+ def on_PHOTO_eventbox_button_press_event(self, widget, event):
+ '''If right-clicked, show popup'''
+ if event.button == 3: # right click
+ menu = gtk.Menu()
+ menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
+ menuitem.connect('activate',
+ gtkgui_helpers.on_avatar_save_as_menuitem_activate,
+ self.contact.jid, self.account, self.contact.name + '.jpeg')
+ menu.append(menuitem)
+ menu.connect('selection-done', lambda w:w.destroy())
+ # show the menu
+ menu.show_all()
+ menu.popup(None, None, None, event.button, event.time)
+
+ def set_value(self, entry_name, value):
+ try:
+ if value and entry_name == 'URL_label':
+ if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
+ widget = gtk.LinkButton(value, value)
+ else:
+ widget = gtk.Label(value)
+ table = self.xml.get_widget('personal_info_table')
+ table.attach(widget, 1, 4, 3, 4, yoptions = 0)
+ else:
+ self.xml.get_widget(entry_name).set_text(value)
+ except AttributeError:
+ pass
+
+ def fill_status_label(self):
+ if self.xml.get_widget('information_notebook').get_n_pages() < 2:
+ return
+ contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
+ # stats holds show and status message
+ stats = ''
+ one = True # Are we adding the first line ?
+ if contact_list:
+ for c in contact_list:
+ if not one:
+ stats += '\n'
+ stats += helpers.get_uf_show(c.show)
+ if c.status:
+ stats += ': ' + c.status
+ if c.last_status_time:
+ stats += '\n' + _('since %s') % time.strftime('%c',
+ c.last_status_time).decode(locale.getpreferredencoding())
+ one = False
+ else: # Maybe gc_vcard ?
+ stats = helpers.get_uf_show(self.contact.show)
+ if self.contact.status:
+ stats += ': ' + self.contact.status
+ status_label = self.xml.get_widget('status_label')
+ status_label.set_max_width_chars(15)
+ status_label.set_text(stats)
+
+ tip = gtk.Tooltips()
+ status_label_eventbox = self.xml.get_widget('status_label_eventbox')
+ tip.set_tip(status_label_eventbox, stats)
+
+ def fill_contact_page(self):
+ tooltips = gtk.Tooltips()
+ self.xml.get_widget('nickname_label').set_markup(
+ '<b><span size="x-large">' +
+ self.contact.get_shown_name() +
+ '</span></b>')
+ self.xml.get_widget('local_jid_label').set_text(self.contact.jid)
+
+ log = True
+ if self.contact.jid in gajim.config.get_per('accounts', self.account,
+ 'no_log_for').split(' '):
+ log = False
+ checkbutton = self.xml.get_widget('log_history_checkbutton')
+ checkbutton.set_active(log)
+ checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled)
+
+ resources = '%s (%s)' % (self.contact.resource, unicode(
+ self.contact.priority))
+ uf_resources = self.contact.resource + _(' resource with priority ')\
+ + unicode(self.contact.priority)
+ if not self.contact.status:
+ self.contact.status = ''
+
+ # Request list time status
+ # gajim.connections[self.account].request_last_status_time(self.contact.jid,
+ # self.contact.resource)
+
+ self.xml.get_widget('resource_prio_label').set_text(resources)
+ resource_prio_label_eventbox = self.xml.get_widget(
+ 'resource_prio_label_eventbox')
+ tooltips.set_tip(resource_prio_label_eventbox, uf_resources)
+
+ self.fill_status_label()
+
+ # gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
+
+ def fill_personal_page(self):
+ contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
+ self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st'])
+ self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last'])
+ self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid'])
+ self.xml.get_widget('email_label').set_text(contact['txt_dict']['email'])
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()