merge zeroconf branch to trunk
This commit is contained in:
commit
1dbb2a891f
|
@ -2,6 +2,7 @@
|
|||
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
|
||||
|
||||
<glade-interface>
|
||||
|
||||
<widget class="GtkWindow" id="accounts_window">
|
||||
<property name="border_width">12</property>
|
||||
<property name="title" translatable="yes">Accounts</property>
|
||||
|
@ -64,7 +65,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">If you have 2 or more accounts and it is checked, Gajim will list all contacts as if you had one account</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">_Merge accounts</property>
|
||||
<property name="label" translatable="yes">Mer_ge accounts</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
|
@ -80,6 +81,38 @@
|
|||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkHSeparator" id="hseparator1">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkCheckButton" id="enable_zeroconf_checkbutton">
|
||||
<property name="visible">True</property>
|
||||
<property name="tooltip" translatable="yes">If checked, all local contacts that use a Bonjour compatible chat client (like iChat, Trillian or Gaim) will be shown in roster. You don't need to be connected to a jabber server for it to work.
|
||||
This is only available if python-avahi is installed and avahi-daemon is running.</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">_Enable link-local messaging</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="relief">GTK_RELIEF_NORMAL</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<property name="active">False</property>
|
||||
<property name="inconsistent">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkHButtonBox" id="hbuttonbox15">
|
||||
<property name="visible">True</property>
|
||||
|
@ -267,4 +300,5 @@
|
|||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
|
||||
</glade-interface>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"><b>Personal Information</b></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"><b>OpenPGP</b></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>
|
|
@ -6,7 +6,8 @@
|
|||
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
|
||||
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
|
||||
##
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
## by the Free Software Foundation; version 2 only.
|
||||
|
@ -82,6 +83,7 @@ class Config:
|
|||
'saveposition': [ opt_bool, True ],
|
||||
'mergeaccounts': [ opt_bool, False, '', True ],
|
||||
'sort_by_show': [ opt_bool, True, '', True ],
|
||||
'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
|
||||
'use_speller': [ opt_bool, False, ],
|
||||
'speller_language': [ opt_str, '', _('Language used by speller')],
|
||||
'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
|
||||
|
@ -259,6 +261,11 @@ class Config:
|
|||
'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
|
||||
'msgwin-width': [opt_int, 480],
|
||||
'msgwin-height': [opt_int, 440],
|
||||
'is_zeroconf': [opt_bool, False],
|
||||
'zeroconf_first_name': [ opt_str, '', '', True ],
|
||||
'zeroconf_last_name': [ opt_str, '', '', True ],
|
||||
'zeroconf_jabber_id': [ opt_str, '', '', True ],
|
||||
'zeroconf_email': [ opt_str, '', '', True ],
|
||||
}, {}),
|
||||
'statusmsg': ({
|
||||
'message': [ opt_str, '' ],
|
||||
|
|
|
@ -46,6 +46,7 @@ class Connection(ConnectionHandlers):
|
|||
self.connection = None # xmpppy ClientCommon instance
|
||||
# this property is used to prevent double connections
|
||||
self.last_connection = None # last ClientCommon instance
|
||||
self.is_zeroconf = False
|
||||
self.gpg = None
|
||||
self.status = ''
|
||||
self.priority = gajim.get_priority(name, 'offline')
|
||||
|
|
|
@ -120,6 +120,12 @@ status_before_autoaway = {}
|
|||
SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible']
|
||||
|
||||
# zeroconf account name
|
||||
ZEROCONF_ACC_NAME = 'Local'
|
||||
priority_dict = {}
|
||||
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||
priority_dict[status] = config.get('autopriority' + status)
|
||||
|
||||
def get_nick_from_jid(jid):
|
||||
pos = jid.find('@')
|
||||
return jid[:pos]
|
||||
|
|
|
@ -74,7 +74,6 @@ class Dispatcher(PlugIn):
|
|||
self.RegisterProtocol('presence', Presence)
|
||||
self.RegisterProtocol('message', Message)
|
||||
self.RegisterDefaultHandler(self.returnStanzaHandler)
|
||||
# Register Gajim's event handler as soon as dispatcher begins
|
||||
self.RegisterEventHandler(self._owner._caller._event_dispatcher)
|
||||
self.on_responses = {}
|
||||
|
||||
|
@ -84,7 +83,10 @@ class Dispatcher(PlugIn):
|
|||
self._owner.lastErrNode = None
|
||||
self._owner.lastErr = None
|
||||
self._owner.lastErrCode = None
|
||||
self.StreamInit()
|
||||
if hasattr(self._owner, 'StreamInit'):
|
||||
self._owner.StreamInit()
|
||||
else:
|
||||
self.StreamInit()
|
||||
|
||||
def plugout(self):
|
||||
''' Prepares instance to be destructed. '''
|
||||
|
@ -134,7 +136,7 @@ class Dispatcher(PlugIn):
|
|||
return 0
|
||||
except ExpatError:
|
||||
sys.exc_clear()
|
||||
self.DEBUG('Invalid XML received from server. Forcing disconnect.')
|
||||
self.DEBUG('Invalid XML received from server. Forcing disconnect.', 'error')
|
||||
self._owner.Connection.pollend()
|
||||
return 0
|
||||
if len(self._pendingExceptions) > 0:
|
||||
|
|
|
@ -183,7 +183,7 @@ class Session:
|
|||
if self.sendbuffer:
|
||||
try:
|
||||
# LOCK_QUEUE
|
||||
sent=self._send(self.sendbuffer) # âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ!
|
||||
sent=self._send(self.sendbuffer) # blocking socket
|
||||
except:
|
||||
# UNLOCK_QUEUE
|
||||
self.set_socket_state(SOCKET_DEAD)
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
'''
|
330
src/config.py
330
src/config.py
|
@ -4,6 +4,7 @@
|
|||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -38,6 +39,7 @@ from common import helpers
|
|||
from common import gajim
|
||||
from common import connection
|
||||
from common import passwords
|
||||
from common import zeroconf
|
||||
from common import dbus_support
|
||||
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
@ -1374,6 +1376,9 @@ class AccountModificationWindow:
|
|||
config['custom_host'] = self.xml.get_widget(
|
||||
'custom_host_entry').get_text().decode('utf-8')
|
||||
|
||||
# update in case the name was changed to local account's name
|
||||
config['is_zeroconf'] = False
|
||||
|
||||
config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
|
||||
if config['keyname'] == '': #no key selected
|
||||
config['keyid'] = ''
|
||||
|
@ -1808,6 +1813,22 @@ class AccountsWindow:
|
|||
st = gajim.config.get('mergeaccounts')
|
||||
self.xml.get_widget('merge_checkbutton').set_active(st)
|
||||
|
||||
import os
|
||||
|
||||
avahi_error = False
|
||||
try:
|
||||
import avahi
|
||||
except ImportError:
|
||||
avahi_error = True
|
||||
|
||||
# enable zeroconf
|
||||
st = gajim.config.get('enable_zeroconf')
|
||||
w = self.xml.get_widget('enable_zeroconf_checkbutton')
|
||||
w.set_active(st)
|
||||
if os.name == 'nt' or (avahi_error and not w.get_active()):
|
||||
w.set_sensitive(False)
|
||||
self.zeroconf_toggled_id = w.connect('toggled', self.on_enable_zeroconf_checkbutton_toggled)
|
||||
|
||||
def on_accounts_window_key_press_event(self, widget, event):
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
self.window.destroy()
|
||||
|
@ -1847,6 +1868,17 @@ class AccountsWindow:
|
|||
dialogs.ErrorDialog(_('Unread events'),
|
||||
_('Read all pending events before removing this account.'))
|
||||
return
|
||||
|
||||
if gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
w = self.xml.get_widget('enable_zeroconf_checkbutton')
|
||||
w.set_active(False)
|
||||
else:
|
||||
if gajim.interface.instances[account].has_key('remove_account'):
|
||||
gajim.interface.instances[account]['remove_account'].window.present()
|
||||
else:
|
||||
gajim.interface.instances[account]['remove_account'] = \
|
||||
RemoveAccountWindow(account)
|
||||
|
||||
win_opened = False
|
||||
if gajim.interface.msg_win_mgr.get_controls(acct = account):
|
||||
win_opened = True
|
||||
|
@ -1891,21 +1923,107 @@ class AccountsWindow:
|
|||
self.show_modification_window(account)
|
||||
|
||||
def show_modification_window(self, account):
|
||||
if gajim.interface.instances[account].has_key('account_modification'):
|
||||
gajim.interface.instances[account]['account_modification'].window.present()
|
||||
if gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
if gajim.interface.instances.has_key('zeroconf_properties'):
|
||||
gajim.interface.instances['zeroconf_properties'].window.present()
|
||||
else:
|
||||
gajim.interface.instances['zeroconf_properties'] = \
|
||||
ZeroconfPropertiesWindow()
|
||||
else:
|
||||
gajim.interface.instances[account]['account_modification'] = \
|
||||
AccountModificationWindow(account)
|
||||
if gajim.interface.instances[account].has_key('account_modification'):
|
||||
gajim.interface.instances[account]['account_modification'].window.present()
|
||||
else:
|
||||
gajim.interface.instances[account]['account_modification'] = \
|
||||
AccountModificationWindow(account)
|
||||
|
||||
def on_checkbutton_toggled(self, widget, config_name,
|
||||
change_sensitivity_widgets = None):
|
||||
gajim.config.set(config_name, widget.get_active())
|
||||
if change_sensitivity_widgets:
|
||||
for w in change_sensitivity_widgets:
|
||||
w.set_sensitive(widget.get_active())
|
||||
gajim.interface.save_config()
|
||||
|
||||
def on_merge_checkbutton_toggled(self, widget):
|
||||
gajim.config.set('mergeaccounts', widget.get_active())
|
||||
gajim.interface.save_config()
|
||||
self.on_checkbutton_toggled(widget, 'mergeaccounts')
|
||||
if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
|
||||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
|
||||
|
||||
def on_enable_zeroconf_checkbutton_toggled(self, widget):
|
||||
# don't do anything if there is an account with the local name but is a normal account
|
||||
if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME) and not gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME].dispatch('ERROR', (_('Account Local already exists.'),_('Please rename or remove it before enabling link-local messaging.')))
|
||||
widget.disconnect(self.zeroconf_toggled_id)
|
||||
widget.set_active(False)
|
||||
self.zeroconf_toggled_id = widget.connect('toggled', self.on_enable_zeroconf_checkbutton_toggled)
|
||||
return
|
||||
|
||||
if gajim.config.get('enable_zeroconf'):
|
||||
#disable
|
||||
gajim.interface.roster.close_all(gajim.ZEROCONF_ACC_NAME)
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME].disable_account()
|
||||
del gajim.connections[gajim.ZEROCONF_ACC_NAME]
|
||||
gajim.interface.save_config()
|
||||
del gajim.interface.instances[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.nicks[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.groups[gajim.ZEROCONF_ACC_NAME]
|
||||
gajim.contacts.remove_account(gajim.ZEROCONF_ACC_NAME)
|
||||
del gajim.gc_connected[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.newly_added[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.last_message_time[gajim.ZEROCONF_ACC_NAME]
|
||||
del gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME]
|
||||
if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
|
||||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.actions_menu_needs_rebuild = True
|
||||
if gajim.interface.instances.has_key('accounts'):
|
||||
gajim.interface.instances['accounts'].init_accounts()
|
||||
|
||||
else:
|
||||
# enable (will create new account if not present)
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
|
||||
# update variables
|
||||
gajim.interface.instances[gajim.ZEROCONF_ACC_NAME] = {'infos': {}, 'disco': {},
|
||||
'gc_config': {}}
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME].connected = 0
|
||||
gajim.groups[gajim.ZEROCONF_ACC_NAME] = {}
|
||||
gajim.contacts.add_account(gajim.ZEROCONF_ACC_NAME)
|
||||
gajim.gc_connected[gajim.ZEROCONF_ACC_NAME] = {}
|
||||
gajim.automatic_rooms[gajim.ZEROCONF_ACC_NAME] = {}
|
||||
gajim.newly_added[gajim.ZEROCONF_ACC_NAME] = []
|
||||
gajim.to_be_removed[gajim.ZEROCONF_ACC_NAME] = []
|
||||
gajim.nicks[gajim.ZEROCONF_ACC_NAME] = gajim.ZEROCONF_ACC_NAME
|
||||
gajim.block_signed_in_notifications[gajim.ZEROCONF_ACC_NAME] = True
|
||||
gajim.sleeper_state[gajim.ZEROCONF_ACC_NAME] = 'off'
|
||||
gajim.encrypted_chats[gajim.ZEROCONF_ACC_NAME] = []
|
||||
gajim.last_message_time[gajim.ZEROCONF_ACC_NAME] = {}
|
||||
gajim.status_before_autoaway[gajim.ZEROCONF_ACC_NAME] = ''
|
||||
# refresh accounts window
|
||||
if gajim.interface.instances.has_key('accounts'):
|
||||
gajim.interface.instances['accounts'].init_accounts()
|
||||
# refresh roster
|
||||
if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
|
||||
gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
|
||||
else:
|
||||
gajim.interface.roster.regroup = False
|
||||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.actions_menu_needs_rebuild = True
|
||||
gajim.interface.save_config()
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME].change_status('online', '')
|
||||
|
||||
self.on_checkbutton_toggled(widget, 'enable_zeroconf')
|
||||
|
||||
class DataFormWindow:
|
||||
def __init__(self, account, config):
|
||||
self.account = account
|
||||
|
@ -3034,3 +3152,203 @@ _('You can set advanced account options by pressing Advanced button, or later by
|
|||
gajim.interface.roster.draw_roster()
|
||||
gajim.interface.roster.actions_menu_needs_rebuild = True
|
||||
gajim.interface.save_config()
|
||||
|
||||
#---------- ZeroconfPropertiesWindow class -------------#
|
||||
class ZeroconfPropertiesWindow:
|
||||
def __init__(self):
|
||||
self.xml = gtkgui_helpers.get_glade('zeroconf_properties_window.glade')
|
||||
self.window = self.xml.get_widget('zeroconf_properties_window')
|
||||
self.window.set_transient_for(gajim.interface.roster.window)
|
||||
self.xml.signal_autoconnect(self)
|
||||
|
||||
self.init_account()
|
||||
self.init_account_gpg()
|
||||
|
||||
self.xml.get_widget('save_button').grab_focus()
|
||||
self.window.show_all()
|
||||
|
||||
def init_account(self):
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect')
|
||||
if st:
|
||||
self.xml.get_widget('autoconnect_checkbutton').set_active(st)
|
||||
|
||||
list_no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,'no_log_for').split()
|
||||
if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
|
||||
self.xml.get_widget('log_history_checkbutton').set_active(0)
|
||||
else:
|
||||
self.xml.get_widget('log_history_checkbutton').set_active(1)
|
||||
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
|
||||
if st:
|
||||
self.xml.get_widget('sync_with_global_status_checkbutton').set_active(st)
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name')
|
||||
if st:
|
||||
self.xml.get_widget('first_name_entry').set_text(st)
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name')
|
||||
if st:
|
||||
self.xml.get_widget('last_name_entry').set_text(st)
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id')
|
||||
if st:
|
||||
self.xml.get_widget('jabber_id_entry').set_text(st)
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
|
||||
if st:
|
||||
self.xml.get_widget('email_entry').set_text(st)
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
|
||||
if st:
|
||||
self.xml.get_widget('custom_port_entry').set_text(str(st))
|
||||
|
||||
st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'use_custom_host')
|
||||
if st:
|
||||
self.xml.get_widget('custom_port_checkbutton').set_active(st)
|
||||
|
||||
self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
|
||||
|
||||
if not st:
|
||||
gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', '5298')
|
||||
|
||||
def init_account_gpg(self):
|
||||
keyid = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyid')
|
||||
keyname = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'keyname')
|
||||
savegpgpass = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,'savegpgpass')
|
||||
|
||||
if not keyid or not gajim.config.get('usegpg'):
|
||||
return
|
||||
|
||||
self.xml.get_widget('gpg_key_label').set_text(keyid)
|
||||
self.xml.get_widget('gpg_name_label').set_text(keyname)
|
||||
gpg_save_password_checkbutton = \
|
||||
self.xml.get_widget('gpg_save_password_checkbutton')
|
||||
gpg_save_password_checkbutton.set_sensitive(True)
|
||||
gpg_save_password_checkbutton.set_active(savegpgpass)
|
||||
|
||||
if savegpgpass:
|
||||
entry = self.xml.get_widget('gpg_password_entry')
|
||||
entry.set_sensitive(True)
|
||||
gpgpassword = gajim.config.get_per('accounts',
|
||||
gajim.ZEROCONF_ACC_NAME, 'gpgpassword')
|
||||
entry.set_text(gpgpassword)
|
||||
|
||||
def on_zeroconf_properties_window_destroy(self, widget):
|
||||
#close window
|
||||
if gajim.interface.instances.has_key('zeroconf_properties'):
|
||||
del gajim.interface.instances['zeroconf_properties']
|
||||
|
||||
def on_custom_port_checkbutton_toggled(self, widget):
|
||||
st = self.xml.get_widget('custom_port_checkbutton').get_active()
|
||||
self.xml.get_widget('custom_port_entry').set_sensitive(bool(st))
|
||||
|
||||
def on_cancel_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
def on_save_button_clicked(self, widget):
|
||||
config = {}
|
||||
|
||||
st = self.xml.get_widget('autoconnect_checkbutton').get_active()
|
||||
config['autoconnect'] = st
|
||||
list_no_log_for = gajim.config.get_per('accounts',
|
||||
gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
|
||||
if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
|
||||
list_no_log_for.remove(gajim.ZEROCONF_ACC_NAME)
|
||||
if not self.xml.get_widget('log_history_checkbutton').get_active():
|
||||
list_no_log_for.append(gajim.ZEROCONF_ACC_NAME)
|
||||
config['no_log_for'] = ' '.join(list_no_log_for)
|
||||
|
||||
st = self.xml.get_widget('sync_with_global_status_checkbutton').get_active()
|
||||
config['sync_with_global_status'] = st
|
||||
|
||||
st = self.xml.get_widget('first_name_entry').get_text()
|
||||
config['zeroconf_first_name'] = st.decode('utf-8')
|
||||
|
||||
st = self.xml.get_widget('last_name_entry').get_text()
|
||||
config['zeroconf_last_name'] = st.decode('utf-8')
|
||||
|
||||
st = self.xml.get_widget('jabber_id_entry').get_text()
|
||||
config['zeroconf_jabber_id'] = st.decode('utf-8')
|
||||
|
||||
st = self.xml.get_widget('email_entry').get_text()
|
||||
config['zeroconf_email'] = st.decode('utf-8')
|
||||
|
||||
use_custom_port = self.xml.get_widget('custom_port_checkbutton').get_active()
|
||||
config['use_custom_host'] = use_custom_port
|
||||
|
||||
old_port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port')
|
||||
if use_custom_port:
|
||||
port = self.xml.get_widget('custom_port_entry').get_text()
|
||||
else:
|
||||
port = 5298
|
||||
|
||||
config['custom_port'] = port
|
||||
|
||||
config['keyname'] = self.xml.get_widget('gpg_name_label').get_text().decode('utf-8')
|
||||
if config['keyname'] == '': #no key selected
|
||||
config['keyid'] = ''
|
||||
config['savegpgpass'] = False
|
||||
config['gpgpassword'] = ''
|
||||
else:
|
||||
config['keyid'] = self.xml.get_widget('gpg_key_label').get_text().decode('utf-8')
|
||||
config['savegpgpass'] = self.xml.get_widget(
|
||||
'gpg_save_password_checkbutton').get_active()
|
||||
config['gpgpassword'] = self.xml.get_widget('gpg_password_entry'
|
||||
).get_text().decode('utf-8')
|
||||
|
||||
reconnect = False
|
||||
for opt in ('zeroconf_first_name','zeroconf_last_name', 'zeroconf_jabber_id', 'zeroconf_email', 'custom_port'):
|
||||
if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, opt) != config[opt]:
|
||||
reconnect = True
|
||||
|
||||
for opt in config:
|
||||
gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, opt, config[opt])
|
||||
|
||||
if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
|
||||
if port != old_port or reconnect:
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
|
||||
|
||||
self.window.destroy()
|
||||
|
||||
def on_gpg_choose_button_clicked(self, widget, data = None):
|
||||
if gajim.connections.has_key(gajim.ZEROCONF_ACC_NAME):
|
||||
secret_keys = gajim.connections[gajim.ZEROCONF_ACC_NAME].ask_gpg_secrete_keys()
|
||||
|
||||
# self.account is None and/or gajim.connections is {}
|
||||
else:
|
||||
from common import GnuPG
|
||||
if GnuPG.USE_GPG:
|
||||
secret_keys = GnuPG.GnuPG().get_secret_keys()
|
||||
else:
|
||||
secret_keys = []
|
||||
if not secret_keys:
|
||||
dialogs.ErrorDialog(_('Failed to get secret keys'),
|
||||
_('There was a problem retrieving your OpenPGP secret keys.'))
|
||||
return
|
||||
secret_keys[_('None')] = _('None')
|
||||
instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
|
||||
_('Choose your OpenPGP key'), secret_keys)
|
||||
keyID = instance.run()
|
||||
if keyID is None:
|
||||
return
|
||||
checkbutton = self.xml.get_widget('gpg_save_password_checkbutton')
|
||||
gpg_key_label = self.xml.get_widget('gpg_key_label')
|
||||
gpg_name_label = self.xml.get_widget('gpg_name_label')
|
||||
if keyID[0] == _('None'):
|
||||
gpg_key_label.set_text(_('No key selected'))
|
||||
gpg_name_label.set_text('')
|
||||
checkbutton.set_sensitive(False)
|
||||
self.xml.get_widget('gpg_password_entry').set_sensitive(False)
|
||||
else:
|
||||
gpg_key_label.set_text(keyID[0])
|
||||
gpg_name_label.set_text(keyID[1])
|
||||
checkbutton.set_sensitive(True)
|
||||
checkbutton.set_active(False)
|
||||
self.xml.get_widget('gpg_password_entry').set_text('')
|
||||
|
||||
def on_gpg_save_password_checkbutton_toggled(self, widget):
|
||||
st = widget.get_active()
|
||||
w = self.xml.get_widget('gpg_password_entry')
|
||||
w.set_sensitive(bool(st))
|
||||
# w.set_text = ''
|
||||
|
|
28
src/gajim.py
28
src/gajim.py
|
@ -31,6 +31,7 @@ import message_control
|
|||
from chat_control import ChatControlBase
|
||||
|
||||
from common import exceptions
|
||||
from common.zeroconf import connection_zeroconf
|
||||
|
||||
if os.name == 'posix': # dl module is Unix Only
|
||||
try: # rename the process name to gajim
|
||||
|
@ -618,7 +619,9 @@ class Interface:
|
|||
jids = full_jid_with_resource.split('/', 1)
|
||||
jid = jids[0]
|
||||
gc_control = self.msg_win_mgr.get_control(jid, account)
|
||||
if gc_control and gc_control.type_id == message_control.TYPE_GC:
|
||||
if gc_control and gc_control.type_id != message_control.TYPE_GC:
|
||||
gc_control = None
|
||||
if gc_control:
|
||||
if len(jids) > 1: # it's a pm
|
||||
nick = jids[1]
|
||||
if not self.msg_win_mgr.get_control(full_jid_with_resource,
|
||||
|
@ -1406,6 +1409,20 @@ class Interface:
|
|||
if win.startswith('privacy_list_'):
|
||||
self.instances[account][win].check_active_default(data)
|
||||
|
||||
def handle_event_zc_name_conflict(self, account, data):
|
||||
dlg = dialogs.InputDialog(_('Username Conflict'),
|
||||
_('Please type a new username for your local account'),
|
||||
is_modal = True)
|
||||
dlg.input_entry.set_text(data)
|
||||
response = dlg.get_response()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
new_name = dlg.input_entry.get_text()
|
||||
gajim.config.set_per('accounts', account, 'name', new_name)
|
||||
status = gajim.connections[account].status
|
||||
gajim.connections[account].username = new_name
|
||||
gajim.connections[account].change_status(status, '')
|
||||
|
||||
|
||||
def read_sleepy(self):
|
||||
'''Check idle status and change that status if needed'''
|
||||
if not self.sleeper.poll():
|
||||
|
@ -1711,6 +1728,7 @@ class Interface:
|
|||
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
|
||||
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
|
||||
self.handle_event_privacy_lists_active_default,
|
||||
'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
|
||||
}
|
||||
gajim.handlers = self.handlers
|
||||
|
||||
|
@ -1867,9 +1885,13 @@ class Interface:
|
|||
self.handle_event_file_progress)
|
||||
gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
|
||||
self.register_handlers()
|
||||
if gajim.config.get('enable_zeroconf'):
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
|
||||
for account in gajim.config.get_per('accounts'):
|
||||
gajim.connections[account] = common.connection.Connection(account)
|
||||
|
||||
if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
gajim.connections[account] = common.connection.Connection(account)
|
||||
|
||||
# gtk hooks
|
||||
# gtk hooks
|
||||
gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
|
||||
gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
|
||||
|
|
|
@ -1119,6 +1119,14 @@ class RosterWindow:
|
|||
else:
|
||||
info[contact.jid] = vcard.VcardWindow(contact, account)
|
||||
|
||||
def on_info_zeroconf(self, widget, contact, account):
|
||||
info = gajim.interface.instances[account]['infos']
|
||||
if info.has_key(contact.jid):
|
||||
info[contact.jid].window.present()
|
||||
else:
|
||||
info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
|
||||
|
||||
|
||||
def show_tooltip(self, contact):
|
||||
pointer = self.tree.get_pointer()
|
||||
props = self.tree.get_path_at_pos(pointer[0], pointer[1])
|
||||
|
@ -1363,6 +1371,118 @@ class RosterWindow:
|
|||
if not contact:
|
||||
return
|
||||
|
||||
if gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade')
|
||||
zeroconf_contact_context_menu = xml.get_widget('zeroconf_contact_context_menu')
|
||||
|
||||
start_chat_menuitem = xml.get_widget('start_chat_menuitem')
|
||||
rename_menuitem = xml.get_widget('rename_menuitem')
|
||||
edit_groups_menuitem = xml.get_widget('edit_groups_menuitem')
|
||||
# separator has with send file, assign_openpgp_key_menuitem, etc..
|
||||
above_send_file_separator = xml.get_widget('above_send_file_separator')
|
||||
send_file_menuitem = xml.get_widget('send_file_menuitem')
|
||||
assign_openpgp_key_menuitem = xml.get_widget(
|
||||
'assign_openpgp_key_menuitem')
|
||||
add_special_notification_menuitem = xml.get_widget(
|
||||
'add_special_notification_menuitem')
|
||||
|
||||
add_special_notification_menuitem.hide()
|
||||
add_special_notification_menuitem.set_no_show_all(True)
|
||||
|
||||
if not our_jid:
|
||||
# add a special img for rename menuitem
|
||||
path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
|
||||
'kbd_input.png')
|
||||
img = gtk.Image()
|
||||
img.set_from_file(path_to_kbd_input_img)
|
||||
rename_menuitem.set_image(img)
|
||||
|
||||
above_information_separator = xml.get_widget(
|
||||
'above_information_separator')
|
||||
|
||||
# skip a separator
|
||||
information_menuitem = xml.get_widget('information_menuitem')
|
||||
history_menuitem = xml.get_widget('history_menuitem')
|
||||
|
||||
contacts = gajim.contacts.get_contact(account, jid)
|
||||
if len(contacts) > 1: # sevral resources
|
||||
sub_menu = gtk.Menu()
|
||||
start_chat_menuitem.set_submenu(sub_menu)
|
||||
|
||||
iconset = gajim.config.get('iconset')
|
||||
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
|
||||
for c in contacts:
|
||||
# icon MUST be different instance for every item
|
||||
state_images = self.load_iconset(path)
|
||||
item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')')
|
||||
icon_name = helpers.get_icon_name_to_show(c, account)
|
||||
icon = state_images[icon_name]
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_open_chat_window, c, account,
|
||||
c.resource)
|
||||
|
||||
else: # one resource
|
||||
start_chat_menuitem.connect('activate',
|
||||
self.on_roster_treeview_row_activated, tree_path)
|
||||
|
||||
if contact.resource:
|
||||
send_file_menuitem.connect('activate',
|
||||
self.on_send_file_menuitem_activate, account, contact)
|
||||
else: # if we do not have resource we cannot send file
|
||||
send_file_menuitem.hide()
|
||||
send_file_menuitem.set_no_show_all(True)
|
||||
|
||||
rename_menuitem.connect('activate', self.on_rename, iter, tree_path)
|
||||
information_menuitem.connect('activate', self.on_info_zeroconf, contact,
|
||||
account)
|
||||
history_menuitem.connect('activate', self.on_history, contact,
|
||||
account)
|
||||
|
||||
if _('Not in Roster') not in contact.groups:
|
||||
#contact is in normal group
|
||||
edit_groups_menuitem.set_no_show_all(False)
|
||||
assign_openpgp_key_menuitem.set_no_show_all(False)
|
||||
edit_groups_menuitem.connect('activate', self.on_edit_groups, [(
|
||||
contact,account)])
|
||||
|
||||
if gajim.config.get('usegpg'):
|
||||
assign_openpgp_key_menuitem.connect('activate',
|
||||
self.on_assign_pgp_key, contact, account)
|
||||
|
||||
else: # contact is in group 'Not in Roster'
|
||||
edit_groups_menuitem.hide()
|
||||
edit_groups_menuitem.set_no_show_all(True)
|
||||
# hide first of the two consecutive separators
|
||||
above_send_file_separator.hide()
|
||||
above_send_file_separator.set_no_show_all(True)
|
||||
assign_openpgp_key_menuitem.hide()
|
||||
assign_openpgp_key_menuitem.set_no_show_all(True)
|
||||
|
||||
# Remove many items when it's self contact row
|
||||
if our_jid:
|
||||
for menuitem in (rename_menuitem, edit_groups_menuitem,
|
||||
above_information_separator):
|
||||
menuitem.set_no_show_all(True)
|
||||
menuitem.hide()
|
||||
|
||||
# Unsensitive many items when account is offline
|
||||
if gajim.connections[account].connected < 2:
|
||||
for widget in [start_chat_menuitem, rename_menuitem, edit_groups_menuitem, send_file_menuitem]:
|
||||
widget.set_sensitive(False)
|
||||
|
||||
event_button = gtkgui_helpers.get_possible_button_event(event)
|
||||
|
||||
zeroconf_contact_context_menu.attach_to_widget(self.tree, None)
|
||||
zeroconf_contact_context_menu.connect('selection-done',
|
||||
gtkgui_helpers.destroy_widget)
|
||||
zeroconf_contact_context_menu.show_all()
|
||||
zeroconf_contact_context_menu.popup(None, None, None, event_button,
|
||||
event.time)
|
||||
return
|
||||
|
||||
|
||||
# normal account
|
||||
xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade')
|
||||
roster_contact_context_menu = xml.get_widget(
|
||||
'roster_contact_context_menu')
|
||||
|
@ -1773,6 +1893,14 @@ class RosterWindow:
|
|||
gajim.interface.instances[account]['account_modification'] = \
|
||||
config.AccountModificationWindow(account)
|
||||
|
||||
def on_zeroconf_properties(self, widget, account):
|
||||
if gajim.interface.instances.has_key('zeroconf_properties'):
|
||||
gajim.interface.instances['zeroconf_properties'].\
|
||||
window.present()
|
||||
else:
|
||||
gajim.interface.instances['zeroconf_properties'] = \
|
||||
config.ZeroconfPropertiesWindow()
|
||||
|
||||
def on_open_gmail_inbox(self, widget, account):
|
||||
url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
|
||||
gajim.config.get_per('accounts', account, 'name'))
|
||||
|
@ -1792,71 +1920,123 @@ class RosterWindow:
|
|||
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
|
||||
state_images = self.load_iconset(path)
|
||||
|
||||
xml = gtkgui_helpers.get_glade('account_context_menu.glade')
|
||||
account_context_menu = xml.get_widget('account_context_menu')
|
||||
if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
xml = gtkgui_helpers.get_glade('account_context_menu.glade')
|
||||
account_context_menu = xml.get_widget('account_context_menu')
|
||||
|
||||
status_menuitem = xml.get_widget('status_menuitem')
|
||||
join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
|
||||
open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
|
||||
new_message_menuitem = xml.get_widget('new_message_menuitem')
|
||||
add_contact_menuitem = xml.get_widget('add_contact_menuitem')
|
||||
service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
|
||||
edit_account_menuitem = xml.get_widget('edit_account_menuitem')
|
||||
sub_menu = gtk.Menu()
|
||||
status_menuitem.set_submenu(sub_menu)
|
||||
status_menuitem = xml.get_widget('status_menuitem')
|
||||
join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
|
||||
open_gmail_inbox_menuitem = xml.get_widget('open_gmail_inbox_menuitem')
|
||||
new_message_menuitem = xml.get_widget('new_message_menuitem')
|
||||
add_contact_menuitem = xml.get_widget('add_contact_menuitem')
|
||||
service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
|
||||
edit_account_menuitem = xml.get_widget('edit_account_menuitem')
|
||||
sub_menu = gtk.Menu()
|
||||
status_menuitem.set_submenu(sub_menu)
|
||||
|
||||
for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||
uf_show = helpers.get_uf_show(show, use_mnemonic = True)
|
||||
for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||
uf_show = helpers.get_uf_show(show, use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
icon = state_images[show]
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.change_status, account, show)
|
||||
|
||||
item = gtk.SeparatorMenuItem()
|
||||
sub_menu.append(item)
|
||||
|
||||
item = gtk.ImageMenuItem(_('_Change Status Message'))
|
||||
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
|
||||
img = gtk.Image()
|
||||
img.set_from_file(path)
|
||||
item.set_image(img)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_change_status_message_activate, account)
|
||||
if gajim.connections[account].connected < 2:
|
||||
item.set_sensitive(False)
|
||||
|
||||
uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
icon = state_images[show]
|
||||
icon = state_images['offline']
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.change_status, account, show)
|
||||
item.connect('activate', self.change_status, account, 'offline')
|
||||
|
||||
item = gtk.SeparatorMenuItem()
|
||||
sub_menu.append(item)
|
||||
if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
|
||||
open_gmail_inbox_menuitem.set_no_show_all(True)
|
||||
open_gmail_inbox_menuitem.hide()
|
||||
else:
|
||||
open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
|
||||
account)
|
||||
|
||||
item = gtk.ImageMenuItem(_('_Change Status Message'))
|
||||
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
|
||||
img = gtk.Image()
|
||||
img.set_from_file(path)
|
||||
item.set_image(img)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_change_status_message_activate, account)
|
||||
if gajim.connections[account].connected < 2:
|
||||
item.set_sensitive(False)
|
||||
edit_account_menuitem.connect('activate', self.on_edit_account, account)
|
||||
add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
|
||||
service_discovery_menuitem.connect('activate',
|
||||
self.on_service_disco_menuitem_activate, account)
|
||||
|
||||
gc_sub_menu = gtk.Menu() # gc is always a submenu
|
||||
join_group_chat_menuitem.set_submenu(gc_sub_menu)
|
||||
self.add_bookmarks_list(gc_sub_menu, account)
|
||||
new_message_menuitem.connect('activate',
|
||||
self.on_new_message_menuitem_activate, account)
|
||||
|
||||
uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
icon = state_images['offline']
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.change_status, account, 'offline')
|
||||
|
||||
if gajim.config.get_per('accounts', account, 'hostname') not in gajim.gmail_domains:
|
||||
open_gmail_inbox_menuitem.set_no_show_all(True)
|
||||
open_gmail_inbox_menuitem.hide()
|
||||
# make some items insensitive if account is offline
|
||||
if gajim.connections[account].connected < 2:
|
||||
for widget in [add_contact_menuitem, service_discovery_menuitem,
|
||||
join_group_chat_menuitem, new_message_menuitem]:
|
||||
widget.set_sensitive(False)
|
||||
else:
|
||||
open_gmail_inbox_menuitem.connect('activate', self.on_open_gmail_inbox,
|
||||
account)
|
||||
xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
|
||||
account_context_menu = xml.get_widget('zeroconf_context_menu')
|
||||
|
||||
edit_account_menuitem.connect('activate', self.on_edit_account, account)
|
||||
add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
|
||||
service_discovery_menuitem.connect('activate',
|
||||
self.on_service_disco_menuitem_activate, account)
|
||||
|
||||
gc_sub_menu = gtk.Menu() # gc is always a submenu
|
||||
join_group_chat_menuitem.set_submenu(gc_sub_menu)
|
||||
self.add_bookmarks_list(gc_sub_menu, account)
|
||||
new_message_menuitem.connect('activate',
|
||||
self.on_new_message_menuitem_activate, account)
|
||||
status_menuitem = xml.get_widget('status_menuitem')
|
||||
#join_group_chat_menuitem =xml.get_widget('join_group_chat_menuitem')
|
||||
new_message_menuitem = xml.get_widget('new_message_menuitem')
|
||||
zeroconf_properties_menuitem = xml.get_widget('zeroconf_properties_menuitem')
|
||||
sub_menu = gtk.Menu()
|
||||
status_menuitem.set_submenu(sub_menu)
|
||||
|
||||
# make some items insensitive if account is offline
|
||||
if gajim.connections[account].connected < 2:
|
||||
for widget in [add_contact_menuitem, service_discovery_menuitem,
|
||||
join_group_chat_menuitem, new_message_menuitem]:
|
||||
widget.set_sensitive(False)
|
||||
|
||||
for show in ('online', 'away', 'dnd', 'invisible'):
|
||||
uf_show = helpers.get_uf_show(show, use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
icon = state_images[show]
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.change_status, account, show)
|
||||
|
||||
item = gtk.SeparatorMenuItem()
|
||||
sub_menu.append(item)
|
||||
|
||||
item = gtk.ImageMenuItem(_('_Change Status Message'))
|
||||
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png')
|
||||
img = gtk.Image()
|
||||
img.set_from_file(path)
|
||||
item.set_image(img)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_change_status_message_activate, account)
|
||||
if gajim.connections[account].connected < 2:
|
||||
item.set_sensitive(False)
|
||||
|
||||
uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
icon = state_images['offline']
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.change_status, account, 'offline')
|
||||
|
||||
zeroconf_properties_menuitem.connect('activate', self.on_zeroconf_properties, account)
|
||||
#gc_sub_menu = gtk.Menu() # gc is always a submenu
|
||||
#join_group_chat_menuitem.set_submenu(gc_sub_menu)
|
||||
#self.add_bookmarks_list(gc_sub_menu, account)
|
||||
#new_message_menuitem.connect('activate',
|
||||
# self.on_new_message_menuitem_activate, account)
|
||||
|
||||
# make some items insensitive if account is offline
|
||||
#if gajim.connections[account].connected < 2:
|
||||
# for widget in [join_group_chat_menuitem, new_message_menuitem]:
|
||||
# widget.set_sensitive(False)
|
||||
# new_message_menuitem.set_sensitive(False)
|
||||
|
||||
return account_context_menu
|
||||
|
||||
def make_account_menu(self, event, iter):
|
||||
|
@ -1967,6 +2147,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
|
|||
if not len(list_of_paths):
|
||||
return
|
||||
type = model[list_of_paths[0]][C_TYPE]
|
||||
account = model[list_of_paths[0]][C_ACCOUNT]
|
||||
list_ = []
|
||||
for path in list_of_paths:
|
||||
if model[path][C_TYPE] != type:
|
||||
|
@ -1976,7 +2157,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
|
|||
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||
jid)
|
||||
list_.append((contact, account))
|
||||
if type in ('account', 'group', 'self_contact'):
|
||||
if type in ('account', 'group', 'self_contact') or account == gajim.ZEROCONF_ACC_NAME:
|
||||
return
|
||||
if type == 'contact':
|
||||
self.on_req_usub(widget, list_)
|
||||
|
@ -3572,6 +3753,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
|
|||
account_dest, c_dest, path)
|
||||
return
|
||||
|
||||
if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
|
||||
# drop on zeroconf account, no contact adds possible
|
||||
return
|
||||
|
||||
if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2:
|
||||
# dropped before a group : we drop it in the previous group
|
||||
path_dest = (path_dest[0], path_dest[1]-1)
|
||||
|
@ -3583,6 +3768,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
|
|||
return
|
||||
if type_dest == 'account' and account_source == account_dest:
|
||||
return
|
||||
if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
|
||||
return
|
||||
it = iter_source
|
||||
while model[it][C_TYPE] == 'contact':
|
||||
it = model.iter_parent(it)
|
||||
|
|
|
@ -307,7 +307,14 @@ class GCTooltip(BaseTooltip):
|
|||
properties.append((show, None))
|
||||
|
||||
if contact.jid.strip() != '':
|
||||
properties.append((_('Jabber ID: '), contact.jid))
|
||||
jid_markup = '<span weight="bold">' + contact.jid + '</span>'
|
||||
else:
|
||||
jid_markup = '<span weight="bold">' + \
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \
|
||||
+ '</span>'
|
||||
properties.append((jid_markup, None))
|
||||
properties.append((_('Role: '), helpers.get_uf_role(contact.role)))
|
||||
properties.append((_('Affiliation: '), contact.affiliation.capitalize()))
|
||||
if hasattr(contact, 'resource') and contact.resource.strip() != '':
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
|
||||
|
@ -408,10 +415,25 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
vcard_table.set_homogeneous(False)
|
||||
vcard_current_row = 1
|
||||
properties = []
|
||||
name_markup = '<b>%s</b>' % gtkgui_helpers.escape_for_pango_markup(
|
||||
prim_contact.get_shown_name())
|
||||
properties.append((name_markup, None))
|
||||
|
||||
jid_markup = '<span weight="bold">' + prim_contact.jid + '</span>'
|
||||
properties.append((jid_markup, None))
|
||||
|
||||
properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
|
||||
prim_contact.get_shown_name())))
|
||||
if prim_contact.sub:
|
||||
properties.append(( _('Subscription: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
|
||||
if prim_contact.keyID:
|
||||
keyID = None
|
||||
if len(prim_contact.keyID) == 8:
|
||||
keyID = prim_contact.keyID
|
||||
elif len(prim_contact.keyID) == 16:
|
||||
keyID = prim_contact.keyID[8:]
|
||||
if keyID:
|
||||
properties.append((_('OpenPGP: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(keyID)))
|
||||
|
||||
num_resources = 0
|
||||
# put contacts in dict, where key is priority
|
||||
contacts_dict = {}
|
||||
|
@ -422,6 +444,11 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
contacts_dict[contact.priority].append(contact)
|
||||
else:
|
||||
contacts_dict[contact.priority] = [contact]
|
||||
|
||||
if num_resources == 1 and contact.resource:
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
|
||||
unicode(contact.priority) + ')'))
|
||||
if num_resources > 1:
|
||||
properties.append((_('Status: '), ' '))
|
||||
transport = gajim.get_transport_name_from_jid(
|
||||
|
|
148
src/vcard.py
148
src/vcard.py
|
@ -2,6 +2,7 @@
|
|||
##
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -336,3 +337,150 @@ class VcardWindow:
|
|||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
|
||||
class ZeroconfVcardWindow:
|
||||
def __init__(self, contact, account, is_fake = False):
|
||||
# the contact variable is the jid if vcard is true
|
||||
self.xml = gtkgui_helpers.get_glade('zeroconf_information_window.glade')
|
||||
self.window = self.xml.get_widget('zeroconf_information_window')
|
||||
|
||||
self.contact = contact
|
||||
self.account = account
|
||||
self.is_fake = is_fake
|
||||
|
||||
# self.avatar_mime_type = None
|
||||
# self.avatar_encoded = None
|
||||
|
||||
self.fill_contact_page()
|
||||
self.fill_personal_page()
|
||||
|
||||
self.xml.signal_autoconnect(self)
|
||||
self.window.show_all()
|
||||
|
||||
def on_zeroconf_information_window_destroy(self, widget):
|
||||
del gajim.interface.instances[self.account]['infos'][self.contact.jid]
|
||||
|
||||
def on_zeroconf_information_window_key_press_event(self, widget, event):
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
self.window.destroy()
|
||||
|
||||
def on_log_history_checkbutton_toggled(self, widget):
|
||||
#log conversation history?
|
||||
oldlog = True
|
||||
no_log_for = gajim.config.get_per('accounts', self.account,
|
||||
'no_log_for').split()
|
||||
if self.contact.jid in no_log_for:
|
||||
oldlog = False
|
||||
log = widget.get_active()
|
||||
if not log and not self.contact.jid in no_log_for:
|
||||
no_log_for.append(self.contact.jid)
|
||||
if log and self.contact.jid in no_log_for:
|
||||
no_log_for.remove(self.contact.jid)
|
||||
if oldlog != log:
|
||||
gajim.config.set_per('accounts', self.account, 'no_log_for',
|
||||
' '.join(no_log_for))
|
||||
|
||||
def on_PHOTO_eventbox_button_press_event(self, widget, event):
|
||||
'''If right-clicked, show popup'''
|
||||
if event.button == 3: # right click
|
||||
menu = gtk.Menu()
|
||||
menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.contact.jid, self.account, self.contact.name + '.jpeg')
|
||||
menu.append(menuitem)
|
||||
menu.connect('selection-done', lambda w:w.destroy())
|
||||
# show the menu
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
def set_value(self, entry_name, value):
|
||||
try:
|
||||
if value and entry_name == 'URL_label':
|
||||
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
|
||||
widget = gtk.LinkButton(value, value)
|
||||
else:
|
||||
widget = gtk.Label(value)
|
||||
table = self.xml.get_widget('personal_info_table')
|
||||
table.attach(widget, 1, 4, 3, 4, yoptions = 0)
|
||||
else:
|
||||
self.xml.get_widget(entry_name).set_text(value)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def fill_status_label(self):
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 2:
|
||||
return
|
||||
contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
|
||||
# stats holds show and status message
|
||||
stats = ''
|
||||
one = True # Are we adding the first line ?
|
||||
if contact_list:
|
||||
for c in contact_list:
|
||||
if not one:
|
||||
stats += '\n'
|
||||
stats += helpers.get_uf_show(c.show)
|
||||
if c.status:
|
||||
stats += ': ' + c.status
|
||||
if c.last_status_time:
|
||||
stats += '\n' + _('since %s') % time.strftime('%c',
|
||||
c.last_status_time).decode(locale.getpreferredencoding())
|
||||
one = False
|
||||
else: # Maybe gc_vcard ?
|
||||
stats = helpers.get_uf_show(self.contact.show)
|
||||
if self.contact.status:
|
||||
stats += ': ' + self.contact.status
|
||||
status_label = self.xml.get_widget('status_label')
|
||||
status_label.set_max_width_chars(15)
|
||||
status_label.set_text(stats)
|
||||
|
||||
tip = gtk.Tooltips()
|
||||
status_label_eventbox = self.xml.get_widget('status_label_eventbox')
|
||||
tip.set_tip(status_label_eventbox, stats)
|
||||
|
||||
def fill_contact_page(self):
|
||||
tooltips = gtk.Tooltips()
|
||||
self.xml.get_widget('nickname_label').set_markup(
|
||||
'<b><span size="x-large">' +
|
||||
self.contact.get_shown_name() +
|
||||
'</span></b>')
|
||||
self.xml.get_widget('local_jid_label').set_text(self.contact.jid)
|
||||
|
||||
log = True
|
||||
if self.contact.jid in gajim.config.get_per('accounts', self.account,
|
||||
'no_log_for').split(' '):
|
||||
log = False
|
||||
checkbutton = self.xml.get_widget('log_history_checkbutton')
|
||||
checkbutton.set_active(log)
|
||||
checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled)
|
||||
|
||||
resources = '%s (%s)' % (self.contact.resource, unicode(
|
||||
self.contact.priority))
|
||||
uf_resources = self.contact.resource + _(' resource with priority ')\
|
||||
+ unicode(self.contact.priority)
|
||||
if not self.contact.status:
|
||||
self.contact.status = ''
|
||||
|
||||
# Request list time status
|
||||
# gajim.connections[self.account].request_last_status_time(self.contact.jid,
|
||||
# self.contact.resource)
|
||||
|
||||
self.xml.get_widget('resource_prio_label').set_text(resources)
|
||||
resource_prio_label_eventbox = self.xml.get_widget(
|
||||
'resource_prio_label_eventbox')
|
||||
tooltips.set_tip(resource_prio_label_eventbox, uf_resources)
|
||||
|
||||
self.fill_status_label()
|
||||
|
||||
# gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
|
||||
|
||||
def fill_personal_page(self):
|
||||
contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
|
||||
self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st'])
|
||||
self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last'])
|
||||
self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid'])
|
||||
self.xml.get_widget('email_label').set_text(contact['txt_dict']['email'])
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
|
Loading…
Reference in New Issue