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"> <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface> <glade-interface>
<widget class="GtkWindow" id="accounts_window"> <widget class="GtkWindow" id="accounts_window">
<property name="border_width">12</property> <property name="border_width">12</property>
<property name="title" translatable="yes">Accounts</property> <property name="title" translatable="yes">Accounts</property>
@ -64,7 +65,7 @@
<property name="visible">True</property> <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="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="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="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property> <property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
@ -80,6 +81,38 @@
</packing> </packing>
</child> </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> <child>
<widget class="GtkHButtonBox" id="hbuttonbox15"> <widget class="GtkHButtonBox" id="hbuttonbox15">
<property name="visible">True</property> <property name="visible">True</property>
@ -267,4 +300,5 @@
</widget> </widget>
</child> </child>
</widget> </widget>
</glade-interface> </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 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2005 Travis Shirk <travis@pobox.com> ## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za> ## 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 ## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published ## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only. ## by the Free Software Foundation; version 2 only.
@ -82,6 +83,7 @@ class Config:
'saveposition': [ opt_bool, True ], 'saveposition': [ opt_bool, True ],
'mergeaccounts': [ opt_bool, False, '', True ], 'mergeaccounts': [ opt_bool, False, '', True ],
'sort_by_show': [ opt_bool, True, '', True ], 'sort_by_show': [ opt_bool, True, '', True ],
'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
'use_speller': [ opt_bool, False, ], 'use_speller': [ opt_bool, False, ],
'speller_language': [ opt_str, '', _('Language used by speller')], '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.')], '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-y-position': [opt_int, -1], # Default is to let the wm decide
'msgwin-width': [opt_int, 480], 'msgwin-width': [opt_int, 480],
'msgwin-height': [opt_int, 440], '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': ({ 'statusmsg': ({
'message': [ opt_str, '' ], 'message': [ opt_str, '' ],

View File

@ -46,6 +46,7 @@ class Connection(ConnectionHandlers):
self.connection = None # xmpppy ClientCommon instance self.connection = None # xmpppy ClientCommon instance
# this property is used to prevent double connections # this property is used to prevent double connections
self.last_connection = None # last ClientCommon instance self.last_connection = None # last ClientCommon instance
self.is_zeroconf = False
self.gpg = None self.gpg = None
self.status = '' self.status = ''
self.priority = gajim.get_priority(name, 'offline') 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', SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible'] '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): def get_nick_from_jid(jid):
pos = jid.find('@') pos = jid.find('@')
return jid[:pos] return jid[:pos]

View File

@ -74,7 +74,6 @@ class Dispatcher(PlugIn):
self.RegisterProtocol('presence', Presence) self.RegisterProtocol('presence', Presence)
self.RegisterProtocol('message', Message) self.RegisterProtocol('message', Message)
self.RegisterDefaultHandler(self.returnStanzaHandler) self.RegisterDefaultHandler(self.returnStanzaHandler)
# Register Gajim's event handler as soon as dispatcher begins
self.RegisterEventHandler(self._owner._caller._event_dispatcher) self.RegisterEventHandler(self._owner._caller._event_dispatcher)
self.on_responses = {} self.on_responses = {}
@ -84,7 +83,10 @@ class Dispatcher(PlugIn):
self._owner.lastErrNode = None self._owner.lastErrNode = None
self._owner.lastErr = None self._owner.lastErr = None
self._owner.lastErrCode = None self._owner.lastErrCode = None
self.StreamInit() if hasattr(self._owner, 'StreamInit'):
self._owner.StreamInit()
else:
self.StreamInit()
def plugout(self): def plugout(self):
''' Prepares instance to be destructed. ''' ''' Prepares instance to be destructed. '''
@ -134,7 +136,7 @@ class Dispatcher(PlugIn):
return 0 return 0
except ExpatError: except ExpatError:
sys.exc_clear() 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() self._owner.Connection.pollend()
return 0 return 0
if len(self._pendingExceptions) > 0: if len(self._pendingExceptions) > 0:

View File

@ -183,7 +183,7 @@ class Session:
if self.sendbuffer: if self.sendbuffer:
try: try:
# LOCK_QUEUE # LOCK_QUEUE
sent=self._send(self.sendbuffer) # âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ! sent=self._send(self.sendbuffer) # blocking socket
except: except:
# UNLOCK_QUEUE # UNLOCK_QUEUE
self.set_socket_state(SOCKET_DEAD) 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-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com> ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org> ## 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 ## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published ## 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 gajim
from common import connection from common import connection
from common import passwords from common import passwords
from common import zeroconf
from common import dbus_support from common import dbus_support
from common.exceptions import GajimGeneralException from common.exceptions import GajimGeneralException
@ -1374,6 +1376,9 @@ class AccountModificationWindow:
config['custom_host'] = self.xml.get_widget( config['custom_host'] = self.xml.get_widget(
'custom_host_entry').get_text().decode('utf-8') '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') config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
if config['keyname'] == '': #no key selected if config['keyname'] == '': #no key selected
config['keyid'] = '' config['keyid'] = ''
@ -1808,6 +1813,22 @@ class AccountsWindow:
st = gajim.config.get('mergeaccounts') st = gajim.config.get('mergeaccounts')
self.xml.get_widget('merge_checkbutton').set_active(st) 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): def on_accounts_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape: if event.keyval == gtk.keysyms.Escape:
self.window.destroy() self.window.destroy()
@ -1847,6 +1868,17 @@ class AccountsWindow:
dialogs.ErrorDialog(_('Unread events'), dialogs.ErrorDialog(_('Unread events'),
_('Read all pending events before removing this account.')) _('Read all pending events before removing this account.'))
return 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 win_opened = False
if gajim.interface.msg_win_mgr.get_controls(acct = account): if gajim.interface.msg_win_mgr.get_controls(acct = account):
win_opened = True win_opened = True
@ -1891,21 +1923,107 @@ class AccountsWindow:
self.show_modification_window(account) self.show_modification_window(account)
def show_modification_window(self, account): def show_modification_window(self, account):
if gajim.interface.instances[account].has_key('account_modification'): if gajim.config.get_per('accounts', account, 'is_zeroconf'):
gajim.interface.instances[account]['account_modification'].window.present() if gajim.interface.instances.has_key('zeroconf_properties'):
gajim.interface.instances['zeroconf_properties'].window.present()
else:
gajim.interface.instances['zeroconf_properties'] = \
ZeroconfPropertiesWindow()
else: else:
gajim.interface.instances[account]['account_modification'] = \ if gajim.interface.instances[account].has_key('account_modification'):
AccountModificationWindow(account) 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): def on_merge_checkbutton_toggled(self, widget):
gajim.config.set('mergeaccounts', widget.get_active()) self.on_checkbutton_toggled(widget, 'mergeaccounts')
gajim.interface.save_config()
if len(gajim.connections) >= 2: # Do not merge accounts if only one exists if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts') gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
else: else:
gajim.interface.roster.regroup = False gajim.interface.roster.regroup = False
gajim.interface.roster.draw_roster() 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: class DataFormWindow:
def __init__(self, account, config): def __init__(self, account, config):
self.account = account 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.draw_roster()
gajim.interface.roster.actions_menu_needs_rebuild = True gajim.interface.roster.actions_menu_needs_rebuild = True
gajim.interface.save_config() 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 chat_control import ChatControlBase
from common import exceptions from common import exceptions
from common.zeroconf import connection_zeroconf
if os.name == 'posix': # dl module is Unix Only if os.name == 'posix': # dl module is Unix Only
try: # rename the process name to gajim try: # rename the process name to gajim
@ -618,7 +619,9 @@ class Interface:
jids = full_jid_with_resource.split('/', 1) jids = full_jid_with_resource.split('/', 1)
jid = jids[0] jid = jids[0]
gc_control = self.msg_win_mgr.get_control(jid, account) 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 if len(jids) > 1: # it's a pm
nick = jids[1] nick = jids[1]
if not self.msg_win_mgr.get_control(full_jid_with_resource, if not self.msg_win_mgr.get_control(full_jid_with_resource,
@ -1406,6 +1409,20 @@ class Interface:
if win.startswith('privacy_list_'): if win.startswith('privacy_list_'):
self.instances[account][win].check_active_default(data) 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): def read_sleepy(self):
'''Check idle status and change that status if needed''' '''Check idle status and change that status if needed'''
if not self.sleeper.poll(): if not self.sleeper.poll():
@ -1711,6 +1728,7 @@ class Interface:
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received, 'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
'PRIVACY_LISTS_ACTIVE_DEFAULT': \ 'PRIVACY_LISTS_ACTIVE_DEFAULT': \
self.handle_event_privacy_lists_active_default, self.handle_event_privacy_lists_active_default,
'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
} }
gajim.handlers = self.handlers gajim.handlers = self.handlers
@ -1867,9 +1885,13 @@ class Interface:
self.handle_event_file_progress) self.handle_event_file_progress)
gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue) gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
self.register_handlers() 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'): 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 hooks
gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')

