merge from trunk

This commit is contained in:
Yann Leboulanger 2011-12-29 11:39:02 +01:00
commit 49bc202421
132 changed files with 18862 additions and 20008 deletions

View File

@ -1,3 +1,34 @@
Gajim 0.15 (XX XX 2011)
* Plugin system
* Whiteboard (via a plugin)
* Message archiving
* Stream managment
* IBB
* Nested roster group
* Roster filtrering
* UPower support
* GPG support for windows
Gajim 0.14.4 (22 July 2011)
* Fix translation issue
* other minor fixes
Gajim 0.14.3 (19 June 2011)
* Fix history viewer
* Fix closing roster window
* Prevent some erros with metacontacts
Gajim 0.14.2 (07 June 2011)
* Fix CPU usage when testing file transfer proxies
* Fix invalid XML char regex
* Fix subscription request window handling
* Fix URL display in chat message banner
* Other minor bugfixes
Gajim 0.14.1 (26 October 2010)
* Fix changing account name

View File

@ -1,20 +1,12 @@
SUBDIRS = src data plugins po icons
SUBDIRS = src data po icons plugins
ACLOCAL_AMFLAGS = -I m4
bin_SCRIPTS = scripts/gajim scripts/gajim-history-manager scripts/gajim-remote
docfilesdir = $(docdir)
docfiles_DATA = README \
README.html \
ChangeLog \
COPYING \
THANKS \
THANKS.artists \
AUTHORS
EXTRA_DIST = \
$(docfiles_DATA) \
README.html \
THANKS.artists \
autogen.sh \
intltool-extract.in \
intltool-merge.in \

View File

@ -72,7 +72,7 @@ or if you use hg version and you didn't 'make install' you can also run from gaj
</ul>
<p>steps to compile gajim:</p>
<pre>
$ sh autogen.sh
$ ./autogen.sh
$ ./configure
$ make
</pre>
@ -95,7 +95,7 @@ If you want to remove it from custom directory provide it as:
<h2>Miscellaneous</h2>
<h3>XML &amp; Debugging</h3>
<p>If you want to see the xml stanzas and/or help us debugging
<p>If you want to see the xml stanzas and/or help us debugging
you're advised to enable verbose via advanced configuration window.
If you don't want to make this permanent, execute gajim with --verbose
everytime you want to have verbose output.</p>

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
gajimversion="0.14.0.1"
gajimversion="0.15-beta2"
if [ -d ".hg" ]; then
node=$(hg tip --template "{node}")
hgversion="-${node:0:12}"

View File

@ -63,13 +63,42 @@
</child>
<child>
<object class="GtkButton" id="add_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_add_button_clicked"/>
<child>
<object class="GtkAlignment" id="alignment5">
<property name="visible">True</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkHBox" id="hbox4">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-add</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="label" translatable="yes">Add</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -78,13 +107,42 @@
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_remove_button_clicked"/>
<child>
<object class="GtkAlignment" id="alignment7">
<property name="visible">True</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkHBox" id="hbox7">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-remove</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label26">
<property name="visible">True</property>
<property name="label" translatable="yes">Delete</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -99,52 +157,37 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<signal name="clicked" handler="on_rename_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox8">
<object class="GtkAlignment" id="alignment6">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">6</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<child>
<object class="GtkLabel" id="label26">
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage" id="rename_image">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label25">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Re_name</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkImage" id="rename_image">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label25">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Re_name</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label27">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
@ -451,48 +494,70 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkHBox" id="hbox2">
<object class="GtkVBox" id="vbox13">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label28">
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Client Cert File:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">cert_entry1</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label28">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Client Cert File:</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">cert_entry1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="cert_entry1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="browse_for_client_cert_button">
<property name="label" translatable="yes">Browse...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_browse_for_client_cert_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="cert_entry1">
<object class="GtkCheckButton" id="client_cert_encrypted_checkbutton1">
<property name="label" translatable="yes">Certificate is e_ncrypted</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The path to the client certificate and key in PKCS#12 format</property>
<property name="invisible_char">&#x25CF;</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_client_cert_encrypted_checkbutton1_toggled"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="browse_for_client_cert_button">
<property name="label" translatable="yes">Browse...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Choose Client Cert</property>
<signal name="clicked" handler="on_browse_for_client_cert_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label">

View File

@ -450,7 +450,7 @@
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-info</property>
<property name="icon-size">2</property>
<property name="icon-size">1</property>
</object>
</child>
</object>

View File

@ -98,7 +98,7 @@
</child>
<child>
<object class="GtkImageMenuItem" id="manage_contact">
<property name="label" translatable="yes">_Manage Contact</property>
<property name="label" translatable="yes">M_anage Contact</property>
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="use_underline">True</property>

View File

@ -80,6 +80,22 @@
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="search_in_date">
<property name="label" translatable="yes">_In date search</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">searching only in the selected day </property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -104,14 +120,17 @@
<object class="GtkCalendar" id="calendar">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="day_selected" handler="on_calendar_day_selected"/>
<signal name="month_changed" handler="on_calendar_month_changed"/>
<signal name="day_selected" handler="on_calendar_day_selected"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="resize">False</property>

View File

@ -25,24 +25,82 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">250</property>
<property name="position_set">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">6</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="xalign">0</property>
<child>
<object class="GtkTreeView" id="installed_plugins_treeview">
<object class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">6</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkTreeView" id="installed_plugins_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAspectFrame" id="aspectframe1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<property name="xalign">0</property>
<child>
<object class="GtkButton" id="install_plugin_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_install_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-apply</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="configure_plugin_button_label2">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Install from zip</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
@ -166,7 +224,6 @@
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox5">
<property name="visible">True</property>
@ -181,15 +238,7 @@
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
<placeholder/>
</child>
</object>
<packing>
@ -220,58 +269,21 @@
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<object class="GtkHButtonBox" id="hbuttonbox3">
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="install_plugin_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_install_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox13">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="stock">gtk-apply</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="install_plugin_button_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Install</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="uninstall_plugin_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_uninstall_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox11">
<object class="GtkHBox" id="hbox6">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image1">
<object class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="stock">gtk-cancel</property>
</object>
@ -280,7 +292,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="uninstall_plugin_button_label">
<object class="GtkLabel" id="uninstall_plugin_button_label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Uninstall</property>
@ -305,10 +317,10 @@
<property name="receives_default">True</property>
<signal name="clicked" handler="on_configure_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox12">
<object class="GtkHBox" id="hbox7">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image2">
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="stock">gtk-preferences</property>
</object>
@ -317,7 +329,7 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="configure_plugin_button_label">
<object class="GtkLabel" id="configure_plugin_button_label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Configure</property>
@ -358,264 +370,6 @@
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkHPaned" id="hpaned2">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="treeview2">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox6">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="plugin_name_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox7">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">Version:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_version_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">Authors:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_authors_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox9">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">Homepage:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="plugin_homepage_linkbutton1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox10">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="label" translatable="yes">Descrition:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTextView" id="plugin_description_textview1">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox3">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="uninstall_plugin_button1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="configure_plugin_button1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Available</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>

View File

@ -2,6 +2,20 @@
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">20</property>
<property name="lower">1</property>
<property name="upper">1440</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="value">12</property>
<property name="lower">1</property>
<property name="upper">720</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item -->
@ -124,6 +138,17 @@
</row>
</data>
</object>
<object class="GtkListStore" id="liststore8">
<columns>
<!-- column-name item -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0">None</col>
</row>
</data>
</object>
<object class="GtkWindow" id="preferences_window">
<property name="border_width">6</property>
<property name="title" translatable="yes">Preferences</property>
@ -134,7 +159,6 @@
<child>
<object class="GtkVBox" id="vbox13">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkNotebook" id="preferences_notebook">
@ -145,7 +169,6 @@
<object class="GtkVBox" id="vbox41">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame1">
@ -163,7 +186,6 @@
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="show_avatars_in_roster_checkbutton">
@ -537,7 +559,6 @@
<object class="GtkVBox" id="vbox42">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkFrame" id="frame29">
@ -552,7 +573,6 @@
<child>
<object class="GtkVBox" id="vbox67">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox1">
@ -655,7 +675,6 @@
<child>
<object class="GtkVBox" id="vbox_gmail">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="notify_gmail_checkbutton">
@ -778,7 +797,6 @@
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox4">
@ -870,7 +888,6 @@
<object class="GtkVBox" id="vbox11">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame13">
@ -1002,7 +1019,6 @@
<object class="GtkVBox" id="status_vbox">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame81">
@ -1196,7 +1212,6 @@ $T will be replaced by auto-not-available timeout</property>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox2934">
@ -1268,8 +1283,6 @@ $T will be replaced by auto-not-available timeout</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">If enabled, Gajim will not ask for a status message. The specified default message will be used instead.</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="default_msg_treeview">
@ -1365,8 +1378,6 @@ $T will be replaced by auto-not-available timeout</property>
<object class="GtkScrolledWindow" id="scrolledwindow22">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="msg_treeview">
@ -1439,7 +1450,6 @@ $T will be replaced by auto-not-available timeout</property>
<object class="GtkVBox" id="vbox58">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame9">
@ -2062,7 +2072,6 @@ $T will be replaced by auto-not-available timeout</property>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="audio_frame">
@ -2365,7 +2374,6 @@ to discover one from server.</property>
<object class="GtkVBox" id="vbox45">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="applications_frame">
@ -2380,7 +2388,6 @@ to discover one from server.</property>
<child>
<object class="GtkVBox" id="vbox50">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkComboBox" id="applications_combobox">
@ -2547,7 +2554,7 @@ to discover one from server.</property>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="n_rows">4</property>
<property name="n_rows">6</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
@ -2567,7 +2574,7 @@ to discover one from server.</property>
</child>
<child>
<object class="GtkCheckButton" id="send_os_info_checkbutton">
<property name="label" translatable="yes">Allow _OS information to be sent</property>
<property name="label" translatable="yes">Allow client / _OS information to be sent</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
@ -2581,6 +2588,22 @@ to discover one from server.</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="send_time_info_checkbutton">
<property name="label" translatable="yes">Allow local system time information to be sent</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, Gajim will allow others to detect the time on your system</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_send_time_info_checkbutton_toggled"/>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="log_encrypted_chats_checkbutton">
<property name="label" translatable="yes">Log _encrypted chat session</property>
@ -2593,8 +2616,8 @@ to discover one from server.</property>
<signal name="toggled" handler="on_log_encrypted_chats_checkbutton_toggled"/>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
@ -2608,8 +2631,58 @@ to discover one from server.</property>
<signal name="toggled" handler="on_send_idle_time_checkbutton_toggled"/>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="box1">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label29">
<property name="visible">True</property>
<property name="label" translatable="yes">Global proxy:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="proxies_combobox">
<property name="visible">True</property>
<property name="model">liststore8</property>
<signal name="changed" handler="on_proxies_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext8"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="manage_proxies_button">
<property name="label" translatable="yes">_Manage...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_manage_proxies_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</object>
@ -2643,7 +2716,6 @@ to discover one from server.</property>
<child>
<object class="GtkVBox" id="vbox66">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="log_show_changes_checkbutton">
@ -2817,18 +2889,4 @@ to discover one from server.</property>
</object>
</child>
</object>
<object class="GtkAdjustment" id="adjustment1">
<property name="value">20</property>
<property name="lower">1</property>
<property name="upper">1440</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="value">12</property>
<property name="lower">1</property>
<property name="upper">720</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
</interface>

View File

@ -7,7 +7,7 @@
<property name="height_request">200</property>
<property name="title" translatable="yes">Gajim</property>
<property name="role">roster</property>
<property name="default_width">200</property>
<property name="default_width">250</property>
<property name="default_height">400</property>
<accel-groups>
<group name="accelgroup1"/>
@ -202,7 +202,7 @@
<child>
<object class="GtkCheckMenuItem" id="show_transports_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Show Trans_ports</property>
<property name="label" translatable="yes">Show T_rans_ports</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_show_transports_menuitem_activate"/>
</object>
@ -218,16 +218,6 @@
<signal name="toggled" handler="on_show_roster_menuitem_toggled"/>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="show_rfilter_menuitem">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Show Roster Fi_lter</property>
<property name="use_underline">True</property>
<accelerator key="L" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<signal name="toggled" handler="on_show_rfilter_menuitem_toggled"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator3">
<property name="visible">True</property>

View File

@ -1,8 +1,6 @@
gajim for Debian
----------------
If you want to use OpenPGP in gajim, you have to install python-gnupginterface.
For video chat support, you have to install python-farsight.
-- Yann Le Boulanger <asterix@lagaule.org>, Mon, 20 Jun 2005 12:02:31 +0200

33
debian/changelog vendored
View File

@ -1,3 +1,36 @@
gajim (0.15~alpha1-1) unstable; urgency=low
* New upstream release.
* remove 00_debian-copying.diff because upstream doesn't install it anymore
* remove 01_configure-ac.diff because upstream changed configure dependencies
* remove python-gnupginterface from recommands list, it's no more used
-- Yann Leboulanger <asterix@lagaule.org> Fri, 26 Aug 2011 12:13:51 +0200
gajim (0.14.4-1) unstable; urgency=low
* New upstream release. Closes: #637071
* Fixes weird error. Closes: #632226
* Stop suggesting unused python-sexy. Closes: #633301
* Modify 00_debian-copying.diff to also not install ChangeLog file.
dh_changelogs will do it.
-- Yann Leboulanger <asterix@lagaule.org> Fri, 22 Jul 2011 12:56:30 +0200
gajim (0.14.3-1) unstable; urgency=low
* New upstream release.
* Fix closing roster window. Closes: #630315
-- Yann Leboulanger <asterix@lagaule.org> Sun, 19 Jun 2011 21:46:09 +0200
gajim (0.14.2-1) unstable; urgency=low
* New upstream release.
* Fix CPU usage when testing file transfer proxies. Closes: #626576
-- Yann Leboulanger <asterix@lagaule.org> Tue, 07 Jun 2011 19:30:43 +0200
gajim (0.14.1-1) unstable; urgency=low
[ Yann Leboulanger ]

6
debian/control vendored
View File

@ -2,7 +2,7 @@ Source: gajim
Section: net
Priority: optional
Maintainer: Yann Leboulanger <asterix@lagaule.org>
Build-Depends: debhelper (>= 7.0.50~), python (>= 2.6.6-3~), dh-autoreconf, gettext (>= 0.17-4), intltool (>= 0.40.1), imagemagick, libglib2.0-dev
Build-Depends: debhelper (>= 7.0.50~), python (>= 2.6.6-3~), gettext (>= 0.17-4), intltool (>= 0.40.1), imagemagick, libglib2.0-dev
Standards-Version: 3.9.2
Homepage: http://www.gajim.org
Vcs-Hg: http://hg.gajim.org/gajim/
@ -11,8 +11,8 @@ Vcs-Browser: http://hg.gajim.org/gajim/file
Package: gajim
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-gtk2 (>= 2.16.0), dnsutils
Recommends: dbus, python-dbus, notification-daemon, python-gnupginterface, python-openssl (>= 0.9), python-crypto
Suggests: python-gconf, python-gnome2, nautilus-sendto, avahi-daemon, python-avahi, network-manager, libgtkspell0, aspell-en, python-gnomekeyring, gnome-keyring, python-sexy, python-kerberos (>= 1.1), texlive-latex-base, dvipng, python-farsight, gstreamer0.10-plugins-ugly
Recommends: dbus, python-dbus, notification-daemon, python-openssl (>= 0.9), python-crypto
Suggests: python-gconf, python-gnome2, nautilus-sendto, avahi-daemon, python-avahi, network-manager, libgtkspell0, aspell-en, python-gnomekeyring, gnome-keyring, python-kerberos (>= 1.1), texlive-latex-base, dvipng, python-farsight, gstreamer0.10-plugins-ugly
Description: Jabber client written in PyGTK
Gajim is a Jabber client. It has a tabbed user interface with normal chats,
group chats, and has many features such as, TLS, GPG, SSL, multiple accounts,

4
debian/copyright vendored
View File

@ -5,8 +5,8 @@ It was downloaded from:
http://www.gajim.org/downloads/
Upstream Authors:
- Alexander Cherniuk <ts33kr@gmail.com>
- Yann Le Boulanger <asterix@lagaule.org>
- Denis Fomin <fominde@gmail.com>
- Yann Leboulanger <asterix@lagaule.org>
Copyright: (c) 2003-2011 Gajim Team

2
debian/docs vendored
View File

@ -1 +1 @@
README
README.html

1
debian/install vendored Normal file
View File

@ -0,0 +1 @@
debian/gajim.xpm usr/share/pixmaps

View File

@ -1,23 +0,0 @@
Description: don't install useless COPYING file
Forwarded: not-neded
--- a/Makefile.am
+++ b/Makefile.am
@@ -8,7 +8,6 @@
docfiles_DATA = README \
README.html \
ChangeLog \
- COPYING \
THANKS \
THANKS.artists \
AUTHORS
--- a/Makefile.in
+++ b/Makefile.in
@@ -299,7 +299,6 @@
docfiles_DATA = README \
README.html \
ChangeLog \
- COPYING \
THANKS \
THANKS.artists \
AUTHORS

View File

@ -1,34 +0,0 @@
Description: don't require python headers to remove build-dep on python-dev and python-gtk2-dev
Author: Yann Leboulanger <asterix@lagaule.org>
Origin: upstream,http://hg.gajim.org/gajim/diff/252bb3cf2c59/configure.ac
Last-Update: 2011-05-07
--- a/configure.ac
+++ b/configure.ac
@@ -36,25 +36,10 @@
AM_NLS
-dnl ****
-dnl pygtk and gtk+
-dnl ****
-PKG_CHECK_MODULES([PYGTK], [gtk+-2.0 >= 2.16.0 pygtk-2.0 >= 2.16.0])
-AC_SUBST(PYGTK_CFLAGS)
-AC_SUBST(PYGTK_LIBS)
-PYGTK_DEFS=`$PKG_CONFIG --variable=defsdir pygtk-2.0`
-AC_SUBST(PYGTK_DEFS)
-
-AM_PATH_PYTHON([2.5])
-if test "x$PYTHON" = "x:"; then
- AC_MSG_ERROR([Python not found])
-fi
-
ACLOCAL_AMFLAGS="\${ACLOCAL_FLAGS}"
AC_SUBST(ACLOCAL_AMFLAGS)
-AM_CHECK_PYTHON_HEADERS(,[AC_MSG_ERROR(could not find Python headers)])
-AC_SUBST([PYTHON_INCLUDES])
+AM_PATH_PYTHON([2.5])
dnl ****
dnl enable installation in python-2.x/site-packages/gajim

View File

@ -1,2 +0,0 @@
00_debian-copying.diff
01_configure-ac.diff

6
debian/rules vendored
View File

@ -1,7 +1,7 @@
#!/usr/bin/make -f
%:
dh $@ --with python2,autoreconf
dh $@ --with python2
# test target is broken
override_dh_auto_test:
@ -10,10 +10,6 @@ override_dh_auto_configure:
convert icons/hicolor/64x64/apps/gajim.png -resize 32x32 debian/gajim.xpm
dh_auto_configure
override_dh_auto_install:
dh_auto_install
rm debian/gajim/usr/share/gajim/src/common/GnuPGInterface.py*
override_dh_auto_clean:
-rm -f debian/gajim.xpm
dh_auto_clean

View File

@ -146,6 +146,7 @@ Section "Gajim" SecGajim
File "THANKS.artists"
SetOutPath "$INSTDIR\bin"
File "bin\_bsddb.pyd"
File "bin\_ctypes.pyd"
File "bin\_hashlib.pyd"
File "bin\_socket.pyd"
@ -158,20 +159,23 @@ Section "Gajim" SecGajim
File "bin\Crypto.Hash.SHA256.pyd"
File "bin\Crypto.Random.OSRNG.winrandom.pyd"
File "bin\Crypto.Util._counter.pyd"
File "bin\Crypto.Util.strxor.pyd"
File "bin\gajim.exe"
File "bin\gio._gio.pyd"
File "bin\glib._glib.pyd"
File "bin\gobject._gobject.pyd"
File "bin\goocanvas.pyd"
File "bin\gtk._gtk.pyd"
File "bin\history_manager.exe"
File "bin\OpenSSL.crypto.pyd"
File "bin\libeay32.dll"
File "bin\libgoocanvas-3.dll"
File "bin\library.zip"
File "bin\pangocairo.pyd"
File "bin\pango.pyd"
File "bin\pyexpat.pyd"
File "bin\python26.dll"
File "bin\pywintypes26.dll"
File "bin\python27.dll"
File "bin\pywintypes27.dll"
File "bin\OpenSSL.rand.pyd"
File "bin\select.pyd"
File "bin\sqlite3.dll"
@ -188,7 +192,7 @@ Section "Gajim" SecGajim
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "DisplayName" "Gajim"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "UninstallString" "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "DisplayIcon" "$INSTDIR\bin\Gajim.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "DisplayVersion" "0.14.1"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "DisplayVersion" "0.15"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gajim" "URLInfoAbout" "http://www.gajim.org/"
WriteUninstaller "$INSTDIR\Uninstall.exe"
@ -234,6 +238,11 @@ Section "Gtk+ 2" SecGtk
File /r "bin\gtk\share\xml"
SectionEnd
Section "Plugins" SecPlugins
SetOutPath "$INSTDIR\plugins"
File /r "plugins\plugin_installer"
SectionEnd
SectionGroup $(NAME_Emoticons)
Section "animated" SecEmoticonsAnimated
@ -641,6 +650,7 @@ Section "Uninstall"
RMDir /r "$INSTDIR\bin\gtk\share\xml"
RMDir "$INSTDIR\bin\gtk\share"
RMDir "$INSTDIR\bin\gtk"
Delete "$INSTDIR\bin\_bsddb.pyd"
Delete "$INSTDIR\bin\_ctypes.pyd"
Delete "$INSTDIR\bin\_hashlib.pyd"
Delete "$INSTDIR\bin\_socket.pyd"
@ -653,13 +663,16 @@ Section "Uninstall"
Delete "$INSTDIR\bin\Crypto.Hash.SHA256.pyd"
Delete "$INSTDIR\bin\Crypto.Random.OSRNG.winrandom.pyd"
Delete "$INSTDIR\bin\Crypto.Util._counter.pyd"
Delete "$INSTDIR\bin\Crypto.Util.strxor.pyd"
Delete "$INSTDIR\bin\gajim.exe"
Delete "$INSTDIR\bin\gio._gio.pyd"
Delete "$INSTDIR\bin\glib._glib.pyd"
Delete "$INSTDIR\bin\gobject._gobject.pyd"
Delete "$INSTDIR\bin\goocanvas.pyd"
Delete "$INSTDIR\bin\gtk._gtk.pyd"
Delete "$INSTDIR\bin\history_manager.exe"
Delete "$INSTDIR\bin\libeay32.dll"
Delete "$INSTDIR\bin\libgoocanvas-3.dll"
Delete "$INSTDIR\bin\library.zip"
Delete "$INSTDIR\bin\OpenSSL.crypto.pyd"
Delete "$INSTDIR\bin\OpenSSL.rand.pyd"
@ -667,8 +680,8 @@ Section "Uninstall"
Delete "$INSTDIR\bin\pango.pyd"
Delete "$INSTDIR\bin\pangocairo.pyd"
Delete "$INSTDIR\bin\pyexpat.pyd"
Delete "$INSTDIR\bin\python26.dll"
Delete "$INSTDIR\bin\pywintypes26.dll"
Delete "$INSTDIR\bin\python27.dll"
Delete "$INSTDIR\bin\pywintypes27.dll"
Delete "$INSTDIR\bin\select.pyd"
Delete "$INSTDIR\bin\sqlite3.dll"
Delete "$INSTDIR\bin\ssleay32.dll"
@ -677,7 +690,8 @@ Section "Uninstall"
Delete "$INSTDIR\bin\win32file.pyd"
Delete "$INSTDIR\bin\win32pipe.pyd"
Delete "$INSTDIR\bin\winsound.pyd"
RMDir "$INSTDIR\bin"
Delete "$INSTDIR\bin\msvcr90.dll"
RMDir /r "$INSTDIR\bin"
RMDir /r "$INSTDIR\data\gui"
RMDir /r "$INSTDIR\data\moods"
RMDir /r "$INSTDIR\data\activities"
@ -698,6 +712,8 @@ Section "Uninstall"
RMDir /r "$INSTDIR\data\iconsets\transports"
RMDir "$INSTDIR\data\iconsets"
RMDir "$INSTDIR\data"
RMDir /r "$INSTDIR\plugins\plugin_installer"
RMDir "$INSTDIR\plugins"
RMDir /r "$INSTDIR\icons\hicolor"
RMDir "$INSTDIR\icons"
RMDir /r "$INSTDIR\po\be"

View File

@ -3,15 +3,13 @@ INCLUDES = \
gajimpluginsdir = $(gajim_pluginsdir)
installedplugins = acronyms_expander banner_tweaks ftp_manager length_notifier whiteboard
dist_gajimplugins_PYTHON =
installedpluginsfiles = $(wildcard ${srcdir}/${p}/*.py ${srcdir}/${p}/manifest.ini ${srcdir}/${p}/*.ui ${srcdir}/${p}/*.png)
pluginsdirs = ${sort ${dir ${wildcard ${srcdir}/*/ ${srcdir}/*/*/}}}
nobase_dist_gajimplugins_PYTHON = $(foreach p, ${installedplugins}, $(installedpluginsfiles))
pluginsfiles = $(wildcard ${p}/*.py ${p}/manifest.ini ${p}/*.ui ${p}/*.png)
nobase_gajimplugins_DATA = $(foreach p, ${pluginsdirs}, $(pluginsfiles))
EXTRA_DIST = \
$(srcdir)/*/*.py \
$(srcdir)/*/manifest.ini \
$(srcdir)/*/*.ui
MAINTAINERCLEANFILES = Makefile.in

View File

@ -36,6 +36,8 @@ class AcronymsExpanderPlugin(GajimPlugin):
@log_calls('AcronymsExpanderPlugin')
def init(self):
self.description = _('Replaces acronyms (or other strings) '
'with given expansions/substitutes.')
self.config_dialog = None
self.gui_extension_points = {
@ -44,7 +46,7 @@ class AcronymsExpanderPlugin(GajimPlugin):
}
self.config_default_values = {
'INVOKER': (' ', _('')),
'INVOKER': (' ', ''),
'ACRONYMS': ({'RTFM': 'Read The Friendly Manual',
'/slap': '/me slaps',
'PS-': 'plug-in system',
@ -53,7 +55,7 @@ class AcronymsExpanderPlugin(GajimPlugin):
'GW-': 'http://trac.gajim.org/',
'GTS-': 'http://trac.gajim.org/report',
},
_('')),
''),
}
@log_calls('AcronymsExpanderPlugin')

View File

@ -1,2 +0,0 @@
from plugin import BannerTweaksPlugin

View File

@ -1,75 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVBox" id="banner_tweaks_config_vbox">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkCheckButton" id="show_banner_image_checkbutton">
<property name="label" translatable="yes">Display status icon</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, status icon will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_image_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_banner_online_msg_checkbutton">
<property name="label" translatable="yes">Display status message of contact</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, status message of contact will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_online_msg_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_banner_resource_checkbutton">
<property name="label" translatable="yes">Display resource name of contact</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, resource name of contact will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_resource_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="banner_small_fonts_checkbutton">
<property name="label" translatable="yes">Use small fonts for contact name and resource name</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, smaller font will be used to display resource name and contact name in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_banner_small_fonts_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -1,10 +0,0 @@
[info]
name: Banner Tweaks
short_name: banner_tweaks
version: 0.1
description: Allows user to tweak chat window banner appearance (eg. make it compact).
Based on patch by pb in ticket #4133:
http://trac.gajim.org/attachment/ticket/4133.
authors = Mateusz Biliński <mateusz@bilinski.it>
homepage = http://blog.bilinski.it

View File

@ -1,201 +0,0 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Adjustable chat window banner.
Includes tweaks to make it compact.
Based on patch by pb in ticket #4133:
http://trac.gajim.org/attachment/ticket/4133/gajim-chatbanneroptions-svn10008.patch
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 30 July 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
import gobject
import message_control
from common import i18n
from common import gajim
from common import helpers
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
from plugins.gui import GajimPluginConfigDialog
class BannerTweaksPlugin(GajimPlugin):
@log_calls('BannerTweaksPlugin')
def init(self):
self.config_dialog = BannerTweaksPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control_base_draw_banner': (self.chat_control_base_draw_banner_called,
self.chat_control_base_draw_banner_deactivation)
}
self.config_default_values = {
'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')),
'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')),
'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')),
'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')),
'old_chat_avatar_height': (52, _('chat_avatar_height value before plugin was activated')),
}
@log_calls('BannerTweaksPlugin')
def activate(self):
self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height')
#gajim.config.set('chat_avatar_height', 28)
@log_calls('BannerTweaksPlugin')
def deactivate(self):
gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height'])
@log_calls('BannerTweaksPlugin')
def chat_control_base_draw_banner_called(self, chat_control):
if not self.config['show_banner_online_msg']:
chat_control.banner_status_label.hide()
chat_control.banner_status_label.set_no_show_all(True)
status_text = ''
chat_control.banner_status_label.set_markup(status_text)
if not self.config['show_banner_image']:
if chat_control.TYPE_ID == message_control.TYPE_GC:
banner_status_img = chat_control.xml.get_object(
'gc_banner_status_image')
else:
banner_status_img = chat_control.xml.get_object(
'banner_status_image')
banner_status_img.clear()
# TODO: part below repeats a lot of code from ChatControl.draw_banner_text()
# This could be rewritten using re module: getting markup text from
# banner_name_label and replacing some elements based on plugin config.
# Would it be faster?
if self.config['show_banner_resource'] or self.config['banner_small_fonts']:
banner_name_label = chat_control.xml.get_object('banner_name_label')
label_text = banner_name_label.get_label()
contact = chat_control.contact
jid = contact.jid
name = contact.get_shown_name()
if chat_control.resource:
name += '/' + chat_control.resource
elif contact.resource and self.config['show_banner_resource']:
name += '/' + contact.resource
if chat_control.TYPE_ID == message_control.TYPE_PM:
name = _('%(nickname)s from group chat %(room_name)s') %\
{'nickname': name, 'room_name': chat_control.room_name}
name = gobject.markup_escape_text(name)
# We know our contacts nick, but if another contact has the same nick
# in another account we need to also display the account.
# except if we are talking to two different resources of the same contact
acct_info = ''
for account in gajim.contacts.get_accounts():
if account == chat_control.account:
continue
if acct_info: # We already found a contact with same nick
break
for jid in gajim.contacts.get_jid_list(account):
other_contact_ = \
gajim.contacts.get_first_contact_from_jid(account, jid)
if other_contact_.get_shown_name() == chat_control.contact.get_shown_name():
acct_info = ' (%s)' % \
gobject.markup_escape_text(chat_control.account)
break
font_attrs, font_attrs_small = chat_control.get_font_attrs()
if self.config['banner_small_fonts']:
font_attrs = font_attrs_small
st = gajim.config.get('displayed_chat_state_notifications')
cs = contact.chatstate
if cs and st in ('composing_only', 'all'):
if contact.show == 'offline':
chatstate = ''
elif contact.composing_xep == 'XEP-0085':
if st == 'all' or cs == 'composing':
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
elif contact.composing_xep == 'XEP-0022':
if cs in ('composing', 'paused'):
# only print composing, paused
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
else:
# When does that happen ? See [7797] and [7804]
chatstate = helpers.get_uf_chatstate(cs)
label_text = '<span %s>%s</span><span %s>%s %s</span>' % \
(font_attrs, name, font_attrs_small, acct_info, chatstate)
else:
# weight="heavy" size="x-large"
label_text = '<span %s>%s</span><span %s>%s</span>' % \
(font_attrs, name, font_attrs_small, acct_info)
banner_name_label.set_markup(label_text)
@log_calls('BannerTweaksPlugin')
def chat_control_base_draw_banner_deactivation(self, chat_control):
pass
#chat_control.draw_banner()
class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['banner_tweaks_config_vbox'])
self.config_vbox = self.xml.get_object('banner_tweaks_config_vbox')
self.child.pack_start(self.config_vbox)
self.show_banner_image_checkbutton = self.xml.get_object('show_banner_image_checkbutton')
self.show_banner_online_msg_checkbutton = self.xml.get_object('show_banner_online_msg_checkbutton')
self.show_banner_resource_checkbutton = self.xml.get_object('show_banner_resource_checkbutton')
self.banner_small_fonts_checkbutton = self.xml.get_object('banner_small_fonts_checkbutton')
self.xml.connect_signals(self)
def on_run(self):
self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image'])
self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg'])
self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource'])
self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts'])
def on_show_banner_image_checkbutton_toggled(self, button):
self.plugin.config['show_banner_image'] = button.get_active()
def on_show_banner_online_msg_checkbutton_toggled(self, button):
self.plugin.config['show_banner_online_msg'] = button.get_active()
def on_show_banner_resource_checkbutton_toggled(self, button):
self.plugin.config['show_banner_resource'] = button.get_active()
def on_banner_small_fonts_checkbutton_toggled(self, button):
self.plugin.config['banner_small_fonts'] = button.get_active()

