merge zeroconf branch to trunk

This commit is contained in:
Stefan Bethge 2006-10-11 02:12:56 +00:00
commit 1dbb2a891f
21 changed files with 4905 additions and 74 deletions

View File

@ -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>
@ -80,6 +81,38 @@
</packing>
</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>
@ -267,4 +300,5 @@
</widget>
</child>
</widget>
</glade-interface>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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, '' ],

View File

@ -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')

View File

@ -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]

View File

@ -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:

View File

@ -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)

View File

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

373
src/common/zeroconf/zeroconf.py Executable file
View File

@ -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)
'''

View File

@ -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_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):
gajim.config.set('mergeaccounts', widget.get_active())
gajim.interface.save_config()
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 = ''

View File

@ -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')

View File

@ -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)
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)
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.connect('activate', self.change_status, account, 'offline')
item = gtk.SeparatorMenuItem()
sub_menu.append(item)
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)
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)
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)
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')
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)

View File

@ -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(

View File

@ -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()