merge from trunk
This commit is contained in:
commit
49bc202421
132 changed files with 18862 additions and 20008 deletions
31
ChangeLog
31
ChangeLog
|
@ -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
|
||||
|
|
14
Makefile.am
14
Makefile.am
|
@ -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 \
|
||||
|
|
|
@ -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 & 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>
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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">●</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">●</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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"><empty></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"><empty></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"><empty></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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
2
debian/README.Debian
vendored
2
debian/README.Debian
vendored
|
@ -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
33
debian/changelog
vendored
|
@ -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
6
debian/control
vendored
|
@ -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
4
debian/copyright
vendored
|
@ -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
2
debian/docs
vendored
|
@ -1 +1 @@
|
|||
README
|
||||
README.html
|
||||
|
|
1
debian/install
vendored
Normal file
1
debian/install
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
debian/gajim.xpm usr/share/pixmaps
|
23
debian/patches/00_debian-copying.diff
vendored
23
debian/patches/00_debian-copying.diff
vendored
|
@ -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
|
34
debian/patches/01_configure-ac.diff
vendored
34
debian/patches/01_configure-ac.diff
vendored
|
@ -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
|
2
debian/patches/series
vendored
2
debian/patches/series
vendored
|
@ -1,2 +0,0 @@
|
|||
00_debian-copying.diff
|
||||
01_configure-ac.diff
|
6
debian/rules
vendored
6
debian/rules
vendored
|
@ -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
|
||||
|
|
28
gajim.nsi
28
gajim.nsi
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
from plugin import BannerTweaksPlugin
|
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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()
|
|
@ -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 = {}
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from ftp_manager import FtpManager
|
|
@ -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"><empty></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"><empty></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">●</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -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()
|
|
@ -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/
|
|
@ -1 +0,0 @@
|
|||
from plugin import GoogleTranslationPlugin
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
from length_notifier import LengthNotifierPlugin
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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 = {}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from triggers import Triggers
|
|
@ -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"><b>Conditions</b></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"><b>Actions</b></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"><b>Sounds</b></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>
|
|
@ -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/
|
|
@ -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]
|
|
@ -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 KiB |
|
@ -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 |
|
@ -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 |
|
@ -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)
|
|
@ -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>
|
5346
po/zh_TW.po
5346
po/zh_TW.po
File diff suppressed because it is too large
Load diff
95
scripts/dev/plugins_translate
Executable file
95
scripts/dev/plugins_translate
Executable 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
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
130
src/common/xmpp/smacks.py
Normal 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
|
|
@ -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
Loading…
Add table
Reference in a new issue