View File

@ -688,6 +688,9 @@ class DBusPlugin(GajimPlugin):
@log_calls('DBusPlugin')
def init(self):
self.description = _('D-Bus support.'
' Based on remote_control module from'
'\nGajim core but uses new events handling system.')
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}

View File

@ -36,6 +36,7 @@ class EventsDumpPlugin(GajimPlugin):
@log_calls('EventsDumpPlugin')
def init(self):
self.description = _('Dumps info about selected events to console.')
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}

View File

@ -1 +0,0 @@
from ftp_manager import FtpManager

View File

@ -1,281 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkTextBuffer" id="textbuffer1">
<property name="text" translatable="yes">Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization.</property>
</object>
<object class="GtkWindow" id="window1">
<child>
<object class="GtkHPaned" id="hpaned2">
<property name="width_request">600</property>
<property name="height_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">340</property>
<property name="position_set">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">6</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="available_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="search_column">1</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="plugin_name_label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">Authors:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_authors_label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xpad">6</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox9">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">Homepage:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="plugin_homepage_linkbutton1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox10">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="label" translatable="yes">Description:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTextView" id="plugin_description_textview1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="pixels_above_lines">6</property>
<property name="wrap_mode">word</property>
<property name="left_margin">6</property>
<property name="right_margin">6</property>
<property name="indent">1</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox15">
<property name="visible">True</property>
<child>
<object class="GtkProgressBar" id="progressbar">
<property name="ellipsize">end</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox3">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="inslall_upgrade_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_inslall_upgrade_clicked"/>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-refresh</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Install/Upgrade</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="window2">
<child>
<object class="GtkHBox" id="hbox111">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Ftp server:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="ftp_server">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -1,460 +0,0 @@
# -*- coding: utf-8 -*-
#
## plugins/ftp_manager/ftp_manager.py
##
## Copyright (C) 2010 Denis Fomin <fominde AT gmail.com>
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import gtk
import pango
import gobject
import ftplib
import io
import threading
import ConfigParser
import os
import fnmatch
import sys
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from dialogs import WarningDialog, HigDialog
from plugins.gui import GajimPluginConfigDialog
from common import i18n
class FtpManager(GajimPlugin):
@log_calls('FtpManagerPlugin')
def init(self):
self.config_dialog = FtpManagerPluginConfigDialog(self)
self.config_default_values = {'ftp_server': ('ftp.gajim.org', '')}
@log_calls('FtpManagerPlugin')
def activate(self):
self.pl_menuitem = gajim.interface.roster.xml.get_object(
'plugins_menuitem')
self.id_ = self.pl_menuitem.connect_after('activate', self.on_activate)
if 'plugins' in gajim.interface.instances:
self.on_activate(None)
@log_calls('FtpManagerPlugin')
def deactivate(self):
self.pl_menuitem.disconnect(self.id_)
if hasattr(self, 'page_num'):
self.notebook.remove_page(self.page_num)
self.notebook.set_current_page(0)
if hasattr(self, 'ftp'):
del self.ftp
def on_activate(self, widget):
if 'plugins' not in gajim.interface.instances:
return
self.installed_plugins_model = gajim.interface.instances[
'plugins'].installed_plugins_model
self.notebook = gajim.interface.instances['plugins'].plugins_notebook
self.id_n = self.notebook.connect('switch-page',
self.on_notebook_switch_page)
self.window = gajim.interface.instances['plugins'].window
self.window.connect('destroy', self.on_win_destroy)
self.GTK_BUILDER_FILE_PATH = self.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['hpaned2'])
hpaned = self.xml.get_object('hpaned2')
self.page_num = self.notebook.append_page(hpaned,
gtk.Label('Ftp Manager'))
widgets_to_extract = ('plugin_name_label1',
'available_treeview', 'progressbar', 'inslall_upgrade_button',
'plugin_authors_label1', 'plugin_authors_label1',
'plugin_homepage_linkbutton1', 'plugin_description_textview1')
for widget_name in widgets_to_extract:
setattr(self, widget_name, self.xml.get_object(widget_name))
attr_list = pango.AttrList()
attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
self.plugin_name_label1.set_attributes(attr_list)
self.available_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT,
gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING,
gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
gobject.TYPE_PYOBJECT)
self.available_treeview.set_model(self.available_plugins_model)
self.progressbar.set_property('no-show-all', True)
renderer = gtk.CellRendererText()
col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1)
col.set_resizable(True)
col.set_property('expand', True)
col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
self.available_treeview.append_column(col)
col = gtk.TreeViewColumn(_('Installed\nversion'), renderer, text=2)
self.available_treeview.append_column(col)
col = gtk.TreeViewColumn(_('Available\nversion'), renderer, text=3)
col.set_property('expand', False)
self.available_treeview.append_column(col)
renderer = gtk.CellRendererToggle()
renderer.set_property('activatable', True)
renderer.connect('toggled', self.available_plugins_toggled_cb)
col = gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, active=4)
self.available_treeview.append_column(col)
if gobject.signal_lookup('error_signal', self.window) is 0:
gobject.signal_new('error_signal', self.window,
gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING,
(gobject.TYPE_STRING,))
gobject.signal_new('plugin_downloaded', self.window,
gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING,
(gobject.TYPE_PYOBJECT,))
self.window.connect('error_signal', self.on_some_ftp_error)
self.window.connect('plugin_downloaded', self.on_plugin_downloaded)
selection = self.available_treeview.get_selection()
selection.connect('changed',
self.available_plugins_treeview_selection_changed)
selection.set_mode(gtk.SELECTION_SINGLE)
self._clear_available_plugin_info()
self.xml.connect_signals(self)
self.window.show_all()
def on_win_destroy(self, widget):
if hasattr(self, 'ftp'):
del self.ftp
def available_plugins_toggled_cb(self, cell, path):
is_active = self.available_plugins_model[path][4]
self.available_plugins_model[path][4] = not is_active
dir_list = []
for i in xrange(len(self.available_plugins_model)):
if self.available_plugins_model[i][4]:
dir_list.append(self.available_plugins_model[i][0])
if not dir_list:
self.inslall_upgrade_button.set_property('sensitive', False)
else:
self.inslall_upgrade_button.set_property('sensitive', True)
def on_notebook_switch_page(self, widget, page, page_num,):
if not hasattr(self, 'ftp') and self.page_num == page_num:
self.available_plugins_model.clear()
self.progressbar.show()
self.ftp = Ftp(self)
self.ftp.remote_dirs = None
self.ftp.start()
def on_inslall_upgrade_clicked(self, widget):
self.inslall_upgrade_button.set_property('sensitive', False)
dir_list = []
for i in xrange(len(self.available_plugins_model)):
if self.available_plugins_model[i][4]:
dir_list.append(self.available_plugins_model[i][0])
ftp = Ftp(self)
ftp.remote_dirs = dir_list
ftp.start()
def on_some_ftp_error(self, widget, error_text):
for i in xrange(len(self.available_plugins_model)):
self.available_plugins_model[i][4] = False
self.progressbar.hide()
WarningDialog('Ftp error', error_text, self.window)
def on_plugin_downloaded(self, widget, plugin_dirs):
for _dir in plugin_dirs:
is_active = False
plugins = None
plugin_dir = os.path.join(gajim.PLUGINS_DIRS[1], _dir)
plugin = gajim.plugin_manager.get_plugin_by_path(plugin_dir)
if plugin:
if plugin.active and plugin.name != self.name:
is_active = True
gobject.idle_add(gajim.plugin_manager.deactivate_plugin,
plugin)
gajim.plugin_manager.plugins.remove(plugin)
model = self.installed_plugins_model
for row in xrange(len(model)):
if plugin == model[row][0]:
model.remove(model.get_iter((row, 0)))
break
plugins = self.scan_dir_for_plugin(plugin_dir)
if not plugins:
continue
gajim.plugin_manager.add_plugin(plugins[0])
plugin = gajim.plugin_manager.plugins[-1]
for row in xrange(len(self.available_plugins_model)):
if plugin.name == self.available_plugins_model[row][1]:
self.available_plugins_model[row][2] = plugin.version
self.available_plugins_model[row][4] = False
continue
if is_active and plugin.name != self.name:
gobject.idle_add(gajim.plugin_manager.activate_plugin, plugin)
if plugin.name != 'Ftp Manager':
self.installed_plugins_model.append([plugin, plugin.name,
is_active])
dialog = HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
'', 'All selected plugins downloaded')
dialog.set_modal(False)
dialog.set_transient_for(self.window)
dialog.popup()
def available_plugins_treeview_selection_changed(self, treeview_selection):
model, iter = treeview_selection.get_selected()
if iter:
self.plugin_name_label1.set_text(model.get_value(iter, 1))
self.plugin_authors_label1.set_text(model.get_value(iter, 6))
self.plugin_homepage_linkbutton1.set_uri(model.get_value(iter, 7))
self.plugin_homepage_linkbutton1.set_label(model.get_value(iter, 7))
label = self.plugin_homepage_linkbutton1.get_children()[0]
label.set_ellipsize(pango.ELLIPSIZE_END)
self.plugin_homepage_linkbutton1.set_property('sensitive', True)
desc_textbuffer = self.plugin_description_textview1.get_buffer()
desc_textbuffer.set_text(model.get_value(iter, 5))
self.plugin_description_textview1.set_property('sensitive', True)
else:
self._clear_available_plugin_info()
def _clear_available_plugin_info(self):
self.plugin_name_label1.set_text('')
self.plugin_authors_label1.set_text('')
self.plugin_homepage_linkbutton1.set_uri('')
self.plugin_homepage_linkbutton1.set_label('')
self.plugin_homepage_linkbutton1.set_property('sensitive', False)
desc_textbuffer = self.plugin_description_textview1.get_buffer()
desc_textbuffer.set_text('')
self.plugin_description_textview1.set_property('sensitive', False)
def scan_dir_for_plugin(self, path):
plugins_found = []
conf = ConfigParser.ConfigParser()
fields = ('name', 'short_name', 'version', 'description', 'authors',
'homepage')
if not os.path.isdir(path):
return plugins_found
dir_list = os.listdir(path)
dir_, mod = os.path.split(path)
sys.path.insert(0, dir_)
manifest_path = os.path.join(path, 'manifest.ini')
if not os.path.isfile(manifest_path):
return plugins_found
for elem_name in dir_list:
file_path = os.path.join(path, elem_name)
module = None
if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'):
module_name = os.path.splitext(elem_name)[0]
if module_name == '__init__':
continue
try:
module = __import__('%s.%s' % (mod, module_name))
except ValueError, value_error:
pass
except ImportError, import_error:
pass
except AttributeError, attribute_error:
pass
if module is None:
continue
for module_attr_name in [attr_name for attr_name in dir(module)
if not (attr_name.startswith('__') or attr_name.endswith('__'))]:
module_attr = getattr(module, module_attr_name)
try:
if not issubclass(module_attr, GajimPlugin) or \
module_attr is GajimPlugin:
continue
module_attr.__path__ = os.path.abspath(os.path.dirname(
file_path))
# read metadata from manifest.ini
conf.readfp(open(manifest_path, 'r'))
for option in fields:
if conf.get('info', option) is '':
raise ConfigParser.NoOptionError, 'field empty'
setattr(module_attr, option, conf.get('info', option))
conf.remove_section('info')
plugins_found.append(module_attr)
except TypeError, type_error:
pass
except ConfigParser.NoOptionError, type_error:
# all fields are required
pass
return plugins_found
class Ftp(threading.Thread):
def __init__(self, plugin):
super(Ftp, self).__init__()
self.window = plugin.window
self.server = plugin.config['ftp_server']
self.progressbar = plugin.progressbar
self.model = plugin.available_plugins_model
self.config = ConfigParser.ConfigParser()
self.buffer_ = io.BytesIO()
self.remote_dirs = None
self.append_to_model = True
def model_append(self, row):
self.model.append(row)
return False
def progressbar_pulse(self):
self.progressbar.pulse()
return True
def get_plugin_version(self, plugin_name):
for plugin in gajim.plugin_manager.plugins:
if plugin.name == plugin_name:
return plugin.version
def run(self):
try:
gobject.idle_add(self.progressbar.set_text,
'Connecting to server')
self.ftp = ftplib.FTP(self.server)
self.ftp.login()
self.ftp.cwd('plugins')
if not self.remote_dirs:
self.plugins_dirs = self.ftp.nlst()
progress_step = 1.0 / len(self.plugins_dirs)
gobject.idle_add(self.progressbar.set_text,
'Scan files on the server')
for dir_ in self.plugins_dirs:
fract = self.progressbar.get_fraction() + progress_step
gobject.idle_add(self.progressbar.set_fraction, fract)
gobject.idle_add(self.progressbar.set_text,
'Read "%s"' % dir_)
try:
self.ftp.retrbinary('RETR %s/manifest.ini' % dir_,
self.handleDownload)
except Exception, error:
if str(error).startswith('550'):
continue
self.config.readfp(io.BytesIO(self.buffer_.getvalue()))
local_version = self.get_plugin_version(
self.config.get('info', 'name'))
gobject.idle_add(self.model_append, [dir_,
self.config.get('info', 'name'), local_version,
self.config.get('info', 'version'), False,
self.config.get('info', 'description'),
self.config.get('info', 'authors'),
self.config.get('info', 'homepage'), ])
self.plugins_dirs = None
self.ftp.quit()
gobject.idle_add(self.progressbar.set_fraction, 0)
if self.remote_dirs:
self.download_plugin()
gobject.idle_add(self.progressbar.hide)
except Exception, e:
self.window.emit('error_signal', str(e))
def handleDownload(self, block):
self.buffer_.write(block)
def download_plugin(self):
gobject.idle_add(self.progressbar.show)
self.pulse = gobject.timeout_add(150, self.progressbar_pulse)
gobject.idle_add(self.progressbar.set_text,
'Create a list of files')
for remote_dir in self.remote_dirs:
def nlstr(dir_, subdir=None):
if subdir:
dir_ = dir_ + '/' + subdir
list_ = self.ftp.nlst(dir_)
for i in list_:
name = i.split('/')[-1]
if '.' not in name:
try:
if i == self.ftp.nlst(i)[0]:
files.append(i[1:])
del dirs[i[1:]]
except Exception, e:
# empty dir or file
continue
dirs.append(i[1:])
subdirs = name
nlstr(dir_, subdirs)
else:
files.append(i[1:])
dirs, files = [], []
nlstr('/plugins/' + remote_dir)
if not os.path.isdir(gajim.PLUGINS_DIRS[1]):
os.mkdir(gajim.PLUGINS_DIRS[1])
local_dir = ld = os.path.join(gajim.PLUGINS_DIRS[1], remote_dir)
if not os.path.isdir(local_dir):
os.mkdir(local_dir)
local_dir = os.path.split(gajim.PLUGINS_DIRS[1])[0]
# creating dirs
for dir_ in dirs:
try:
os.mkdir(os.path.join(local_dir, dir_))
except OSError, e:
if str(e).startswith('[Errno 17]'):
continue
raise
# downloading files
for filename in files:
gobject.idle_add(self.progressbar.set_text,
'Downloading "%s"' % filename)
full_filename = os.path.join(local_dir, filename)
try:
self.ftp.retrbinary('RETR /%s' % filename,
open(full_filename, 'wb').write)
#full_filename.close()
except ftplib.error_perm:
print 'ERROR: cannot read file "%s"' % filename
os.unlink(filename)
self.ftp.quit()
self.window.emit('plugin_downloaded', self.remote_dirs)
gobject.source_remove(self.pulse)
class FtpManagerPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['hbox111'])
hbox = self.xml.get_object('hbox111')
self.child.pack_start(hbox)
self.xml.connect_signals(self)
self.connect('hide', self.on_hide)
def on_run(self):
widget = self.xml.get_object('ftp_server')
widget.set_text(str(self.plugin.config['ftp_server']))
def on_hide(self, widget):
widget = self.xml.get_object('ftp_server')
self.plugin.config['ftp_server'] = widget.get_text()

View File

@ -1,7 +0,0 @@
[info]
name: Ftp Manager
short_name: ftp_manager
version: 0.3
description: Install and upgrade plugins from ftp
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/

View File

@ -1 +0,0 @@
from plugin import GoogleTranslationPlugin

View File

@ -1,8 +0,0 @@
[info]
name: Google Translation
short_name: google_translation
version: 0.1
description: Translates (currently only incoming) messages using Google Translate.
authors = Mateusz Biliński <mateusz@bilinski.it>
homepage = http://blog.bilinski.it

View File

@ -1,118 +0,0 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Google Translation plugin.
Translates (currently only incoming) messages using Google Translate.
:note: consider this as proof-of-concept
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 25th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import re
import urllib2
import new
from pprint import pformat
from sys import getfilesystemencoding
from common import helpers
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
from common import nec
class GoogleTranslationPlugin(GajimPlugin):
@log_calls('GoogleTranslationPlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
self.config_default_values = {
'from_lang' :
(u'en', _(u'Language of text to be translated')),
'to_lang' :
(u'fr', _(u'Language to which translation will be made')),
'user_agent' :
(u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) '
'Gecko/20080213 Firefox/2.0.0.11',
_(u'User Agent data to be used with urllib2 '
'when connecting to Google Translate service'))}
#self.events_handlers = {}
self.events = [GoogleTranslateMessageReceivedEvent]
self.translated_text_re = re.compile(
r'google.language.callbacks.id100\(\'22\','
'{"translatedText":"(?P<text>[^"]*)"}, 200, null, 200\)')
@log_calls('GoogleTranslationPlugin')
def translate_text(self, text, from_lang, to_lang):
# Converts text so it can be used within URL as query to Google
# Translate.
quoted_text = urllib2.quote(text.encode(getfilesystemencoding()))
# prepare url
headers = { 'User-Agent' : self.config['user_agent'] }
translation_url = u'http://www.google.com/uds/Gtranslate?callback='\
'google.language.callbacks.id100&context=22&q=%(quoted_text)s&'\
'langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0' % \
locals()
request = urllib2.Request(translation_url, headers=headers)
try:
response = urllib2.urlopen(request)
except urllib2.URLError, e:
# print e
return text
results = response.read()
translated_text = self.translated_text_re.search(results).group('text')
if translated_text:
return translated_text
return text
@log_calls('GoogleTranslationPlugin')
def activate(self):
pass
@log_calls('GoogleTranslationPlugin')
def deactivate(self):
pass
class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent):
name = 'google-translate-message-received'
base_network_events = ['raw-message-received']
def generate(self):
msg_type = self.base_event.stanza.attrs.get('type', None)
if msg_type == u'chat':
msg_text = "".join(self.base_event.stanza.kids[0].data)
if msg_text:
from_lang = self.plugin.config['from_lang']
to_lang = self.plugin.config['to_lang']
self.base_event.stanza.kids[0].setData(
self.plugin.translate_text(msg_text, from_lang, to_lang))
# We only want to modify old event, not emit another, so we return False
# here.
return False

View File

@ -1,2 +0,0 @@
from length_notifier import LengthNotifierPlugin

View File

@ -1,152 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkTable" id="length_notifier_config_table">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">7</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Message length:</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Notification color:</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<property name="xalign">0</property>
<property name="label" translatable="yes">JabberIDs to include:</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="jids_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<signal name="editing_done" handler="on_jids_entry_editing_done"/>
<signal name="changed" handler="on_jids_entry_changed"/>
</object>
<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">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkColorButton" id="notification_colorbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="title" translatable="yes">Pick a color for notification</property>
<property name="color">#000000000000</property>
<signal name="color_set" handler="on_notification_colorbutton_color_set"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<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">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<object class="GtkSpinButton" id="message_length_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
<property name="width_chars">6</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="on_message_length_spinbutton_value_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Message length notifier plugin.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 1st June 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
from common import i18n
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
from plugins.gui import GajimPluginConfigDialog
class LengthNotifierPlugin(GajimPlugin):
@log_calls('LengthNotifierPlugin')
def init(self):
self.config_dialog = LengthNotifierPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control' : (self.connect_with_chat_control,
self.disconnect_from_chat_control)
}
self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')),
'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')),
'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]'))
}
@log_calls('LengthNotifierPlugin')
def textview_length_warning(self, tb, chat_control):
tv = chat_control.msg_textview
d = chat_control.length_notifier_plugin_data
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
if t:
len_t = len(t)
#print("len_t: %d"%(len_t))
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
if not d['prev_color']:
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
elif d['prev_color']:
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
d['prev_color'] = None
@log_calls('LengthNotifierPlugin')
def connect_with_chat_control(self, chat_control):
jid = chat_control.contact.jid
if self.jid_is_ok(jid):
d = {'prev_color' : None}
tv = chat_control.msg_textview
tb = tv.get_buffer()
h_id = tb.connect('changed', self.textview_length_warning, chat_control)
d['h_id'] = h_id
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
if t:
len_t = len(t)
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
chat_control.length_notifier_plugin_data = d
return True
return False
@log_calls('LengthNotifierPlugin')
def disconnect_from_chat_control(self, chat_control):
try:
d = chat_control.length_notifier_plugin_data
tv = chat_control.msg_textview
tv.get_buffer().disconnect(d['h_id'])
if d['prev_color']:
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
except AttributeError, error:
pass
#log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error))
@log_calls('LengthNotifierPlugin')
def jid_is_ok(self, jid):
if jid in self.config['JIDS'] or not self.config['JIDS']:
return True
return False
class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['length_notifier_config_table'])
self.config_table = self.xml.get_object('length_notifier_config_table')
self.child.pack_start(self.config_table)
self.message_length_spinbutton = self.xml.get_object(
'message_length_spinbutton')
self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1,
10, 0)
self.notification_colorbutton = self.xml.get_object(
'notification_colorbutton')
self.jids_entry = self.xml.get_object('jids_entry')
self.xml.connect_signals(self)
def on_run(self):
self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH'])
self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR']))
#self.jids_entry.set_text(self.plugin.config['JIDS'])
self.jids_entry.set_text(','.join(self.plugin.config['JIDS']))
@log_calls('LengthNotifierPluginConfigDialog')
def on_message_length_spinbutton_value_changed(self, spinbutton):
self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value()
@log_calls('LengthNotifierPluginConfigDialog')
def on_notification_colorbutton_color_set(self, colorbutton):
self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string()
@log_calls('LengthNotifierPluginConfigDialog')
def on_jids_entry_changed(self, entry):
text = entry.get_text()
if len(text)>0:
self.plugin.config['JIDS'] = entry.get_text().split(',')
else:
self.plugin.config['JIDS'] = []
@log_calls('LengthNotifierPluginConfigDialog')
def on_jids_entry_editing_done(self, entry):
pass

View File

@ -1,9 +0,0 @@
[info]
name: Message Length Notifier
short_name: length_notifier
version: 0.1
description: Highlights message entry field in chat window when given length of message is exceeded.
authors = Mateusz Biliński <mateusz@bilinski.it>
homepage = http://blog.bilinski.it

View File

@ -41,6 +41,8 @@ class NewEventsExamplePlugin(GajimPlugin):
@log_calls('NewEventsExamplePlugin')
def init(self):
self.description = _('Shows how to generate new network events based '
'on existing one using Network Events Controller.')
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}

View File

@ -27,7 +27,6 @@ Roster buttons plug-in.
import sys
import gtk
from common import i18n
from common import gajim
from plugins import GajimPlugin
@ -37,6 +36,7 @@ class RosterButtonsPlugin(GajimPlugin):
@log_calls('RosterButtonsPlugin')
def init(self):
self.description = _('Adds quick action buttons to roster window.')
self.GTK_BUILDER_FILE_PATH = self.local_file_path('roster_buttons.ui')
self.roster_vbox = gajim.interface.roster.xml.get_object('roster_vbox2')
self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_object('show_offline_contacts_menuitem')
@ -46,7 +46,7 @@ class RosterButtonsPlugin(GajimPlugin):
@log_calls('RosterButtonsPlugin')
def activate(self):
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.set_translation_domain('gajim_plugins')
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['roster_buttons_buttonbox'])
self.buttonbox = self.xml.get_object('roster_buttons_buttonbox')

View File

@ -10,7 +10,7 @@
<property name="layout_style">spread</property>
<child>
<object class="GtkButton" id="roster_button_1">
<property name="label" translatable="yes">1</property>
<property name="label">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@ -24,7 +24,7 @@
</child>
<child>
<object class="GtkButton" id="roster_button_2">
<property name="label" translatable="yes">2</property>
<property name="label">2</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@ -38,7 +38,7 @@
</child>
<child>
<object class="GtkButton" id="roster_button_3">
<property name="label" translatable="yes">3</property>
<property name="label">3</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
@ -52,7 +52,7 @@
</child>
<child>
<object class="GtkButton" id="roster_button_4">
<property name="label" translatable="yes">4</property>
<property name="label">4</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>

View File

@ -41,6 +41,10 @@ class SnarlNotificationsPlugin(GajimPlugin):
@log_calls('SnarlNotificationsPlugin')
def init(self):
self.description = _('Shows events notification using Snarl '
'(http://www.fullphat.net/) under Windows. '
'Snarl needs to be installed in system.\n'
'PySnarl bindings are used (http://code.google.com/p/pysnarl/).')
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}

View File

@ -1 +0,0 @@
from triggers import Triggers

View File

@ -1,900 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">contact(s)</col>
</row>
<row>
<col id="0" translatable="yes">group(s)</col>
</row>
<row>
<col id="0" translatable="yes">everybody</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name item -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Receive a Message</col>
</row>
</data>
</object>
<object class="GtkWindow" id="advanced_notifications_window">
<property name="border_width">6</property>
<property name="title" translatable="yes">Advanced Notifications Control</property>
<property name="role">Advanced Notifications Control</property>
<property name="resizable">False</property>
<property name="destroy_with_parent">True</property>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkVBox" id="vbox100">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow23">
<property name="height_request">90</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="conditions_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="cursor_changed" handler="on_conditions_treeview_cursor_changed"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment99">
<property name="visible">True</property>
<property name="xalign">1</property>
<property name="left_padding">212</property>
<child>
<object class="GtkHBox" id="hbox3045">
<property name="visible">True</property>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="spacing">10</property>
<child>
<object class="GtkButton" id="new_button">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_new_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="up_button">
<property name="label">gtk-go-up</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_up_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="down_button">
<property name="label">gtk-go-down</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_down_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="delete_button">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_delete_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="config_vbox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label391">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Conditions&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox101">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkHBox" id="hbox3042">
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label401">
<property name="visible">True</property>
<property name="label" translatable="yes">When </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="event_combobox">
<property name="visible">True</property>
<property name="model">liststore2</property>
<signal name="changed" handler="on_event_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3048">
<property name="visible">True</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="label400">
<property name="visible">True</property>
<property name="label" translatable="yes">for </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="recipient_type_combobox">
<property name="visible">True</property>
<property name="model">liststore1</property>
<signal name="changed" handler="on_recipient_type_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="recipient_list_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="no_show_all">True</property>
<signal name="changed" handler="on_recipient_list_entry_changed"/>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3049">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label402">
<property name="visible">True</property>
<property name="label" translatable="yes">when I'm in</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="status_hbox">
<property name="visible">True</property>
<property name="spacing">3</property>
<child>
<object class="GtkRadioButton" id="all_status_rb">
<property name="label" translatable="yes">All statuses</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_radiobutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="special_status_rb">
<property name="label" translatable="yes">One or more special statuses...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<property name="group">all_status_rb</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="online_cb">
<property name="label" translatable="yes">Online / Free For Chat</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="away_cb">
<property name="label" translatable="yes">Away</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="xa_cb">
<property name="label" translatable="yes">Not Available</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="dnd_cb">
<property name="label" translatable="yes">Busy </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="invisible_cb">
<property name="label" translatable="yes">Invisible</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_status_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3053">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label408">
<property name="visible">True</property>
<property name="label" translatable="yes">and I </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="tab_opened_cb">
<property name="label" translatable="yes">Have </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_tab_opened_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="not_tab_opened_cb">
<property name="label" translatable="yes">Don't have </property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_not_tab_opened_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label409">
<property name="visible">True</property>
<property name="label" translatable="yes"> a window/tab opened with that contact </property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label392">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Actions&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="frame35">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkHBox" id="hbox3027">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="use_popup_cb">
<property name="label" translatable="yes">_Inform me with a popup window</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_popup_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_popup_cb">
<property name="label" translatable="yes">_Disable existing popup window</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_popup_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkFrame" id="frame38">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment93">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox98">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox3028">
<property name="visible">True</property>
<property name="spacing">6</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkCheckButton" id="use_sound_cb">
<property name="label" translatable="yes">Play a sound</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_sound_cb_toggled"/>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="sound_file_hbox">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="sound_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_sound_entry_changed"/>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button4">
<property name="label">...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_browse_for_sounds_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="play_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<signal name="clicked" handler="on_play_button_clicked"/>
<child>
<object class="GtkImage" id="image1372">
<property name="visible">True</property>
<property name="stock">gtk-media-play</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_sound_cb">
<property name="label" translatable="yes">_Disable existing sound for this event</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_sound_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label394">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Sounds&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3032">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="use_auto_open_cb">
<property name="label" translatable="yes">_Open chat window with user</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_auto_open_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_auto_open_cb">
<property name="label" translatable="yes">_Disable auto opening chat window</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_auto_open_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="expanded">True</property>
<child>
<object class="GtkVBox" id="vbox99">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkHBox" id="hbox3033">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="run_command_cb">
<property name="label" translatable="yes">Launch a command</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_run_command_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="command_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<signal name="changed" handler="on_command_entry_changed"/>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3035">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="use_systray_cb">
<property name="label" translatable="yes">_Show event in notification area</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_systray_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_systray_cb">
<property name="label" translatable="yes">_Disable showing event in notification area</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_systray_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3052">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="use_roster_cb">
<property name="label" translatable="yes">_Show event in roster</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_roster_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_roster_cb">
<property name="label" translatable="yes">_Disable showing event in roster</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_roster_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="use_urgency_hint_cb">
<property name="label" translatable="yes">_Activate window manager's UrgencyHint to make chat window in taskbar flash</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_use_urgency_hint_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="disable_urgency_hint_cb">
<property name="label" translatable="yes">_Deactivate window manager's UrgencyHint</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="no_show_all">True</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_disable_urgency_hint_cb_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label395">
<property name="visible">True</property>
<property name="label" translatable="yes">Advanced Actions</property>
</object>
</child>
</object>
<packing>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -1,7 +0,0 @@
[info]
name: Triggers
short_name: triggers
version: 0.0.1
description: Configure Gajim's behaviour for each contact
authors: Yann Leboulanger <asterix@lagaule.org>
homepage: http://trac.gajim.org/wiki/

View File