View File

@ -1119,6 +1119,14 @@ class RosterWindow:
else: else:
info[contact.jid] = vcard.VcardWindow(contact, account) 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): def show_tooltip(self, contact):
pointer = self.tree.get_pointer() pointer = self.tree.get_pointer()
props = self.tree.get_path_at_pos(pointer[0], pointer[1]) props = self.tree.get_path_at_pos(pointer[0], pointer[1])
@ -1363,6 +1371,118 @@ class RosterWindow:
if not contact: if not contact:
return 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') xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade')
roster_contact_context_menu = xml.get_widget( roster_contact_context_menu = xml.get_widget(
'roster_contact_context_menu') 'roster_contact_context_menu')
@ -1773,6 +1893,14 @@ class RosterWindow:
gajim.interface.instances[account]['account_modification'] = \ gajim.interface.instances[account]['account_modification'] = \
config.AccountModificationWindow(account) 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): def on_open_gmail_inbox(self, widget, account):
url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote( url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
gajim.config.get_per('accounts', account, 'name')) gajim.config.get_per('accounts', account, 'name'))
@ -1792,71 +1920,123 @@ class RosterWindow:
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
state_images = self.load_iconset(path) state_images = self.load_iconset(path)
xml = gtkgui_helpers.get_glade('account_context_menu.glade') if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
account_context_menu = xml.get_widget('account_context_menu') 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') status_menuitem = xml.get_widget('status_menuitem')
join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem') join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem') open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
new_message_menuitem = xml.get_widget('new_message_menuitem') new_message_menuitem = xml.get_widget('new_message_menuitem')
add_contact_menuitem = xml.get_widget('add_contact_menuitem') add_contact_menuitem = xml.get_widget('add_contact_menuitem')
service_discovery_menuitem = xml.get_widget('service_discovery_menuitem') service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
edit_account_menuitem = xml.get_widget('edit_account_menuitem') edit_account_menuitem = xml.get_widget('edit_account_menuitem')
sub_menu = gtk.Menu() sub_menu = gtk.Menu()
status_menuitem.set_submenu(sub_menu) status_menuitem.set_submenu(sub_menu)
for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
uf_show = helpers.get_uf_show(show, use_mnemonic = True) 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) item = gtk.ImageMenuItem(uf_show)
icon = state_images[show] icon = state_images['offline']
item.set_image(icon) item.set_image(icon)
sub_menu.append(item) sub_menu.append(item)
item.connect('activate', self.change_status, account, show) item.connect('activate', self.change_status, account, 'offline')
item = gtk.SeparatorMenuItem() if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
sub_menu.append(item) 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')) edit_account_menuitem.connect('activate', self.on_edit_account, account)
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
img = gtk.Image() service_discovery_menuitem.connect('activate',
img.set_from_file(path) self.on_service_disco_menuitem_activate, account)
item.set_image(img)
sub_menu.append(item) gc_sub_menu = gtk.Menu() # gc is always a submenu
item.connect('activate', self.on_change_status_message_activate, account) join_group_chat_menuitem.set_submenu(gc_sub_menu)
if gajim.connections[account].connected < 2: self.add_bookmarks_list(gc_sub_menu, account)
item.set_sensitive(False) new_message_menuitem.connect('activate',
self.on_new_message_menuitem_activate, account)
uf_show = helpers.get_uf_show('offline', use_mnemonic = True) # make some items insensitive if account is offline
item = gtk.ImageMenuItem(uf_show) if gajim.connections[account].connected < 2:
icon = state_images['offline'] for widget in [add_contact_menuitem, service_discovery_menuitem,
item.set_image(icon) join_group_chat_menuitem, new_message_menuitem]:
sub_menu.append(item) widget.set_sensitive(False)
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()
else: else:
open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox, xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
account) account_context_menu = xml.get_widget('zeroconf_context_menu')
edit_account_menuitem.connect('activate', self.on_edit_account, account) status_menuitem = xml.get_widget('status_menuitem')
add_contact_menuitem.connect('activate', self.on_add_new_contact, account) #join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
service_discovery_menuitem.connect('activate', new_message_menuitem = xml.get_widget('new_message_menuitem')
self.on_service_disco_menuitem_activate, account) zeroconf_properties_menuitem = xml.get_widget('zeroconf_properties_menuitem')
sub_menu = gtk.Menu()
gc_sub_menu = gtk.Menu() # gc is always a submenu status_menuitem.set_submenu(sub_menu)
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 for show in ('online', 'away', 'dnd', 'invisible'):
if gajim.connections[account].connected < 2: uf_show = helpers.get_uf_show(show, use_mnemonic = True)
for widget in [add_contact_menuitem, service_discovery_menuitem, item = gtk.ImageMenuItem(uf_show)
join_group_chat_menuitem, new_message_menuitem]: icon = state_images[show]
widget.set_sensitive(False) 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 return account_context_menu
def make_account_menu(self, event, iter): 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): if not len(list_of_paths):
return return
type = model[list_of_paths[0]][C_TYPE] type = model[list_of_paths[0]][C_TYPE]
account = model[list_of_paths[0]][C_ACCOUNT]
list_ = [] list_ = []
for path in list_of_paths: for path in list_of_paths:
if model[path][C_TYPE] != type: 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, contact = gajim.contacts.get_contact_with_highest_priority(account,
jid) jid)
list_.append((contact, account)) 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 return
if type == 'contact': if type == 'contact':
self.on_req_usub(widget, list_) 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) account_dest, c_dest, path)
return 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: if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
# dropped before a group : we drop it in the previous group # dropped before a group : we drop it in the previous group
path_dest = (path_dest[0], path_dest[1]-1) 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 return
if type_dest == 'account' and account_source == account_dest: if type_dest == 'account' and account_source == account_dest:
return return
if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
return
it = iter_source it = iter_source
while model[it][C_TYPE] == 'contact': while model[it][C_TYPE] == 'contact':
it = model.iter_parent(it) it = model.iter_parent(it)