@ -1,637 +0,0 @@
# -*- coding: utf-8 -*-
#
## plugins/triggers/triggers.py
##
## Copyright (C) 2011 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import gtk
import sys
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from plugins.gui import GajimPluginConfigDialog
from common import i18n
from common import ged
from common import helpers
class Triggers(GajimPlugin):
@log_calls('TriggersPlugin')
def init(self):
self.config_dialog = TriggersPluginConfigDialog(self)
self.config_default_values = {}
self.events_handlers = {'notification' : (ged.PREGUI, self._nec_notif),
'decrypted-message-received': (ged.PREGUI2,
self._nec_decrypted_message_received)}
def _check_rule_recipients(self, obj, rule):
rule_recipients = [t.strip() for t in rule['recipients'].split(',')]
if rule['recipient_type'] == 'contact' and obj.jid not in \
rule_recipients:
return False
contact_groups = gajim.contacts.get_first_contact_from_jid(
obj.conn.name, obj.jid).groups
group_found = False
for group in contact_groups:
if group in rule_recipients:
group_found = True
break
if rule['recipient_type'] == 'group' and not group_found:
return False
return True
def _check_rule_status(self, obj, rule):
rule_statuses = rule['status'].split()
our_status = gajim.SHOW_LIST[obj.conn.connected]
if rule['status'] != 'all' and our_status not in rule_statuses:
return False
return True
def _check_rule_tab_opened(self, obj, rule):
if rule['tab_opened'] == 'both':
return True
tab_opened = False
if gajim.interface.msg_win_mgr.get_control(obj.jid, obj.conn.name):
tab_opened = True
if tab_opened and rule['tab_opened'] == 'no':
return False
elif not tab_opened and rule['tab_opened'] == 'yes':
return False
return True
def check_rule_apply_notif(self, obj, rule):
# Check notification type
notif_type = ''
if obj.notif_type == 'msg':
notif_type = 'message_received'
if notif_type != rule['event']:
return False
# notification type is ok. Now check recipient
if not self._check_rule_recipients(obj, rule):
return False
# recipient is ok. Now check our status
if not self._check_rule_status(obj, rule):
return False
# our_status is ok. Now check opened chat window
if not self._check_rule_tab_opened(obj, rule):
return False
# All is ok
return True
def check_rule_apply_decrypted_msg(self, obj, rule):
# Check notification type
if rule['event'] != 'message_received':
return False
# notification type is ok. Now check recipient
if not self._check_rule_recipients(obj, rule):
return False
# recipient is ok. Now check our status
if not self._check_rule_status(obj, rule):
return False
# our_status is ok. Now check opened chat window
if not self._check_rule_tab_opened(obj, rule):
return False
# All is ok
return True
def apply_rule(self, obj, rule):
if rule['sound'] == 'no':
obj.do_sound = False
elif rule['sound'] == 'yes':
obj.do_sound = True
obj.sound_event = ''
obj.sound_file = rule['sound_file']
if rule['popup'] == 'no':
obj.do_popup = False
elif rule['popup'] == 'yes':
obj.do_popup = True
if rule['run_command']:
obj.do_command = True
obj.command = rule['command']
else:
obj.do_command = False
if rule['systray'] == 'no':
obj.show_in_notification_area = False
elif rule['systray'] == 'yes':
obj.show_in_notification_area = True
if rule['roster'] == 'no':
obj.show_in_roster = False
elif rule['roster'] == 'yes':
obj.show_in_roster = True
# if rule['urgency_hint'] == 'no':
# ?? not in obj actions
# elif rule['urgency_hint'] == 'yes':
def _nec_notif(self, obj):
# check rules in order
rules_num = [int(i) for i in self.config.keys()]
rules_num.sort()
for num in rules_num:
if self.check_rule_apply_notif(obj, self.config[str(num)]):
self.apply_rule(obj, self.config[str(num)])
# Should we stop after first valid rule ?
# break
def _nec_decrypted_message_received(self, obj):
rules_num = [int(i) for i in self.config.keys()]
rules_num.sort()
for num in rules_num:
rule = self.config[str(num)]
if self.check_rule_apply_decrypted_msg(obj, rule):
if rule['auto_open'] == 'no':
obj.popup = False
elif rule['auto_open'] == 'yes':
obj.popup = True
class TriggersPluginConfigDialog(GajimPluginConfigDialog):
events_list = ['message_received']#, 'contact_connected',
#'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
#'gc_msg']
recipient_types_list = ['contact', 'group', 'all']
config_options = ['event', 'recipient_type', 'recipients', 'status',
'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
'run_command', 'command', 'systray', 'roster', 'urgency_hint']
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['vbox', 'liststore1', 'liststore2'])
vbox = self.xml.get_object('vbox')
self.child.pack_start(vbox)
self.xml.connect_signals(self)
self.connect('hide', self.on_hide)
def on_run(self):
# fill window
for w in ('conditions_treeview', 'config_vbox', 'event_combobox',
'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
'run_command_cb', 'command_entry', 'use_urgency_hint_cb',
'disable_urgency_hint_cb'):
self.__dict__[w] = self.xml.get_object(w)
self.config = {}
for n in self.plugin.config:
self.config[int(n)] = self.plugin.config[n]
# Contains status checkboxes
childs = self.status_hbox.get_children()
self.all_status_rb = childs[0]
self.special_status_rb = childs[1]
self.online_cb = childs[2]
self.away_cb = childs[3]
self.xa_cb = childs[4]
self.dnd_cb = childs[5]
self.invisible_cb = childs[6]
if not self.conditions_treeview.get_column(0):
# window never opened
model = gtk.ListStore(int, str)
model.set_sort_column_id(0, gtk.SORT_ASCENDING)
self.conditions_treeview.set_model(model)
# means number
col = gtk.TreeViewColumn(_('#'))
self.conditions_treeview.append_column(col)
renderer = gtk.CellRendererText()
col.pack_start(renderer, expand=False)
col.set_attributes(renderer, text=0)
col = gtk.TreeViewColumn(_('Condition'))
self.conditions_treeview.append_column(col)
renderer = gtk.CellRendererText()
col.pack_start(renderer, expand=True)
col.set_attributes(renderer, text=1)
else:
model = self.conditions_treeview.get_model()
model.clear()
# Fill conditions_treeview
num = 0
while num in self.config:
iter_ = model.append((num, ''))
path = model.get_path(iter_)
self.conditions_treeview.set_cursor(path)
self.active_num = num
self.initiate_rule_state()
self.set_treeview_string()
num += 1
# No rule selected at init time
self.conditions_treeview.get_selection().unselect_all()
self.active_num = -1
self.config_vbox.set_sensitive(False)
self.delete_button.set_sensitive(False)
self.down_button.set_sensitive(False)
self.up_button.set_sensitive(False)
def initiate_rule_state(self):
"""
Set values for all widgets
"""
if self.active_num < 0:
return
# event
value = self.config[self.active_num]['event']
if value:
self.event_combobox.set_active(self.events_list.index(value))
else:
self.event_combobox.set_active(-1)
# recipient_type
value = self.config[self.active_num]['recipient_type']
if value:
self.recipient_type_combobox.set_active(
self.recipient_types_list.index(value))
else:
self.recipient_type_combobox.set_active(-1)
# recipient
value = self.config[self.active_num]['recipients']
if not value:
value = ''
self.recipient_list_entry.set_text(value)
# status
value = self.config[self.active_num]['status']
if value == 'all':
self.all_status_rb.set_active(True)
else:
self.special_status_rb.set_active(True)
values = value.split()
for v in ('online', 'away', 'xa', 'dnd', 'invisible'):
if v in values:
self.__dict__[v + '_cb'].set_active(True)
else:
self.__dict__[v + '_cb'].set_active(False)
self.on_status_radiobutton_toggled(self.all_status_rb)
# tab_opened
value = self.config[self.active_num]['tab_opened']
self.tab_opened_cb.set_active(True)
self.not_tab_opened_cb.set_active(True)
if value == 'no':
self.tab_opened_cb.set_active(False)
elif value == 'yes':
self.not_tab_opened_cb.set_active(False)
# sound_file
value = self.config[self.active_num]['sound_file']
self.sound_entry.set_text(value)
# sound, popup, auto_open, systray, roster
for option in ('sound', 'popup', 'auto_open', 'systray', 'roster',
'urgency_hint'):
value = self.config[self.active_num][option]
if value == 'yes':
self.__dict__['use_' + option + '_cb'].set_active(True)
else:
self.__dict__['use_' + option + '_cb'].set_active(False)
if value == 'no':
self.__dict__['disable_' + option + '_cb'].set_active(True)
else:
self.__dict__['disable_' + option + '_cb'].set_active(False)
# run_command
value = self.config[self.active_num]['run_command']
self.run_command_cb.set_active(value)
# command
value = self.config[self.active_num]['command']
self.command_entry.set_text(value)
def set_treeview_string(self):
(model, iter_) = self.conditions_treeview.get_selection().get_selected()
if not iter_:
return
event = self.event_combobox.get_active_text()
recipient_type = self.recipient_type_combobox.get_active_text()
recipient = ''
if recipient_type != 'everybody':
recipient = self.recipient_list_entry.get_text()
if self.all_status_rb.get_active():
status = ''
else:
status = _('when I am ')
for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
if self.__dict__[st + '_cb'].get_active():
status += helpers.get_uf_show(st) + ' '
model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type,
recipient, status)
def on_conditions_treeview_cursor_changed(self, widget):
(model, iter_) = widget.get_selection().get_selected()
if not iter_:
self.active_num = ''
return
self.active_num = model[iter_][0]
if self.active_num == '0':
self.up_button.set_sensitive(False)
else:
self.up_button.set_sensitive(True)
max = self.conditions_treeview.get_model().iter_n_children(None)
if self.active_num == max - 1:
self.down_button.set_sensitive(False)
else:
self.down_button.set_sensitive(True)
self.initiate_rule_state()
self.config_vbox.set_sensitive(True)
self.delete_button.set_sensitive(True)
def on_new_button_clicked(self, widget):
model = self.conditions_treeview.get_model()
num = self.conditions_treeview.get_model().iter_n_children(None)
self.config[num] = {'event': '', 'recipient_type': 'all',
'recipients': '', 'status': 'all', 'tab_opened': 'both',
'sound': '', 'sound_file': '', 'popup': '', 'auto_open': '',
'run_command': False, 'command': '', 'systray': '', 'roster': '',
'urgency_hint': False}
iter_ = model.append((num, ''))
path = model.get_path(iter_)
self.conditions_treeview.set_cursor(path)
self.active_num = num
self.set_treeview_string()
self.config_vbox.set_sensitive(True)
def on_delete_button_clicked(self, widget):
(model, iter_) = self.conditions_treeview.get_selection().get_selected()
if not iter_:
return
# up all others
iter2 = model.iter_next(iter_)
num = self.active_num
while iter2:
num = model[iter2][0]
model[iter2][0] = num - 1
self.config[num-1] = self.config[num].copy()
iter2 = model.iter_next(iter2)
model.remove(iter_)
del self.config[num]
self.active_num = ''
self.config_vbox.set_sensitive(False)
self.delete_button.set_sensitive(False)
self.up_button.set_sensitive(False)
self.down_button.set_sensitive(False)
def on_up_button_clicked(self, widget):
(model, iter_) = self.conditions_treeview.get_selection().get_selected()
if not iter_:
return
conf = self.config[self.active_num].copy()
self.config[self.active_num] = self.config[self.active_num - 1]
self.config[self.active_num - 1] = conf
model[iter_][0] =self.active_num - 1
# get previous iter
path = model.get_path(iter_)
iter_ = model.get_iter((path[0] - 1,))
model[iter_][0] = self.active_num
self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
def on_down_button_clicked(self, widget):
(model, iter_) = self.conditions_treeview.get_selection().get_selected()
if not iter_:
return
conf = self.config[self.active_num].copy()
self.config[self.active_num] = self.config[self.active_num + 1]
self.config[self.active_num + 1] = conf
model[iter_][0] = self.active_num + 1
iter_ = model.iter_next(iter_)
model[iter_][0] = self.active_num
self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
def on_event_combobox_changed(self, widget):
if self.active_num < 0:
return
active = self.event_combobox.get_active()
if active == -1:
event = ''
else:
event = self.events_list[active]
self.config[self.active_num]['event'] = event
self.set_treeview_string()
def on_recipient_type_combobox_changed(self, widget):
if self.active_num < 0:
return
recipient_type = self.recipient_types_list[
self.recipient_type_combobox.get_active()]
self.config[self.active_num]['recipient_type'] = recipient_type
if recipient_type == 'all':
self.recipient_list_entry.hide()
else:
self.recipient_list_entry.show()
self.set_treeview_string()
def on_recipient_list_entry_changed(self, widget):
if self.active_num < 0:
return
recipients = widget.get_text().decode('utf-8')
#TODO: do some check
self.config[self.active_num]['recipients'] = recipients
self.set_treeview_string()
def set_status_config(self):
if self.active_num < 0:
return
status = ''
for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
if self.__dict__[st + '_cb'].get_active():
status += st + ' '
if status:
status = status[:-1]
self.config[self.active_num]['status'] = status
self.set_treeview_string()
def on_status_radiobutton_toggled(self, widget):
if self.active_num < 0:
return
if self.all_status_rb.get_active():
self.config[self.active_num]['status'] = 'all'
# 'All status' clicked
for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
self.__dict__[st + '_cb'].hide()
self.special_status_rb.show()
else:
self.set_status_config()
# 'special status' clicked
for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
self.__dict__[st + '_cb'].show()
self.special_status_rb.hide()
self.set_treeview_string()
def on_status_cb_toggled(self, widget):
if self.active_num < 0:
return
self.set_status_config()
# tab_opened OR (not xor) not_tab_opened must be active
def on_tab_opened_cb_toggled(self, widget):
if self.active_num < 0:
return
if self.tab_opened_cb.get_active():
if self.not_tab_opened_cb.get_active():
self.config[self.active_num]['tab_opened'] = 'both'
else:
self.config[self.active_num]['tab_opened'] = 'yes'
else:
self.not_tab_opened_cb.set_active(True)
self.config[self.active_num]['tab_opened'] = 'no'
def on_not_tab_opened_cb_toggled(self, widget):
if self.active_num < 0:
return
if self.not_tab_opened_cb.get_active():
if self.tab_opened_cb.get_active():
self.config[self.active_num]['tab_opened'] = 'both'
else:
self.config[self.active_num]['tab_opened'] = 'no'
else:
self.tab_opened_cb.set_active(True)
self.config[self.active_num]['tab_opened'] = 'yes'
def on_use_it_toggled(self, widget, oposite_widget, option):
if widget.get_active():
if oposite_widget.get_active():
oposite_widget.set_active(False)
self.config[self.active_num][option] = 'yes'
elif oposite_widget.get_active():
self.config[self.active_num][option] = 'no'
else:
self.config[self.active_num][option] = ''
def on_disable_it_toggled(self, widget, oposite_widget, option):
if widget.get_active():
if oposite_widget.get_active():
oposite_widget.set_active(False)
self.config[self.active_num][option] = 'no'
elif oposite_widget.get_active():
self.config[self.active_num][option] = 'yes'
else:
self.config[self.active_num][option] = ''
def on_use_sound_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound')
if widget.get_active():
self.sound_file_hbox.set_sensitive(True)
else:
self.sound_file_hbox.set_sensitive(False)
def on_browse_for_sounds_button_clicked(self, widget, data=None):
if self.active_num < 0:
return
def on_ok(widget, path_to_snd_file):
dialog.destroy()
if not path_to_snd_file:
path_to_snd_file = ''
self.config[self.active_num]['sound_file'] = path_to_snd_file
self.sound_entry.set_text(path_to_snd_file)
path_to_snd_file = self.sound_entry.get_text().decode('utf-8')
path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file)
dialog = SoundChooserDialog(path_to_snd_file, on_ok)
def on_play_button_clicked(self, widget):
helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8'))
def on_disable_sound_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound')
def on_sound_entry_changed(self, widget):
self.config[self.active_num]['sound_file'] = widget.get_text().\
decode('utf-8')
def on_use_popup_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup')
def on_disable_popup_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup')
def on_use_auto_open_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open')
def on_disable_auto_open_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open')
def on_run_command_cb_toggled(self, widget):
self.config[self.active_num]['run_command'] = widget.get_active()
if widget.get_active():
self.command_entry.set_sensitive(True)
else:
self.command_entry.set_sensitive(False)
def on_command_entry_changed(self, widget):
self.config[self.active_num]['command'] = widget.get_text().\
decode('utf-8')
def on_use_systray_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray')
def on_disable_systray_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray')
def on_use_roster_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster')
def on_disable_roster_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster')
def on_use_urgency_hint_cb_toggled(self, widget):
self.on_use_it_toggled(widget, self.disable_urgency_hint_cb,
'uregency_hint')
def on_disable_urgency_hint_cb_toggled(self, widget):
self.on_disable_it_toggled(widget, self.use_urgency_hint_cb,
'uregency_hint')
def on_hide(self, widget):
# save config
for n in self.plugin.config:
del self.plugin.config[n]
for n in self.config:
self.plugin.config[str(n)] = self.config[n]

View File

@ -1 +0,0 @@
from plugin import WhiteboardPlugin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,7 +0,0 @@
[info]
name: Whiteboard
short_name: whiteboard
version: 0.1
description: Shows a whiteboard in chat. python-pygoocanvas is required.
authors = Yann Leboulanger <asterix@lagaule.org>
homepage = www.gajim.org

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

View File

@ -1,477 +0,0 @@
## plugins/whiteboard/plugin.py
##
## Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
## Copyright (C) 2010 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Whiteboard plugin.
:author: Yann Leboulanger <asterix@lagaule.org>
:since: 1st November 2010
:copyright: Copyright (2010) Yann Leboulanger <asterix@lagaule.org>
:license: GPL
'''
from common import helpers
from common import gajim
from plugins import GajimPlugin
from plugins.plugin import GajimPluginException
from plugins.helpers import log_calls, log
import common.xmpp
import gtk
import chat_control
from common import ged
from common.jingle_session import JingleSession
from common.jingle_content import JingleContent
from common.jingle_transport import JingleTransport, TransportType
import dialogs
from whiteboard_widget import Whiteboard, HAS_GOOCANVAS
from common import xmpp
from common import caps_cache
NS_JINGLE_XHTML = 'urn:xmpp:tmp:jingle:apps:xhtml'
NS_JINGLE_SXE = 'urn:xmpp:tmp:jingle:transports:sxe'
NS_SXE = 'urn:xmpp:sxe:0'
class WhiteboardPlugin(GajimPlugin):
@log_calls('WhiteboardPlugin')
def init(self):
self.config_dialog = None
self.events_handlers = {
'jingle-request-received': (ged.GUI1, self._nec_jingle_received),
'jingle-connected-received': (ged.GUI1, self._nec_jingle_connected),
'jingle-disconnected-received': (ged.GUI1,
self._nec_jingle_disconnected),
'raw-message-received': (ged.GUI1, self._nec_raw_message),
}
self.gui_extension_points = {
'chat_control_base' : (self.connect_with_chat_control,
self.disconnect_from_chat_control),
'chat_control_base_update_toolbar': (self.update_button_state,
None),
}
self.controls = []
self.sid = None
@log_calls('WhiteboardPlugin')
def _compute_caps_hash(self):
for a in gajim.connections:
gajim.caps_hash[a] = caps_cache.compute_caps_hash([
gajim.gajim_identity], gajim.gajim_common_features + \
gajim.gajim_optional_features[a])
# re-send presence with new hash
connected = gajim.connections[a].connected
if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
gajim.connections[a].status)
@log_calls('WhiteboardPlugin')
def activate(self):
if not HAS_GOOCANVAS:
raise GajimPluginException('python-pygoocanvas is missing!')
if NS_JINGLE_SXE not in gajim.gajim_common_features:
gajim.gajim_common_features.append(NS_JINGLE_SXE)
if NS_SXE not in gajim.gajim_common_features:
gajim.gajim_common_features.append(NS_SXE)
self._compute_caps_hash()
@log_calls('WhiteboardPlugin')
def deactivate(self):
if NS_JINGLE_SXE in gajim.gajim_common_features:
gajim.gajim_common_features.remove(NS_JINGLE_SXE)
if NS_SXE in gajim.gajim_common_features:
gajim.gajim_common_features.remove(NS_SXE)
self._compute_caps_hash()
@log_calls('WhiteboardPlugin')
def connect_with_chat_control(self, control):
if isinstance(control, chat_control.ChatControl):
base = Base(self, control)
self.controls.append(base)
@log_calls('WhiteboardPlugin')
def disconnect_from_chat_control(self, chat_control):
for base in self.controls:
base.disconnect_from_chat_control()
self.controls = []
@log_calls('WhiteboardPlugin')
def update_button_state(self, control):
for base in self.controls:
if base.chat_control == control:
if control.contact.supports(NS_JINGLE_SXE) and \
control.contact.supports(NS_SXE):
base.button.set_sensitive(True)
else:
base.button.set_sensitive(False)
@log_calls('WhiteboardPlugin')
def show_request_dialog(self, account, fjid, jid, sid, content_types):
def on_ok():
session = gajim.connections[account].get_jingle_session(fjid, sid)
self.sid = session.sid
if not session.accepted:
session.approve_session()
for content in content_types:
session.approve_content(content)
for _jid in (fjid, jid):
ctrl = gajim.interface.msg_win_mgr.get_control(_jid, account)
if ctrl:
break
if not ctrl:
# create it
gajim.interface.new_chat_from_jid(account, jid)
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
session = session.contents[('initiator', 'xhtml')]
ctrl.draw_whiteboard(session)
def on_cancel():
session = gajim.connections[account].get_jingle_session(fjid, sid)
session.decline_session()
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if contact:
name = contact.get_shown_name()
else:
name = jid
pritext = _('Incoming Whiteboard')
sectext = _('%(name)s (%(jid)s) wants to start a whiteboard with '
'you. Do you want to accept?') % {'name': name, 'jid': jid}
dialog = dialogs.NonModalConfirmationDialog(pritext, sectext=sectext,
on_response_ok=on_ok, on_response_cancel=on_cancel)
dialog.popup()
@log_calls('WhiteboardPlugin')
def _nec_jingle_received(self, obj):
if not HAS_GOOCANVAS:
return
content_types = set(c[0] for c in obj.contents)
if 'xhtml' not in content_types:
return
self.show_request_dialog(obj.conn.name, obj.fjid, obj.jid, obj.sid,
content_types)
@log_calls('WhiteboardPlugin')
def _nec_jingle_connected(self, obj):
if not HAS_GOOCANVAS:
return
account = obj.conn.name
ctrl = (gajim.interface.msg_win_mgr.get_control(obj.fjid, account)
or gajim.interface.msg_win_mgr.get_control(obj.jid, account))
if not ctrl:
return
session = gajim.connections[obj.conn.name].get_jingle_session(obj.fjid,
obj.sid)
if ('initiator', 'xhtml') not in session.contents:
return
session = session.contents[('initiator', 'xhtml')]
ctrl.draw_whiteboard(session)
@log_calls('WhiteboardPlugin')
def _nec_jingle_disconnected(self, obj):
for base in self.controls:
if base.sid == obj.sid:
base.stop_whiteboard(reason = obj.reason)
@log_calls('WhiteboardPlugin')
def _nec_raw_message(self, obj):
if not HAS_GOOCANVAS:
return
if obj.stanza.getTag('sxe', namespace=NS_SXE):
account = obj.conn.name
try:
fjid = helpers.get_full_jid_from_iq(obj.stanza)
except helpers.InvalidFormat:
obj.conn.dispatch('ERROR', (_('Invalid Jabber ID'),
_('A message from a non-valid JID arrived, it has been '
'ignored.')))
jid = gajim.get_jid_without_resource(fjid)
ctrl = (gajim.interface.msg_win_mgr.get_control(fjid, account)
or gajim.interface.msg_win_mgr.get_control(jid, account))
if not ctrl:
return
sxe = obj.stanza.getTag('sxe')
if not sxe:
return
sid = sxe.getAttr('session')
if (jid, sid) not in obj.conn._sessions:
pass
# newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
# self.addJingle(newjingle)
# we already have such session in dispatcher...
session = obj.conn.get_jingle_session(fjid, sid)
cn = session.contents[('initiator', 'xhtml')]
error = obj.stanza.getTag('error')
if error:
action = 'iq-error'
else:
action = 'edit'
cn.on_stanza(obj.stanza, sxe, error, action)
# def __editCB(self, stanza, content, error, action):
#new_tags = sxe.getTags('new')
#remove_tags = sxe.getTags('remove')
#if new_tags is not None:
## Process new elements
#for tag in new_tags:
#if tag.getAttr('type') == 'element':
#ctrl.whiteboard.recieve_element(tag)
#elif tag.getAttr('type') == 'attr':
#ctrl.whiteboard.recieve_attr(tag)
#ctrl.whiteboard.apply_new()
#if remove_tags is not None:
## Delete rids
#for tag in remove_tags:
#target = tag.getAttr('target')
#ctrl.whiteboard.image.del_rid(target)
# Stop propagating this event, it's handled
return True
class Base(object):
def __init__(self, plugin, chat_control):
self.plugin = plugin
self.chat_control = chat_control
self.chat_control.draw_whiteboard = self.draw_whiteboard
self.contact = self.chat_control.contact
self.account = self.chat_control.account
self.jid = self.contact.get_full_jid()
self.create_buttons()
self.whiteboard = None
self.sid = None
def create_buttons(self):
# create juick button
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
self.button = gtk.ToggleButton(label=None, use_underline=True)
self.button.set_property('relief', gtk.RELIEF_NONE)
self.button.set_property('can-focus', False)
img = gtk.Image()
img_path = self.plugin.local_file_path('whiteboard.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory = gtk.IconFactory()
factory.add('whiteboard', iconset)
img_path = self.plugin.local_file_path('brush_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('brush_tool', iconset)
img_path = self.plugin.local_file_path('line_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('line_tool', iconset)
img_path = self.plugin.local_file_path('oval_tool.png')
pixbuf = gtk.gdk.pixbuf_new_from_file(img_path)
iconset = gtk.IconSet(pixbuf=pixbuf)
factory.add('oval_tool', iconset)
factory.add_default()
img.set_from_stock('whiteboard', gtk.ICON_SIZE_BUTTON)
self.button.set_image(img)
send_button = self.chat_control.xml.get_object('send_button')
send_button_pos = actions_hbox.child_get_property(send_button,
'position')
actions_hbox.add_with_properties(self.button, 'position',
send_button_pos - 1, 'expand', False)
id_ = self.button.connect('toggled', self.on_whiteboard_button_toggled)
self.chat_control.handlers[id_] = self.button
self.button.show()
def draw_whiteboard(self, content):
hbox = self.chat_control.xml.get_object('chat_control_hbox')
if len(hbox.get_children()) == 1:
self.whiteboard = Whiteboard(self.account, self.contact, content,
self.plugin)
# set minimum size
self.whiteboard.hbox.set_size_request(300, 0)
hbox.pack_start(self.whiteboard.hbox, expand=False, fill=False)
self.whiteboard.hbox.show_all()
self.button.set_active(True)
content.control = self
self.sid = content.session.sid
def on_whiteboard_button_toggled(self, widget):
"""
Popup whiteboard
"""
if widget.get_active():
if not self.whiteboard:
self.start_whiteboard()
else:
self.stop_whiteboard()
def start_whiteboard(self):
conn = gajim.connections[self.chat_control.account]
jingle = JingleSession(conn, weinitiate=True, jid=self.jid)
self.sid = jingle.sid
conn._sessions[jingle.sid] = jingle
content = JingleWhiteboard(jingle)
content.control = self
jingle.add_content('xhtml', content)
jingle.start_session()
def stop_whiteboard(self, reason=None):
conn = gajim.connections[self.chat_control.account]
self.sid = None
session = conn.get_jingle_session(self.jid, media='xhtml')
if session:
session.end_session()
self.button.set_active(False)
if reason:
txt = _('Whiteboard stopped: %(reason)s') % {'reason': reason}
self.chat_control.print_conversation(txt, 'info')
if not self.whiteboard:
return
hbox = self.chat_control.xml.get_object('chat_control_hbox')
if self.whiteboard.hbox in hbox.get_children():
if hasattr(self.whiteboard, 'hbox'):
hbox.remove(self.whiteboard.hbox)
self.whiteboard = None
def disconnect_from_chat_control(self):
actions_hbox = self.chat_control.xml.get_object('actions_hbox')
actions_hbox.remove(self.button)
class JingleWhiteboard(JingleContent):
''' Jingle Whiteboard sessions consist of xhtml content'''
def __init__(self, session, transport=None):
if not transport:
transport = JingleTransportSXE()
JingleContent.__init__(self, session, transport)
self.media = 'xhtml'
self.negotiated = True # there is nothing to negotiate
self.last_rid = 0
self.callbacks['session-accept'] += [self._sessionAcceptCB]
self.callbacks['session-terminate'] += [self._stop]
self.callbacks['session-terminate-sent'] += [self._stop]
self.callbacks['edit'] = [self._EditCB]
def _EditCB(self, stanza, content, error, action):
new_tags = content.getTags('new')
remove_tags = content.getTags('remove')
if new_tags is not None:
# Process new elements
for tag in new_tags:
if tag.getAttr('type') == 'element':
self.control.whiteboard.recieve_element(tag)
elif tag.getAttr('type') == 'attr':
self.control.whiteboard.recieve_attr(tag)
self.control.whiteboard.apply_new()
if remove_tags is not None:
# Delete rids
for tag in remove_tags:
target = tag.getAttr('target')
self.control.whiteboard.image.del_rid(target)
def _sessionAcceptCB(self, stanza, content, error, action):
log.debug('session accepted')
self.session.connection.dispatch('WHITEBOARD_ACCEPTED',
(self.session.peerjid, self.session.sid))
def generate_rids(self, x):
# generates x number of rids and returns in list
rids = []
for x in range(x):
rids.append(str(self.last_rid))
self.last_rid += 1
return rids
def send_whiteboard_node(self, items, rids):
# takes int rid and dict items and sends it as a node
# sends new item
jid = self.session.peerjid
sid = self.session.sid
message = xmpp.Message(to=jid)
sxe = message.addChild(name='sxe', attrs={'session': sid},
namespace=NS_SXE)
for x in rids:
if items[x]['type'] == 'element':
parent = x
attrs = {'rid': x,
'name': items[x]['data'][0].getName(),
'type': items[x]['type']}
sxe.addChild(name='new', attrs=attrs)
if items[x]['type'] == 'attr':
attr_name = items[x]['data']
chdata = items[parent]['data'][0].getAttr(attr_name)
attrs = {'rid': x,
'name': attr_name,
'type': items[x]['type'],
'chdata': chdata,
'parent': parent}
sxe.addChild(name='new', attrs=attrs)
self.session.connection.connection.send(message)
def delete_whiteboard_node(self, rids):
message = xmpp.Message(to=self.session.peerjid)
sxe = message.addChild(name='sxe', attrs={'session': self.session.sid},
namespace=NS_SXE)
for x in rids:
sxe.addChild(name='remove', attrs = {'target': x})
self.session.connection.connection.send(message)
def send_items(self, items, rids):
# recieves dict items and a list of rids of items to send
# TODO: is there a less clumsy way that doesn't involve passing
# whole list
self.send_whiteboard_node(items, rids)
def del_item(self, rids):
self.delete_whiteboard_node(rids)
def encode(self, xml):
# encodes it sendable string
return 'data:text/xml,' + urllib.quote(xml)
def _fill_content(self, content):
content.addChild(NS_JINGLE_XHTML + ' description')
def _stop(self, *things):
pass
def __del__(self):
pass
def get_content(desc):
return JingleWhiteboard
common.jingle_content.contents[NS_JINGLE_XHTML] = get_content
class JingleTransportSXE(JingleTransport):
def __init__(self):
JingleTransport.__init__(self, TransportType.streaming)
def make_transport(self, candidates=None):
transport = JingleTransport.make_transport(self, candidates)
transport.setNamespace(NS_JINGLE_SXE)
transport.setTagData('host', 'TODO')
return transport
common.jingle_transport.transports[NS_JINGLE_SXE] = JingleTransportSXE

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,419 +0,0 @@
## plugins/whiteboard/whiteboard_widget.py
##
## Copyright (C) 2009 Jeff Ling <jeff.ummu AT gmail.com>
## Copyright (C) 2010 Yann Leboulanger <asterix AT lagaule.org>
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim 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.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import gtk
import gtkgui_helpers
try:
import goocanvas
HAS_GOOCANVAS = True
except:
HAS_GOOCANVAS = False
from common.xmpp import Node
from common import gajim
from common import i18n
from dialogs import FileChooserDialog
'''
A whiteboard widget made for Gajim.
- Ummu
'''
class Whiteboard(object):
def __init__(self, account, contact, session, plugin):
self.plugin = plugin
file_path = plugin.local_file_path('whiteboard_widget.ui')
xml = gtk.Builder()
xml.set_translation_domain(i18n.APP)
xml.add_from_file(file_path)
self.hbox = xml.get_object('whiteboard_hbox')
self.canevas = goocanvas.Canvas()
self.hbox.pack_start(self.canevas)
self.hbox.reorder_child(self.canevas, 0)
self.canevas.set_flags(gtk.CAN_FOCUS)
self.fg_color_select_button = xml.get_object('fg_color_button')
self.root = self.canevas.get_root_item()
self.tool_buttons = []
for tool in ('brush', 'oval', 'line', 'delete'):
self.tool_buttons.append(xml.get_object(tool + '_button'))
xml.get_object('brush_button').set_active(True)
# Events
self.canevas.connect('button-press-event', self.button_press_event)
self.canevas.connect('button-release-event', self.button_release_event)
self.canevas.connect('motion-notify-event', self.motion_notify_event)
self.canevas.connect('item-created', self.item_created)
# Config
self.line_width = 2
xml.get_object('size_scale').set_value(2)
self.color = str(self.fg_color_select_button.get_color())
# SVG Storage
self.image = SVGObject(self.root, session)
xml.connect_signals(self)
# Temporary Variables for items
self.item_temp = None
self.item_temp_coords = (0, 0)
self.item_data = None
# Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance
self.recieving = {}
def on_tool_button_toggled(self, widget):
for btn in self.tool_buttons:
if btn == widget:
continue
btn.set_active(False)
def on_brush_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'brush'
self.on_tool_button_toggled(widget)
def on_oval_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'oval'
self.on_tool_button_toggled(widget)
def on_line_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'line'
self.on_tool_button_toggled(widget)
def on_delete_button_toggled(self, widget):
if widget.get_active():
self.image.draw_tool = 'delete'
self.on_tool_button_toggled(widget)
def on_clear_button_clicked(self, widget):
self.image.clear_canvas()
def on_export_button_clicked(self, widget):
SvgChooserDialog(self.image.export_svg)
def on_fg_color_button_color_set(self, widget):
self.color = str(self.fg_color_select_button.get_color())
def item_created(self, canvas, item, model):
print 'item created'
item.connect('button-press-event', self.item_button_press_events)
def item_button_press_events(self, item, target_item, event):
if self.image.draw_tool == 'delete':
self.image.del_item(item)
def on_size_scale_format_value(self, widget):
self.line_width = int(widget.get_value())
def button_press_event(self, widget, event):
x = event.x
y = event.y
state = event.state
self.item_temp_coords = (x, y)
if self.image.draw_tool == 'brush':
self.item_temp = goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color=self.color,
fill_color=self.color,
line_width=self.line_width)
self.item_data = 'M %s,%s L ' % (x, y)
elif self.image.draw_tool == 'oval':
self.item_data = True
if self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % (x, y)
def motion_notify_event(self, widget, event):
x = event.x
y = event.y
state = event.state
if self.item_temp is not None:
self.item_temp.remove()
if self.item_data is not None:
if self.image.draw_tool == 'brush':
self.item_data = self.item_data + '%s,%s ' % (x, y)
self.item_temp = goocanvas.Path(parent=self.root,
data=self.item_data, line_width=self.line_width,
stroke_color=self.color)
elif self.image.draw_tool == 'oval':
self.item_temp = goocanvas.Ellipse(parent=self.root,
center_x=self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2,
center_y=self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2,
radius_x=abs(x - self.item_temp_coords[0]) / 2,
radius_y=abs(y - self.item_temp_coords[1]) / 2,
stroke_color=self.color,
line_width=self.line_width)
elif self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % self.item_temp_coords
self.item_data = self.item_data + ' %s,%s' % (x, y)
self.item_temp = goocanvas.Path(parent=self.root,
data=self.item_data, line_width=self.line_width,
stroke_color=self.color)
def button_release_event(self, widget, event):
x = event.x
y = event.y
state = event.state
if self.image.draw_tool == 'brush':
self.item_data = self.item_data + '%s,%s' % (x, y)
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color=self.color,
fill_color=self.color,
line_width=self.line_width)
self.image.add_path(self.item_data, self.line_width, self.color)
if self.image.draw_tool == 'oval':
cx = self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2
cy = self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2
rx = abs(x - self.item_temp_coords[0]) / 2
ry = abs(y - self.item_temp_coords[1]) / 2
self.image.add_ellipse(cx, cy, rx, ry, self.line_width, self.color)
if self.image.draw_tool == 'line':
self.item_data = 'M %s,%s L' % self.item_temp_coords
self.item_data = self.item_data + ' %s,%s' % (x, y)
if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]:
goocanvas.Ellipse(parent=self.root,
center_x=x,
center_y=y,
radius_x=1,
radius_y=1,
stroke_color='black',
fill_color='black',
line_width=self.line_width)
self.image.add_path(self.item_data, self.line_width, self.color)
if self.image.draw_tool == 'delete':
pass
self.item_data = None
if self.item_temp is not None:
self.item_temp.remove()
self.item_temp = None
def recieve_element(self, element):
node = self.image.g.addChild(name=element.getAttr('name'))
self.image.g.addChild(node=node)
self.recieving[element.getAttr('rid')] = {'type':'element',
'data':[node],
'children':[]}
def recieve_attr(self, element):
node = self.recieving[element.getAttr('parent')]['data'][0]
node.setAttr(element.getAttr('name'), element.getAttr('chdata'))
self.recieving[element.getAttr('rid')] = {'type':'attr',
'data':element.getAttr('name'),
'parent':node}
self.recieving[element.getAttr('parent')]['children'].append(element.getAttr('rid'))
def apply_new(self):
for x in self.recieving.keys():
if self.recieving[x]['type'] == 'element':
self.image.add_recieved(x, self.recieving)
self.recieving = {}
class SvgChooserDialog(FileChooserDialog):
def __init__(self, on_response_ok=None, on_response_cancel=None):
'''
Choose in which SVG file to store the image
'''
def on_ok(widget, callback):
'''
check if file exists and call callback
'''
path_to_file = self.get_filename()
path_to_file = gtkgui_helpers.decode_filechooser_file_paths(
(path_to_file,))[0]
widget.destroy()
callback(path_to_file)
FileChooserDialog.__init__(self,
title_text=_('Save Image as...'),
action=gtk.FILE_CHOOSER_ACTION_SAVE,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
gtk.RESPONSE_OK),
current_folder='',
default_response=gtk.RESPONSE_OK,
on_response_ok=(on_ok, on_response_ok),
on_response_cancel=on_response_cancel)
filter_ = gtk.FileFilter()
filter_.set_name(_('All files'))
filter_.add_pattern('*')
self.add_filter(filter_)
filter_ = gtk.FileFilter()
filter_.set_name(_('SVG Files'))
filter_.add_pattern('*.svg')
self.add_filter(filter_)
self.set_filter(filter_)
class SVGObject():
''' A class to store the svg document and make changes to it.'''
def __init__(self, root, session, height=300, width=300):
# Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance
self.items = {}
self.root = root
self.draw_tool = 'brush'
# sxe session
self.session = session
# initialize svg document
self.svg = Node(node='<svg/>')
self.svg.setAttr('version', '1.1')
self.svg.setAttr('height', str(height))
self.svg.setAttr('width', str(width))
self.svg.setAttr('xmlns', 'http://www.w3.org/2000/svg')
# TODO: make this settable
self.g = self.svg.addChild(name='g')
self.g.setAttr('fill', 'none')
self.g.setAttr('stroke-linecap', 'round')
def add_path(self, data, line_width, color):
''' adds the path to the items listing, both minidom node and goocanvas
object in a tuple '''
goocanvas_obj = goocanvas.Path(parent=self.root, data=data,
line_width=line_width, stroke_color=color)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
node = self.g.addChild(name='path')
node.setAttr('d', data)
node.setAttr('stroke-width', str(line_width))
node.setAttr('stroke', color)
self.g.addChild(node=node)
rids = self.session.generate_rids(4)
self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]}
self.items[rids[1]] = {'type':'attr', 'data':'d', 'parent':node}
self.items[rids[2]] = {'type':'attr', 'data':'stroke-width', 'parent':node}
self.items[rids[3]] = {'type':'attr', 'data':'stroke', 'parent':node}
self.session.send_items(self.items, rids)
def add_recieved(self, parent_rid, new_items):
''' adds the path to the items listing, both minidom node and goocanvas
object in a tuple '''
node = new_items[parent_rid]['data'][0]
self.items[parent_rid] = new_items[parent_rid]
for x in new_items[parent_rid]['children']:
self.items[x] = new_items[x]
if node.getName() == 'path':
goocanvas_obj = goocanvas.Path(parent=self.root,
data=node.getAttr('d'),
line_width=int(node.getAttr('stroke-width')),
stroke_color=node.getAttr('stroke'))
if node.getName() == 'ellipse':
goocanvas_obj = goocanvas.Ellipse(parent=self.root,
center_x=float(node.getAttr('cx')),
center_y=float(node.getAttr('cy')),
radius_x=float(node.getAttr('rx')),
radius_y=float(node.getAttr('ry')),
stroke_color=node.getAttr('stroke'),
line_width=float(node.getAttr('stroke-width')))
self.items[parent_rid]['data'].append(goocanvas_obj)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
def add_ellipse(self, cx, cy, rx, ry, line_width, stroke_color):
''' adds the ellipse to the items listing, both minidom node and goocanvas
object in a tuple '''
goocanvas_obj = goocanvas.Ellipse(parent=self.root,
center_x=cx,
center_y=cy,
radius_x=rx,
radius_y=ry,
stroke_color=stroke_color,
line_width=line_width)
goocanvas_obj.connect('button-press-event', self.item_button_press_events)
node = self.g.addChild(name='ellipse')
node.setAttr('cx', str(cx))
node.setAttr('cy', str(cy))
node.setAttr('rx', str(rx))
node.setAttr('ry', str(ry))
node.setAttr('stroke-width', str(line_width))
node.setAttr('stroke', stroke_color)
self.g.addChild(node=node)
rids = self.session.generate_rids(7)
self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]}
self.items[rids[1]] = {'type':'attr', 'data':'cx', 'parent':node}
self.items[rids[2]] = {'type':'attr', 'data':'cy', 'parent':node}
self.items[rids[3]] = {'type':'attr', 'data':'rx', 'parent':node}
self.items[rids[4]] = {'type':'attr', 'data':'ry', 'parent':node}
self.items[rids[5]] = {'type':'attr', 'data':'stroke-width', 'parent':node}
self.items[rids[6]] = {'type':'attr', 'data':'stroke', 'parent':node}
self.session.send_items(self.items, rids)
def del_item(self, item):
rids = []
for x in self.items.keys():
if self.items[x]['type'] == 'element':
if self.items[x]['data'][1] == item:
for y in self.items[x]['children']:
rids.append(y)
self.del_rid(y)
rids.append(x)
self.del_rid(x)
break
self.session.del_item(rids)
def clear_canvas(self):
for x in self.items.keys():
if self.items[x]['type'] == 'element':
self.del_rid(x)
def del_rid(self, rid):
if self.items[rid]['type'] == 'element':
self.items[rid]['data'][1].remove()
del self.items[rid]
def export_svg(self, filename):
f = open(filename, 'w')
f.writelines(str(self.svg))
f.close()
def item_button_press_events(self, item, target_item, event):
self.del_item(item)

View File

@ -1,192 +0,0 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkHBox" id="whiteboard_hbox">
<property name="visible">True</property>
<property name="border_width">3</property>
<property name="spacing">6</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkVBox" id="vbuttonbox1">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkToggleButton" id="brush_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Brush Tool: Draw freehand lines</property>
<signal name="toggled" handler="on_brush_button_toggled"/>
<child>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="stock">brush_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="oval_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Oval Tool: Draw circles and ellipses</property>
<signal name="toggled" handler="on_oval_button_toggled"/>
<child>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="stock">oval_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="line_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Line Tool: Draw straight lines</property>
<signal name="toggled" handler="on_line_button_toggled"/>
<child>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="stock">line_tool</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="delete_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Delete Tool: Remove individual figures</property>
<signal name="toggled" handler="on_delete_button_toggled"/>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Clear Canvas: Cleanup canvas</property>
<signal name="clicked" handler="on_clear_button_clicked"/>
<child>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="stock">gtk-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="export_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Export Image: Save image to svg file</property>
<signal name="clicked" handler="on_export_button_clicked"/>
<child>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="stock">gtk-save-as</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkVScale" id="size_scale">
<property name="height_request">68</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Line width</property>
<property name="orientation">vertical</property>
<property name="adjustment">adjustment1</property>
<property name="inverted">True</property>
<property name="digits">0</property>
<property name="value_pos">bottom</property>
<signal name="value_changed" handler="on_size_scale_format_value"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkColorButton" id="fg_color_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Foreground color</property>
<property name="color">#000000000000</property>
<signal name="color_set" handler="on_fg_color_button_color_set"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">7</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-delete</property>
</object>
<object class="GtkAdjustment" id="adjustment1">
<property name="value">2</property>
<property name="lower">1</property>
<property name="upper">110</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
<property name="page_size">10</property>
</object>
</interface>

4617
po/de.po

File diff suppressed because it is too large Load Diff

6294
po/fr.po

File diff suppressed because it is too large Load Diff

5584
po/ja.po

File diff suppressed because it is too large Load Diff

4039
po/ru.po

File diff suppressed because it is too large Load Diff

3354
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

95
scripts/dev/plugins_translate Executable file
View File

@ -0,0 +1,95 @@
#!/bin/sh
PYFILES=$(find -L ./ -type f -name "*.py")
GLADEFILES=$(find -L ./ -type f -name "*.ui")
check_args()
{
if [ $# -ne 2 ]; then
echo "Missing lang argument" >&2
exit 4
fi
}
init_ln()
{
if [ -e "core_plugins" ] && [ ! -L "core_plugins" ]; then
echo "core_plugins must be a symbolic link" >&2
exit 1
fi
if [ ! -e "core_plugins" ]; then
ln -s ../../plugins core_plugins
fi
if [ ! -L "community_plugins" ]; then
echo "community_plugins should be a symbolic link to gajim-plugins repository, else they won't be in po files"
fi
}
make_pot()
{
# Generate .ui.h
find -L ./ -type f -name "*.ui" -exec intltool-extract --type="gettext/glade" {} \;
GLADEHFILES=$(find -L ./ -type f -name "*.ui.h")
xgettext -k_ -kN_ -o plugins_translations.pot $PYFILES $GLADEHFILES --from-code=utf-8
rm $GLADEHFILES
}
make_po()
{
if [ -f $1.po ]; then
echo "Updating '$1' language";
msgmerge -U $1.po plugins_translations.pot;
else
msginit -l $1.UTF-8 -o $1.po;
fi
}
make_mo()
{
if [ ! -f $1.po ]; then
echo "$1.po doesn't existe. Use plugins_translation make_po $1 to create it.";
exit 3
fi
mkdir -p locale/$1/LC_MESSAGES
msgfmt -o $1.mo $1.po
}
install_mo()
{
if [ -L community_plugins ]; then
cp $1.mo community_plugins/plugins_translate/
fi
mkdir -p ~/.local/share/gajim/plugins/locale/$1/LC_MESSAGES/
cp $1.mo ~/.local/share/gajim/plugins/locale/$1/LC_MESSAGES/gajim_plugins.mo
}
case "$1" in
make_po)
check_args $@
init_ln
make_pot
make_po $2
;;
make_mo)
check_args $@
make_mo $2
;;
install_mo)
install_mo
;;
all)
check_args $@
init_ln
make_pot
make_po $2
make_mo $2
install_mo $2
;;
*)
echo "Usage: plugins_translation {all|make_po|make_mo|install_mo}" >&2
echo "example: plugins_translation make_po fr_FR"
exit 2
;;
esac

View File

@ -31,7 +31,8 @@ if 'gtk' in os.listdir('.'):
options = {
'build_exe': {
'includes': ['gtk.keysyms'],
'includes': ['gtk.keysyms', 'dumbdbm', 'dbhash', 'bsddb', 'new', 'potr',
'goocanvas'],
'base': 'Win32GUI',
'bin_excludes': [
'iconv.dll', 'intl.dll', 'libatk-1.0-0.dll',
@ -49,11 +50,11 @@ options = {
setup(
name='Gajim',
version='0.14.1',
version='0.15',
description='A full featured Jabber client',
author='Gajim Development Team',
url='http://www.gajim.org/',
download_url='http://www.gajim.org/downloads.php',
url='http://gajim.org/',
download_url='http://gajim.org/downloads.php',
license='GPL',
options=options,
executables=[Executable('src/gajim.py', icon='data/pixmaps/gajim.ico'),

View File

@ -54,6 +54,7 @@ from common.pep import MOODS, ACTIVITIES
from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER
from common.xmpp.protocol import NS_CHATSTATES
from common.connection_handlers_events import MessageOutgoingEvent
from command_system.implementation.middleware import ChatCommandProcessor
@ -63,6 +64,7 @@ from command_system.implementation.hosts import ChatCommands
# Here we load the module with the standard commands, so they are being detected
# and dispatched.
import command_system.implementation.standard
import command_system.implementation.execute
try:
import gtkspell
@ -309,9 +311,16 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def on_seclabels_ready(self):
lb = self.seclabel_combo.get_model()
lb.clear()
for label in gajim.connections[self.account].seclabel_catalogues[self.contact.jid][2]:
i = 0
sel = 0
catalogue = gajim.connections[self.account].seclabel_catalogues[
self.contact.jid]
for label in catalogue[2]:
lb.append([label])
self.seclabel_combo.set_active(0)
if label == catalogue[3]:
sel = i
i += 1
self.seclabel_combo.set_active(sel)
self.seclabel_combo.set_no_show_all(False)
self.seclabel_combo.show_all()
@ -521,8 +530,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
super(ChatControlBase, self).shutdown()
# PluginSystem: removing GUI extension points connected with ChatControlBase
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self)
gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
self)
gajim.plugin_manager.remove_gui_extension_point(
'chat_control_base_draw_banner', self)
gajim.plugin_manager.remove_gui_extension_point('print_special_text',
self)
gajim.ged.remove_event_handler('our-show', ged.GUI1,
self._nec_our_status)
@ -850,8 +863,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
return label
def send_message(self, message, keyID='', type_='chat', chatstate=None,
msg_id=None, composing_xep=None, resource=None, xhtml=None, callback=None,
callback_args=[], process_commands=True):
msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
process_commands=True):
"""
Send the given message to the active tab. Doesn't return None if error
"""
@ -866,9 +879,9 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=self.account, jid=self.contact.jid, message=message,
keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
composing_xep=composing_xep, resource=resource,
user_nick=self.user_nick, xhtml=xhtml, label=label,
callback=callback, callback_args= callback_args))
resource=resource, user_nick=self.user_nick, xhtml=xhtml,
label=label, callback=callback, callback_args=callback_args,
control=self))
# Record the history of sent messages
self.save_message(message, 'sent')
@ -1426,18 +1439,6 @@ class ChatControl(ChatControlBase):
ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
'chat_control', contact, acct, resource)
self._dbus_message_sent_match = None
if dbus_support.supported:
bus = dbus_support.session_bus.bus()
try:
obj = bus.get_object(remote_control.SERVICE, remote_control.OBJ_PATH)
except:
# likely dbus service not started
pass
else:
iface = dbus.Interface(obj, remote_control.INTERFACE)
self._dbus_message_sent_match = iface.connect_to_signal("MessageSent", self.on_message_sent)
self.gpg_is_active = False
# for muc use:
# widget = self.xml.get_object('muc_window_actions_button')
@ -1591,6 +1592,22 @@ class ChatControl(ChatControlBase):
id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed)
self.handlers[id_] = widget
self.info_bar = gtk.InfoBar()
content_area = self.info_bar.get_content_area()
self.info_bar_label = gtk.Label()
self.info_bar_label.set_use_markup(True)
self.info_bar_label.set_alignment(0, 0)
content_area.add(self.info_bar_label)
self.info_bar.set_no_show_all(True)
widget = self.xml.get_object('vbox2')
widget.pack_start(self.info_bar, expand=False, padding=5)
widget.reorder_child(self.info_bar, 1)
# List of waiting infobar messages
self.info_bar_queue = []
self.subscribe_events()
if not session:
# Don't use previous session if we want to a specific resource
# and it's not the same
@ -1660,6 +1677,20 @@ class ChatControl(ChatControlBase):
# instance object
gajim.plugin_manager.gui_extension_point('chat_control', self)
def subscribe_events(self):
"""
Register listeners to the events class
"""
gajim.events.event_added_subscribe(self.on_event_added)
gajim.events.event_removed_subscribe(self.on_event_removed)
def unsubscribe_events(self):
"""
Unregister listeners to the events class
"""
gajim.events.event_added_unsubscribe(self.on_event_added)
gajim.events.event_removed_unsubscribe(self.on_event_removed)
def _update_toolbar(self):
# Formatting
if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active:
@ -1669,7 +1700,8 @@ class ChatControl(ChatControlBase):
# Add to roster
if not isinstance(self.contact, GC_Contact) \
and _('Not in Roster') in self.contact.groups:
and _('Not in Roster') in self.contact.groups and \
gajim.connections[self.account].roster_supported:
self._add_to_roster_button.show()
else:
self._add_to_roster_button.hide()
@ -2036,24 +2068,13 @@ class ChatControl(ChatControlBase):
if cs and st in ('composing_only', 'all'):
if contact.show == 'offline':
chatstate = ''
elif contact.composing_xep == 'XEP-0085':
if st == 'all' or cs == 'composing':
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
elif contact.composing_xep == 'XEP-0022':
if cs in ('composing', 'paused'):
# only print composing, paused
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
else:
# When does that happen ? See [7797] and [7804]
elif st == 'all' or cs == 'composing':
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
label_text = '<span %s>%s</span><span %s>%s %s</span>' \
% (font_attrs, name, font_attrs_small,
acct_info, chatstate)
% (font_attrs, name, font_attrs_small, acct_info, chatstate)
if acct_info:
acct_info = ' ' + acct_info
label_tooltip = '%s%s %s' % (name, acct_info, chatstate)
@ -2209,7 +2230,7 @@ class ChatControl(ChatControlBase):
dialogs.ESessionInfoWindow(self.session)
def send_message(self, message, keyID='', chatstate=None, xhtml=None,
process_commands=True):
process_commands=True):
"""
Send a message to contact
"""
@ -2233,25 +2254,9 @@ class ChatControl(ChatControlBase):
chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
'disabled'
composing_xep = contact.composing_xep
chatstate_to_send = None
if chatstates_on and contact is not None:
if composing_xep is None:
# no info about peer
# send active to discover chat state capabilities
# this is here (and not in send_chatstate)
# because we want it sent with REAL message
# (not standlone) eg. one that has body
if contact.our_chatstate:
# We already asked for xep 85, don't ask it twice
composing_xep = 'asked_once'
chatstate_to_send = 'active'
contact.our_chatstate = 'ask' # pseudo state
# if peer supports jep85 and we are not 'ask', send 'active'
# NOTE: first active and 'ask' is set in gajim.py
elif composing_xep is not False:
if contact.supports(NS_CHATSTATES):
# send active chatstate on every message (as XEP says)
chatstate_to_send = 'active'
contact.our_chatstate = 'active'
@ -2274,27 +2279,9 @@ class ChatControl(ChatControlBase):
xep0184_id=xep0184_id, xhtml=xhtml, displaymarking=displaymarking)
ChatControlBase.send_message(self, message, keyID, type_='chat',
chatstate=chatstate_to_send, composing_xep=composing_xep,
xhtml=xhtml, callback=_on_sent,
callback_args=[contact, message, encrypted, xhtml, self.get_seclabel()],
process_commands=process_commands)
def on_message_sent(self, account_and_message):
# this is called when an external application sends a chat
# message using DBus. So we likely need to update the UI
# accordingly.
message = account_and_message[1][1]
jid_and_resource = account_and_message[1][0]
if not message:
return
# try to filter based on jid/resource to avoid duplicate
# messages.
if jid_and_resource.find('/') > -1:
jid = jid_and_resource.split('/')[0]
if jid == self.contact.jid:
self.print_conversation(message, frm='outgoing')
chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
callback_args=[contact, message, encrypted, xhtml,
self.get_seclabel()], process_commands=process_commands)
def check_for_possible_paused_chatstate(self, arg):
"""
@ -2393,12 +2380,16 @@ class ChatControl(ChatControlBase):
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
self.session.is_loggable(), self.session and self.session.verified_identity)
def print_session_details(self):
if isinstance(self.session, EncryptedStanzaSession):
def print_session_details(self, old_session=None):
if isinstance(self.session, EncryptedStanzaSession) or \
(old_session and isinstance(old_session, EncryptedStanzaSession)):
self.print_esession_details()
elif isinstance(self.session, ArchivingStanzaSession):
self.print_archiving_session_details()
def get_our_nick(self):
return gajim.nicks[self.account]
def print_conversation(self, text, frm='', tim=None, encrypted=False,
subject=None, xhtml=None, simple=False, xep0184_id=None,
displaymarking=None):
@ -2455,7 +2446,7 @@ class ChatControl(ChatControlBase):
name = contact.get_shown_name()
else:
kind = 'outgoing'
name = gajim.nicks[self.account]
name = self.get_our_nick()
if not xhtml and not (encrypted and self.gpg_is_active) and \
gajim.config.get('rst_formatting_outgoing_messages'):
from common.rst_xhtml_generator import create_xhtml
@ -2597,7 +2588,7 @@ class ChatControl(ChatControlBase):
if contact.show == 'offline':
return
if contact.composing_xep is False: # jid cannot do xep85 nor xep22
if not contact.supports(NS_CHATSTATES):
return
# if the new state we wanna send (state) equals
@ -2605,41 +2596,21 @@ class ChatControl(ChatControlBase):
if contact.our_chatstate == state:
return
if contact.composing_xep is None:
# we don't know anything about jid, so return
# NOTE:
# send 'active', set current state to 'ask' and return is done
# in self.send_message() because we need REAL message (with <body>)
# for that procedure so return to make sure we send only once
# 'active' until we know peer supports jep85
return
if contact.our_chatstate == 'ask':
return
# in JEP22, when we already sent stop composing
# notification on paused, don't resend it
if contact.composing_xep == 'XEP-0022' and \
contact.our_chatstate in ('paused', 'active', 'inactive') and \
state is not 'composing': # not composing == in (active, inactive, gone)
contact.our_chatstate = 'active'
self.reset_kbd_mouse_timeout_vars()
return
# if we're inactive prevent composing (JEP violation)
# if wel're inactive prevent composing (XEP violation)
if contact.our_chatstate == 'inactive' and state == 'composing':
# go active before
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=self.account, jid=self.contact.jid, chatstate='active'))
account=self.account, jid=self.contact.jid, chatstate='active',
control=self))
contact.our_chatstate = 'active'
self.reset_kbd_mouse_timeout_vars()
gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
account=self.account, jid=self.contact.jid, chatstate=state,
msg_id=contact.msg_id, composing_xep=contact.composing_xep))
msg_id=contact.msg_id, control=self))
contact.our_chatstate = state
if contact.our_chatstate == 'active':
if state == 'active':
self.reset_kbd_mouse_timeout_vars()
def shutdown(self):
@ -2647,10 +2618,6 @@ class ChatControl(ChatControlBase):
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control', self)
# disconnect from the dbus MessageSent signal.
if self._dbus_message_sent_match:
self._dbus_message_sent_match.remove()
gajim.ged.remove_event_handler('pep-received', ged.GUI1,
self._nec_pep_received)
gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
@ -2662,6 +2629,8 @@ class ChatControl(ChatControlBase):
gajim.ged.remove_event_handler('caps-received', ged.GUI1,
self._nec_caps_received)
self.unsubscribe_events()
# Send 'gone' chatstate
self.send_chatstate('gone', self.contact)
self.contact.chatstate = None
@ -2906,7 +2875,7 @@ class ChatControl(ChatControlBase):
if row[1] in (constants.KIND_CHAT_MSG_SENT,
constants.KIND_SINGLE_MSG_SENT):
kind = 'outgoing'
name = gajim.nicks[self.account]
name = self.get_our_nick()
elif row[1] in (constants.KIND_SINGLE_MSG_RECV,
constants.KIND_CHAT_MSG_RECV):
kind = 'incoming'
@ -2956,12 +2925,12 @@ class ChatControl(ChatControlBase):
kind = 'info'
else:
kind = 'print_queue'
dm = None
if len(data) > 10:
dm = data[10]
if data[11]:
kind = 'out'
dm = data[10]
self.print_conversation(data[0], kind, tim=data[3],
encrypted=data[4], subject=data[1], xhtml=data[7],
displaymarking=dm)
encrypted=data[4], subject=data[1], xhtml=data[7],
displaymarking=dm)
if len(data) > 6 and isinstance(data[6], int):
message_ids.append(data[6])
@ -3173,3 +3142,144 @@ class ChatControl(ChatControlBase):
self.print_conversation(' (', 'status', simple=True)
self.print_conversation('%s' % (status), 'status', simple=True)
self.print_conversation(')', 'status', simple=True)
def _info_bar_show_message(self):
if self.info_bar.get_visible():
# A message is already shown
return
if not self.info_bar_queue:
return
markup, buttons, args, type_ = self.info_bar_queue[0]
self.info_bar_label.set_markup(markup)
# Remove old buttons
area = self.info_bar.get_action_area()
for b in area.get_children():
area.remove(b)
# Add new buttons
for button in buttons:
self.info_bar.add_action_widget(button, 0)
self.info_bar.set_message_type(type_)
self.info_bar.set_no_show_all(False)
self.info_bar.show_all()
def _add_info_bar_message(self, markup, buttons, args,
type_=gtk.MESSAGE_INFO):
self.info_bar_queue.append((markup, buttons, args, type_))
self._info_bar_show_message()
def _get_file_props_event(self, file_props, type_):
evs = gajim.events.get_events(self.account, self.contact.jid, [type_])
for ev in evs:
if ev.parameters == file_props:
return ev
return None
def _on_accept_file_request(self, widget, file_props):
gajim.interface.instances['file_transfers'].on_file_request_accepted(
self.account, self.contact, file_props)
ev = self._get_file_props_event(file_props, 'file-request')
if ev:
gajim.events.remove_events(self.account, self.contact.jid, event=ev)
def _on_cancel_file_request(self, widget, file_props):
gajim.connections[self.account].send_file_rejection(file_props)
ev = self._get_file_props_event(file_props, 'file-request')
if ev:
gajim.events.remove_events(self.account, self.contact.jid, event=ev)
def _got_file_request(self, file_props):
"""
Show an InfoBar on top of control
"""
markup = '<b>%s:</b> %s' % (_('File transfer'), file_props['name'])
if 'desc' in file_props and file_props['desc']:
markup += ' (%s)' % file_props['desc']
markup += '\n%s: %s' % (_('Size'), helpers.convert_bytes(
file_props['size']))
b1 = gtk.Button(_('_Accept'))
b1.connect('clicked', self._on_accept_file_request, file_props)
b2 = gtk.Button(stock=gtk.STOCK_CANCEL)
b2.connect('clicked', self._on_cancel_file_request, file_props)
self._add_info_bar_message(markup, [b1, b2], file_props,
gtk.MESSAGE_QUESTION)
def _on_open_ft_folder(self, widget, file_props):
if 'file-name' not in file_props:
return
path = os.path.split(file_props['file-name'])[0]
if os.path.exists(path) and os.path.isdir(path):
helpers.launch_file_manager(path)
ev = self._get_file_props_event(file_props, 'file-completed')
if ev:
gajim.events.remove_events(self.account, self.contact.jid, event=ev)
def _on_ok(self, widget, file_props, type_):
ev = self._get_file_props_event(file_props, type_)
if ev:
gajim.events.remove_events(self.account, self.contact.jid, event=ev)
def _got_file_completed(self, file_props):
markup = '<b>%s:</b> %s' % (_('File transfer completed'),
file_props['name'])
if 'desc' in file_props and file_props['desc']:
markup += ' (%s)' % file_props['desc']
b1 = gtk.Button(_('_Open Containing Folder'))
b1.connect('clicked', self._on_open_ft_folder, file_props)
b2 = gtk.Button(stock=gtk.STOCK_OK)
b2.connect('clicked', self._on_ok, file_props, 'file-completed')
self._add_info_bar_message(markup, [b1, b2], file_props)
def _got_file_error(self, file_props, type_, pri_txt, sec_txt):
markup = '<b>%s:</b> %s' % (pri_txt, sec_txt)
b = gtk.Button(stock=gtk.STOCK_OK)
b.connect('clicked', self._on_ok, file_props, type_)
self._add_info_bar_message(markup, [b], file_props, gtk.MESSAGE_ERROR)
def on_event_added(self, event):
if event.account != self.account:
return
if event.jid != self.contact.jid:
return
if event.type_ == 'file-request':
self._got_file_request(event.parameters)
elif event.type_ == 'file-completed':
self._got_file_completed(event.parameters)
elif event.type_ in ('file-error', 'file-stopped'):
msg_err = ''
if event.parameters['error'] == -1:
msg_err = _('Remote contact stopped transfer')
elif event.parameters['error'] == -6:
msg_err = _('Error opening file')
self._got_file_error(event.parameters, event.type_,
_('File transfer stopped'), msg_err)
elif event.type_ in ('file-request-error', 'file-send-error'):
self._got_file_error(event.parameters, event.type_,
_('File transfer cancelled'),
_('Connection with peer cannot be established.'))
def on_event_removed(self, event_list):
"""
Called when one or more events are removed from the event list
"""
for ev in event_list:
if ev.account != self.account:
continue
if ev.jid != self.contact.jid:
continue
if ev.type_ not in ('file-request', 'file-completed', 'file-error',
'file-stopped', 'file-request-error', 'file-send-error'):
continue
i = 0
for ib_msg in self.info_bar_queue:
if ib_msg[2] == ev.parameters:
self.info_bar_queue.remove(ib_msg)
if i == 0:
# We are removing the one currently displayed
self.info_bar.hide()
# show next one?
gobject.idle_add(self._info_bar_show_message)
break
i += 1

View File

@ -117,4 +117,4 @@ class Show(Execute):
if success and stdout:
processor.send(stdout)
elif not success and stderr:
processor.echo_error(stderr)
processor.echo_error(stderr)

View File

@ -46,11 +46,6 @@ class StandardCommonCommands(CommandContainer):
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command
@doc(_("Clear the text window"))
def clear(self):
self.conv_textview.clear()
@command
@doc(_("Hide the chat buttons"))
def compact(self):
@ -168,6 +163,11 @@ class StandardCommonChatCommands(CommandContainer):
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands
@command
@doc(_("Clear the text window"))
def clear(self):
self.conv_textview.clear()
@command
@doc(_("Toggle the GPG encryption"))
def gpg(self):
@ -240,6 +240,13 @@ class StandardGroupChatCommands(CommandContainer):
AUTOMATIC = True
HOSTS = GroupChatCommands,
@command
@doc(_("Clear the text window"))
def clear(self):
self.conv_textview.clear()
self.gc_count_nicknames_colors = -1
self.gc_custom_colors = {}
@command(raw=True)
@doc(_("Change your nickname in a group chat"))
def nick(self, new_nick):
@ -353,4 +360,4 @@ class StandardGroupChatCommands(CommandContainer):
@command('unignore', raw=True)
@doc(_("Allow an occupant to send you public or private messages"))
def unblock(self, who):
self.on_unblock(None, who)
self.on_unblock(None, who)

View File

@ -27,6 +27,9 @@ import helpers
import dataforms
import gajim
import logging
log = logging.getLogger('gajim.c.commands')
class AdHocCommand:
commandnode = 'command'
commandname = 'The Command'
@ -371,7 +374,11 @@ class ConnectionCommands:
Send disco#info result for query for command (JEP-0050, example 6.).
Return True if the result was sent, False if not
"""
jid = helpers.get_full_jid_from_iq(iq_obj)
try:
jid = helpers.get_full_jid_from_iq(iq_obj)
except helpers.InvalidFormat:
log.warn('Invalid JID: %s, ignoring it' % iq_obj.getFrom())
return
node = iq_obj.getTagAttr('query', 'node')
if node not in self.__commands: return False

View File

@ -86,7 +86,8 @@ class Config:
'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxa time.') ],
'ask_online_status': [ opt_bool, False ],
'ask_offline_status': [ opt_bool, False ],
'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), True],
'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), False],
'allow_hide_roster': [opt_bool, False, _("Allow to hide the roster window even if the tray icon is not shown."), False],
'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ],
'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ],
@ -184,7 +185,7 @@ class Config:
'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True],
'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')],
'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')],
'autodetect_browser_mailer': [opt_bool, False, '', True],
'autodetect_browser_mailer': [opt_bool, True, '', True],
'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')],
@ -286,9 +287,10 @@ class Config:
'video_size': [opt_str, '', _('Optionally resize jingle output video. Example: 320x240')],
'audio_input_volume': [opt_int, 50],
'audio_output_volume': [opt_int, 50],
'use_stun_server': [opt_bool, True, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')],
'use_stun_server': [opt_bool, False, _('If True, Gajim will try to use a STUN server when using jingle. The one in "stun_server" option, or the one given by the jabber server.')],
'stun_server': [opt_str, '', _('STUN server to use when using jingle')],
'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
'global_proxy': [opt_str, '', _('Proxy used for all outgoing connections if the account does not have a specific proxy configured')],
}
__options_per_key = {
@ -297,6 +299,7 @@ class Config:
'hostname': [ opt_str, '', '', True ],
'anonymous_auth': [ opt_bool, False ],
'client_cert': [ opt_str, '', '', True ],
'client_cert_encrypted': [ opt_bool, False, '', False ],
'savepass': [ opt_bool, False ],
'password': [ opt_str, '' ],
'resource': [ opt_str, 'gajim', '', True ],
@ -321,7 +324,7 @@ class Config:
'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.')],
'autonegotiate_esessions': [opt_bool, True, _('Should Gajim automatically start an encrypted session when possible?')],
'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
'warn_when_plaintext_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an plaintext connection.') ],
'action_when_plaintext_connection': [ opt_str, 'warn', _('Show a warning dialog before sending password on an plaintext connection. Can be \'warn\', \'connect\', \'disconnect\'') ],
'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ],
'warn_when_insecure_password': [ opt_bool, True, _('Show a warning dialog before sending PLAIN password over a plain connection.') ],
'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
@ -331,7 +334,8 @@ class Config:
'custom_port': [ opt_int, 5222, '', True ],
'custom_host': [ opt_str, '', '', True ],
'sync_with_global_status': [ opt_bool, False, ],
'no_log_for': [ opt_str, '' ],
'no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you do not want to store logs. You can also add account name to log nothing for this account.')],
'allow_no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you accept to not log conversations if he does not want to.')],
'minimized_gc': [ opt_str, '' ],
'attached_gpg_keys': [ opt_str, '' ],
'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')],
@ -347,6 +351,7 @@ class Config:
# proxy65 for FT
'file_transfer_proxies': [opt_str, 'proxy.eu.jabber.org, proxy.jabber.ru, proxy.jabbim.cz'],
'use_ft_proxies': [opt_bool, True, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True],
'test_ft_proxies_on_startup': [opt_bool, True, _('If True, Gajim will test file transfer proxies on startup to be sure it works. Openfire\'s proxies are known to fail this test even if they work.')],
'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
'msgwin-width': [opt_int, 480],
@ -377,6 +382,11 @@ class Config:
'roster_version': [opt_str, ''],
'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
'enable_message_carbons': [ opt_bool, False, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
'ft_send_local_ips': [ opt_bool, True, _('If enabled, Gajim will send your local IPs so your contact can connect to your machine to transfer files.')],
'oauth2_refresh_token': [ opt_str, '', _('Latest token for Oauth2 authentication.')],
'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for Oauth2 authentication.')],
'oauth2_redirect_url': [ opt_str, 'http%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for Oauth2 authentication.')],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
@ -512,6 +522,10 @@ class Config:
}
proxies_default = {
_('Tor'): ['socks5', 'localhost', 9050],
}
def foreach(self, cb, data = None):
for opt in self.__options:
cb(data, opt, None, self.__options[opt])

View File

@ -40,6 +40,7 @@ import operator
import time
import locale
import hmac
import json
try:
randomsource = random.SystemRandom()
@ -57,9 +58,9 @@ from common import gajim
from common import gpg
from common import passwords
from common import exceptions
from connection_handlers import *
from xmpp import Smacks
from string import Template
import logging
log = logging.getLogger('gajim.c.connection')
@ -155,6 +156,7 @@ class CommonConnection:
self.private_storage_supported = False
self.archiving_supported = False
self.archive_pref_supported = False
self.roster_supported = True
self.muc_jid = {} # jid of muc server for each transport type
self._stun_servers = [] # STUN servers of our jabber server
@ -247,17 +249,18 @@ class CommonConnection:
raise NotImplementedError
def _prepare_message(self, jid, msg, keyID, type_='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None,
label=None, original_message=None, delayed=None, callback=None):
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
session=None, forward_from=None, form_node=None, label=None,
original_message=None, delayed=None, callback=None):
if not self.connection or self.connected < 2:
return 1
try:
jid = self.check_jid(jid)
except helpers.InvalidFormat:
self.dispatch('ERROR', (_('Invalid Jabber ID'),
_('It is not possible to send a message to %s, this JID is not '
'valid.') % jid))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Invalid Jabber ID'), sec_txt=_(
'It is not possible to send a message to %s, this JID is not '
'valid.') % jid))
return
if msg and not xhtml and gajim.config.get(
@ -298,48 +301,48 @@ class CommonConnection:
self._message_encrypted_cb(output, type_, msg,
msgtxt, original_message, fjid, resource,
jid, xhtml, subject, chatstate, msg_id,
composing_xep, label, forward_from, delayed,
session, form_node, user_nick, keyID,
callback)
label, forward_from, delayed, session,
form_node, user_nick, keyID, callback)
gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
conn=self, callback=_on_always_trust))
else:
self._message_encrypted_cb(output, type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml,
subject, chatstate, msg_id, composing_xep, label,
forward_from, delayed, session, form_node,
user_nick, keyID, callback)
subject, chatstate, msg_id, label, forward_from,
delayed, session, form_node, user_nick, keyID,
callback)
gajim.thread_interface(encrypt_thread, [msg, keyID, False],
_on_encrypted, [])
_on_encrypted, [])
return
self._message_encrypted_cb(('', error), type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject,
chatstate, msg_id, composing_xep, label, forward_from, delayed,
session, form_node, user_nick, keyID, callback)
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, keyID, callback)
return
self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
composing_xep, label, forward_from, delayed, session, form_node,
user_nick, callback)
label, forward_from, delayed, session, form_node, user_nick,
callback)
def _message_encrypted_cb(self, output, type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
composing_xep, label, forward_from, delayed, session, form_node, user_nick,
keyID, callback):
label, forward_from, delayed, session, form_node, user_nick, keyID,
callback):
msgenc, error = output
if msgenc and not error:
msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
lang = os.getenv('LANG')
if lang is not None and lang != 'en': # we're not english
# one in locale and one en
if lang is not None and not lang.startswith('en'):
# we're not english: one in locale and one en
msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
' (' + msgtxt + ')'
self._on_continue_message(type_, msg, msgtxt, original_message,
fjid, resource, jid, xhtml, subject, msgenc, keyID,
chatstate, msg_id, composing_xep, label, forward_from, delayed,
session, form_node, user_nick, callback)
chatstate, msg_id, label, forward_from, delayed, session,
form_node, user_nick, callback)
return
# Encryption failed, do not send message
tim = localtime()
@ -348,8 +351,7 @@ class CommonConnection:
def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
composing_xep, label, forward_from, delayed, session, form_node, user_nick,
callback):
label, forward_from, delayed, session, form_node, user_nick, callback):
if type_ == 'chat':
msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
xhtml=xhtml)
@ -385,27 +387,17 @@ class CommonConnection:
contact = gajim.contacts.get_contact_with_highest_priority(self.name,
jid)
# chatstates - if peer supports xep85 or xep22, 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 and contact:
if ((composing_xep == 'XEP-0085' or not composing_xep) \
and composing_xep != 'asked_once') or \
contact.supports(common.xmpp.NS_CHATSTATES):
# XEP-0085
msg_iq.setTag(chatstate, namespace=common.xmpp.NS_CHATSTATES)
if composing_xep in ('XEP-0022', 'asked_once') or \
not composing_xep:
# XEP-0022
chatstate_node = msg_iq.setTag('x', namespace=common.xmpp.NS_EVENT)
if chatstate is 'composing' or msgtxt:
chatstate_node.addChild(name='composing')
# chatstates - if peer supports xep85, send chatstates
# please note that the only valid tag inside a message containing a
# <body> tag is the active event
if chatstate and contact and contact.supports(NS_CHATSTATES):
msg_iq.setTag(chatstate, namespace=NS_CHATSTATES)
if forward_from:
addresses = msg_iq.addChild('addresses',
namespace=common.xmpp.NS_ADDRESS)
namespace=common.xmpp.NS_ADDRESS)
addresses.addChild('address', attrs = {'type': 'ofrom',
'jid': forward_from})
'jid': forward_from})
# XEP-0203
if delayed:
@ -432,7 +424,7 @@ class CommonConnection:
if callback:
callback(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq, xhtml)
subject, type_, msg_iq, xhtml)
def log_message(self, jid, msg, forward_from, session, original_message,
subject, type_, xhtml=None):
@ -511,14 +503,14 @@ class CommonConnection:
raise NotImplementedError
def update_contact(self, jid, name, groups):
if self.connection:
if self.connection and self.roster_supported:
self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
def update_contacts(self, contacts):
"""
Update multiple roster items
"""
if self.connection:
if self.connection and self.roster_supported:
self.connection.getRoster().setItemMulti(contacts)
def new_account(self, name, config, sync=False):
@ -713,8 +705,12 @@ class Connection(CommonConnection, ConnectionHandlers):
self.available_transports = {} # list of available transports on this
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
self.private_storage_supported = True
self.privacy_rules_requested = False
self.streamError = ''
self.secret_hmac = str(random.random())[2:]
self.sm = Smacks(self) # Stream Management
gajim.ged.register_event_handler('privacy-list-received', ged.CORE,
self._nec_privacy_list_received)
gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
@ -749,6 +745,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.pingalives = 0
self.client_cert = gajim.config.get_per('accounts', self.name,
'client_cert')
self.client_cert_passphrase = ''
def check_jid(self, jid):
return helpers.parse_jid(jid)
@ -777,6 +774,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connected = 0
self.time_to_reconnect = None
self.privacy_rules_supported = False
if on_purpose:
self.sm = Smacks(self)
if self.connection:
# make sure previous connection is completely closed
gajim.proxy65_manager.disconnect(self.connection)
@ -785,6 +784,15 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.disconnect()
self.last_connection = None
self.connection = None
def set_oldst(self): # Set old state
if self.old_show:
self.connected = gajim.SHOW_LIST.index(self.old_show)
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=self.connected))
else: # we default to online
self.connected = 2
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=gajim.SHOW_LIST[self.connected]))
def _disconnectedReconnCB(self):
"""
@ -797,8 +805,9 @@ class Connection(CommonConnection, ConnectionHandlers):
self.old_show = gajim.SHOW_LIST[self.connected]
self.connected = 0
if not self.on_purpose:
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show='offline'))
if not (self.sm and self.sm.resumption):
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show='offline'))
self.disconnect()
if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
self.connected = -1
@ -906,9 +915,10 @@ class Connection(CommonConnection, ConnectionHandlers):
self.disconnect(on_purpose=True)
return
if not data[1]: # wrong answer
self.dispatch('ERROR', (_('Invalid answer'),
_('Transport %(name)s answered wrongly to register '
'request: %(error)s') % {'name': data[0],
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self, level='error', pri_txt=_('Invalid answer'),
sec_txt=_('Transport %(name)s answered wrongly to '
'register request: %(error)s') % {'name': data[0],
'error': data[3]}))
return
is_form = data[2]
@ -975,7 +985,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if weightsum >= rndint:
return host
def connect(self, data = None):
def connect(self, data=None):
"""
Start a connection to the Jabber server
@ -986,10 +996,25 @@ class Connection(CommonConnection, ConnectionHandlers):
if self.connection:
return self.connection, ''
if data:
if self.sm.resuming and self.sm.location:
# If resuming and server gave a location, connect from there
hostname = self.sm.location
self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
self.name, 'try_connecting_for_foo_secs')
use_custom = False
proxy = helpers.get_proxy_info(self.name)
elif data:
hostname = data['hostname']
self.try_connecting_for_foo_secs = 45
p = data['proxy']
if p and p in gajim.config.get_per('proxies'):
proxy = {}
proxyptr = gajim.config.get_per('proxies', p)
for key in proxyptr.keys():
proxy[key] = proxyptr[key][1]
else:
proxy = None
use_srv = True
use_custom = data['use_custom_host']
if use_custom:
@ -1000,7 +1025,7 @@ class Connection(CommonConnection, ConnectionHandlers):
usessl = gajim.config.get_per('accounts', self.name, 'usessl')
self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
self.name, 'try_connecting_for_foo_secs')
p = gajim.config.get_per('accounts', self.name, 'proxy')
proxy = helpers.get_proxy_info(self.name)
use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
use_custom = gajim.config.get_per('accounts', self.name,
'use_custom_host')
@ -1009,52 +1034,10 @@ class Connection(CommonConnection, ConnectionHandlers):
# create connection if it doesn't already exist
self.connected = 1
if p and p in gajim.config.get_per('proxies'):
proxy = {}
proxyptr = gajim.config.get_per('proxies', p)
for key in proxyptr.keys():
proxy[key] = proxyptr[key][1]
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try:
try:
env_http_proxy = os.environ['HTTP_PROXY']
except Exception:
env_http_proxy = os.environ['http_proxy']
env_http_proxy = env_http_proxy.strip('"')
# Dispose of the http:// prefix
env_http_proxy = env_http_proxy.split('://')
env_http_proxy = env_http_proxy[len(env_http_proxy)-1]
env_http_proxy = env_http_proxy.split('@')
if len(env_http_proxy) == 2:
login = env_http_proxy[0].split(':')
addr = env_http_proxy[1].split(':')
else:
login = ['', '']
addr = env_http_proxy[0].split(':')
proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
if len(addr) == 2:
proxy['port'] = addr[1]
else:
proxy['port'] = 3128
if len(login) == 2:
proxy['pass'] = login[1]
proxy['useauth'] = True
else:
proxy['pass'] = u''
except Exception:
proxy = None
else:
proxy = None
h = hostname
p = 5222
ssl_p = 5223
# use_srv = False # wants ssl? disable srv lookup
if use_custom:
h = custom_h
p = custom_p
@ -1069,8 +1052,8 @@ class Connection(CommonConnection, ConnectionHandlers):
if use_srv:
# add request for srv query to the resolve, on result '_on_resolve'
# will be called
gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(h),
self._on_resolve)
gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(
h), self._on_resolve)
else:
self._on_resolve('', [])
@ -1081,7 +1064,8 @@ class Connection(CommonConnection, ConnectionHandlers):
# Add ssl port
ssl_p = 5223
if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
ssl_p = gajim.config.get_per('accounts', self.name, 'custom_port')
ssl_p = gajim.config.get_per('accounts', self.name,
'custom_port')
for i in self._hosts:
i['ssl_port'] = ssl_p
self._connect_to_next_host()
@ -1155,29 +1139,42 @@ class Connection(CommonConnection, ConnectionHandlers):
secure_tuple = (self._current_type, cacerts, mycerts)
con = common.xmpp.NonBlockingClient(
domain=self._hostname,
caller=self,
idlequeue=gajim.idlequeue)
domain=self._hostname,
caller=self,
idlequeue=gajim.idlequeue)
self.last_connection = con
# increase default timeout for server responses
common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs
common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = \
self.try_connecting_for_foo_secs
# FIXME: this is a hack; need a better way
if self.on_connect_success == self._on_new_account:
con.RegisterDisconnectHandler(self._on_new_account)
self.log_hosttype_info(port)
con.connect(
hostname=self._current_host['host'],
port=port,
on_connect=self.on_connect_success,
on_proxy_failure=self.on_proxy_failure,
on_connect_failure=self.connect_to_next_type,
proxy=self._proxy,
secure_tuple = secure_tuple)
if self.client_cert and gajim.config.get_per('accounts', self.name,
'client_cert_encrypted'):
gajim.nec.push_incoming_event(ClientCertPassphraseEvent(
None, conn=self, con=con, port=port,
secure_tuple=secure_tuple))
return
self.on_client_cert_passphrase('', con, port, secure_tuple)
else:
self._connect_to_next_host(retry)
def on_client_cert_passphrase(self, passphrase, con, port, secure_tuple):
self.client_cert_passphrase = passphrase
self.log_hosttype_info(port)
con.connect(
hostname=self._current_host['host'],
port=port,
on_connect=self.on_connect_success,
on_proxy_failure=self.on_proxy_failure,
on_connect_failure=self.connect_to_next_type,
proxy=self._proxy,
secure_tuple = secure_tuple)
def log_hosttype_info(self, port):
msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
self._current_host['host'], port, self._current_type)
@ -1202,7 +1199,9 @@ class Connection(CommonConnection, ConnectionHandlers):
key = common.xmpp.NS_XMPP_STREAMS + ' ' + self.streamError
if key in common.xmpp.ERRORS:
sectxt2 = _('Server replied: %s') % common.xmpp.ERRORS[key][2]
self.dispatch('ERROR', (pritxt, '%s\n%s' % (sectxt2, sectxt)))
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self, level='error', pri_txt=pritxt,
sec_txt='%s\n%s' % (sectxt2, sectxt)))
return
# show popup
gajim.nec.push_incoming_event(ConnectionLostEvent(None,
@ -1228,10 +1227,16 @@ class Connection(CommonConnection, ConnectionHandlers):
return
con.RegisterDisconnectHandler(self._on_disconnected)
if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
'warn_when_plaintext_connection'):
'action_when_plaintext_connection') == 'warn':
gajim.nec.push_incoming_event(PlainConnectionEvent(None, conn=self,
xmpp_client=con))
return True
if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
'action_when_plaintext_connection') == 'disconnect':
self.disconnect(on_purpose=True)
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show='offline'))
return False
if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
and gajim.config.get_per('accounts', self.name,
'warn_when_insecure_ssl_connection') and \
@ -1364,9 +1369,10 @@ class Connection(CommonConnection, ConnectionHandlers):
self.disconnect(on_purpose = True)
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show='offline'))
self.dispatch('ERROR', (_('Authentication failed with "%s"') % \
self._hostname,
_('Please check your login and password for correctness.')))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Authentication failed with "%s"') % \
self._hostname, sec_txt=_('Please check your login and password'
'for correctness.')))
if self.on_connect_auth:
self.on_connect_auth(None)
self.on_connect_auth = None
@ -1439,10 +1445,11 @@ class Connection(CommonConnection, ConnectionHandlers):
gajim.nec.push_incoming_event(PrivacyListRemovedEvent(None,
conn=self, list_name=privacy_list))
else:
self.dispatch('ERROR', (_('Error while removing privacy list'),
_('Privacy list %s has not been removed. It is maybe active in '
'one of your connected resources. Deactivate it and try '
'again.') % privacy_list))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Error while removing privacy '
'list'), sec_txt=_('Privacy list %s has not been removed. '
'It is maybe active in one of your connected resources. '
'Deactivate it and tryagain.') % privacy_list))
common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list,
_on_del_privacy_list_result)
@ -1471,14 +1478,14 @@ class Connection(CommonConnection, ConnectionHandlers):
Build a Privacy rule stanza for invisibility
"""
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
l = iq.getTag('query').setTag('list', {'name': name})
l = iq.setQuery().setTag('list', {'name': name})
i = l.setTag('item', {'action': action, 'order': str(order)})
i.setTag('presence-out')
return iq
def build_invisible_rule(self):
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
l = iq.getTag('query').setTag('list', {'name': 'invisible'})
l = iq.setQuery().setTag('list', {'name': 'invisible'})
if self.name in gajim.interface.status_sent_to_groups and \
len(gajim.interface.status_sent_to_groups[self.name]) > 0:
for group in gajim.interface.status_sent_to_groups[self.name]:
@ -1508,7 +1515,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
iq.getTag('query').setTag('active', {'name': name})
iq.setQuery().setTag('active', {'name': name})
self.connection.send(iq)
def send_invisible_presence(self, msg, signed, initial = False):
@ -1517,8 +1524,10 @@ class Connection(CommonConnection, ConnectionHandlers):
if not self.privacy_rules_supported:
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show=gajim.SHOW_LIST[self.connected]))
self.dispatch('ERROR', (_('Invisibility not supported'),
_('Account %s doesn\'t support invisibility.') % self.name))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Invisibility not supported',
sec_txt=_('Account %s doesn\'t support invisibility.') % \
self.name)))
return
# If we are already connected, and privacy rules are supported, send
# offline presence first as it's required by XEP-0126
@ -1591,12 +1600,16 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.set_send_timeout2(self.pingalives, self.sendPing)
self.connection.onreceive(None)
self.request_message_archiving_preferences()
self.privacy_rules_requested = False
self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'),
id_prefix='Gajim_')
# If we are not resuming, we ask for discovery info
# and archiving preferences
if not self.sm.resuming:
self.request_message_archiving_preferences()
self.discoverInfo(gajim.config.get_per('accounts', self.name,
'hostname'), id_prefix='Gajim_')
self.sm.resuming = False # back to previous state
# Discover Stun server(s)
gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(
self.connected_hostname), self._on_stun_resolved)
@ -1606,6 +1619,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self._stun_servers = self._hosts = [i for i in result_array]
def _request_privacy(self):
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
id_ = self.connection.getAnID()
iq.setID(id_)
@ -1671,12 +1686,21 @@ class Connection(CommonConnection, ConnectionHandlers):
self.archive_manual_supported = True
if common.xmpp.NS_ARCHIVE_PREF in obj.features:
self.archive_pref_supported = True
if common.xmpp.NS_CARBONS in obj.features and \
gajim.config.get_per('accounts', self.name,
'enable_message_carbons'):
# Server supports carbons, activate it
iq = common.xmpp.Iq('set')
iq.setTag('enable', namespace=common.xmpp.NS_CARBONS)
self.connection.send(iq)
if common.xmpp.NS_BYTESTREAM in obj.features and \
gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
our_fjid = helpers.parse_jid(our_jid + '/' + \
self.server_resource)
testit = gajim.config.get_per('accounts', self.name,
'test_ft_proxies_on_startup')
gajim.proxy65_manager.resolve(obj.fjid, self.connection,
our_fjid, self.name)
our_fjid, default=self.name, testit=testit)
if common.xmpp.NS_MUC in obj.features and is_muc:
type_ = transport_type or 'jabber'
self.muc_jid[type_] = obj.fjid
@ -1749,10 +1773,10 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.send(msg_iq)
def send_message(self, jid, msg, keyID=None, type_='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, label=None, session=None, forward_from=None,
form_node=None, original_message=None, delayed=None, callback=None,
callback_args=[], now=False):
chatstate=None, msg_id=None, resource=None, user_nick=None, xhtml=None,
label=None, session=None, forward_from=None, form_node=None,
original_message=None, delayed=None, callback=None, callback_args=[],
now=False):
def cb(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq, xhtml):
@ -1767,9 +1791,9 @@ class Connection(CommonConnection, ConnectionHandlers):
subject, type_, xhtml)
self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
resource=resource, user_nick=user_nick, xhtml=xhtml, label=label,
session=session, forward_from=forward_from, form_node=form_node,
chatstate=chatstate, msg_id=msg_id, resource=resource,
user_nick=user_nick, xhtml=xhtml, label=label, session=session,
forward_from=forward_from, form_node=form_node,
original_message=original_message, delayed=delayed, callback=cb)
def _nec_message_outgoing(self, obj):
@ -1792,9 +1816,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self._prepare_message(obj.jid, obj.message, obj.keyID, type_=obj.type_,
subject=obj.subject, chatstate=obj.chatstate, msg_id=obj.msg_id,
composing_xep=obj.composing_xep, resource=obj.resource,
user_nick=obj.user_nick, xhtml=obj.xhtml, label=obj.label,
session=obj.session, forward_from=obj.forward_from,
resource=obj.resource, user_nick=obj.user_nick, xhtml=obj.xhtml,
label=obj.label, session=obj.session, forward_from=obj.forward_from,
form_node=obj.form_node, original_message=obj.original_message,
delayed=obj.delayed, callback=cb)
@ -1853,7 +1876,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if name:
infos['name'] = name
iq = common.xmpp.Iq('set', common.xmpp.NS_ROSTER)
q = iq.getTag('query')
q = iq.setQuery()
item = q.addChild('item', attrs=infos)
for g in groups:
item.addChild('group').setData(g)
@ -1898,7 +1921,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to = agent)
iq.getTag('query').setTag('remove')
iq.setQuery().setTag('remove')
id_ = self.connection.getAnID()
iq.setID(id_)
self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
@ -2021,7 +2044,8 @@ class Connection(CommonConnection, ConnectionHandlers):
if not gajim.account_is_connected(self.name):
return
self.seclabel_catalogue_request(to, callback)
iq = common.xmpp.Iq(typ='get')
server = gajim.get_jid_from_account(self.name).split("@")[1] # Really, no better way?
iq = common.xmpp.Iq(typ='get', to=server)
iq2 = iq.addChild(name='catalog', namespace=common.xmpp.NS_SECLABEL_CATALOG)
iq2.setAttr('to', to)
self.connection.send(iq)
@ -2334,7 +2358,7 @@ class Connection(CommonConnection, ConnectionHandlers):
msg_iq.addChild(node = label)
self.connection.send(msg_iq)
gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
jid=jid, message=msg, keyID=None))
jid=jid, message=msg, keyID=None, chatstate=None))
def send_gc_subject(self, jid, subject):
if not gajim.account_is_connected(self.name):
@ -2355,7 +2379,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'set', queryNS = common.xmpp.NS_MUC_OWNER,
to = room_jid)
destroy = iq.getTag('query').setTag('destroy')
destroy = iq.setQuery().setTag('destroy')
if reason:
destroy.setTagData('reason', reason)
if jid:
@ -2402,7 +2426,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
common.xmpp.NS_MUC_ADMIN)
item = iq.getTag('query').setTag('item')
item = iq.setQuery().setTag('item')
item.setAttr('nick', nick)
item.setAttr('role', role)
if reason:
@ -2417,7 +2441,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
common.xmpp.NS_MUC_ADMIN)
item = iq.getTag('query').setTag('item')
item = iq.setQuery().setTag('item')
item.setAttr('jid', jid)
item.setAttr('affiliation', affiliation)
if reason:
@ -2429,7 +2453,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS = \
common.xmpp.NS_MUC_ADMIN)
item = iq.getTag('query')
item = iq.setQuery()
for jid in users_dict:
item_tag = item.addChild('item', {'jid': jid,
'affiliation': users_dict[jid]['affiliation']})
@ -2442,7 +2466,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'get', to = room_jid, queryNS = \
common.xmpp.NS_MUC_ADMIN)
item = iq.getTag('query').setTag('item')
item = iq.setQuery().setTag('item')
item.setAttr('affiliation', affiliation)
self.connection.send(iq)
@ -2451,7 +2475,7 @@ class Connection(CommonConnection, ConnectionHandlers):
return
iq = common.xmpp.Iq(typ = 'set', to = room_jid, queryNS =\
common.xmpp.NS_MUC_OWNER)
query = iq.getTag('query')
query = iq.setQuery()
form.setAttr('type', 'submit')
query.addChild(node = form)
self.connection.send(iq)
@ -2469,6 +2493,32 @@ class Connection(CommonConnection, ConnectionHandlers):
def get_password(self, callback, type_):
self.pasword_callback = (callback, type_)
if type_ == 'X-MESSENGER-OAUTH2':
client_id = gajim.config.get_per('accounts', self.name,
'oauth2_client_id')
refresh_token = gajim.config.get_per('accounts', self.name,
'oauth2_refresh_token')
if refresh_token:
renew_URL = 'https://oauth.live.com/token?client_id=' \
'%(client_id)s&redirect_uri=https%%3A%%2F%%2Foauth.live.' \
'com%%2Fdesktop&grant_type=refresh_token&refresh_token=' \
'%(refresh_token)s' % locals()
result = helpers.download_image(self.name, {'src': renew_URL})[0]
if result:
dict_ = json.loads(result)
if 'access_token' in dict_:
self.set_password(dict_['access_token'])
return
script_url = gajim.config.get_per('accounts', self.name,
'oauth2_redirect_url')
token_URL = 'https://oauth.live.com/authorize?client_id=' \
'%(client_id)s&scope=wl.messenger%%20wl.offline_access&' \
'response_type=code&redirect_uri=%(script_url)s' % locals()
helpers.launch_browser_mailer('url', token_URL)
self.disconnect(on_purpose=True)
gajim.nec.push_incoming_event(Oauth2CredentialsRequiredEvent(None,
conn=self))
return
if self.password:
self.set_password(self.password)
return
@ -2508,9 +2558,11 @@ class Connection(CommonConnection, ConnectionHandlers):
if result.getID() == id_:
on_remove_success(True)
return
self.dispatch('ERROR', (_('Unregister failed'),
_('Unregistration with server %(server)s failed: '
'%(error)s') % {'server': hostname,
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self, level='error',
pri_txt=_('Unregister failed'),
sec_txt=_('Unregistration with server %(server)s '
'failed: %(error)s') % {'server': hostname,
'error': result.getErrorMsg()}))
on_remove_success(False)
con.RegisterHandler('iq', _on_answer, 'result', system=True)
@ -2536,7 +2588,7 @@ class Connection(CommonConnection, ConnectionHandlers):
c.setTagData('reason', reason)
self.connection.send(message)
def request_voice(self, room, nick):
def request_voice(self, room):
"""
Request voice in a moderated room
"""
@ -2576,7 +2628,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def send_search_form(self, jid, form, is_form):
iq = common.xmpp.Iq(typ = 'set', to = jid, queryNS = \
common.xmpp.NS_SEARCH)
item = iq.getTag('query')
item = iq.setQuery()
if is_form:
item.addChild(node=form)
else:

View File

@ -62,14 +62,7 @@ from common import ged
from common import nec
from common.nec import NetworkEvent
if gajim.HAVE_FARSIGHT:
from common.jingle import ConnectionJingle
else:
class ConnectionJingle():
def __init__(self):
pass
def _JingleCB(self, con, stanza):
pass
from common.jingle import ConnectionJingle
from common import dbus_support
if dbus_support.supported:
@ -133,8 +126,9 @@ class ConnectionDisco:
def _agent_registered_cb(self, con, resp, agent):
if resp.getType() == 'result':
self.dispatch('INFORMATION', (_('Registration succeeded'),
_('Registration with agent %s succeeded') % agent))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='info', pri_txt=_('Registration succeeded'), sec_txt=_(
'Registration with agent %s succeeded') % agent))
self.request_subscription(agent, auto_auth=True)
self.agent_registrations[agent]['roster_push'] = True
if self.agent_registrations[agent]['sub_received']:
@ -142,9 +136,10 @@ class ConnectionDisco:
p = self.add_sha(p)
self.connection.send(p)
if resp.getType() == 'error':
self.dispatch('ERROR', (_('Registration failed'), _('Registration '
'with agent %(agent)s failed with error %(error)s: '
'%(error_msg)s') % {'agent': agent, 'error': resp.getError(),
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Registration failed'), sec_txt=_(
'Registration with agent %(agent)s failed with error %(error)s:'
' %(error_msg)s') % {'agent': agent, 'error': resp.getError(),
'error_msg': resp.getErrorMsg()}))
def register_agent(self, agent, info, is_form=False):
@ -152,7 +147,7 @@ class ConnectionDisco:
return
if is_form:
iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=agent)
query = iq.getTag('query')
query = iq.setQuery()
info.setAttr('type', 'submit')
query.addChild(node=info)
self.connection.SendAndCallForResponse(iq,
@ -237,8 +232,7 @@ class ConnectionDisco:
log.debug('DiscoverInfoGetCB')
if not self.connection or self.connected < 2:
return
q = iq_obj.getTag('query')
node = q.getAttr('node')
node = iq_obj.getQuerynode()
if self.commandInfoQuery(con, iq_obj):
raise common.xmpp.NodeProcessed
@ -249,7 +243,7 @@ class ConnectionDisco:
raise common.xmpp.NodeProcessed
iq = iq_obj.buildReply('result')
q = iq.getTag('query')
q = iq.setQuery()
if node:
q.setAttr('node', node)
q.addChild('identity', attrs=gajim.gajim_identity)
@ -340,7 +334,8 @@ class ConnectionVcard:
fil.write(str(card))
fil.close()
except IOError, e:
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Disk Write Error'), sec_txt=str(e)))
def get_cached_vcard(self, fjid, is_fake_jid=False):
"""
@ -394,7 +389,7 @@ class ConnectionVcard:
iq = common.xmpp.Iq(typ='get')
if jid:
iq.setTo(jid)
iq.setTag(common.xmpp.NS_VCARD + ' vCard')
iq.setQuery('vCard').setNamespace(common.xmpp.NS_VCARD)
id_ = self.connection.getAnID()
iq.setID(id_)
@ -536,6 +531,8 @@ class ConnectionVcard:
return
if iq_obj.getType() == 'result':
query = iq_obj.getTag('query')
if not query:
return
delimiter = query.getTagData('roster')
if delimiter:
self.nested_group_delimiter = delimiter
@ -554,6 +551,15 @@ class ConnectionVcard:
roster = self.connection.getRoster(force=True)
roster.setRaw(roster_data)
self._getRoster()
elif iq_obj.getType() == 'error':
self.roster_supported = False
self.discoverItems(gajim.config.get_per('accounts', self.name,
'hostname'), id_prefix='Gajim_')
if gajim.config.get_per('accounts', self.name,
'use_ft_proxies'):
self.discover_ft_proxies()
gajim.nec.push_incoming_event(RosterReceivedEvent(None,
conn=self))
elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
if iq_obj.getType() != 'error':
self.privacy_rules_supported = True
@ -565,9 +571,10 @@ class ConnectionVcard:
self.disconnect(on_purpose=True)
gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
show='offline'))
self.dispatch('ERROR', (_('Invisibility not supported'),
_('Account %s doesn\'t support invisibility.') % \
self.name))
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self, level='error', pri_txt=_('Invisibility not '
'supported'), sec_txt=_('Account %s doesn\'t support '
'invisibility.') % self.name))
return
# Ask metacontacts before roster
self.get_metacontacts()
@ -646,9 +653,8 @@ class ConnectionVcard:
with_ = element.getAttr('with')
start_ = element.getAttr('start')
self.request_collection_page(with_, start_)
elif element.getName() == 'removed':
#elif element.getName() == 'removed':
# do nothing
pass
del self.awaiting_answers[id_]
@ -765,6 +771,8 @@ class ConnectionHandlersBase:
self._nec_iq_error_received)
gajim.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
gajim.ged.register_event_handler('gc-presence-received', ged.CORE,
self._nec_gc_presence_received)
gajim.ged.register_event_handler('message-received', ged.CORE,
self._nec_message_received)
gajim.ged.register_event_handler('decrypted-message-received', ged.CORE,
@ -775,6 +783,8 @@ class ConnectionHandlersBase:
self._nec_iq_error_received)
gajim.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
gajim.ged.remove_event_handler('gc-presence-received', ged.CORE,
self._nec_gc_presence_received)
gajim.ged.remove_event_handler('message-received', ged.CORE,
self._nec_message_received)
gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE,
@ -902,8 +912,7 @@ class ConnectionHandlersBase:
# reset chatstate if needed:
# (when contact signs out or has errors)
if obj.show in ('offline', 'error'):
obj.contact.our_chatstate = obj.contact.chatstate = \
obj.contact.composing_xep = None
obj.contact.our_chatstate = obj.contact.chatstate = None
# TODO: This causes problems when another
# resource signs off!
@ -917,27 +926,15 @@ class ConnectionHandlersBase:
# there won't be any sessions here if the contact terminated
# their sessions before going offline (which we do)
for sess in self.get_sessions(jid):
if obj.fjid != str(sess.jid):
sess_fjid = sess.jid.getStripped()
if sess.resource:
sess_fjid += '/' + sess.resource
if obj.fjid != sess_fjid:
continue
if sess.control:
sess.control.no_autonegotiation = False
if sess.enable_encryption:
sess.terminate_e2e()
self.delete_session(jid, sess.thread_id)
if obj.ptype == 'unavailable':
for jid in (obj.jid, obj.fjid):
if jid not in self.sessions:
continue
# automatically terminate sessions that they haven't sent a
# thread ID in, only if other part support thread ID
for sess in self.sessions[jid].values():
if not sess.received_thread_id:
contact = gajim.contacts.get_contact(self.name, jid)
if contact and (contact.supports(xmpp.NS_SSN) or \
contact.supports(xmpp.NS_ESESSION)):
sess.terminate()
del self.sessions[jid][sess.thread_id]
if gajim.config.get('log_contact_status_changes') and \
gajim.config.should_log(self.name, obj.jid):
@ -953,6 +950,15 @@ class ConnectionHandlersBase:
self.dispatch('DB_ERROR', (pritext, sectext))
our_jid = gajim.get_jid_from_account(self.name)
def _nec_gc_presence_received(self, obj):
if obj.conn.name != self.name:
return
for sess in self.get_sessions(obj.fjid):
if obj.fjid != sess.jid:
continue
if sess.enable_encryption:
sess.terminate_e2e()
def _nec_message_received(self, obj):
if obj.conn.name != self.name:
return
@ -972,6 +978,8 @@ class ConnectionHandlersBase:
if keyID:
def decrypt_thread(encmsg, keyID, obj):
decmsg = self.gpg.decrypt(encmsg, keyID)
decmsg = self.connection.Dispatcher.replace_non_character(
decmsg)
# \x00 chars are not allowed in C (so in GTK)
obj.msgtxt = helpers.decode_string(decmsg.replace('\x00',
''))
@ -1406,17 +1414,21 @@ ConnectionJingle, ConnectionIBBytestream):
log.debug('SecLabelCB')
query = iq_obj.getTag('catalog')
to = query.getAttr('to')
items = query.getTags('securitylabel')
items = query.getTags('item')
labels = {}
ll = []
default = None
for item in items:
label = item.getTag('displaymarking').getData()
labels[label] = item
label = item.getAttr('selector')
labels[label] = item.getTag('securitylabel')
ll.append(label)
if item.getAttr('default') == 'true':
default = label
if to not in self.seclabel_catalogues:
self.seclabel_catalogues[to] = [[], None, None]
self.seclabel_catalogues[to] = [[], None, None, None]
self.seclabel_catalogues[to][1] = labels
self.seclabel_catalogues[to][2] = ll
self.seclabel_catalogues[to][3] = default
for callback in self.seclabel_catalogues[to][0]:
callback()
self.seclabel_catalogues[to][0] = []
@ -1458,13 +1470,18 @@ ConnectionJingle, ConnectionIBBytestream):
def _nec_version_request_received(self, obj):
if obj.conn.name != self.name:
return
iq_obj = obj.stanza.buildReply('result')
qp = iq_obj.getTag('query')
qp.setTagData('name', 'Gajim')
qp.setTagData('version', gajim.version)
send_os = gajim.config.get_per('accounts', self.name, 'send_os_info')
if send_os:
iq_obj = obj.stanza.buildReply('result')
qp = iq_obj.getQuery()
qp.setTagData('name', 'Gajim')
qp.setTagData('version', gajim.version)
qp.setTagData('os', helpers.get_os_info())
else:
iq_obj = obj.stanza.buildReply('error')
err = common.xmpp.ErrorNode(name=common.xmpp.NS_STANZAS + \
' service-unavailable')
iq_obj.addChild(node=err)
self.connection.send(iq_obj)
def _LastCB(self, con, iq_obj):
@ -1482,7 +1499,7 @@ ConnectionJingle, ConnectionIBBytestream):
if HAS_IDLE and gajim.config.get_per('accounts', self.name,
'send_idle_time'):
iq_obj = obj.stanza.buildReply('result')
qp = iq_obj.getTag('query')
qp = iq_obj.setQuery()
qp.attrs['seconds'] = int(self.sleeper.getIdleSec())
else:
iq_obj = obj.stanza.buildReply('error')
@ -1509,7 +1526,7 @@ ConnectionJingle, ConnectionIBBytestream):
return
if gajim.config.get_per('accounts', self.name, 'send_time_info'):
iq_obj = obj.stanza.buildReply('result')
qp = iq_obj.getTag('query')
qp = iq_obj.setQuery()
qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime()))
qp.setTagData('tz', helpers.decode_string(tzname[daylight]))
qp.setTagData('display', helpers.decode_string(strftime('%c',
@ -1662,55 +1679,6 @@ ConnectionJingle, ConnectionIBBytestream):
self.connection.SendAndCallForResponse(iq, self._on_bob_received,
{'cid': cid})
# process and dispatch a groupchat message
def dispatch_gc_message(self, msg, frm, msgtxt, jid, tim):
has_timestamp = bool(msg.timestamp)
statusCode = msg.getStatusCode()
displaymarking = None
seclabel = msg.getTag('securitylabel')
if seclabel and seclabel.getNamespace() == common.xmpp.NS_SECLABEL:
# Ignore message from room in which we are not
displaymarking = seclabel.getTag('displaymarking')
if jid not in self.last_history_time:
return
captcha = msg.getTag('captcha', namespace=common.xmpp.NS_CAPTCHA)
if captcha:
captcha = captcha.getTag('x', namespace=common.xmpp.NS_DATA)
found = helpers.replace_dataform_media(captcha, msg)
if not found:
self.get_bob_data(uri_data, frm, self.dispatch_gc_message,
[msg, frm, msgtxt, jid, tim], 0)
return
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp,
msg.getXHTML(), statusCode, displaymarking, captcha))
tim_int = int(float(mktime(tim)))
if gajim.config.should_log(self.name, jid) and not \
tim_int <= self.last_history_time[jid] and msgtxt and frm.find('/') >= 0:
# if frm.find('/') < 0, it means message comes from room itself
# usually it hold description and can be send at each connection
# so don't store it in logs
try:
gajim.logger.write('gc_msg', frm, msgtxt, tim=tim)
# store in memory time of last message logged.
# this will also be saved in rooms_last_message_time table
# when we quit this muc
self.last_history_time[jid] = mktime(tim)
except exceptions.PysqliteOperationalError, e:
self.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
except exceptions.DatabaseMalformed:
pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
'or remove it (all history will be lost).') % \
common.logger.LOG_DB_PATH
self.dispatch('DB_ERROR', (pritext, sectext))
def _presenceCB(self, con, prs):
"""
Called when we receive a presence
@ -1877,10 +1845,13 @@ ConnectionJingle, ConnectionIBBytestream):
'file_transfer_proxies')
our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + \
'/' + self.server_resource)
testit = gajim.config.get_per('accounts', self.name,
'test_ft_proxies_on_startup')
if cfg_proxies:
proxies = [e.strip() for e in cfg_proxies.split(',')]
for proxy in proxies:
gajim.proxy65_manager.resolve(proxy, self.connection, our_jid)
gajim.proxy65_manager.resolve(proxy, self.connection, our_jid,
testit=testit)
def _on_roster_set(self, roster):
gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,

View File

@ -20,6 +20,7 @@
import datetime
import sys
import os
from time import (localtime, time as time_time)
from calendar import timegm
import hmac
@ -34,6 +35,7 @@ from common import exceptions
from common.zeroconf import zeroconf
from common.logger import LOG_DB_PATH
from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from common.xmpp.protocol import NS_CHATSTATES
from common.jingle_transport import JingleTransportSocks5
import gtkgui_helpers
@ -41,6 +43,28 @@ import gtkgui_helpers
import logging
log = logging.getLogger('gajim.c.connection_handlers_events')
CONDITION_TO_CODE = {
'realjid-public': 100,
'affiliation-changed': 101,
'unavailable-shown': 102,
'unavailable-not-shown': 103,
'configuration-changed': 104,
'self-presence': 110,
'logging-enabled': 170,
'logging-disabled': 171,
'non-anonymous': 172,
'semi-anonymous': 173,
'fully-anonymous': 174,
'room-created': 201,
'nick-assigned': 210,
'banned': 301,
'new-nick': 303,
'kicked': 307,
'removed-affiliation': 321,
'removed-membership': 322,
'removed-shutdown': 332,
}
class HelperEvent:
def get_jid_resource(self, check_fake_jid=False):
if check_fake_jid and hasattr(self, 'id_') and \
@ -74,28 +98,16 @@ class HelperEvent:
Extract chatstate from a <message/> stanza
Requires self.stanza and self.msgtxt
"""
self.composing_xep = None
self.chatstate = None
# chatstates - look for chatstate tags in a message if not delayed
delayed = self.stanza.getTag('x', namespace=xmpp.NS_DELAY) is not None
if not delayed:
self.composing_xep = False
children = self.stanza.getChildren()
for child in children:
if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
if child.getNamespace() == NS_CHATSTATES:
self.chatstate = child.getName()
self.composing_xep = 'XEP-0085'
break
# No XEP-0085 support, fallback to XEP-0022
if not self.chatstate:
chatstate_child = self.stanza.getTag('x',
namespace=xmpp.NS_EVENT)
if chatstate_child:
self.chatstate = 'active'
self.composing_xep = 'XEP-0022'
if not self.msgtxt and chatstate_child.getTag('composing'):
self.chatstate = 'composing'
class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
name = 'http-auth-received'
@ -711,6 +723,8 @@ PresenceHelperEvent):
self.need_add_in_roster = False
self.need_redraw = False
self.popup = False # Do we want to open chat window ?
if not self.conn or self.conn.connected < 2:
log.debug('account is no more connected')
return
@ -719,6 +733,7 @@ PresenceHelperEvent):
try:
self.get_jid_resource()
except Exception:
log.warn('Invalid JID: %s, ignoring it' % self.stanza.getFrom())
return
jid_list = gajim.contacts.get_jid_list(self.conn.name)
self.timestamp = None
@ -825,6 +840,7 @@ class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
self.transport_auto_auth = False
self.errcode = None
self.errmsg = ''
self.popup = False # Do we want to open chat window ?
return True
class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
@ -905,7 +921,15 @@ class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.status_code = ['destroyed']
else:
self.reason = self.stanza.getReason()
self.status_code = self.stanza.getStatusCode()
conditions = self.stanza.getStatusConditions()
if conditions:
self.status_code = []
for condition in conditions:
if condition in CONDITION_TO_CODE:
self.status_code.append(CONDITION_TO_CODE[condition])
else:
self.status_code = self.stanza.getStatusCode()
self.role = self.stanza.getRole()
self.affiliation = self.stanza.getAffiliation()
self.real_jid = self.stanza.getJid()
@ -954,6 +978,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.conn = self.base_event.conn
self.stanza = self.base_event.stanza
self.get_id()
self.forwarded = False
self.sent = False
account = self.conn.name
@ -972,8 +998,9 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
try:
self.get_jid_resource()
except helpers.InvalidFormat:
self.conn.dispatch('ERROR', (_('Invalid Jabber ID'),
_('A message from a non-valid JID arrived, it has been '
gajim.nec.push_incoming_event(InformationEvent(None, conn=self.conn,
level='error', pri_txt=_('Invalid Jabber ID'),
sec_txt=_('A message from a non-valid JID arrived, it has been '
'ignored.')))
return
@ -990,6 +1017,43 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
return
self.jid = gajim.get_jid_without_resource(self.fjid)
forward_tag = self.stanza.getTag('forwarded', namespace=xmpp.NS_FORWARD)
# Be sure it comes from one of our resource, else ignore forward element
if forward_tag and self.jid == gajim.get_jid_from_account(account):
received_tag = forward_tag.getTag('received',
namespace=xmpp.NS_CARBONS)
sent_tag = forward_tag.getTag('sent', namespace=xmpp.NS_CARBONS)
if received_tag:
msg = forward_tag.getTag('message')
self.stanza = xmpp.Message(node=msg)
try:
self.get_jid_resource()
except helpers.InvalidFormat:
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.conn, level='error',
pri_txt=_('Invalid Jabber ID'),
sec_txt=_('A message from a non-valid JID arrived, it '
'has been ignored.')))
return
self.forwarded = True
elif sent_tag:
msg = forward_tag.getTag('message')
self.stanza = xmpp.Message(node=msg)
to = self.stanza.getTo()
self.stanza.setTo(self.stanza.getFrom())
self.stanza.setFrom(to)
try:
self.get_jid_resource()
except helpers.InvalidFormat:
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.conn, level='error',
pri_txt=_('Invalid Jabber ID'),
sec_txt=_('A message from a non-valid JID arrived, it '
'has been ignored.')))
return
self.forwarded = True
self.sent = True
self.enc_tag = self.stanza.getTag('x', namespace=xmpp.NS_ENCRYPTED)
self.invite_tag = None
@ -1009,6 +1073,12 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.get_gc_control()
if self.gc_control and self.jid == self.fjid:
if self.mtype == 'error':
self.msgtxt = _('error while sending %(message)s ( %(error)s )'\
) % {'message': self.msgtxt,
'error': self.stanza.getErrorMsg()}
if self.stanza.getTag('html'):
self.stanza.delChild('html')
# message from a gc without a resource
self.mtype = 'groupchat'
@ -1023,7 +1093,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.session.last_receive = time_time()
# check if the message is a XEP-0020 feature negotiation request
if self.stanza.getTag('feature', namespace=xmpp.NS_FEATURE):
if not self.forwarded and self.stanza.getTag('feature',
namespace=xmpp.NS_FEATURE):
if gajim.HAVE_PYCRYPTO:
feature = self.stanza.getTag(name='feature',
namespace=xmpp.NS_FEATURE)
@ -1040,7 +1111,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.conn.connection.send(reply)
return
if self.stanza.getTag('init', namespace=xmpp.NS_ESESSION_INIT):
if not self.forwarded and self.stanza.getTag('init',
namespace=xmpp.NS_ESESSION_INIT):
init = self.stanza.getTag(name='init',
namespace=xmpp.NS_ESESSION_INIT)
form = xmpp.DataForm(node=init.getTag('x'))
@ -1055,6 +1127,9 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
xep_200_encrypted = self.stanza.getTag('c',
namespace=xmpp.NS_STANZA_CRYPTO)
if xep_200_encrypted:
if self.forwarded:
# Ignore E2E forwarded encrypted messages
return False
self.encrypted = 'xep200'
return True
@ -1126,6 +1201,10 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.session = self.msg_obj.session
self.timestamp = self.msg_obj.timestamp
self.encrypted = self.msg_obj.encrypted
self.forwarded = self.msg_obj.forwarded
self.sent = self.msg_obj.sent
self.popup = False
self.msg_id = None # id in log database
self.receipt_request_tag = self.stanza.getTag('request',
namespace=xmpp.NS_RECEIPTS)
@ -1162,7 +1241,6 @@ class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
self.jid = self.msg_obj.jid
self.fjid = self.msg_obj.fjid
self.resource = self.msg_obj.resource
self.composing_xep = self.msg_obj.composing_xep
self.chatstate = self.msg_obj.chatstate
return True
@ -1199,7 +1277,14 @@ class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
conn=self.conn, msg_event=self))
return
self.status_code = self.stanza.getStatusCode()
conditions = self.stanza.getStatusConditions()
if conditions:
self.status_code = []
for condition in conditions:
if condition in CONDITION_TO_CODE:
self.status_code.append(CONDITION_TO_CODE[condition])
else:
self.status_code = self.stanza.getStatusCode()
if not self.stanza.getTag('body'): # no <body>
# It could be a config change. See
@ -1699,6 +1784,10 @@ class PasswordRequiredEvent(nec.NetworkIncomingEvent):
name = 'password-required'
base_network_events = []
class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
name = 'oauth2-credentials-required'
base_network_events = []
class FailedDecryptEvent(nec.NetworkIncomingEvent):
name = 'failed-decrypt'
base_network_events = []
@ -1934,13 +2023,16 @@ class GatewayPromptReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
class NotificationEvent(nec.NetworkIncomingEvent):
name = 'notification'
base_network_events = ['decrypted-message-received', 'gc-message-received']
base_network_events = ['decrypted-message-received', 'gc-message-received',
'presence-received']
def detect_type(self):
if self.base_event.name == 'decrypted-message-received':
self.notif_type = 'msg'
if self.base_event.name == 'gc-message-received':
self.notif_type = 'gc-msg'
if self.base_event.name == 'presence-received':
self.notif_type = 'pres'
def get_focused(self):
self.control_focused = False
@ -1985,11 +2077,13 @@ class NotificationEvent(nec.NetworkIncomingEvent):
# We don't want message preview, do_preview = False
self.popup_text = ''
if msg_obj.mtype == 'normal': # single message
self.popup_msg_type = 'normal'
self.popup_event_type = _('New Single Message')
self.popup_image = 'gajim-single_msg_recv'
self.popup_title = _('New Single Message from %(nickname)s') % \
{'nickname': nick}
elif msg_obj.mtype == 'pm':
self.popup_msg_type = 'pm'
self.popup_event_type = _('New Private Message')
self.popup_image = 'gajim-priv_msg_recv'
self.popup_title = _('New Private Message from group chat %s') % \
@ -2001,6 +2095,7 @@ class NotificationEvent(nec.NetworkIncomingEvent):
self.popup_text = _('Messaged by %(nickname)s') % \
{'nickname': nick}
else: # chat message
self.popup_msg_type = 'chat'
self.popup_event_type = _('New Message')
self.popup_image = 'gajim-chat_msg_recv'
self.popup_title = _('New Message from %(nickname)s') % \
@ -2031,24 +2126,134 @@ class NotificationEvent(nec.NetworkIncomingEvent):
self.do_sound = True
def handle_incoming_gc_msg_event(self, msg_obj):
if not msg_obj.msg_obj.gc_control:
# we got a message from a room we're not in? ignore it
return
sound = msg_obj.msg_obj.gc_control.highlighting_for_message(
msg_obj.msgtxt, msg_obj.timestamp)[1]
self.do_sound = True
if sound == 'received':
self.sound_event = 'muc_message_received'
elif sound == 'highlight':
self.sound_event = 'muc_message_highlight'
if msg_obj.nickname != msg_obj.msg_obj.gc_control.nick:
self.do_sound = True
if sound == 'received':
self.sound_event = 'muc_message_received'
elif sound == 'highlight':
self.sound_event = 'muc_message_highlight'
else:
self.do_sound = False
else:
self.do_sound = False
self.do_popup = False
def handle_incoming_pres_event(self, msg_obj):
pass
def handle_incoming_pres_event(self, pres_obj):
if gajim.jid_is_transport(pres_obj.jid):
return True
account = pres_obj.conn.name
self.jid = pres_obj.jid
resource = pres_obj.resource or ''
# It isn't an agent
for c in pres_obj.contact_list:
if c.resource == resource:
# we look for other connected resources
continue
if c.show not in ('offline', 'error'):
return True
# no other resource is connected, let's look in metacontacts
family = gajim.contacts.get_metacontacts_family(account, self.jid)
for info in family:
acct_ = info['account']
jid_ = info['jid']
c_ = gajim.contacts.get_contact_with_highest_priority(acct_, jid_)
if not c_:
continue
if c_.jid == self.jid:
continue
if c_.show not in ('offline', 'error'):
return True
if pres_obj.old_show < 2 and pres_obj.new_show > 1:
event = 'contact_connected'
show_image = 'online.png'
suffix = '_notif_size_colored'
server = gajim.get_server_from_jid(self.jid)
account_server = account + '/' + server
block_transport = False
if account_server in gajim.block_signed_in_notifications and \
gajim.block_signed_in_notifications[account_server]:
block_transport = True
if helpers.allow_showing_notification(account, 'notify_on_signin') \
and not gajim.block_signed_in_notifications[account] and \
not block_transport:
self.do_popup = True
if gajim.config.get_per('soundevents', 'contact_connected',
'enabled') and not gajim.block_signed_in_notifications[account] and\
not block_transport and helpers.allow_sound_notification(account,
'contact_connected'):
self.sound_event = event
self.do_sound = True
elif pres_obj.old_show > 1 and pres_obj.new_show < 2:
event = 'contact_disconnected'
show_image = 'offline.png'
suffix = '_notif_size_bw'
if helpers.allow_showing_notification(account, 'notify_on_signout'):
self.do_popup = True
if gajim.config.get_per('soundevents', 'contact_disconnected',
'enabled') and helpers.allow_sound_notification(account, event):
self.sound_event = event
self.do_sound = True
# Status change (not connected/disconnected or error (<1))
elif pres_obj.new_show > 1:
event = 'status_change'
# FIXME: we don't always 'online.png', but we first need 48x48 for
# all status
show_image = 'online.png'
suffix = '_notif_size_colored'
else:
return True
transport_name = gajim.get_transport_name_from_jid(self.jid)
img_path = None
if transport_name:
img_path = os.path.join(helpers.get_transport_path(
transport_name), '48x48', show_image)
if not img_path or not os.path.isfile(img_path):
iconset = gajim.config.get('iconset')
img_path = os.path.join(helpers.get_iconset_path(iconset),
'48x48', show_image)
self.popup_image = gtkgui_helpers.get_path_to_generic_or_avatar(
img_path, jid=self.jid, suffix=suffix)
if event == 'status_change':
self.popup_title = _('%(nick)s Changed Status') % \
{'nick': gajim.get_name_from_jid(account, self.jid)}
self.popup_text = _('%(nick)s is now %(status)s') % \
{'nick': gajim.get_name_from_jid(account, self.jid),\
'status': helpers.get_uf_show(pres_obj.show)}
if pres_obj.status:
self.popup_text = self.popup_text + " : " + pres_obj.status
self.popup_event_type = _('Contact Changed Status')
elif event == 'contact_connected':
self.popup_title = _('%(nickname)s Signed In') % \
{'nickname': gajim.get_name_from_jid(account, self.jid)}
self.popup_text = ''
if pres_obj.status:
self.popup_text = pres_obj.status
self.popup_event_type = _('Contact Signed In')
elif event == 'contact_disconnected':
self.popup_title = _('%(nickname)s Signed Out') % \
{'nickname': gajim.get_name_from_jid(account, self.jid)}
self.popup_text = ''
if pres_obj.status:
self.popup_text = pres_obj.status
self.popup_event_type = _('Contact Signed Out')
def generate(self):
# what's needed to compute output
self.conn = self.base_event.conn
self.jid = ''
self.control = None
self.control_focused = False
self.first_unread = False
@ -2082,7 +2287,7 @@ class NotificationEvent(nec.NetworkIncomingEvent):
self.handle_incoming_pres_event(self.base_event)
return True
class MessageOutgoingEvent(nec.NetworkIncomingEvent):
class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
name = 'message-outgoing'
base_network_events = []
@ -2093,7 +2298,6 @@ class MessageOutgoingEvent(nec.NetworkIncomingEvent):
self.subject = ''
self.chatstate = None
self.msg_id = None
self.composing_xep = None
self.resource = None
self.user_nick = None
self.xhtml = None
@ -2107,6 +2311,18 @@ class MessageOutgoingEvent(nec.NetworkIncomingEvent):
self.callback_args = []
self.now = False
self.is_loggable = True
self.control = None
def generate(self):
return True
class ClientCertPassphraseEvent(nec.NetworkIncomingEvent):
name = 'client-cert-passphrase'
base_network_events = []
class InformationEvent(nec.NetworkIncomingEvent):
name = 'information'
base_network_events = []
def init(self):
self.popup = True

View File

@ -46,7 +46,7 @@ class XMPPEntity(object):
class CommonContact(XMPPEntity):
def __init__(self, jid, account, resource, show, status, name,
our_chatstate, composing_xep, chatstate, client_caps=None):
our_chatstate, chatstate, client_caps=None):
XMPPEntity.__init__(self, jid, account, resource)
@ -57,17 +57,8 @@ class CommonContact(XMPPEntity):
self.client_caps = client_caps or caps_cache.NullClientCaps()
# please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
# we keep track of xep85 support with the peer by three extra states:
# None, False and 'ask'
# None if no info about peer
# False if peer does not support xep85
# 'ask' if we sent the first 'active' chatstate and are waiting for reply
# this holds what WE SEND to contact (our current chatstate)
self.our_chatstate = our_chatstate
# tell which XEP we're using for composing state
# None = have to ask, XEP-0022 = use this xep,
# XEP-0085 = use this xep, False = no composing support
self.composing_xep = composing_xep
# this is contact's chatstate
self.chatstate = chatstate
@ -97,12 +88,12 @@ class Contact(CommonContact):
Information concerning a contact
"""
def __init__(self, jid, account, name='', groups=[], show='', status='',
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
our_chatstate=None, chatstate=None, last_status_time=None, msg_id=
None, composing_xep=None, last_activity_time=None):
sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
our_chatstate=None, chatstate=None, last_status_time=None, msg_id=None,
last_activity_time=None):
CommonContact.__init__(self, jid, account, resource, show, status, name,
our_chatstate, composing_xep, chatstate, client_caps=client_caps)
our_chatstate, chatstate, client_caps=client_caps)
self.contact_name = '' # nick choosen by contact
self.groups = [i for i in set(groups)] # filter duplicate values
@ -184,11 +175,11 @@ class GC_Contact(CommonContact):
"""
def __init__(self, room_jid, account, name='', show='', status='', role='',
affiliation='', jid='', resource='', our_chatstate=None,
composing_xep=None, chatstate=None):
affiliation='', jid='', resource='', our_chatstate=None,
chatstate=None):
CommonContact.__init__(self, jid, account, resource, show, status, name,
our_chatstate, composing_xep, chatstate)
our_chatstate, chatstate)
self.room_jid = room_jid
self.role = role
@ -205,8 +196,8 @@ class GC_Contact(CommonContact):
Create a Contact instance from this GC_Contact instance
"""
return Contact(jid=self.get_full_jid(), account=self.account,
name=self.name, groups=[], show=self.show, status=self.status,
sub='none', client_caps=self.client_caps)
name=self.name, groups=[], show=self.show, status=self.status,
sub='none', client_caps=self.client_caps)
class LegacyContactsAPI:
@ -249,15 +240,15 @@ class LegacyContactsAPI:
def create_contact(self, jid, account, name='', groups=[], show='',
status='', sub='', ask='', resource='', priority=0, keyID='',
client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
composing_xep=None, last_activity_time=None):
last_activity_time=None):
# Use Account object if available
account = self._accounts.get(account, account)
return Contact(jid=jid, account=account, name=name, groups=groups,
show=show, status=status, sub=sub, ask=ask, resource=resource,
priority=priority, keyID=keyID, client_caps=client_caps,
our_chatstate=our_chatstate, chatstate=chatstate,
last_status_time=last_status_time, composing_xep=composing_xep,
last_activity_time=last_activity_time)
show=show, status=status, sub=sub, ask=ask, resource=resource,
priority=priority, keyID=keyID, client_caps=client_caps,
our_chatstate=our_chatstate, chatstate=chatstate,
last_status_time=last_status_time,
last_activity_time=last_activity_time)
def create_self_contact(self, jid, account, resource, show, status, priority,
name='', keyID=''):
@ -266,27 +257,28 @@ class LegacyContactsAPI:
account = self._accounts.get(account, account) # Use Account object if available
self_contact = self.create_contact(jid=jid, account=account,
name=nick, groups=['self_contact'], show=show, status=status,
sub='both', ask='none', priority=priority, keyID=keyID,
sub='both', ask='none', priority=priority, keyID=keyID,
resource=resource)
self_contact.pep = conn.pep
return self_contact
def create_not_in_roster_contact(self, jid, account, resource='', name='', keyID=''):
account = self._accounts.get(account, account) # Use Account object if available
def create_not_in_roster_contact(self, jid, account, resource='', name='',
keyID=''):
# Use Account object if available
account = self._accounts.get(account, account)
return self.create_contact(jid=jid, account=account, resource=resource,
name=name, groups=[_('Not in Roster')], show='not in roster',
status='', sub='none', keyID=keyID)
name=name, groups=[_('Not in Roster')], show='not in roster',
status='', sub='none', keyID=keyID)
def copy_contact(self, contact):
return self.create_contact(contact.jid, contact.account,
name=contact.name, groups=contact.groups, show=contact.show,
status=contact.status, sub=contact.sub, ask=contact.ask,
resource=contact.resource, priority=contact.priority,
keyID=contact.keyID, client_caps=contact.client_caps,
our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
last_status_time=contact.last_status_time,
composing_xep=contact.composing_xep,
last_activity_time=contact.last_activity_time)
name=contact.name, groups=contact.groups, show=contact.show,
status=contact.status, sub=contact.sub, ask=contact.ask,
resource=contact.resource, priority=contact.priority,
keyID=contact.keyID, client_caps=contact.client_caps,
our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
last_status_time=contact.last_status_time,
last_activity_time=contact.last_activity_time)
def add_contact(self, account, contact):
if account not in self._accounts:

View File

@ -27,7 +27,7 @@ docdir = '../'
basedir = '../'
localedir = '../po'
version = '0.14.1.1'
version = '0.15-beta3'
import subprocess
try:
node = subprocess.Popen('hg tip --template "{node|short}"', shell=True,

View File

@ -122,8 +122,10 @@ class Events:
def remove_account(self, account):
del self._events[account]
def create_event(self, type_, parameters, time_ = time.time(),
show_in_roster = False, show_in_systray = True):
def create_event(self, type_, parameters, time_=None,
show_in_roster=False, show_in_systray=True):
if not time_:
time_ = time.time()
return Event(type_, time_, parameters, show_in_roster,
show_in_systray)
@ -219,10 +221,12 @@ class Events:
events_list.append(ev)
return events_list
def get_first_event(self, account, jid = None, type_ = None):
def get_first_event(self, account=None, jid=None, type_=None):
"""
Return the first event of type type_ if given
"""
if not account:
return self._get_first_event_with_attribute(self._events)
events_list = self.get_events(account, jid, type_)
# be sure it's bigger than latest event
first_event_time = time.time() + 1

View File

@ -158,8 +158,13 @@ try:
except ImportError:
HAVE_GPG = False
else:
from os import system
if system('gpg -h >/dev/null 2>&1'):
import os
import subprocess
if os.name == 'nt':
gpg_cmd = 'gpg -h >nul 2>&1'
else:
gpg_cmd = 'gpg -h >/dev/null 2>&1'
if subprocess.call(gpg_cmd, shell=True):
HAVE_GPG = False
# Depends on use_latex option. Will be correctly set after we config options are
@ -168,10 +173,34 @@ HAVE_LATEX = False
HAVE_FARSIGHT = True
try:
__import__('farsight')
__import__('gst')
farsight = __import__('farsight')
import gst
import glib
try:
conference = gst.element_factory_make('fsrtpconference')
session = conference.new_session(farsight.MEDIA_TYPE_AUDIO)
del session
del conference
except glib.GError:
HAVE_FARSIGHT = False
except ImportError:
HAVE_FARSIGHT = False
HAVE_UPNP_IGD = True
try:
import gupnp.igd
gupnp_igd = gupnp.igd.Simple()
except ImportError:
HAVE_UPNP_IGD = False
HAVE_PYCURL = True
try:
__import__('pycurl')
except ImportError:
HAVE_PYCURL = False
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE,
xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER,

View File

@ -25,8 +25,10 @@ Global Events Dispatcher module.
:license: GPL
'''
import traceback
import logging
log = logging.getLogger('gajim.common.ged')
log = logging.getLogger('gajim.c.ged')
PRECORE = 10
CORE = 20
@ -85,5 +87,10 @@ class GlobalEventsDispatcher(object):
log.debug('%s\nArgs: %s'%(event_name, str(args)))
if event_name in self.handlers:
for priority, handler in self.handlers[event_name]:
if handler(*args, **kwargs):
return True
try:
if handler(*args, **kwargs):
return True
except Exception, e:
log.error('Error while running an even handler: %s' % \
handler)
traceback.print_exc()