View File

@ -307,7 +307,14 @@ class GCTooltip(BaseTooltip):
properties.append((show, None)) properties.append((show, None))
if contact.jid.strip() != '': 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() != '': if hasattr(contact, 'resource') and contact.resource.strip() != '':
properties.append((_('Resource: '), properties.append((_('Resource: '),
gtkgui_helpers.escape_for_pango_markup(contact.resource) )) gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
@ -408,10 +415,25 @@ class RosterTooltip(NotificationAreaTooltip):
vcard_table.set_homogeneous(False) vcard_table.set_homogeneous(False)
vcard_current_row = 1 vcard_current_row = 1
properties = [] 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 num_resources = 0
# put contacts in dict, where key is priority # put contacts in dict, where key is priority
contacts_dict = {} contacts_dict = {}
@ -422,6 +444,11 @@ class RosterTooltip(NotificationAreaTooltip):
contacts_dict[contact.priority].append(contact) contacts_dict[contact.priority].append(contact)
else: else:
contacts_dict[contact.priority] = [contact] 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: if num_resources > 1:
properties.append((_('Status: '), ' ')) properties.append((_('Status: '), ' '))
transport = gajim.get_transport_name_from_jid( 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) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com> ## 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 ## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published ## 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): def on_close_button_clicked(self, widget):
self.window.destroy() 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()