View File

@ -110,7 +110,7 @@ def _write_passphrase(stream, passphrase, encoding):
passphrase = '%s\n' % passphrase
passphrase = passphrase.encode(encoding)
stream.write(passphrase)
logger.debug("Wrote passphrase: %r", passphrase)
logger.debug("Passphrase written")
def _is_sequence(instance):
return isinstance(instance,list) or isinstance(instance,tuple)

View File

@ -35,12 +35,16 @@ import locale
import os
import subprocess
import urllib
import urllib2
import webbrowser
import errno
import select
import base64
import hashlib
import shlex
import caps_cache
import socket
import time
from encodings.punycode import punycode_encode
from string import Template
@ -57,6 +61,9 @@ try:
except Exception:
pass
import logging
log = logging.getLogger('gajim.c.helpers')
special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
class InvalidFormat(Exception):
@ -381,8 +388,18 @@ def is_in_path(command, return_abs_path=False):
pass
return False
def exec_command(command):
subprocess.Popen('%s &' % command, shell=True).wait()
def exec_command(command, use_shell=False):
"""
execute a command. if use_shell is True, we run the command as is it was
typed in a console. So it may be dangerous if you are not sure about what
is executed.
"""
if use_shell:
subprocess.Popen('%s &' % command, shell=True).wait()
else:
args = shlex.split(command.encode('utf-8'))
p = subprocess.Popen(args)
gajim.thread_interface(p.wait)
def build_command(executable, parameter):
# we add to the parameter (can hold path with spaces)
@ -609,6 +626,9 @@ def datetime_tuple(timestamp):
# import gajim only when needed (after decode_string is defined) see #4764
import gajim
if gajim.HAVE_PYCURL:
import pycurl
from cStringIO import StringIO
def convert_bytes(string):
suffix = ''
@ -1374,3 +1394,156 @@ def replace_dataform_media(form, stanza):
uri.setData(data.getData())
found = True
return found
def get_proxy_info(account):
p = gajim.config.get_per('accounts', account, 'proxy')
if not p:
if gajim.config.get_per('accounts', account, 'use_env_http_proxy'):
try:
try:
env_http_proxy = os.environ['HTTP_PROXY']
except Exception:
env_http_proxy = os.environ['http_proxy']
env_http_proxy = env_http_proxy.strip('"')
# Dispose of the http:// prefix
env_http_proxy = env_http_proxy.split('://')[-1]
env_http_proxy = env_http_proxy.split('@')
if len(env_http_proxy) == 2:
login = env_http_proxy[0].split(':')
addr = env_http_proxy[1].split(':')
else:
login = ['', '']
addr = env_http_proxy[0].split(':')
proxy = {'host': addr[0], 'type' : u'http', 'user':login[0]}
if len(addr) == 2:
proxy['port'] = addr[1]
else:
proxy['port'] = 3128
if len(login) == 2:
proxy['pass'] = login[1]
proxy['useauth'] = True
else:
proxy['pass'] = u''
return proxy
except Exception:
proxy = None
p = gajim.config.get('global_proxy')
if p:
proxy = {}
proxyptr = gajim.config.get_per('proxies', p)
for key in proxyptr.keys():
proxy[key] = proxyptr[key][1]
return proxy
def _get_img_direct(attrs):
"""
Download an image. This function should be launched in a separated thread.
"""
mem, alt = '', ''
# Wait maximum 5s for connection
socket.setdefaulttimeout(5)
try:
req = urllib2.Request(attrs['src'])
req.add_header('User-Agent', 'Gajim ' + gajim.version)
f = urllib2.urlopen(req)
except Exception, ex:
log.debug('Error loading image %s ' % attrs['src'] + str(ex))
pixbuf = None
alt = attrs.get('alt', 'Broken image')
else:
# Wait 0.5s between each byte
try:
f.fp._sock.fp._sock.settimeout(0.5)
except Exception:
pass
# Max image size = 2 MB (to try to prevent DoS)
deadline = time.time() + 3
while True:
if time.time() > deadline:
log.debug('Timeout loading image %s ' % attrs['src'])
mem = ''
alt = attrs.get('alt', '')
if alt:
alt += '\n'
alt += _('Timeout loading image')
break
try:
temp = f.read(100)
except socket.timeout, ex:
log.debug('Timeout loading image %s ' % attrs['src'] + str(ex))
alt = attrs.get('alt', '')
if alt:
alt += '\n'
alt += _('Timeout loading image')
break
if temp:
mem += temp
else:
break
if len(mem) > 2*1024*1024:
alt = attrs.get('alt', '')
if alt:
alt += '\n'
alt += _('Image is too big')
break
return (mem, alt)
def _get_img_proxy(attrs, proxy):
"""
Download an image through a proxy. This function should be launched in a
separated thread.
"""
if not gajim.HAVE_PYCURL:
return '', _('PyCURL is not installed')
mem, alt = '', ''
try:
b = StringIO()
c = pycurl.Curl()
c.setopt(pycurl.URL, attrs['src'].encode('utf-8'))
c.setopt(pycurl.FOLLOWLOCATION, 1)
c.setopt(pycurl.CONNECTTIMEOUT, 5)
c.setopt(pycurl.TIMEOUT, 10)
c.setopt(pycurl.MAXFILESIZE, 2000000)
c.setopt(pycurl.WRITEFUNCTION, b.write)
c.setopt(pycurl.USERAGENT, 'Gajim ' + gajim.version)
# set proxy
c.setopt(pycurl.PROXY, proxy['host'].encode('utf-8'))
c.setopt(pycurl.PROXYPORT, proxy['port'])
if proxy['useauth']:
c.setopt(pycurl.PROXYUSERPWD, proxy['user'].encode('utf-8')\
+ ':' + proxy['pass'].encode('utf-8'))
c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
if proxy['type'] == 'http':
c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
elif proxy['type'] == 'socks5':
c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
x = c.perform()
c.close()
t = b.getvalue()
return (t, attrs.get('alt', ''))
except pycurl.error, ex:
alt = attrs.get('alt', '')
if alt:
alt += '\n'
if ex[0] == pycurl.E_FILESIZE_EXCEEDED:
alt += _('Image is too big')
elif ex[0] == pycurl.E_OPERATION_TIMEOUTED:
alt += _('Timeout loading image')
else:
alt += _('Error loading image')
except Exception, ex:
log.debug('Error loading image %s ' % attrs['src'] + str(ex))
pixbuf = None
alt = attrs.get('alt', 'Broken image')
return ('', alt)
def download_image(account, attrs):
proxy = get_proxy_info(account)
if proxy and proxy['type'] in ('http', 'socks5'):
return _get_img_proxy(attrs, proxy)
return _get_img_direct(attrs)

View File

@ -32,11 +32,12 @@ Handles the jingle signalling protocol
import xmpp
import helpers
import gajim
from jingle_session import JingleSession, JingleStates
from jingle_rtp import JingleAudio, JingleVideo
if gajim.HAVE_FARSIGHT:
from jingle_rtp import JingleAudio, JingleVideo
from jingle_ft import JingleFileTransfer
import gajim
import logging
logger = logging.getLogger('gajim.c.jingle')

View File

@ -28,6 +28,7 @@ import gajim
from jingle_transport import JingleTransportICEUDP
from jingle_content import contents, JingleContent, JingleContentSetupException
from connection_handlers_events import InformationEvent
import logging
@ -103,11 +104,12 @@ class JingleRTPContent(JingleContent):
bin = gst.parse_bin_from_description(pipeline, True)
return bin
except GError, error_str:
self.session.connection.dispatch('ERROR',
(_("%s configuration error") % text.capitalize(),
_("Couldn't setup %s. Check your configuration.\n\n"
"Pipeline was:\n%s\n\n"
"Error was:\n%s") % (text, pipeline, error_str)))
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.session.connection, level='error',
pri_txt=_('%s configuration error') % text.capitalize(),
sec_txt=_("Couldn't setup %s. Check your configuration.\n\n"
"Pipeline was:\n%s\n\nError was:\n%s") % (text, pipeline,
error_str)))
raise JingleContentSetupException
def add_remote_candidates(self, candidates):
@ -201,10 +203,11 @@ class JingleRTPContent(JingleContent):
# or raise an error, Jingle way
# or maybe one-sided stream?
if not self.stream_failed_once:
self.session.connection.dispatch('ERROR',
(_("GStreamer error"),
_("Error: %s\nDebug: %s" % (message.structure['gerror'],
message.structure['debug']))))
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.session.connection, level='error',
pri_txt=_('GStreamer error'), sec_txt=_('Error: %s\nDebug: '
'%s' % (message.structure['gerror'],
message.structure['debug']))))
sink_pad = self.p2psession.get_property('sink-pad')

View File

@ -756,9 +756,10 @@ class JingleSession(object):
def __content_remove(self, content, reason=None):
assert self.state != JingleStates.ended
stanza, jingle = self.__make_jingle('content-remove', reason=reason)
self.__append_content(jingle, content)
self.connection.connection.send(stanza)
if self.connection.connection and self.connection.connected > 1:
stanza, jingle = self.__make_jingle('content-remove', reason=reason)
self.__append_content(jingle, content)
self.connection.connection.send(stanza)
# TODO: this will fail if content is not an RTP content
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
conn=self.connection, jingle_session=self, media=content.media,

View File

@ -59,8 +59,6 @@ class JingleTransport(object):
Build a candidate stanza for the given candidate
"""
pass
def make_transport(self, candidates=None):
"""
@ -323,7 +321,10 @@ class JingleTransportIBB(JingleTransport):
transport.setAttr('sid', self.sid)
return transport
import farsight
try:
import farsight
except Exception:
pass
class JingleTransportICEUDP(JingleTransport):
def __init__(self, node):

View File

@ -569,7 +569,7 @@ class Logger:
except exceptions.PysqliteOperationalError, e:
# Error trying to create a new jid_id. This means there is no log
return []
where_sql = self._build_contact_where(account, jid)
where_sql, jid_tuple = self._build_contact_where(account, jid)
now = int(float(time.time()))
timed_out = now - (timeout * 60) # before that they are too old
@ -577,14 +577,13 @@ class Logger:
# 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
try:
self.cur.execute('''
SELECT time, kind, message FROM logs
WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
ORDER BY time DESC LIMIT %d OFFSET %d
''' % (where_sql, constants.KIND_SINGLE_MSG_RECV,
constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT,
constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR,
timed_out, restore_how_many_rows, pending_how_many)
)
SELECT time, kind, message FROM logs
WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
ORDER BY time DESC LIMIT %d OFFSET %d
''' % (where_sql, constants.KIND_SINGLE_MSG_RECV,
constants.KIND_CHAT_MSG_RECV, constants.KIND_SINGLE_MSG_SENT,
constants.KIND_CHAT_MSG_SENT, constants.KIND_ERROR, timed_out,
restore_how_many_rows, pending_how_many), jid_tuple)
results = self.cur.fetchall()
except sqlite.DatabaseError:
@ -614,23 +613,24 @@ class Logger:
except exceptions.PysqliteOperationalError, e:
# Error trying to create a new jid_id. This means there is no log
return []
where_sql = self._build_contact_where(account, jid)
where_sql, jid_tuple = self._build_contact_where(account, jid)
start_of_day = self.get_unix_time_from_date(year, month, day)
seconds_in_a_day = 86400 # 60 * 60 * 24
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE (%s)
AND time BETWEEN %d AND %d
ORDER BY time
''' % (where_sql, start_of_day, last_second_of_day))
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE (%s)
AND time BETWEEN %d AND %d
ORDER BY time
''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
results = self.cur.fetchall()
return results
def get_search_results_for_query(self, jid, query, account):
def get_search_results_for_query(self, jid, query, account, year=False,
month=False, day=False):
"""
Returns contact_name, time, kind, show, message
@ -651,13 +651,25 @@ class Logger:
return results
else: # user just typed something, we search in message column
where_sql = self._build_contact_where(account, jid)
where_sql, jid_tuple = self._build_contact_where(account, jid)
like_sql = '%' + query.replace("'", "''") + '%'
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE (%s) AND message LIKE '%s'
ORDER BY time
''' % (where_sql, like_sql))
if year:
start_of_day = self.get_unix_time_from_date(year, month, day)
seconds_in_a_day = 86400 # 60 * 60 * 24
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE (%s) AND message LIKE '%s'
AND time BETWEEN %d AND %d
ORDER BY time
''' % (where_sql, like_sql, start_of_day, last_second_of_day),
jid_tuple)
else:
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE (%s) AND message LIKE '%s'
ORDER BY time
''' % (where_sql, like_sql), jid_tuple)
results = self.cur.fetchall()
return results
@ -672,7 +684,7 @@ class Logger:
# Error trying to create a new jid_id. This means there is no log
return []
days_with_logs = []
where_sql = self._build_contact_where(account, jid)
where_sql, jid_tuple = self._build_contact_where(account, jid)
# First select all date of month whith logs we want
start_of_month = self.get_unix_time_from_date(year, month, 1)
@ -684,13 +696,13 @@ class Logger:
# and take only one of the same values (distinct)
# Now we have timestamps of time 0:00 of every day with logs
self.cur.execute('''
SELECT DISTINCT time/(86400)*86400 FROM logs
WHERE (%s)
AND time BETWEEN %d AND %d
AND kind NOT IN (%d, %d)
ORDER BY time
''' % (where_sql, start_of_month, last_second_of_month,
constants.KIND_STATUS, constants.KIND_GCSTATUS))
SELECT DISTINCT time/(86400)*86400 FROM logs
WHERE (%s)
AND time BETWEEN %d AND %d
AND kind NOT IN (%d, %d)
ORDER BY time
''' % (where_sql, start_of_month, last_second_of_month,
constants.KIND_STATUS, constants.KIND_GCSTATUS), jid_tuple)
result = self.cur.fetchall()
# convert timestamps to day of month
@ -706,19 +718,21 @@ class Logger:
"""
where_sql = ''
if not is_room:
where_sql = self._build_contact_where(account, jid)
where_sql, jid_tuple = self._build_contact_where(account, jid)
else:
try:
jid_id = self.get_jid_id(jid, 'ROOM')
except exceptions.PysqliteOperationalError, e:
# Error trying to create a new jid_id. This means there is no log
return None
where_sql = 'jid_id = %s' % jid_id
where_sql = 'jid_id = ?'
jid_tuple = (jid_id,)
self.cur.execute('''
SELECT MAX(time) FROM logs
WHERE (%s)
AND kind NOT IN (%d, %d)
''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS))
SELECT MAX(time) FROM logs
WHERE (%s)
AND kind NOT IN (%d, %d)
''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS),
jid_tuple)
results = self.cur.fetchone()
if results is not None:
@ -766,6 +780,7 @@ class Logger:
Build the where clause for a jid, including metacontacts jid(s) if any
"""
where_sql = ''
jid_tuple = ()
# will return empty list if jid is not associated with
# any metacontacts
family = gajim.contacts.get_metacontacts_family(account, jid)
@ -775,13 +790,15 @@ class Logger:
jid_id = self.get_jid_id(user['jid'])
except exceptions.PysqliteOperationalError, e:
continue
where_sql += 'jid_id = %s' % jid_id
where_sql += 'jid_id = ?'
jid_tuple += (jid_id,)
if user != family[-1]:
where_sql += ' OR '
else: # if jid was not associated with metacontacts
jid_id = self.get_jid_id(jid)
where_sql = 'jid_id = %s' % jid_id
return where_sql
where_sql = 'jid_id = ?'
jid_tuple += (jid_id,)
return where_sql, jid_tuple
def save_transport_type(self, jid, type_):
"""
@ -1106,9 +1123,10 @@ class Logger:
self.write(type_, with_, message=msg, tim=tim)
def _nec_gc_message_received(self, obj):
tim_int = int(float(time.mktime(obj.timestamp)))
tim_f = float(time.mktime(obj.timestamp))
tim_int = int(tim_f)
if gajim.config.should_log(obj.conn.name, obj.jid) and not \
tim_int <= obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
tim_int < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
obj.nick:
# if not obj.nick, it means message comes from room itself
# usually it hold description and can be send at each connection
@ -1118,14 +1136,14 @@ class Logger:
# store in memory time of last message logged.
# this will also be saved in rooms_last_message_time table
# when we quit this muc
obj.conn.last_history_time[obj.jid] = time.mktime(obj.timestamp)
obj.conn.last_history_time[obj.jid] = tim_f
except exceptions.PysqliteOperationalError, e:
self.conn.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
obj.conn.dispatch('DB_ERROR', (_('Disk Write Error'), str(e)))
except exceptions.DatabaseMalformed:
pritext = _('Database Error')
sectext = _('The database file (%s) cannot be read. Try to '
'repair it (see http://trac.gajim.org/wiki/DatabaseBackup) '
'or remove it (all history will be lost).') % \
LOG_DB_PATH
self.conn.dispatch('DB_ERROR', (pritext, sectext))
obj.conn.dispatch('DB_ERROR', (pritext, sectext))

View File

@ -224,6 +224,8 @@ class OptionsParser:
self.update_config_to_013901()
if old < [0, 14, 0, 1] and new >= [0, 14, 0, 1]:
self.update_config_to_01401()
if old < [0, 14, 90, 0] and new >= [0, 14, 90, 0]:
self.update_config_to_014900()
gajim.logger.init_vars()
gajim.logger.attach_cache_database()
@ -899,8 +901,15 @@ class OptionsParser:
def update_config_to_01401(self):
if 'autodetect_browser_mailer' not in self.old_values or 'openwith' \
not in self.old_values or \
(self.old_values['autodetect_browser_mailer'] == 'False' and \
(self.old_values['autodetect_browser_mailer'] == False and \
self.old_values['openwith'] != 'custom'):
gajim.config.set('autodetect_browser_mailer', True)
gajim.config.set('openwith', gajim.config.DEFAULT_OPENWITH)
gajim.config.set('version', '0.14.0.1')
def update_config_to_014900(self):
if 'use_stun_server' in self.old_values and self.old_values[
'use_stun_server'] and not self.old_values['stun_server']:
gajim.config.set('use_stun_server', False)
if os.name == 'nt':
gajim.config.set('autodetect_browser_mailer', True)

View File

@ -381,8 +381,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
self._add_addiditional_streamhosts_to_query(query, file_props)
self._add_local_ips_as_streamhosts_to_query(query, file_props)
self._add_proxy_streamhosts_to_query(query, file_props)
self.connection.send(iq)
self._add_upnp_igd_as_streamhost_to_query(query, file_props, iq)
# Upnp-igd is ascynchronous, so it will send the iq itself
def _add_streamhosts_to_query(self, query, sender, port, hosts):
for host in hosts:
@ -393,6 +393,8 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
streamhost.setAttr('jid', sender)
def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
if not gajim.config.get_per('accounts', self.name, 'ft_send_local_ips'):
return
try:
my_ips = [self.peerhost[0]] # The ip we're connected to server with
# all IPs from local DNS
@ -404,8 +406,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
port = gajim.config.get('file_transfers_port')
self._add_streamhosts_to_query(query, sender, port, my_ips)
except socket.gaierror:
self.dispatch('ERROR', (_('Wrong host'),
_('Invalid local address? :-O')))
from common.connection_handlers_events import InformationEvent
gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
level='error', pri_txt=_('Wrong host'),
sec_txt=_('Invalid local address? :-O')))
def _add_addiditional_streamhosts_to_query(self, query, file_props):
sender = file_props['sender']
@ -418,6 +422,84 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
additional_hosts = []
self._add_streamhosts_to_query(query, sender, port, additional_hosts)
def _add_upnp_igd_as_streamhost_to_query(self, query, file_props, iq):
if not gajim.HAVE_UPNP_IGD:
self.connection.send(iq)
return
def ip_is_local(ip):
if '.' not in ip:
# it's an IPv6
return True
ip_s = ip.split('.')
ip_l = long(ip_s[0])<<24 | long(ip_s[1])<<16 | long(ip_s[2])<<8 | \
long(ip_s[3])
# 10/8
if ip_l & (255<<24) == 10<<24:
return True
# 172.16/12
if ip_l & (255<<24 | 240<<16) == (172<<24 | 16<<16):
return True
# 192.168
if ip_l & (255<<24 | 255<<16) == (192<<24 | 168<<16):
return True
return False
my_ip = self.peerhost[0]
if not ip_is_local(my_ip):
self.connection.send(iq)
return
self.no_gupnp_reply_id = 0
def cleanup_gupnp():
if self.no_gupnp_reply_id:
gobject.source_remove(self.no_gupnp_reply_id)
self.no_gupnp_reply_id = 0
gajim.gupnp_igd.disconnect(self.ok_id)
gajim.gupnp_igd.disconnect(self.fail_id)
def ok(s, proto, ext_ip, re, ext_port, local_ip, local_port, desc):
log.debug('Got GUPnP-IGD answer: external: %s:%s, internal: %s:%s',
ext_ip, ext_port, local_ip, local_port)
if local_port != gajim.config.get('file_transfers_port'):
sender = file_props['sender']
receiver = file_props['receiver']
sha_str = helpers.get_auth_sha(file_props['sid'], sender,
receiver)
listener = gajim.socks5queue.start_listener(local_port, sha_str,
self._result_socks5_sid, file_props['sid'])
if listener:
self._add_streamhosts_to_query(query, sender, ext_port,
[ext_ip])
self.connection.send(iq)
cleanup_gupnp()
def fail(s, error, proto, ext_ip, local_ip, local_port, desc):
log.debug('Got GUPnP-IGD error : %s', str(error))
self.connection.send(iq)
cleanup_gupnp()
def no_upnp_reply():
log.debug('Got not GUPnP-IGD answer')
# stop trying to use it
gajim.HAVE_UPNP_IGD = False
self.no_gupnp_reply_id = 0
self.connection.send(iq)
cleanup_gupnp()
return False
self.ok_id = gajim.gupnp_igd.connect('mapped-external-port', ok)
self.fail_id = gajim.gupnp_igd.connect('error-mapping-port', fail)
port = gajim.config.get('file_transfers_port')
self.no_gupnp_reply_id = gobject.timeout_add_seconds(10, no_upnp_reply)
gajim.gupnp_igd.add_port('TCP', 0, my_ip, port, 3600,
'Gajim file transfer')
def _add_proxy_streamhosts_to_query(self, query, file_props):
proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
if proxyhosts:
@ -554,6 +636,12 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
}
for attr in item.getAttrs():
host_dict[attr] = item.getAttr(attr)
if 'host' not in host_dict:
continue
if 'jid' not in host_dict:
continue
if 'port' not in host_dict:
continue
streamhosts.append(host_dict)
if file_props is None:
if sid in self.files_props:
@ -571,6 +659,10 @@ class ConnectionSocks5Bytestream(ConnectionBytestream):
self.send_success_connect_reply, None)
raise xmpp.NodeProcessed
if file_props is None:
log.warn('Gajim got streamhosts for unknown transfer. Ignoring it.')
raise xmpp.NodeProcessed
file_props['streamhosts'] = streamhosts
if file_props['type'] == 'r':
gajim.socks5queue.connect_to_hosts(self.name, sid,
@ -675,11 +767,6 @@ class ConnectionIBBytestream(ConnectionBytestream):
def __init__(self):
ConnectionBytestream.__init__(self)
self._streams = {}
self._ampnode = xmpp.Node(xmpp.NS_AMP + ' amp', payload=[xmpp.Node(
'rule', {'condition': 'deliver-at', 'value': 'stored',
'action': 'error'}), xmpp.Node('rule',
{'condition': 'match-resource', 'value': 'exact',
'action':'error'})])
self.last_sent_ibb_id = None
def IBBIqHandler(self, conn, stanza):
@ -794,8 +881,9 @@ class ConnectionIBBytestream(ConnectionBytestream):
file_props['started'] = True
if file_props['seq'] == 65536:
file_props['seq'] = 0
self.last_sent_ibb_id = self.connection.send(xmpp.Protocol('iq',
file_props['direction'][1:], 'set', payload=[datanode]))
self.last_sent_ibb_id = self.connection.send(xmpp.Protocol(
name='iq', to=file_props['direction'][1:], typ='set',
payload=[datanode]))
current_time = time.time()
file_props['elapsed-time'] += current_time - file_props[
'last-time']

View File

@ -55,15 +55,17 @@ class Proxy65Manager:
# dict {account: proxy} default proxy for account
self.default_proxies = {}
def resolve(self, proxy, connection, sender_jid, default=None):
def resolve(self, proxy, connection, sender_jid, default=None,
testit=True):
"""
Start
if testit=False, Gajim won't try to resolve it
"""
if proxy in self.proxies:
resolver = self.proxies[proxy]
else:
# proxy is being ressolved for the first time
resolver = ProxyResolver(proxy, sender_jid)
resolver = ProxyResolver(proxy, sender_jid, testit)
self.proxies[proxy] = resolver
resolver.add_connection(connection)
if default:
@ -115,6 +117,9 @@ class ProxyResolver:
self.host = str(host)
self.port = int(port)
self.jid = unicode(jid)
if not self.testit:
self.state = S_FINISHED
return
self.state = S_INITIAL
log.info('start resolving %s:%s' % (self.host, self.port))
self.receiver_tester = ReceiverTester(self.host, self.port, self.jid,
@ -209,7 +214,10 @@ class ProxyResolver:
query.setNamespace(common.xmpp.NS_BYTESTREAM)
connection.send(iq)
def __init__(self, proxy, sender_jid):
def __init__(self, proxy, sender_jid, testit):
"""
if testit is False, don't test it, only get IP/port
"""
self.proxy = proxy
self.state = S_INITIAL
self.active_connection = None
@ -221,6 +229,7 @@ class ProxyResolver:
self.port = None
self.sid = helpers.get_random_string_16()
self.sender_jid = sender_jid
self.testit = testit
class HostTester(Socks5, IdleObject):
"""

View File

@ -76,7 +76,7 @@ class ConnectionPubSub:
self.__callbacks[id_] = (cb, args, kwargs)
def send_pb_publish(self, jid, node, item, id_, options=None):
def send_pb_publish(self, jid, node, item, id_=None, options=None):
"""
Publish item to a node
"""
@ -85,7 +85,10 @@ class ConnectionPubSub:
query = xmpp.Iq('set', to=jid)
e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
p = e.addChild('publish', {'node': node})
p.addChild('item', {'id': id_}, [item])
attrs = {}
if id_:
attrs = {'id': id_}
p.addChild('item', attrs, [item])
if options:
p = e.addChild('publish-options')
p.addChild(node=options)

View File

@ -58,7 +58,7 @@ class StanzaSession(object):
self.conn = conn
self.jid = jid
self.type = type_
self.resource = None
self.resource = jid.getResource()
if thread_id:
self.received_thread_id = True
@ -492,6 +492,12 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
for child in parsed.getChildren():
stanza.addChild(node=child)
# replace non-character unicode
body = stanza.getBody()
if body:
stanza.setBody(
self.conn.connection.Dispatcher.replace_non_character(body))
return stanza
def decrypt(self, ciphertext):
@ -1148,8 +1154,10 @@ class EncryptedStanzaSession(ArchivingStanzaSession):
return xmpp.DataField(name=name, typ='hidden', value=dhs)
def terminate_e2e(self):
self.terminate()
self.enable_encryption = False
if self.control:
self.control.print_session_details()
self.terminate()
def acknowledge_termination(self):
StanzaSession.acknowledge_termination(self)

View File

@ -15,3 +15,4 @@ import simplexml, protocol, auth_nb, transports_nb, roster_nb
import dispatcher_nb, features_nb, idlequeue, bosh, tls_nb, proxy_connectors
from client_nb import NonBlockingClient
from plugin import PlugIn
from smacks import Smacks

View File

@ -22,8 +22,10 @@ See client_nb.py
"""
from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH
from protocol import NS_STREAM_MGMT
from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID
from plugin import PlugIn
from smacks import Smacks
import base64
import random
import itertools
@ -40,13 +42,14 @@ def H(some): return hashlib.md5(some).digest()
def C(some): return ':'.join(some)
try:
import kerberos
kerberos = __import__('kerberos')
have_kerberos = True
except ImportError:
have_kerberos = False
GSS_STATE_STEP = 0
GSS_STATE_WRAP = 1
SASL_FAILURE_IN_PROGRESS = 'failure-in-process'
SASL_FAILURE = 'failure'
SASL_SUCCESS = 'success'
SASL_UNSUPPORTED = 'not-supported'
@ -142,7 +145,7 @@ class SASL(PlugIn):
elif self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else:
@ -154,16 +157,16 @@ class SASL(PlugIn):
"""
if 'features' in self._owner.__dict__:
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
xmlns=NS_STREAMS)
if 'challenge' in self._owner.__dict__:
self._owner.UnregisterHandler('challenge', self.SASLHandler,
xmlns=NS_SASL)
xmlns=NS_SASL)
if 'failure' in self._owner.__dict__:
self._owner.UnregisterHandler('failure', self.SASLHandler,
xmlns=NS_SASL)
xmlns=NS_SASL)
if 'success' in self._owner.__dict__:
self._owner.UnregisterHandler('success', self.SASLHandler,
xmlns=NS_SASL)
xmlns=NS_SASL)
def auth(self):
"""
@ -178,12 +181,12 @@ class SASL(PlugIn):
elif self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else:
self._owner.RegisterHandler('features',
self.FeaturesHandler, xmlns=NS_STREAMS)
self.FeaturesHandler, xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
"""
@ -198,7 +201,8 @@ class SASL(PlugIn):
'mechanism'):
self.mecs.append(mec.getData())
self._owner.RegisterHandler('challenge', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('challenge', self.SASLHandler,
xmlns=NS_SASL)
self._owner.RegisterHandler('failure', self.SASLHandler, xmlns=NS_SASL)
self._owner.RegisterHandler('success', self.SASLHandler, xmlns=NS_SASL)
self.MechanismHandler()
@ -206,7 +210,8 @@ class SASL(PlugIn):
def MechanismHandler(self):
if 'ANONYMOUS' in self.mecs and self.username is None:
self.mecs.remove('ANONYMOUS')
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'ANONYMOUS'})
node = Node('auth', attrs={'xmlns': NS_SASL,
'mechanism': 'ANONYMOUS'})
self.mechanism = 'ANONYMOUS'
self.startsasl = SASL_IN_PROCESS
self._owner.send(str(node))
@ -226,11 +231,11 @@ class SASL(PlugIn):
self.mecs.remove('GSSAPI')
try:
self.gss_vc = kerberos.authGSSClientInit('xmpp@' + \
self._owner.xmpp_hostname)[1]
self._owner.xmpp_hostname)[1]
kerberos.authGSSClientStep(self.gss_vc, '')
response = kerberos.authGSSClientResponse(self.gss_vc)
node=Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'GSSAPI'},
payload=(response or ''))
node=Node('auth', attrs={'xmlns': NS_SASL,
'mechanism': 'GSSAPI'}, payload=(response or ''))
self.mechanism = 'GSSAPI'
self.gss_step = GSS_STATE_STEP
self.startsasl = SASL_IN_PROCESS
@ -247,7 +252,8 @@ class SASL(PlugIn):
raise NodeProcessed
if 'DIGEST-MD5' in self.mecs:
self.mecs.remove('DIGEST-MD5')
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'DIGEST-MD5'})
node = Node('auth', attrs={'xmlns': NS_SASL,
'mechanism': 'DIGEST-MD5'})
self.mechanism = 'DIGEST-MD5'
self.startsasl = SASL_IN_PROCESS
self._owner.send(str(node))
@ -258,6 +264,12 @@ class SASL(PlugIn):
self._owner._caller.get_password(self.set_password, self.mechanism)
self.startsasl = SASL_IN_PROCESS
raise NodeProcessed
if 'X-MESSENGER-OAUTH2' in self.mecs:
self.mecs.remove('X-MESSENGER-OAUTH2')
self.mechanism = 'X-MESSENGER-OAUTH2'
self._owner._caller.get_password(self.set_password, self.mechanism)
self.startsasl = SASL_IN_PROCESS
raise NodeProcessed
self.startsasl = SASL_FAILURE
log.info('I can only use EXTERNAL, SCRAM-SHA-1, DIGEST-MD5, GSSAPI and '
'PLAIN mecanisms.')
@ -271,36 +283,54 @@ class SASL(PlugIn):
"""
if challenge.getNamespace() != NS_SASL:
return
def scram_base64(s):
return ''.join(s.encode('base64').split('\n'))
incoming_data = challenge.getData()
data=base64.decodestring(incoming_data)
### Handle Auth result
def on_auth_fail(reason):
log.info('Failed SASL authentification: %s' % reason)
self._owner.send(str(Node('abort', attrs={'xmlns': NS_SASL})))
if len(self.mecs) > 0:
# There are other mechanisms to test, but wait for <failure>
# answer from server
self.startsasl = SASL_FAILURE_IN_PROGRESS
raise NodeProcessed
if self.on_sasl:
self.on_sasl()
raise NodeProcessed
if challenge.getName() == 'failure':
if self.startsasl == SASL_FAILURE_IN_PROGRESS:
self.MechanismHandler()
raise NodeProcessed
self.startsasl = SASL_FAILURE
try:
reason = challenge.getChildren()[0]
except Exception:
reason = challenge
log.info('Failed SASL authentification: %s' % reason)
if len(self.mecs) > 0:
# There are other mechanisms to test
self.MechanismHandler()
raise NodeProcessed
if self.on_sasl:
self.on_sasl()
raise NodeProcessed
on_auth_fail(reason)
elif challenge.getName() == 'success':
# TODO: Need to validate any data-with-success.
# TODO: Important for DIGEST-MD5 and SCRAM.
if self.mechanism == 'SCRAM-SHA-1':
# check data-with-success
data = scram_parse(data)
if data['v'] != scram_base64(self.scram_ServerSignature):
on_auth_fail('ServerSignature is wrong')
self.startsasl = SASL_SUCCESS
log.info('Successfully authenticated with remote server.')
handlers = self._owner.Dispatcher.dumpHandlers()
# Bosh specific dispatcher replugging
# save old features. They will be used in case we won't get response on
# stream restart after SASL auth (happens with XMPP over BOSH with
# Openfire)
# save old features. They will be used in case we won't get response
# on stream restart after SASL auth (happens with XMPP over BOSH
# with Openfire)
old_features = self._owner.Dispatcher.Stream.features
self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher.get_instance().PlugIn(self._owner,
after_SASL=True, old_features=old_features)
after_SASL=True, old_features=old_features)
self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User = self.username
@ -309,8 +339,6 @@ class SASL(PlugIn):
raise NodeProcessed
### Perform auth step
incoming_data = challenge.getData()
data=base64.decodestring(incoming_data)
log.info('Got challenge:' + data)
if self.mechanism == 'GSSAPI':
@ -322,12 +350,12 @@ class SASL(PlugIn):
rc = kerberos.authGSSClientUnwrap(self.gss_vc, incoming_data)
response = kerberos.authGSSClientResponse(self.gss_vc)
rc = kerberos.authGSSClientWrap(self.gss_vc, response,
kerberos.authGSSClientUserName(self.gss_vc))
kerberos.authGSSClientUserName(self.gss_vc))
response = kerberos.authGSSClientResponse(self.gss_vc)
if not response:
response = ''
self._owner.send(Node('response', attrs={'xmlns': NS_SASL},
payload=response).__str__())
payload=response).__str__())
raise NodeProcessed
if self.mechanism == 'SCRAM-SHA-1':
hashfn = hashlib.sha1
@ -353,12 +381,9 @@ class SASL(PlugIn):
ui = XOR(ui, ui_1)
return ui
def H(s):
def scram_H(s):
return hashfn(s).digest()
def scram_base64(s):
return ''.join(s.encode('base64').split('\n'))
if self.scram_step == 0:
self.scram_step = 1
self.scram_soup += ',' + data + ','
@ -373,7 +398,7 @@ class SASL(PlugIn):
SaltedPassword = Hi(self.password, salt, iter)
# TODO: Could cache this, along with salt+iter.
ClientKey = HMAC(SaltedPassword, 'Client Key')
StoredKey = H(ClientKey)
StoredKey = scram_H(ClientKey)
ClientSignature = HMAC(StoredKey, self.scram_soup)
ClientProof = XOR(ClientKey, ClientSignature)
r += ',p=' + scram_base64(ClientProof)
@ -408,8 +433,8 @@ class SASL(PlugIn):
else:
self.resp['realm'] = self._owner.Server
self.resp['nonce'] = chal['nonce']
self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in
itertools.repeat(random.randint, 7))
self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint \
in itertools.repeat(random.randint, 7))
self.resp['nc'] = ('00000001')
self.resp['qop'] = 'auth'
self.resp['digest-uri'] = 'xmpp/' + self._owner.Server
@ -417,6 +442,9 @@ class SASL(PlugIn):
# Password is now required
self._owner._caller.get_password(self.set_password, self.mechanism)
elif 'rspauth' in chal:
# Check rspauth value
if chal['rspauth'] != self.digest_rspauth:
on_auth_fail('rspauth is wrong')
self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL})))
else:
self.startsasl = SASL_FAILURE
@ -449,10 +477,14 @@ class SASL(PlugIn):
hash_realm = self._convert_to_iso88591(self.resp['realm'])
hash_password = self._convert_to_iso88591(self.password)
A1 = C([H(C([hash_username, hash_realm, hash_password])),
self.resp['nonce'], self.resp['cnonce']])
self.resp['nonce'], self.resp['cnonce']])
A2 = C(['AUTHENTICATE', self.resp['digest-uri']])
response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
self.resp['cnonce'], self.resp['qop'], HH(A2)]))
response = HH(C([HH(A1), self.resp['nonce'], self.resp['nc'],
self.resp['cnonce'], self.resp['qop'], HH(A2)]))
A2 = C(['', self.resp['digest-uri']])
self.digest_rspauth = HH(C([HH(A1), self.resp['nonce'],
self.resp['nc'], self.resp['cnonce'], self.resp['qop'],
HH(A2)]))
self.resp['response'] = response
sasl_data = u''
for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce',
@ -462,14 +494,19 @@ class SASL(PlugIn):
else:
sasl_data += u'%s="%s",' % (key, self.resp[key])
sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace(
'\r', '').replace('\n', '')
node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data])
'\r', '').replace('\n', '')
node = Node('response', attrs={'xmlns': NS_SASL},
payload=[sasl_data])
elif self.mechanism == 'PLAIN':
sasl_data = u'\x00%s\x00%s' % (self.username, self.password)
sasl_data = sasl_data.encode('utf-8').encode('base64').replace(
'\n', '')
'\n', '')
node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'},
payload=[sasl_data])
payload=[sasl_data])
elif self.mechanism == 'X-MESSENGER-OAUTH2':
node = Node('auth', attrs={'xmlns': NS_SASL,
'mechanism': 'X-MESSENGER-OAUTH2'})
node.addData(password)
self._owner.send(str(node))
@ -501,8 +538,8 @@ class NonBlockingNonSASL(PlugIn):
self.owner = owner
owner.Dispatcher.SendAndWaitForResponse(
Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
func=self._on_username)
Iq('get', NS_AUTH, payload=[Node('username', payload=[self.user])]),
func=self._on_username)
def _on_username(self, resp):
if not isResultNode(resp):
@ -517,8 +554,8 @@ class NonBlockingNonSASL(PlugIn):
if query.getTag('digest'):
log.info("Performing digest authentication")
query.setTagData('digest',
hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
+ self.password).hexdigest())
hashlib.sha1(self.owner.Dispatcher.Stream._document_attrs['id']
+ self.password).hexdigest())
if query.getTag('password'):
query.delChild('password')
self._method = 'digest'
@ -533,23 +570,25 @@ class NonBlockingNonSASL(PlugIn):
def hash_n_times(s, count):
return count and hasher(hash_n_times(s, count-1)) or s
hash_ = hash_n_times(hasher(hasher(self.password) + token), int(seq))
hash_ = hash_n_times(hasher(hasher(self.password) + token),
int(seq))
query.setTagData('hash', hash_)
self._method='0k'
else:
log.warn("Secure methods unsupported, performing plain text \
authentication")
authentication")
query.setTagData('password', self.password)
self._method = 'plain'
resp = self.owner.Dispatcher.SendAndWaitForResponse(iq, func=self._on_auth)
resp = self.owner.Dispatcher.SendAndWaitForResponse(iq,
func=self._on_auth)
def _on_auth(self, resp):
if isResultNode(resp):
log.info('Sucessfully authenticated with remote host.')
self.owner.User = self.user
self.owner.Resource = self.resource
self.owner._registered_name = self.owner.User+'@'+self.owner.Server+\
'/'+self.owner.Resource
self.owner._registered_name = self.owner.User + '@' + \
self.owner.Server+ '/' + self.owner.Resource
return self.on_auth(self._method)
log.info('Authentication failed!')
return self.on_auth(None)
@ -564,24 +603,34 @@ class NonBlockingBind(PlugIn):
def __init__(self):
PlugIn.__init__(self)
self.bound = None
self.supports_sm = False
self.resuming = False
def plugin(self, owner):
''' Start resource binding, if allowed at this time. Used internally. '''
if self._owner.Dispatcher.Stream.features:
try:
self.FeaturesHandler(self._owner.Dispatcher,
self._owner.Dispatcher.Stream.features)
self._owner.Dispatcher.Stream.features)
except NodeProcessed:
pass
else:
self._owner.RegisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
xmlns=NS_STREAMS)
def FeaturesHandler(self, conn, feats):
"""
Determine if server supports resource binding and set some internal
attributes accordingly
attributes accordingly.
It also checks if server supports stream management
"""
if feats.getTag('sm', namespace=NS_STREAM_MGMT):
self.supports_sm = True # server supports stream management
if self.resuming:
self._owner._caller.sm.resume_request()
if not feats.getTag('bind', namespace=NS_BIND):
log.info('Server does not requested binding.')
# we try to bind resource anyway
@ -599,12 +648,14 @@ class NonBlockingBind(PlugIn):
Remove Bind handler from owner's dispatcher. Used internally
"""
self._owner.UnregisterHandler('features', self.FeaturesHandler,
xmlns=NS_STREAMS)
xmlns=NS_STREAMS)
def NonBlockingBind(self, resource=None, on_bound=None):
"""
Perform binding. Use provided resource name or random (if not provided).
"""
if self.resuming: # We don't bind if we resume the stream
return
self.on_bound = on_bound
self._resource = resource
if self._resource:
@ -614,8 +665,9 @@ class NonBlockingBind(PlugIn):
self._owner.onreceive(None)
self._owner.Dispatcher.SendAndWaitForResponse(
Protocol('iq', typ='set', payload=[Node('bind', attrs={'xmlns':NS_BIND},
payload=self._resource)]), func=self._on_bound)
Protocol('iq', typ='set', payload=[Node('bind',
attrs={'xmlns': NS_BIND}, payload=self._resource)]),
func=self._on_bound)
def _on_bound(self, resp):
if isResultNode(resp):
@ -625,14 +677,22 @@ class NonBlockingBind(PlugIn):
jid = JID(resp.getTag('bind').getTagData('jid'))
self._owner.User = jid.getNode()
self._owner.Resource = jid.getResource()
# Only negociate stream management after bounded
sm = self._owner._caller.sm
if self.supports_sm:
# starts negociation
sm.set_owner(self._owner)
sm.negociate()
self._owner.Dispatcher.sm = sm
if hasattr(self, 'session') and self.session == -1:
# Server don't want us to initialize a session
log.info('No session required.')
self.on_bound('ok')
else:
self._owner.SendAndWaitForResponse(Protocol('iq', typ='set',
payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
func=self._on_session)
payload=[Node('session', attrs={'xmlns':NS_SESSION})]),
func=self._on_session)
return
if resp:
log.info('Binding failed: %s.' % resp.getTag('error'))

View File

@ -413,15 +413,15 @@ class NonBlockingBOSH(NonBlockingTransport):
'xmlns:xmpp': 'urn:xmpp:xbosh'})
else:
t = BOSHBody(
attrs={ 'content': self.bosh_content,
'hold': str(self.bosh_hold),
'route': '%s:%s' % (self.route_host, self.route_port),
'to': self.bosh_to,
'wait': str(self.bosh_wait),
'xml:lang': self.bosh_xml_lang,
'xmpp:version': '1.0',
'ver': '1.6',
'xmlns:xmpp': 'urn:xmpp:xbosh'})
attrs={ 'content': self.bosh_content,
'hold': str(self.bosh_hold),
'route': 'xmpp:%s:%s' % (self.route_host, self.route_port),
'to': self.bosh_to,
'wait': str(self.bosh_wait),
'xml:lang': self.bosh_xml_lang,
'xmpp:version': '1.0',
'ver': '1.6',
'xmlns:xmpp': 'urn:xmpp:xbosh'})
self.send_BOSH((t, True))
def start_disconnect(self):

View File

@ -493,6 +493,8 @@ class NonBlockingClient:
if self._sasl:
auth_nb.SASL.get_instance(self._User, self._Password,
self._on_start_sasl).PlugIn(self)
if not hasattr(self, 'SASL'):
return
if not self._sasl or self.SASL.startsasl == 'not-supported':
if not self._Resource:
self._Resource = 'xmpppy'
@ -521,7 +523,16 @@ class NonBlockingClient:
self.connected = None # FIXME: is this intended? We use ''elsewhere
self._on_sasl_auth(None)
elif self.SASL.startsasl == 'success':
auth_nb.NonBlockingBind.get_instance().PlugIn(self)
nb_bind = auth_nb.NonBlockingBind.get_instance()
sm = self._caller.sm
if sm._owner and sm.resumption:
nb_bind.resuming = True
sm.set_owner(self)
self.Dispatcher.sm = sm
nb_bind.PlugIn(self)
return
nb_bind.PlugIn(self)
self.onreceive(self._on_auth_bind)
return True

View File

@ -21,6 +21,7 @@ different handlers to different XMPP stanzas and namespaces
"""
import simplexml, sys, locale
import re
from xml.parsers.expat import ExpatError
from plugin import PlugIn
from protocol import (NS_STREAMS, NS_XMPP_STREAMS, NS_HTTP_BIND, Iq, Presence,
@ -90,6 +91,27 @@ class XMPPDispatcher(PlugIn):
self.SendAndWaitForResponse, self.SendAndCallForResponse,
self.getAnID, self.Event, self.send]
# Let the dispatcher know if there is support for stream management
self.sm = None
# \ufddo -> \ufdef range
c = u'\ufdd0'
r = c.encode('utf8')
while (c < u'\ufdef'):
c = unichr(ord(c) + 1)
r += '|' + c.encode('utf8')
# \ufffe-\uffff, \u1fffe-\u1ffff, ..., \u10fffe-\u10ffff
c = u'\ufffe'
r += '|' + c.encode('utf8')
r += '|' + unichr(ord(c) + 1).encode('utf8')
while (c < u'\U0010fffe'):
c = unichr(ord(c) + 0x10000)
r += '|' + c.encode('utf8')
r += '|' + unichr(ord(c) + 1).encode('utf8')
self.invalid_chars_re = re.compile(r)
def getAnID(self):
global outgoingID
outgoingID += 1
@ -175,6 +197,9 @@ class XMPPDispatcher(PlugIn):
raise ValueError('Incorrect stream start: (%s,%s). Terminating.'
% (tag, ns))
def replace_non_character(self, data):
return re.sub(self.invalid_chars_re, u'\ufffd'.encode('utf-8'), data)
def ProcessNonBlocking(self, data):
"""
Check incoming stream for data waiting
@ -190,6 +215,7 @@ class XMPPDispatcher(PlugIn):
# disconnect method will never be called.
# Is this intended?
# also look at transports start_disconnect()
data = self.replace_non_character(data)
for handler in self._cycleHandlers:
handler(self)
if len(self._pendingExceptions) > 0:
@ -417,6 +443,12 @@ class XMPPDispatcher(PlugIn):
stanza.props = stanza.getProperties()
ID = stanza.getID()
# If server supports stream management
if self.sm and self.sm.enabled and (stanza.getName() != 'r' and
stanza.getName() != 'a' and stanza.getName() != 'enabled' and
stanza.getName() != 'resumed'):
# increments the number of stanzas that has been handled
self.sm.in_h = self.sm.in_h + 1
list_ = ['default'] # we will use all handlers:
if typ in self.handlers[xmlns][name]:
list_.append(typ) # from very common...
@ -525,6 +557,14 @@ class XMPPDispatcher(PlugIn):
ID = stanza.getID()
if self._owner._registered_name and not stanza.getAttr('from'):
stanza.setAttr('from', self._owner._registered_name)
# If no ID then it is a whitespace
if self.sm and self.sm.enabled and ID:
self.sm.uqueue.append(stanza)
self.sm.out_h = self.sm.out_h + 1
if len(self.sm.uqueue) > self.sm.max_queue:
self.sm.request_ack()
self._owner.Connection.send(stanza, now)
return ID

View File

@ -47,12 +47,14 @@ NS_BOB = 'urn:xmpp:bob' # XEP-0231
NS_BOOKMARKS = 'storage:bookmarks' # XEP-0048
NS_BROWSE = 'jabber:iq:browse'
NS_BROWSING = 'http://jabber.org/protocol/browsing' # XEP-0195
NS_BYTESTREAM = 'http://jabber.org/protocol/bytestreams' # JEP-0065
NS_CAPS = 'http://jabber.org/protocol/caps' # JEP-0115
NS_BYTESTREAM = 'http://jabber.org/protocol/bytestreams' # XEP-0065
NS_CAPS = 'http://jabber.org/protocol/caps' # XEP-0115
NS_CAPTCHA = 'urn:xmpp:captcha' # XEP-0158
NS_CHATSTATES = 'http://jabber.org/protocol/chatstates' # JEP-0085
NS_CARBONS = 'urn:xmpp:carbons:1' # XEP-0280
NS_CHATSTATES = 'http://jabber.org/protocol/chatstates' # XEP-0085
NS_CHATTING = 'http://jabber.org/protocol/chatting' # XEP-0194
NS_CLIENT = 'jabber:client'
NS_CONDITIONS = 'urn:xmpp:muc:conditions:0' # XEP-0306
NS_COMMANDS = 'http://jabber.org/protocol/commands'
NS_COMPONENT_ACCEPT = 'jabber:component:accept'
NS_COMPONENT_1 = 'http://jabberd.jabberstudio.org/ns/component/1.0'
@ -69,9 +71,10 @@ NS_DISCO_ITEMS = NS_DISCO + '#items'
NS_ENCRYPTED = 'jabber:x:encrypted' # XEP-0027
NS_ESESSION = 'http://www.xmpp.org/extensions/xep-0116.html#ns'
NS_ESESSION_INIT = 'http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116
NS_EVENT = 'jabber:x:event' # XEP-0022
NS_EVENT = 'jabber:x:event' # XEP-0022
NS_FEATURE = 'http://jabber.org/protocol/feature-neg'
NS_FILE = 'http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
NS_FILE = 'http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096
NS_FORWARD = 'urn:xmpp:forward:0' # XEP-0297
NS_GAMING = 'http://jabber.org/protocol/gaming' # XEP-0196
NS_GATEWAY = 'jabber:iq:gateway' # XEP-0100
NS_GEOLOC = 'http://jabber.org/protocol/geoloc' # XEP-0080
@ -105,7 +108,7 @@ NS_MUC_CONFIG = NS_MUC + '#roomconfig'
NS_NICK = 'http://jabber.org/protocol/nick' # XEP-0172
NS_OFFLINE = 'http://www.jabber.org/jeps/jep-0030.html' # XEP-0013
NS_PHYSLOC = 'http://jabber.org/protocol/physloc' # XEP-0112
NS_PING = 'urn:xmpp:ping' # SEP-0199
NS_PING = 'urn:xmpp:ping' # XEP-0199
NS_PRESENCE = 'presence' # Jabberd2
NS_PRIVACY = 'jabber:iq:privacy'
NS_PRIVATE = 'jabber:iq:private'
@ -113,7 +116,7 @@ NS_PROFILE = 'http://jabber.org/protocol/profile' # XEP-0154
NS_PUBSUB = 'http://jabber.org/protocol/pubsub' # XEP-0060
NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event'
NS_PUBSUB_PUBLISH_OPTIONS = NS_PUBSUB + '#publish-options' # XEP-0060
NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' # JEP-0060
NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' # XEP-0060
NS_REGISTER = 'jabber:iq:register'
NS_ROSTER = 'jabber:iq:roster'
NS_ROSTERNOTES = 'storage:rosternotes'
@ -123,7 +126,7 @@ NS_RPC = 'jabber:iq:rpc' # XEP-0009
NS_RSM = 'http://jabber.org/protocol/rsm'
NS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
NS_SECLABEL = 'urn:xmpp:sec-label:0'
NS_SECLABEL_CATALOG = 'urn:xmpp:sec-label:catalog:0'
NS_SECLABEL_CATALOG = 'urn:xmpp:sec-label:catalog:2'
NS_SEARCH = 'jabber:iq:search'
NS_SERVER = 'jabber:server'
NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
@ -153,9 +156,10 @@ NS_DATA_LAYOUT = 'http://jabber.org/protocol/xdata-layout' # XEP-0141
NS_DATA_VALIDATE = 'http://jabber.org/protocol/xdata-validate' # XEP-0122
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
NS_RECEIPTS = 'urn:xmpp:receipts'
NS_PUBKEY_PUBKEY='urn:xmpp:pubkey:2' # XEP-0189
NS_PUBKEY_REVOKE='urn:xmpp:revoke:2'
NS_PUBKEY_ATTEST='urn:xmpp:attest:2'
NS_PUBKEY_PUBKEY = 'urn:xmpp:pubkey:2' # XEP-0189
NS_PUBKEY_REVOKE = 'urn:xmpp:revoke:2'
NS_PUBKEY_ATTEST = 'urn:xmpp:attest:2'
NS_STREAM_MGMT = 'urn:xmpp:sm:2' # XEP-198
xmpp_stream_error_conditions = '''
bad-format -- -- -- The entity has sent XML that cannot be processed.
@ -631,6 +635,17 @@ class Protocol(Node):
"""
return self.getTagAttr('error', 'code')
def getStatusConditions(self):
"""
Return the status conditions list as defined in XEP-0306.
"""
conds = []
condtag = self.getTag('conditions', namespace=NS_CONDITIONS)
if condtag:
for tag in condtag.getChildren():
conds.append(tag.getName())
return conds
def setError(self, error, code=None):
"""
Set the error code. Obsolete. Use error-conditions instead
@ -773,10 +788,11 @@ class Message(Protocol):
def buildReply(self, text=None):
"""
Builds and returns another message object with specified text. The to,
from and thread properties of new message are pre-set as reply to this
message
from, thread and type properties of new message are pre-set as reply to
this message
"""
m = Message(to=self.getFrom(), frm=self.getTo(), body=text)
m = Message(to=self.getFrom(), frm=self.getTo(), body=text,
typ=self.getType())
th = self.getThread()
if th:
m.setThread(th)
@ -933,11 +949,20 @@ class Iq(Protocol):
if queryNS:
self.setQueryNS(queryNS)
def getQuery(self):
"""
Return the IQ's child element if it exists, None otherwise.
"""
children = self.getChildren()
if children and self.getType() != 'error' and \
children[0].getName() != 'error':
return children[0]
def getQueryNS(self):
"""
Return the namespace of the 'query' child element
"""
tag = self.getTag('query')
tag = self.getQuery()
if tag:
return tag.getNamespace()
@ -945,13 +970,15 @@ class Iq(Protocol):
"""
Return the 'node' attribute value of the 'query' child element
"""
return self.getTagAttr('query', 'node')
tag = self.getQuery()
if tag:
return tag.getAttr('node')
def getQueryPayload(self):
"""
Return the 'query' child element payload
"""
tag = self.getTag('query')
tag = self.getQuery()
if tag:
return tag.getPayload()
@ -959,38 +986,79 @@ class Iq(Protocol):
"""
Return the 'query' child element child nodes
"""
tag = self.getTag('query')
tag = self.getQuery()
if tag:
return tag.getChildren()
def setQuery(self, name=None):
"""
Change the name of the query node, creating it if needed. Keep the
existing name if none is given (use 'query' if it's a creation).
Return the query node.
"""
query = self.getQuery()
if query is None:
query = self.addChild('query')
if name is not None:
query.setName(name)
return query
def setQueryNS(self, namespace):
"""
Set the namespace of the 'query' child element
"""
self.setTag('query').setNamespace(namespace)
self.setQuery().setNamespace(namespace)
def setQueryPayload(self, payload):
"""
Set the 'query' child element payload
"""
self.setTag('query').setPayload(payload)
self.setQuery().setPayload(payload)
def setQuerynode(self, node):
"""
Set the 'node' attribute value of the 'query' child element
"""
self.setTagAttr('query', 'node', node)
self.setQuery().setAttr('node', node)
def buildReply(self, typ):
"""
Build and return another Iq object of specified type. The to, from and
query child node of new Iq are pre-set as reply to this Iq.
"""
iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()})
if self.getTag('query'):
iq.setQueryNS(self.getQueryNS())
iq = Iq(typ, to=self.getFrom(), frm=self.getTo(),
attrs={'id': self.getID()})
iq.setQuery(self.getQuery().getName()).setNamespace(self.getQueryNS())
return iq
class Acks(Node):
"""
Acknowledgement elements for Stream Management
"""
def __init__(self, nsp=NS_STREAM_MGMT):
Node.__init__(self, None, {}, [], None, None,False, None)
self.setNamespace(nsp)
def buildAnswer(self, handled):
"""
handled is the number of stanzas handled
"""
self.setName('a')
self.setAttr('h', handled)
def buildRequest(self):
self.setName('r')
def buildEnable(self, resume=False):
self.setName('enable')
if resume:
self.setAttr('resume', 'true')
def buildResume(self, handled, previd):
self.setName('resume')
self.setAttr('h', handled)
self.setAttr('previd', previd)
class ErrorNode(Node):
"""
XMPP-style error element

View File

@ -45,7 +45,7 @@ class NonBlockingRoster(PlugIn):
PlugIn.__init__(self)
self.version = version
self._data = {}
self.set=None
self._set=None
self._exported_methods=[self.getRoster]
self.received_from_server = False
@ -54,8 +54,8 @@ class NonBlockingRoster(PlugIn):
Request roster from server if it were not yet requested (or if the
'force' argument is set)
"""
if self.set is None:
self.set = 0
if self._set is None:
self._set = 0
elif not force:
return
@ -100,7 +100,7 @@ class NonBlockingRoster(PlugIn):
if group.getData() not in self._data[jid]['groups']:
self._data[jid]['groups'].append(group.getData())
self._data[self._owner.User+'@'+self._owner.Server]={'resources': {}, 'name': None, 'ask': None, 'subscription': None, 'groups': None,}
self.set=1
self._set=1
# Looks like we have a workaround
# raise NodeProcessed # a MUST. Otherwise you'll get back an <iq type='error'/>
@ -323,7 +323,7 @@ class NonBlockingRoster(PlugIn):
'subscription': None,
'groups': None
}
self.set = 1
self._set = 1
def plugin(self, owner, request=1):
"""
@ -340,9 +340,9 @@ class NonBlockingRoster(PlugIn):
def _on_roster_set(self, data):
if data:
self._owner.Dispatcher.ProcessNonBlocking(data)
if not self.set:
if not self._set:
return
if not self._owner:
if not hasattr(self, '_owner') or not self._owner:
# Connection has been closed by receiving a <stream:error> for ex,
return
self._owner.onreceive(None)
@ -356,7 +356,7 @@ class NonBlockingRoster(PlugIn):
Request roster from server if neccessary and returns self
"""
return_self = True
if not self.set:
if not self._set:
self.on_ready = on_ready
self._owner.onreceive(self._on_roster_set)
return_self = False

130
src/common/xmpp/smacks.py Normal file
View File

@ -0,0 +1,130 @@
from protocol import Acks
from protocol import NS_STREAM_MGMT
import logging
log = logging.getLogger('gajim.c.x.smacks')
class Smacks():
'''
This is Smacks is the Stream Management class. It takes care of requesting
and sending acks. Also, it keeps track of the unhandled outgoing stanzas.
The dispatcher has to be able to access this class to increment the
number of handled stanzas
'''
def __init__(self, con):
self.con = con # Connection object
self.out_h = 0 # Outgoing stanzas handled
self.in_h = 0 # Incoming stanzas handled
self.uqueue = [] # Unhandled stanzas queue
self.session_id = None
self.resumption = False # If server supports resume
# Max number of stanzas in queue before making a request
self.max_queue = 5
self._owner = None
self.resuming = False
self.enabled = False # If SM is enabled
self.location = None
def set_owner(self, owner):
self._owner = owner
# Register handlers
owner.Dispatcher.RegisterNamespace(NS_STREAM_MGMT)
owner.Dispatcher.RegisterHandler('enabled', self._neg_response,
xmlns=NS_STREAM_MGMT)
owner.Dispatcher.RegisterHandler('r', self.send_ack,
xmlns=NS_STREAM_MGMT)
owner.Dispatcher.RegisterHandler('a', self.check_ack,
xmlns=NS_STREAM_MGMT)
owner.Dispatcher.RegisterHandler('resumed', self.check_ack,
xmlns=NS_STREAM_MGMT)
owner.Dispatcher.RegisterHandler('failed', self.error_handling,
xmlns=NS_STREAM_MGMT)
def _neg_response(self, disp, stanza):
r = stanza.getAttr('resume')
if r == 'true' or r == 'True' or r == '1':
self.resumption = True
self.session_id = stanza.getAttr('id')
if r == 'false' or r == 'False' or r == '0':
self.negociate(False)
l = stanza.getAttr('location')
if l:
self.location = l
def negociate(self, resume=True):
# Every time we attempt to negociate, we must erase all previous info
# about any previous session
self.uqueue = []
self.in_h = 0
self.out_h = 0
self.session_id = None
self.enabled = True
stanza = Acks()
stanza.buildEnable(resume)
self._owner.Connection.send(stanza, now=True)
def resume_request(self):
if not self.session_id:
self.resuming = False
log.error('Attempted to resume without a valid session id ')
return
resume = Acks()
resume.buildResume(self.in_h, self.session_id)
self._owner.Connection.send(resume, False)
def send_ack(self, disp, stanza):
ack = Acks()
ack.buildAnswer(self.in_h)
self._owner.Connection.send(ack, False)
def request_ack(self):
r = Acks()
r.buildRequest()
self._owner.Connection.send(r, False)
def check_ack(self, disp, stanza):
'''
Checks if the number of stanzas sent are the same as the
number of stanzas received by the server. Pops stanzas that were
handled by the server from the queue.
'''
h = int(stanza.getAttr('h'))
diff = self.out_h - h
if len(self.uqueue) < diff or diff < 0:
log.error('Server and client number of stanzas handled mismatch ')
else:
while (len(self.uqueue) > diff):
self.uqueue.pop(0)
if stanza.getName() == 'resumed':
self.resuming = True
self.con.set_oldst()
if self.uqueue != []:
for i in self.uqueue:
self._owner.Connection.send(i, False)
def error_handling(self, disp, stanza):
# If the server doesn't recognize previd, forget about resuming
# Ask for service discovery, etc..
if stanza.getTag('item-not-found'):
self.resuming = False
# we need to bind a resource
self._owner.NonBlockingBind.resuming = False
self._owner._on_auth_bind(None)
return
# Doesn't support resumption
if stanza.getTag('feature-not-implemented'):
self.negociate(False)
return
if stanza.getTag('unexpected-request'):
self.enabled = False
log.error('Gajim failed to negociate Stream Management')
return

View File

@ -2,7 +2,7 @@
## src/common/xmpp/stringprepare.py
##
## Copyright (C) 2001-2005 Twisted Matrix Laboratories
## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2005-2011 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
##
@ -202,6 +202,8 @@ class NamePrep:
def nameprep(self, label):
label = idna.nameprep(label)
self.check_prohibiteds(label)
if len(label) == 0:
raise UnicodeError, "Invalid empty name"
if label[0] == '-':
raise UnicodeError, "Invalid leading hyphen-minus"
if label[-1] == '-':

Some files were not shown because too many files have changed in this diff Show More