merged trunk into session_centric branch

This commit is contained in:
Brendan Taylor 2008-02-05 03:09:31 +00:00
parent b8882ba48e
commit 0b48b05218
68 changed files with 13704 additions and 7863 deletions

13
TODO.pep Normal file
View file

@ -0,0 +1,13 @@
• configure access model when changing it in the combobox
• PEP in status change
Tune use cases:
• on disconnection of an account set Tune to None
Tooltips use cases:
• Show PEP in GC tooltips
Mood/Activity use cases
• on connection of an account set them to None
• on disconnection of an account set them to None
• on explicit set publish them

View file

@ -9,7 +9,7 @@
echo "[encoding: UTF-8]" > po/POTFILES.in \ echo "[encoding: UTF-8]" > po/POTFILES.in \
&& ls -1 -U data/gajim.desktop.in.in data/glade/*.glade \ && ls -1 -U data/gajim.desktop.in.in data/glade/*.glade \
src/*py src/common/*py src/common/zeroconf/*.py >> \ src/*py src/common/*py src/common/zeroconf/*.py src/osx/*.py >> \
po/POTFILES.in || exit 1 po/POTFILES.in || exit 1
if test -z `which pkg-config 2>/dev/null`;then if test -z `which pkg-config 2>/dev/null`;then
echo "***Error: pkg-config not found***" echo "***Error: pkg-config not found***"

View file

@ -1,5 +1,5 @@
AC_INIT([Gajim - A Jabber Instant Messager], AC_INIT([Gajim - A Jabber Instant Messager],
[0.11.4.0-svn],[http://trac.gajim.org/],[gajim]) [0.11.4.2-svn],[http://trac.gajim.org/],[gajim])
AC_PREREQ([2.59]) AC_PREREQ([2.59])
AM_INIT_AUTOMAKE([1.8]) AM_INIT_AUTOMAKE([1.8])
AC_CONFIG_HEADER(config.h) AC_CONFIG_HEADER(config.h)

View file

@ -14,7 +14,13 @@ sounds_DATA = $(srcdir)/sounds/*.wav
otherdir = $(pkgdatadir)/data/other otherdir = $(pkgdatadir)/data/other
other_DATA = other/servers.xml other/cacerts.pem other_DATA = other/servers.xml other/cacerts.pem
man_MANS = gajim.1 gajim-remote.1 if BUILD_REMOTE_CONTROL
OPTIONAL_MAN = gajim-remote.1
else
OPTIONAL_MAN =
endif
man_MANS = gajim.1 $(OPTIONAL_MAN)
EXTRA_DIST = $(desktop_in_files) \ EXTRA_DIST = $(desktop_in_files) \

View file

@ -17,6 +17,20 @@
</child> </child>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkImageMenuItem" id="pep_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">_Personal Events</property>
<property name="use_underline">True</property>
<child internal-child="image">
<widget class="GtkImage" id="menu-item-image7">
<property name="stock">gtk-home</property>
<property name="icon_size">1</property>
</widget>
</child>
</widget>
</child>
<child> <child>
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1"> <widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property> <property name="visible">True</property>

View file

@ -117,7 +117,7 @@ to the Jabber network.</property>
<widget class="GtkLabel" id="label270"> <widget class="GtkLabel" id="label270">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Please fill in the data for your new account&lt;/b&gt;</property> <property name="label" translatable="yes">&lt;b&gt;Please fill in the data for your existing account&lt;/b&gt;</property>
<property name="use_markup">True</property> <property name="use_markup">True</property>
</widget> </widget>
<packing> <packing>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="change_activity_dialog">
<property name="border_width">6</property>
<property name="title" translatable="yes"></property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="default_width">270</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">True</property>
<signal name="key_press_event" handler="on_change_status_message_dialog_key_press_event" last_modification_time="Wed, 16 Mar 2005 00:53:06 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox5">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="cancel_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
<signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:58:52 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="ok_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-ok</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="on_ok_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:57:55 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame38">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0.5</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkAlignment" id="alignment107">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">12</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkVBox" id="vbox112">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child>
<widget class="GtkComboBox" id="combobox1">
<property name="visible">True</property>
<property name="add_tearoffs">False</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="combobox2">
<property name="visible">True</property>
<property name="add_tearoffs">False</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View file

@ -0,0 +1,145 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="change_mood_dialog">
<property name="border_width">6</property>
<property name="title" translatable="yes"></property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="default_width">270</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">True</property>
<signal name="key_press_event" handler="on_change_status_message_dialog_key_press_event" last_modification_time="Wed, 16 Mar 2005 00:53:06 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox5">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="cancel_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
<signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:58:52 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="ok_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-ok</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="on_ok_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:57:55 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame38">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0.5</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkAlignment" id="alignment107">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">12</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkVBox" id="vbox112">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child>
<widget class="GtkComboBox" id="combobox">
<property name="visible">True</property>
<property name="add_tearoffs">False</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View file

@ -43,7 +43,7 @@
<widget class="GtkVBox" id="vbox1"> <widget class="GtkVBox" id="vbox1">
<property name="visible">True</property> <property name="visible">True</property>
<child> <child>
<widget class="GtkVBox" id="vbox2"> <widget class="GtkVBox" id="welcome_vbox">
<property name="visible">True</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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child> <child>

View file

@ -0,0 +1,135 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="manage_pep_services_window">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="title" translatable="yes"></property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="default_width">350</property>
<property name="default_height">150</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<signal name="destroy" handler="on_manage_pep_services_window_destroy"/>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="services_treeview">
<property name="visible">True</property>
<property name="can_focus">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="headers_visible">True</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_DEFAULT_STYLE</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-add</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="on_add_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:57:55 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="delete_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-delete</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="on_delete_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:57:55 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="cancel_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
<signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:58:52 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="ok_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-ok</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="on_ok_button_clicked" last_modification_time="Sat, 31 Mar 2007 07:57:55 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View file

@ -108,15 +108,34 @@
</widget> </widget>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="message_scrolledwindow"> <widget class="GtkHBox" id="hbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">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="border_width">3</property>
<property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<child> <child>
<placeholder/> <widget class="GtkImage" id="lock_image">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="no_show_all">True</property>
<property name="stock">gtk-dialog-authentication</property>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">3</property>
<property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child> </child>
</widget> </widget>
<packing> <packing>
@ -136,38 +155,6 @@
<widget class="GtkHBox" id="hbox3006"> <widget class="GtkHBox" id="hbox3006">
<property name="visible">True</property> <property name="visible">True</property>
<property name="spacing">1</property> <property name="spacing">1</property>
<child>
<widget class="GtkEventBox" id="gpg_eventbox">
<property name="visible">True</property>
<property name="tooltip" translatable="yes">OpenPGP Encryption</property>
<child>
<widget class="GtkToggleButton" id="gpg_togglebutton">
<property name="visible">True</property>
<property name="relief">GTK_RELIEF_NONE</property>
<property name="focus_on_click">False</property>
<property name="response_id">0</property>
<child>
<widget class="GtkImage" id="image1333">
<property name="visible">True</property>
<property name="stock">gtk-dialog-authentication</property>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkVSeparator" id="vseparator4">
<property name="visible">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</widget> </widget>
</child> </child>
<child> <child>
@ -399,6 +386,7 @@
</child> </child>
<child> <child>
<widget class="GtkVBox" id="muc_child_vbox"> <widget class="GtkVBox" id="muc_child_vbox">
<property name="can_focus">True</property>
<property name="border_width">3</property> <property name="border_width">3</property>
<child> <child>
<widget class="GtkAlignment" id="alignment103"> <widget class="GtkAlignment" id="alignment103">

File diff suppressed because it is too large Load diff

View file

@ -193,6 +193,14 @@
</child> </child>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkMenuItem" id="pep_services_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">_Services</property>
<property name="use_underline">True</property>
</widget>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>
@ -222,6 +230,17 @@
<signal name="activate" handler="on_show_transports_menuitem_activate"/> <signal name="activate" handler="on_show_transports_menuitem_activate"/>
</widget> </widget>
</child> </child>
<child>
<widget class="GtkCheckMenuItem" id="show_roster_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</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<signal name="toggled" handler="on_show_roster_menuitem_toggled"/>
<accelerator key="R" modifiers="GDK_CONTROL_MASK" signal="activate"/>
</widget>
</child>
<child> <child>
<widget class="GtkSeparatorMenuItem" id="separator3"> <widget class="GtkSeparatorMenuItem" id="separator3">
<property name="visible">True</property> <property name="visible">True</property>
@ -344,45 +363,65 @@
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow"> <widget class="GtkHPaned" id="roster_hpaned">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="border_width">2</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<child> <child>
<widget class="GtkTreeView" id="roster_treeview"> <widget class="GtkVBox" id="roster_vbox2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">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="headers_visible">False</property> <child>
<property name="reorderable">True</property> <widget class="GtkScrolledWindow" id="scrolledwindow">
<signal name="leave_notify_event" handler="on_roster_treeview_leave_notify_event"/> <property name="visible">True</property>
<signal name="button_press_event" handler="on_roster_treeview_button_press_event"/> <property name="can_focus">True</property>
<signal name="motion_notify_event" handler="on_roster_treeview_motion_notify_event"/> <property name="border_width">2</property>
<signal name="row_collapsed" handler="on_roster_treeview_row_collapsed"/> <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
<signal name="row_expanded" handler="on_roster_treeview_row_expanded"/> <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<signal name="key_press_event" handler="on_roster_treeview_key_press_event"/> <child>
<signal name="row_activated" handler="on_roster_treeview_row_activated"/> <widget class="GtkTreeView" id="roster_treeview">
<signal name="button_release_event" handler="on_roster_treeview_button_release_event"/> <property name="visible">True</property>
<signal name="scroll_event" handler="on_roster_treeview_scroll_event"/> <property name="can_focus">True</property>
<signal name="style_set" handler="on_roster_treeview_style_set"/> <property name="headers_visible">False</property>
<property name="reorderable">True</property>
<signal name="leave_notify_event" handler="on_roster_treeview_leave_notify_event"/>
<signal name="button_press_event" handler="on_roster_treeview_button_press_event"/>
<signal name="motion_notify_event" handler="on_roster_treeview_motion_notify_event"/>
<signal name="row_collapsed" handler="on_roster_treeview_row_collapsed"/>
<signal name="row_expanded" handler="on_roster_treeview_row_expanded"/>
<signal name="key_press_event" handler="on_roster_treeview_key_press_event"/>
<signal name="row_activated" handler="on_roster_treeview_row_activated"/>
<signal name="button_release_event" handler="on_roster_treeview_button_release_event"/>
<signal name="scroll_event" handler="on_roster_treeview_scroll_event"/>
<signal name="style_set" handler="on_roster_treeview_style_set"/>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkComboBox" id="status_combobox">
<property name="visible">True</property>
<signal name="changed" handler="on_status_combobox_changed"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</widget> </widget>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<placeholder/>
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkComboBox" id="status_combobox">
<property name="visible">True</property>
<signal name="changed" handler="on_status_combobox_changed"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>

View file

@ -54,6 +54,7 @@
<property name="xpad">5</property> <property name="xpad">5</property>
<property name="ypad">5</property> <property name="ypad">5</property>
<property name="selectable">True</property> <property name="selectable">True</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
</widget> </widget>
</child> </child>
</widget> </widget>
@ -94,6 +95,7 @@
<property name="xpad">5</property> <property name="xpad">5</property>
<property name="ypad">5</property> <property name="ypad">5</property>
<property name="selectable">True</property> <property name="selectable">True</property>
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
</widget> </widget>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
@ -218,8 +220,8 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="spacing">6</property> <property name="spacing">6</property>
<child> <child>
<widget class="GtkLabel" id="label59"> <widget class="GtkLabel" id="user_avatar_label">
<property name="visible">True</property> <property name="no_show_all">True</property>
<property name="label" translatable="yes">User avatar:</property> <property name="label" translatable="yes">User avatar:</property>
</widget> </widget>
<packing> <packing>
@ -227,17 +229,6 @@
<property name="fill">False</property> <property name="fill">False</property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkLabel" id="no_user_avatar_label">
<property name="no_show_all">True</property>
<property name="label" translatable="yes">None</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child> <child>
<widget class="GtkEventBox" id="PHOTO_eventbox"> <widget class="GtkEventBox" id="PHOTO_eventbox">
<property name="visible">True</property> <property name="visible">True</property>
@ -254,7 +245,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">2</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -265,7 +256,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">3</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -277,11 +268,12 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">4</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
@ -384,9 +376,6 @@
</packing> </packing>
</child> </child>
</widget> </widget>
<packing>
<property name="tab_expand">False</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkLabel" id="label3"> <widget class="GtkLabel" id="label3">
@ -397,7 +386,6 @@
</widget> </widget>
<packing> <packing>
<property name="type">tab</property> <property name="type">tab</property>
<property name="tab_expand">False</property>
<property name="tab_fill">False</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
@ -1014,7 +1002,6 @@
</widget> </widget>
<packing> <packing>
<property name="position">1</property> <property name="position">1</property>
<property name="tab_expand">False</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1027,7 +1014,6 @@
<packing> <packing>
<property name="type">tab</property> <property name="type">tab</property>
<property name="position">1</property> <property name="position">1</property>
<property name="tab_expand">False</property>
<property name="tab_fill">False</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
@ -1459,7 +1445,6 @@
</widget> </widget>
<packing> <packing>
<property name="position">2</property> <property name="position">2</property>
<property name="tab_expand">False</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1472,7 +1457,6 @@
<packing> <packing>
<property name="type">tab</property> <property name="type">tab</property>
<property name="position">2</property> <property name="position">2</property>
<property name="tab_expand">False</property>
<property name="tab_fill">False</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
@ -1497,7 +1481,6 @@
</widget> </widget>
<packing> <packing>
<property name="position">3</property> <property name="position">3</property>
<property name="tab_expand">False</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1510,7 +1493,6 @@
<packing> <packing>
<property name="type">tab</property> <property name="type">tab</property>
<property name="position">3</property> <property name="position">3</property>
<property name="tab_expand">False</property>
<property name="tab_fill">False</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
@ -1533,7 +1515,6 @@
</widget> </widget>
<packing> <packing>
<property name="position">4</property> <property name="position">4</property>
<property name="tab_expand">False</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -1544,7 +1525,6 @@
<packing> <packing>
<property name="type">tab</property> <property name="type">tab</property>
<property name="position">4</property> <property name="position">4</property>
<property name="tab_expand">False</property>
<property name="tab_fill">False</property> <property name="tab_fill">False</property>
</packing> </packing>
</child> </child>
@ -1588,6 +1568,7 @@
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property>
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>

View file

@ -1 +1,2 @@
data/gajim.desktop.in
src/eggtrayicon.c

3412
po/de.po

File diff suppressed because it is too large Load diff

402
po/fr.po
View file

@ -3570,7 +3570,7 @@ msgstr "Fichier"
#: ../src/filetransfers_window.py:87 #: ../src/filetransfers_window.py:87
msgid "Time" msgid "Time"
msgstr "Moment" msgstr "Durée"
#: ../src/filetransfers_window.py:99 #: ../src/filetransfers_window.py:99
msgid "Progress" msgid "Progress"
@ -6537,6 +6537,406 @@ msgstr "Connecté"
msgid "Disconnected" msgid "Disconnected"
msgstr "Déconnecté" msgstr "Déconnecté"
#pep
msgid "afraid"
msgstr "appeuré"
msgid "amazed"
msgstr "surpris, amusé"
msgid "angry"
msgstr "en colère"
msgid "annoyed"
msgstr "dérangé"
msgid "anxious"
msgstr "anxieux"
msgid "aroused"
msgstr "excité"
msgid "ashamed"
msgstr "honteux/éhonté"
msgid "bored"
msgstr "ennuyé"
msgid "brave"
msgstr "courageux"
msgid "calm"
msgstr "calme"
msgid "cold"
msgstr "froid"
msgid "confused"
msgstr "confus"
msgid "contented"
msgstr "contenté"
msgid "cranky"
msgstr "excentrique"
msgid "curious"
msgstr "curieux"
msgid "depressed"
msgstr "déprimé"
msgid "disappointed"
msgstr "décu"
msgid "disgusted"
msgstr "dégouté"
msgid "distracted"
msgstr "distrait"
msgid "embarrassed"
msgstr "embarssé"
msgid "excited"
msgstr "excité"
msgid "flirtatious"
msgstr "coquet"
msgid "frustrated"
msgstr "frustré"
msgid "grumpy"
msgstr "grognon"
msgid "guilty"
msgstr "coupable"
msgid "happy"
msgstr "joyeux"
msgid "hot"
msgstr "chaud"
msgid "humbled"
msgstr "humilié"
msgid "humiliated"
msgstr "humilié"
msgid "hungry"
msgstr "affamé"
msgid "hurt"
msgstr "blessé"
msgid "impressed"
msgstr "impressionné"
msgid "in_awe"
msgstr "dans la crainte"
msgid "in_love"
msgstr "amoureux"
msgid "indignant"
msgstr "indigné"
msgid "interested"
msgstr "interessé"
msgid "intoxicated"
msgstr "intoxiqué"
msgid "invincible"
msgstr "invincible"
msgid "jealous"
msgstr "jaloux"
msgid "lonely"
msgstr "seul/esseulé"
msgid "mean"
msgstr "méchant"
msgid "moody"
msgstr "déprimé"
msgid "nervous"
msgstr "nerveux"
msgid "neutral"
msgstr "neutre"
msgid "offended"
msgstr "offensé"
msgid "playful"
msgstr "joueur"
msgid "proud"
msgstr "fier"
msgid "relieved"
msgstr "soulagé"
msgid "remorseful"
msgstr "plein de remord"
msgid "restless"
msgstr "infatiguable"
msgid "sad"
msgstr "triste"
msgid "sarcastic"
msgstr "sarcastique"
msgid "serious"
msgstr "serieux"
msgid "shocked"
msgstr "choqué"
msgid "shy"
msgstr "timide"
msgid "sick"
msgstr "malade"
msgid "sleepy"
msgstr "endormi"
msgid "stressed"
msgstr "stressé"
msgid "surprised"
msgstr "surpris"
msgid "thirsty"
msgstr "assoiffé"
msgid "worried"
msgstr "inquiet"
msgid "_Personnal Events"
msgstr "Évènements _Personnels"
msgid "Activity"
msgstr "Activité"
msgid "doing_chores"
msgstr "fait des corvées"
msgid "buying_groceries"
msgstr "achète des épiceries"
msgid "cleaning"
msgstr "nettoie"
msgid "cooking"
msgstr "cuisine"
msgid "doing_maintenance"
msgstr "fait de la maintenance"
msgid "doing_the_dishes"
msgstr "fait la vaiselle"
msgid "doing_the_laundry"
msgstr "fait la blanchisserie"
msgid "gardening"
msgstr "jardine"
msgid "running_an_errand"
msgstr "fait une course"
msgid "walking_the_dog"
msgstr "promène le chien"
msgid "drinking"
msgstr "boit"
msgid "having_a_beer"
msgstr "prend une bière"
msgid "having_coffee"
msgstr "prend un café"
msgid "having_tea"
msgstr "prend un thé"
msgid "eating"
msgstr "mange"
msgid "having_a_snack"
msgstr "prend un snack"
msgid "having_breakfast"
msgstr "prend le petit-déjeuner"
msgid "having_dinner"
msgstr "soupe"
msgid "having_lunch"
msgstr "dîne"
msgid "exercising"
msgstr "fait de l'exercice"
msgid "cycling"
msgstr "fait du vélo"
msgid "hiking"
msgstr "fait de la randonnée"
msgid "jogging"
msgstr "fait un jogging"
msgid "playing_sports"
msgstr "fait du sport"
msgid "running"
msgstr "court"
msgid "skiing"
msgstr "skie"
msgid "swimming"
msgstr "nage"
msgid "working_out"
msgstr "élabore"
msgid "grooming"
msgstr "se toilette"
msgid "at_the_spa"
msgstr "à la station thermale"
msgid "brushing_teeth"
msgstr "se brosse les dents"
msgid "getting_a_haircut"
msgstr "se fait couper les cheveux"
msgid "shaving"
msgstr "se rase"
msgid "taking_a_bath"
msgstr "prend un bain"
msgid "taking_a_shower"
msgstr "prend une douche"
msgid "having_appointment"
msgstr "à un rendez-vous"
msgid "inactive"
msgstr "inactif"
msgid "day_off"
msgstr "en congé"
msgid "hanging_out"
msgstr "traîne"
msgid "on_vacation"
msgstr "en vacances"
msgid "scheduled_holiday"
msgstr "en vacances organisées"
msgid "sleeping"
msgstr "dort"
msgid "relaxing"
msgstr "se relaxe"
msgid "gaming"
msgstr "joue"
msgid "going_out"
msgstr "sort"
msgid "partying"
msgstr "fait la fête"
msgid "reading"
msgstr "lit"
msgid "rehearsing"
msgstr "se prépare"
msgid "shopping"
msgstr "fait les magasins"
msgid "socializing"
msgstr "se socialise"
msgid "sunbathing"
msgstr "prend un bain de soleil"
msgid "watching_tv"
msgstr "regarde la TV"
msgid "watching_a_movie"
msgstr "regarde un film"
msgid "talking"
msgstr "discute"
msgid "in_real_life"
msgstr "dans la vraie vie"
msgid "on_the_phone"
msgstr "au téléphone"
msgid "traveling"
msgstr "voyage"
msgid "commuting"
msgstr "permute"
msgid "driving"
msgstr "conduit"
msgid "in_a_car"
msgstr "en voiture"
msgid "on_a_bus"
msgstr "en bus"
msgid "on_a_plane"
msgstr "en avion"
msgid "on_a_train"
msgstr "en train"
msgid "on_a_trip"
msgstr "en séjour"
msgid "walking"
msgstr "marche"
msgid "working"
msgstr "travaille"
msgid "coding"
msgstr "programme"
msgid "in_a_meeting"
msgstr "en réunion"
msgid "studying"
msgstr "étudie"
msgid "writing"
msgstr "écrit"
#~ msgid "2003-12-13T18:30:02Z" #~ msgid "2003-12-13T18:30:02Z"
#~ msgstr "2003-12-13T18:30:02Z" #~ msgstr "2003-12-13T18:30:02Z"
#~ msgid "<small>Romeo and Juliet</small>" #~ msgid "<small>Romeo and Juliet</small>"

3036
po/ru.po

File diff suppressed because it is too large Load diff

7123
po/sk.po

File diff suppressed because it is too large Load diff

View file

@ -44,26 +44,26 @@ trayicon.c:
$(srcdir)/trayicon.defs > $@ $(srcdir)/trayicon.defs > $@
endif endif
gajimsrcdir = $(pkgdatadir)/src gajimsrcdir = $(pkgdatadir)/src
gajimsrc_DATA = $(srcdir)/*.py gajimsrc_PYTHON = $(srcdir)/*.py
gajimsrc1dir = $(pkgdatadir)/src/common gajimsrc1dir = $(pkgdatadir)/src/common
gajimsrc1_DATA = \ gajimsrc1_PYTHON = \
$(srcdir)/common/*.py $(srcdir)/common/*.py
gajimsrc2dir = $(pkgdatadir)/src/common/xmpp gajimsrc2dir = $(pkgdatadir)/src/common/xmpp
gajimsrc2_DATA = \ gajimsrc2_PYTHON = \
$(srcdir)/common/xmpp/*.py $(srcdir)/common/xmpp/*.py
gajimsrc3dir = $(pkgdatadir)/src/common/zeroconf gajimsrc3dir = $(pkgdatadir)/src/common/zeroconf
gajimsrc3_DATA = \ gajimsrc3_PYTHON = \
$(srcdir)/common/zeroconf/*.py $(srcdir)/common/zeroconf/*.py
DISTCLEANFILES = DISTCLEANFILES =
EXTRA_DIST = $(gajimsrc_DATA) \ EXTRA_DIST = $(gajimsrc_PYTHON) \
$(gajimsrc1_DATA) \ $(gajimsrc1_PYTHON) \
$(gajimsrc2_DATA) \ $(gajimsrc2_PYTHON) \
$(gajimsrc3_DATA) \ $(gajimsrc3_PYTHON) \
gtkspellmodule.c \ gtkspellmodule.c \
eggtrayicon.c \ eggtrayicon.c \
trayiconmodule.c \ trayiconmodule.c \

View file

@ -151,7 +151,7 @@ class ChatControlBase(MessageControl):
self._on_banner_eventbox_button_press_event) self._on_banner_eventbox_button_press_event)
self.handlers[id] = widget self.handlers[id] = widget
self.urlfinder = re.compile("(https?://|www|ftp)[^ ]+") self.urlfinder = re.compile(r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
if gajim.HAVE_PYSEXY: if gajim.HAVE_PYSEXY:
import sexy import sexy
@ -643,7 +643,7 @@ class ChatControlBase(MessageControl):
self.orig_msg = None self.orig_msg = None
def print_conversation_line(self, text, kind, name, tim, def print_conversation_line(self, text, kind, name, tim,
other_tags_for_name = [], other_tags_for_time = [], other_tags_for_name = [], other_tags_for_time = [],
other_tags_for_text = [], count_as_new = True, other_tags_for_text = [], count_as_new = True,
subject = None, old_kind = None, xhtml = None): subject = None, old_kind = None, xhtml = None):
'''prints 'chat' type messages''' '''prints 'chat' type messages'''
@ -1017,7 +1017,11 @@ class ChatControl(ChatControlBase):
self.chat_buttons_set_visible(compact_view) self.chat_buttons_set_visible(compact_view)
self.widget_set_visible(self.xml.get_widget('banner_eventbox'), self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
gajim.config.get('hide_chat_banner')) gajim.config.get('hide_chat_banner'))
# Add lock image to show chat encryption
self.lock_image = self.xml.get_widget('lock_image')
self.lock_tooltip = gtk.Tooltips()
# keep timeout id and window obj for possible big avatar # keep timeout id and window obj for possible big avatar
# it is on enter-notify and leave-notify so no need to be per jid # it is on enter-notify and leave-notify so no need to be per jid
self.show_bigger_avatar_timeout_id = None self.show_bigger_avatar_timeout_id = None
@ -1050,15 +1054,25 @@ class ChatControl(ChatControlBase):
self.on_avatar_eventbox_button_press_event) self.on_avatar_eventbox_button_press_event)
self.handlers[id] = widget self.handlers[id] = widget
widget = self.xml.get_widget('gpg_togglebutton')
id = widget.connect('clicked', self.on_toggle_gpg_togglebutton)
self.handlers[id] = widget
if self.contact.jid in gajim.encrypted_chats[self.account]:
self.xml.get_widget('gpg_togglebutton').set_active(True)
self.set_session(session) self.set_session(session)
# Enable ecryption if needed
e2e_is_active = hasattr(self, 'session') and self.session and self.session.enable_encryption
self.gpg_is_active = False
gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled')
if not e2e_is_active and gpg_pref and gajim.config.get_per('accounts', self.account, 'keyid') and\
gajim.connections[self.account].USE_GPG:
self.gpg_is_active = True
gajim.encrypted_chats[self.account].append(contact.jid)
msg = _('GPG encryption enabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
if self.session:
self.session.loggable = gajim.config.get('log_encrypted_sessions')
self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \
self.session.is_loggable())
self.status_tooltip = gtk.Tooltips() self.status_tooltip = gtk.Tooltips()
self.update_ui() self.update_ui()
# restore previous conversation # restore previous conversation
@ -1164,8 +1178,6 @@ class ChatControl(ChatControlBase):
gtk.gdk.INTERP_BILINEAR) gtk.gdk.INTERP_BILINEAR)
banner_status_img.set_from_pixbuf(scaled_pix) banner_status_img.set_from_pixbuf(scaled_pix)
self._update_gpg()
def draw_banner_text(self): def draw_banner_text(self):
'''Draw the text in the fat line at the top of the window that '''Draw the text in the fat line at the top of the window that
houses the name, jid. houses the name, jid.
@ -1254,34 +1266,52 @@ class ChatControl(ChatControlBase):
# setup the label that holds name and jid # setup the label that holds name and jid
banner_name_label.set_markup(label_text) banner_name_label.set_markup(label_text)
def on_toggle_gpg_togglebutton(self, widget): def _toggle_gpg(self):
gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled', ec = gajim.encrypted_chats[self.account]
widget.get_active()) if self.gpg_is_active:
# Disable encryption
def _update_gpg(self): ec.remove(self.contact.jid)
tb = self.xml.get_widget('gpg_togglebutton') self.gpg_is_active = False
# we can do gpg msg = _('GPG encryption disabled')
# if self.contact is our own contact info (transports), ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
# don't enable pgp if self.session:
if self.contact.keyID and not gajim.jid_is_transport(self.contact.jid): self.session.loggable = True
tb.set_sensitive(True)
tt = _('OpenPGP Encryption')
# restore gpg pref
gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
'gpg_enabled')
if gpg_pref == None:
gajim.config.add_per('contacts', self.contact.jid)
gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
'gpg_enabled')
tb.set_active(gpg_pref)
else: else:
tb.set_sensitive(False) # Enable encryption
#we talk about a contact here ec.append(self.contact.jid)
tt = _('%s has not broadcast an OpenPGP key, nor has one been assigned') %\ self.gpg_is_active = True
self.contact.get_shown_name() msg = _('GPG encryption enabled')
gtk.Tooltips().set_tip(self.xml.get_widget('gpg_eventbox'), tt) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
if self.session:
self.session.loggable = gajim.config.get('log_encrypted_sessions');
if self.session and not self.session.is_loggable():
msg = _('Session WILL NOT be logged')
else:
msg = _('Session WILL be logged')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
'gpg_enabled')
if gpg_pref is None:
gajim.config.add_per('contacts', self.contact.jid)
gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled',
self.gpg_is_active)
self._show_lock_image(self.gpg_is_active, 'GPG', self.gpg_is_active, self.session and \
self.session.is_loggable())
def _show_lock_image(self, visible, enc_type = '', enc_enabled = False, chat_logged = False):
'''Set lock icon visibiity and create tooltip'''
status_string = enc_enabled and 'is' or 'is NOT'
logged_string = chat_logged and 'will' or 'will NOT'
tooltip = '%s Encryption %s active. \nYour chat session %s be logged.' %\
(enc_type, status_string, logged_string)
self.lock_tooltip.set_tip(self.lock_image, tooltip)
self.widget_set_visible(self.lock_image, not visible)
self.lock_image.set_sensitive(enc_enabled)
def _process_command(self, message): def _process_command(self, message):
if message[0] != '/': if message[0] != '/':
@ -1369,9 +1399,11 @@ class ChatControl(ChatControlBase):
encrypted = bool(self.session) and self.session.enable_encryption encrypted = bool(self.session) and self.session.enable_encryption
keyID = '' keyID = ''
if self.xml.get_widget('gpg_togglebutton').get_active(): if self.gpg_is_active:
keyID = contact.keyID keyID = contact.keyID
encrypted = True encrypted = True
if not keyID:
keyID = 'UNKNOWN'
chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
'disabled' 'disabled'
@ -1401,7 +1433,7 @@ class ChatControl(ChatControlBase):
gobject.source_remove(self.possible_paused_timeout_id) gobject.source_remove(self.possible_paused_timeout_id)
gobject.source_remove(self.possible_inactive_timeout_id) gobject.source_remove(self.possible_inactive_timeout_id)
self._schedule_activity_timers() self._schedule_activity_timers()
if not ChatControlBase.send_message(self, message, keyID, type = 'chat', if not ChatControlBase.send_message(self, message, keyID, type = 'chat',
chatstate = chatstate_to_send, composing_xep = composing_xep, chatstate = chatstate_to_send, composing_xep = composing_xep,
process_command = process_command): process_command = process_command):
@ -1468,13 +1500,14 @@ class ChatControl(ChatControlBase):
msg = _('Session negotiation cancelled') msg = _('Session negotiation cancelled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
# print esession settings to textview
def print_esession_details(self): def print_esession_details(self):
if self.session and self.session.enable_encryption: '''print esession settings to textview'''
e2e_is_active = self.session and self.session.enable_encryption
if e2e_is_active:
msg = _('E2E encryption enabled') msg = _('E2E encryption enabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
if self.session.loggable: if self.session.is_loggable():
msg = _('Session WILL be logged') msg = _('Session WILL be logged')
else: else:
msg = _('Session WILL NOT be logged') msg = _('Session WILL NOT be logged')
@ -1483,6 +1516,8 @@ class ChatControl(ChatControlBase):
else: else:
msg = _('E2E encryption disabled') msg = _('E2E encryption disabled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
self.session.is_loggable())
def print_conversation(self, text, frm = '', tim = None, def print_conversation(self, text, frm = '', tim = None,
encrypted = False, subject = None, xhtml = None): encrypted = False, subject = None, xhtml = None):
@ -1510,19 +1545,15 @@ class ChatControl(ChatControlBase):
'status', '', tim) 'status', '', tim)
else: else:
# GPG encryption # GPG encryption
ec = gajim.encrypted_chats[self.account] if encrypted and not self.gpg_is_active:
if encrypted and jid not in ec: msg = _('The following message was encrypted')
msg = _('OpenPGP Encryption enabled')
ChatControlBase.print_conversation_line(self, msg, ChatControlBase.print_conversation_line(self, msg,
'status', '', tim) 'status', '', tim)
ec.append(jid) self._toggle_gpg()
elif not encrypted and jid in ec: elif not encrypted and self.gpg_is_active:
msg = _('OpenPGP Encryption disabled') msg = _('The following message was NOT encrypted')
ChatControlBase.print_conversation_line(self, msg, ChatControlBase.print_conversation_line(self, msg,
'status', '', tim) 'status', '', tim)
ec.remove(jid)
self.xml.get_widget('gpg_togglebutton').set_active(encrypted)
if not frm: if not frm:
kind = 'incoming' kind = 'incoming'
name = contact.get_shown_name() name = contact.get_shown_name()
@ -1649,13 +1680,16 @@ class ChatControl(ChatControlBase):
contact = self.parent_win.get_active_contact() contact = self.parent_win.get_active_contact()
jid = contact.jid jid = contact.jid
# check if gpg capabitlies or else make gpg toggle insensitive # check if we support and use gpg
gpg_btn = self.xml.get_widget('gpg_togglebutton') if not gajim.config.get_per('accounts', self.account, 'keyid') or\
isactive = gpg_btn.get_active() not gajim.connections[self.account].USE_GPG or\
is_sensitive = gpg_btn.get_property('sensitive') gajim.jid_is_transport(jid):
toggle_gpg_menuitem.set_active(isactive) toggle_gpg_menuitem.set_sensitive(False)
toggle_gpg_menuitem.set_property('sensitive', is_sensitive) else:
e2e_is_active = int(self.session != None and self.session.enable_encryption)
toggle_gpg_menuitem.set_sensitive(not e2e_is_active)
toggle_gpg_menuitem.set_active(self.gpg_is_active)
# TODO: check that the remote client supports e2e # TODO: check that the remote client supports e2e
if not gajim.HAVE_PYCRYPTO: if not gajim.HAVE_PYCRYPTO:
@ -1663,6 +1697,7 @@ class ChatControl(ChatControlBase):
else: else:
isactive = int(self.session != None and self.session.enable_encryption) isactive = int(self.session != None and self.session.enable_encryption)
toggle_e2e_menuitem.set_active(isactive) toggle_e2e_menuitem.set_active(isactive)
toggle_e2e_menuitem.set_sensitive(not self.gpg_is_active)
# If we don't have resource, we can't do file transfer # If we don't have resource, we can't do file transfer
# in transports, contact holds our info we need to disable it too # in transports, contact holds our info we need to disable it too
@ -1694,14 +1729,15 @@ class ChatControl(ChatControlBase):
id = send_file_menuitem.connect('activate', id = send_file_menuitem.connect('activate',
self._on_send_file_menuitem_activate) self._on_send_file_menuitem_activate)
self.handlers[id] = send_file_menuitem self.handlers[id] = send_file_menuitem
id = add_to_roster_menuitem.connect('activate', id = add_to_roster_menuitem.connect('activate',
self._on_add_to_roster_menuitem_activate) self._on_add_to_roster_menuitem_activate)
self.handlers[id] = add_to_roster_menuitem self.handlers[id] = add_to_roster_menuitem
id = toggle_gpg_menuitem.connect('activate', id = toggle_gpg_menuitem.connect('activate',
self._on_toggle_gpg_menuitem_activate) self._on_toggle_gpg_menuitem_activate)
self.handlers[id] = toggle_gpg_menuitem
id = toggle_e2e_menuitem.connect('activate', id = toggle_e2e_menuitem.connect('activate',
self._on_toggle_e2e_menuitem_activate) self._on_toggle_e2e_menuitem_activate)
self.handlers[id] = toggle_gpg_menuitem self.handlers[id] = toggle_e2e_menuitem
id = information_menuitem.connect('activate', id = information_menuitem.connect('activate',
self._on_contact_information_menuitem_activate) self._on_contact_information_menuitem_activate)
self.handlers[id] = information_menuitem self.handlers[id] = information_menuitem
@ -2154,10 +2190,7 @@ class ChatControl(ChatControlBase):
gajim.interface.roster.on_info(widget, self.contact, self.account) gajim.interface.roster.on_info(widget, self.contact, self.account)
def _on_toggle_gpg_menuitem_activate(self, widget): def _on_toggle_gpg_menuitem_activate(self, widget):
# update the button self._toggle_gpg()
# this is reverse logic, as we are on 'activate' (before change happens)
tb = self.xml.get_widget('gpg_togglebutton')
tb.set_active(not tb.get_active())
def _on_convert_to_gc_menuitem_activate(self, widget): def _on_convert_to_gc_menuitem_activate(self, widget):
'''user want to invite some friends to chat''' '''user want to invite some friends to chat'''

View file

@ -28,24 +28,12 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## ##
import os import gajim
from os import tmpfile from os import tmpfile
from common import helpers from common import helpers
USE_GPG = True if gajim.HAVE_GPG:
import GnuPGInterface
try:
import GnuPGInterface # Debian package doesn't distribute 'our' file
except ImportError:
try:
from common import GnuPGInterface # use 'our' file
except ImportError:
USE_GPG = False # user can't do OpenGPG only if he or she removed the file!
else:
status = os.system('gpg -h >/dev/null 2>&1')
if status != 0:
USE_GPG = False
class GnuPG(GnuPGInterface.GnuPG): class GnuPG(GnuPGInterface.GnuPG):
def __init__(self, use_agent = False): def __init__(self, use_agent = False):
@ -57,8 +45,6 @@ else:
self.options.armor = 1 self.options.armor = 1
self.options.meta_interactive = 0 self.options.meta_interactive = 0
self.options.extra_args.append('--no-secmem-warning') self.options.extra_args.append('--no-secmem-warning')
# Nolith's patch - prevent crashs on non fully-trusted keys
self.options.extra_args.append('--always-trust')
if self.use_agent: if self.use_agent:
self.options.extra_args.append('--use-agent') self.options.extra_args.append('--use-agent')
@ -88,8 +74,6 @@ else:
return resp return resp
def encrypt(self, str, recipients): def encrypt(self, str, recipients):
if not USE_GPG:
return str, 'GnuPG not usable'
self.options.recipients = recipients # a list! self.options.recipients = recipients # a list!
proc = self.run(['--encrypt'], create_fhs=['stdin', 'stdout', 'status', proc = self.run(['--encrypt'], create_fhs=['stdin', 'stdout', 'status',
@ -125,8 +109,6 @@ else:
return self._stripHeaderFooter(output), error return self._stripHeaderFooter(output), error
def decrypt(self, str, keyID): def decrypt(self, str, keyID):
if not USE_GPG:
return str
proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout']) proc = self.run(['--decrypt', '-q', '-u %s'%keyID], create_fhs=['stdin', 'stdout'])
enc = self._addHeaderFooter(str, 'MESSAGE') enc = self._addHeaderFooter(str, 'MESSAGE')
proc.handles['stdin'].write(enc) proc.handles['stdin'].write(enc)
@ -140,8 +122,6 @@ else:
return output return output
def sign(self, str, keyID): def sign(self, str, keyID):
if not USE_GPG:
return str
proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr']) proc = self.run(['-b', '-u %s'%keyID], create_fhs=['stdin', 'stdout', 'status', 'stderr'])
proc.handles['stdin'].write(str) proc.handles['stdin'].write(str)
try: try:
@ -170,8 +150,6 @@ else:
return 'BAD_PASSPHRASE' return 'BAD_PASSPHRASE'
def verify(self, str, sign): def verify(self, str, sign):
if not USE_GPG:
return str
if str == None: if str == None:
return '' return ''
f = tmpfile() f = tmpfile()
@ -193,15 +171,13 @@ else:
try: proc.wait() try: proc.wait()
except IOError: pass except IOError: pass
keyid = '' keyid = ''
if resp.has_key('GOODSIG'): if resp.has_key('GOODSIG'):
keyid = resp['GOODSIG'].split()[0] keyid = resp['GOODSIG'].split()[0]
return keyid return keyid
def get_keys(self, secret = False): def get_keys(self, secret = False):
if not USE_GPG:
return {}
if secret: if secret:
opt = '--list-secret-keys' opt = '--list-secret-keys'
else: else:
@ -217,8 +193,10 @@ else:
sline = line.split(':') sline = line.split(':')
if (sline[0] == 'sec' and secret) or \ if (sline[0] == 'sec' and secret) or \
(sline[0] == 'pub' and not secret): (sline[0] == 'pub' and not secret):
# decode escaped chars
name = eval('"' + sline[9].replace('"', '\\"') + '"')
# make it unicode instance # make it unicode instance
keys[sline[4][8:]] = helpers.decode_string(sline[9]) keys[sline[4][8:]] = helpers.decode_string(name)
return keys return keys
try: proc.wait() try: proc.wait()
except IOError: pass except IOError: pass

View file

@ -45,7 +45,7 @@ opt_int = [ 'integer', 0 ]
opt_str = [ 'string', 0 ] opt_str = [ 'string', 0 ]
opt_bool = [ 'boolean', 0 ] opt_bool = [ 'boolean', 0 ]
opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ] opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
opt_one_window_types = ['never', 'always', 'peracct', 'pertype'] opt_one_window_types = ['never', 'always', 'always_with_roster', 'peracct', 'pertype']
opt_treat_incoming_messages = ['', 'chat', 'normal'] opt_treat_incoming_messages = ['', 'chat', 'normal']
class Config: class Config:
@ -100,7 +100,6 @@ class Config:
'urlmsgcolor': [ opt_color, '#0000ff', '', True ], 'urlmsgcolor': [ opt_color, '#0000ff', '', True ],
'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ], 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
'roster_theme': [ opt_str, _('default'), '', True ], 'roster_theme': [ opt_str, _('default'), '', True ],
'saveposition': [ opt_bool, True ],
'mergeaccounts': [ opt_bool, False, '', True ], 'mergeaccounts': [ opt_bool, False, '', True ],
'sort_by_show': [ opt_bool, True, '', True ], 'sort_by_show': [ opt_bool, True, '', True ],
'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')], 'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
@ -214,6 +213,7 @@ class Config:
'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')], 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True], 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
'show_avatars_in_roster': [opt_bool, True, '', True], 'show_avatars_in_roster': [opt_bool, True, '', True],
'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')], 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')], 'print_status_in_chats': [opt_bool, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], 'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
@ -229,7 +229,7 @@ class Config:
'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')], 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
'one_message_window': [opt_str, 'always', 'one_message_window': [opt_str, 'always',
#always, never, peracct, pertype should not be translated #always, never, peracct, pertype should not be translated
_('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window. Note, changing this option requires restarting Gajim before the changes will take effect.')], _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window.')],
'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')], 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')], 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')], 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
@ -250,7 +250,14 @@ class Config:
'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')], 'use_latex': [opt_bool, False, _('If True, Gajim will convert string between $$ and $$ to an image using dvips and convert before insterting it in chat window.')],
'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')], 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')], 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
'publish_mood': [opt_bool, False],
'publish_activity': [opt_bool, False],
'publish_tune': [opt_bool, False],
'subscribe_mood': [opt_bool, True],
'subscribe_activity': [opt_bool, True],
'subscribe_tune': [opt_bool, True],
'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')], 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to systray icon.')],
'use_pep': [opt_bool, False, 'temporary variable to enable pep support'],
} }
__options_per_key = { __options_per_key = {
@ -275,7 +282,8 @@ class Config:
'keyid': [ opt_str, '', '', True ], 'keyid': [ opt_str, '', '', True ],
'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ], 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
'keyname': [ opt_str, '', '', True ], 'keyname': [ opt_str, '', '', True ],
'usessl': [ opt_bool, False, '', True ], 'connection_types': [ opt_str, 'tls ssl plain', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
'warn_when_insecure_connection': [ opt_bool, True, _('Show a warning dialog before sending password on an insecure connection.') ],
'ssl_fingerprint_sha1': [ opt_str, '', '', True ], 'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
'use_srv': [ opt_bool, True, '', True ], 'use_srv': [ opt_bool, True, '', True ],
'use_custom_host': [ opt_bool, False, '', True ], 'use_custom_host': [ opt_bool, False, '', True ],
@ -306,6 +314,7 @@ class Config:
'zeroconf_last_name': [ opt_str, '', '', True ], 'zeroconf_last_name': [ opt_str, '', '', True ],
'zeroconf_jabber_id': [ opt_str, '', '', True ], 'zeroconf_jabber_id': [ opt_str, '', '', True ],
'zeroconf_email': [ opt_str, '', '', True ], 'zeroconf_email': [ opt_str, '', '', True ],
'use_env_http_proxy' : [opt_bool, False],
}, {}), }, {}),
'statusmsg': ({ 'statusmsg': ({
'message': [ opt_str, '' ], 'message': [ opt_str, '' ],
@ -354,7 +363,7 @@ class Config:
'state_muc_directed_msg_color': [ opt_color, 'red2' ], 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
}, {}), }, {}),
'contacts': ({ 'contacts': ({
'gpg_enabled': [ opt_bool, True, _('Is OpenPGP enabled for this contact?')], 'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')], 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
}, {}), }, {}),
'rooms': ({ 'rooms': ({
@ -400,7 +409,8 @@ class Config:
soundevents_default = { soundevents_default = {
'first_message_received': [ True, '../data/sounds/message1.wav' ], 'first_message_received': [ True, '../data/sounds/message1.wav' ],
'next_message_received': [ True, '../data/sounds/message2.wav' ], 'next_message_received_focused': [ True, '../data/sounds/message2.wav' ],
'next_message_received_unfocused': [ True, '../data/sounds/message2.wav' ],
'contact_connected': [ True, '../data/sounds/connected.wav' ], 'contact_connected': [ True, '../data/sounds/connected.wav' ],
'contact_disconnected': [ True, '../data/sounds/disconnected.wav' ], 'contact_disconnected': [ True, '../data/sounds/disconnected.wav' ],
'message_sent': [ True, '../data/sounds/sent.wav' ], 'message_sent': [ True, '../data/sounds/sent.wav' ],

View file

@ -48,7 +48,6 @@ from common import passwords
from common import exceptions from common import exceptions
from connection_handlers import * from connection_handlers import *
USE_GPG = GnuPG.USE_GPG
from common.rst_xhtml_generator import create_xhtml from common.rst_xhtml_generator import create_xhtml
@ -56,8 +55,6 @@ from string import Template
import logging import logging
log = logging.getLogger('gajim.c.connection') log = logging.getLogger('gajim.c.connection')
import gtkgui_helpers
ssl_error = { ssl_error = {
2: _("Unable to get issuer certificate"), 2: _("Unable to get issuer certificate"),
3: _("Unable to get certificate CRL"), 3: _("Unable to get certificate CRL"),
@ -75,9 +72,9 @@ ssl_error = {
15: _("Format error in CRL's lastUpdate field"), 15: _("Format error in CRL's lastUpdate field"),
16: _("Format error in CRL's nextUpdate field"), 16: _("Format error in CRL's nextUpdate field"),
17: _("Out of memory"), 17: _("Out of memory"),
18: _("Self signed certificate in certificate chain"), 18: _("Self signed certificate"),
19: _("Unable to get local issuer certificate"), 19: _("Self signed certificate in certificate chain"),
20: _("Unable to verify the first certificate"), 20: _("Unable to get local issuer certificate"),
21: _("Unable to verify the first certificate"), 21: _("Unable to verify the first certificate"),
22: _("Certificate chain too long"), 22: _("Certificate chain too long"),
23: _("Certificate revoked"), 23: _("Certificate revoked"),
@ -107,7 +104,9 @@ class Connection(ConnectionHandlers):
self.last_connection = None # last ClientCommon instance self.last_connection = None # last ClientCommon instance
self.is_zeroconf = False self.is_zeroconf = False
self.gpg = None self.gpg = None
if USE_GPG: self.USE_GPG = False
if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
self.status = '' self.status = ''
self.priority = gajim.get_priority(name, 'offline') self.priority = gajim.get_priority(name, 'offline')
@ -142,6 +141,9 @@ class Connection(ConnectionHandlers):
self.blocked_contacts = [] self.blocked_contacts = []
self.blocked_groups = [] self.blocked_groups = []
self.pep_supported = False self.pep_supported = False
self.mood = {}
self.tune = {}
self.activity = {}
# Do we continue connection when we get roster (send presence,get vcard..) # Do we continue connection when we get roster (send presence,get vcard..)
self.continue_connect_info = None self.continue_connect_info = None
# To know the groupchat jid associated with a sranza ID. Useful to # To know the groupchat jid associated with a sranza ID. Useful to
@ -178,7 +180,7 @@ class Connection(ConnectionHandlers):
self.dispatch('STATUS', 'connecting') self.dispatch('STATUS', 'connecting')
self.retrycount += 1 self.retrycount += 1
self.on_connect_auth = self._init_roster self.on_connect_auth = self._init_roster
self.connect_and_init(self.old_show, self.status, self.gpg != None) self.connect_and_init(self.old_show, self.status, self.USE_GPG)
else: else:
# reconnect succeeded # reconnect succeeded
self.time_to_reconnect = None self.time_to_reconnect = None
@ -186,6 +188,8 @@ class Connection(ConnectionHandlers):
# We are doing disconnect at so many places, better use one function in all # We are doing disconnect at so many places, better use one function in all
def disconnect(self, on_purpose=False): def disconnect(self, on_purpose=False):
#FIXME: set the Tune to None before disconnection per account
#gajim.interface.roster._music_track_changed(None, None)
self.on_purpose = on_purpose self.on_purpose = on_purpose
self.connected = 0 self.connected = 0
self.time_to_reconnect = None self.time_to_reconnect = None
@ -265,7 +269,8 @@ class Connection(ConnectionHandlers):
if not common.xmpp.isResultNode(result): if not common.xmpp.isResultNode(result):
self.dispatch('ACC_NOT_OK', (result.getError())) self.dispatch('ACC_NOT_OK', (result.getError()))
return return
if USE_GPG: if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get( self.gpg = GnuPG.GnuPG(gajim.config.get(
'use_gpg_agent')) 'use_gpg_agent'))
self.dispatch('ACC_OK', (self.new_account_info)) self.dispatch('ACC_OK', (self.new_account_info))
@ -279,7 +284,7 @@ class Connection(ConnectionHandlers):
# typed, so send them # typed, so send them
if is_form: if is_form:
#TODO: Check if form has changed #TODO: Check if form has changed
iq = Iq('set', NS_REGISTER, to=self._hostname) iq = common.xmpp.Iq('set', common.xmpp.NS_REGISTER, to=self._hostname)
iq.setTag('query').addChild(node=self.new_account_form) iq.setTag('query').addChild(node=self.new_account_form)
self.connection.SendAndCallForResponse(iq, self.connection.SendAndCallForResponse(iq,
_on_register_result) _on_register_result)
@ -313,7 +318,7 @@ class Connection(ConnectionHandlers):
ssl_fingerprint = \ ssl_fingerprint = \
self.connection.Connection.ssl_fingerprint_sha1 self.connection.Connection.ssl_fingerprint_sha1
self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg, self.dispatch('NEW_ACC_CONNECTED', (conf, is_form, ssl_msg,
ssl_cert, ssl_fingerprint)) errnum, ssl_cert, ssl_fingerprint))
self.connection.UnregisterDisconnectHandler( self.connection.UnregisterDisconnectHandler(
self._on_new_account) self._on_new_account)
self.disconnect(on_purpose=True) self.disconnect(on_purpose=True)
@ -423,27 +428,56 @@ class Connection(ConnectionHandlers):
proxy['user'] = gajim.config.get_per('proxies', p, 'user') proxy['user'] = gajim.config.get_per('proxies', p, 'user')
proxy['password'] = gajim.config.get_per('proxies', p, 'pass') proxy['password'] = gajim.config.get_per('proxies', p, 'pass')
proxy['type'] = gajim.config.get_per('proxies', p, 'type') proxy['type'] = gajim.config.get_per('proxies', p, 'type')
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try:
try:
env_http_proxy = os.environ['HTTP_PROXY']
except:
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['password'] = login[1]
else:
proxy['password'] = u''
except:
proxy = None
else: else:
proxy = None proxy = None
h = hostname h = hostname
p = 5222 p = 5222
# autodetect [for SSL in 5223/443 and for TLS if broadcasted] ssl_p = 5223
secur = None # use_srv = False # wants ssl? disable srv lookup
if usessl:
p = 5223
secur = 1 # 1 means force SSL no matter what the port will be
use_srv = False # wants ssl? disable srv lookup
if use_custom: if use_custom:
h = custom_h h = custom_h
p = custom_p p = custom_p
ssl_p = custom_p
use_srv = False use_srv = False
hosts = []
# SRV resolver # SRV resolver
self._proxy = proxy self._proxy = proxy
self._secure = secur self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
self._hosts = [ {'host': h, 'port': p, 'prio': 10, 'weight': 10} ] 'weight': 10} ]
self._hostname = hostname self._hostname = hostname
if use_srv: if use_srv:
# add request for srv query to the resolve, on result '_on_resolve' # add request for srv query to the resolve, on result '_on_resolve'
@ -457,6 +491,12 @@ class Connection(ConnectionHandlers):
# SRV query returned at least one valid result, we put it in hosts dict # SRV query returned at least one valid result, we put it in hosts dict
if len(result_array) != 0: if len(result_array) != 0:
self._hosts = [i for i in result_array] self._hosts = [i for i in result_array]
# 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')
for i in self._hosts:
i['ssl_port'] = ssl_p
self.connect_to_next_host() self.connect_to_next_host()
def on_proxy_failure(self, reason): def on_proxy_failure(self, reason):
@ -468,8 +508,9 @@ class Connection(ConnectionHandlers):
self.dispatch('CONNECTION_LOST', self.dispatch('CONNECTION_LOST',
(_('Connection to proxy failed'), reason)) (_('Connection to proxy failed'), reason))
def connect_to_next_host(self, retry = False): def connect_to_next_type(self, retry=False):
if len(self._hosts): if len(self._connection_types):
self._current_type = self._connection_types.pop(0)
if self.last_connection: if self.last_connection:
self.last_connection.socket.disconnect() self.last_connection.socket.disconnect()
self.last_connection = None self.last_connection = None
@ -478,27 +519,49 @@ class Connection(ConnectionHandlers):
con = common.xmpp.NonBlockingClient(self._hostname, caller = self, con = common.xmpp.NonBlockingClient(self._hostname, caller = self,
on_connect = self.on_connect_success, on_connect = self.on_connect_success,
on_proxy_failure = self.on_proxy_failure, on_proxy_failure = self.on_proxy_failure,
on_connect_failure = self.connect_to_next_host) on_connect_failure = self.connect_to_next_type)
else: else:
con = common.xmpp.NonBlockingClient(self._hostname, debug = [], caller = self, con = common.xmpp.NonBlockingClient(self._hostname, debug = [],
on_connect = self.on_connect_success, caller = self, on_connect = self.on_connect_success,
on_proxy_failure = self.on_proxy_failure, on_proxy_failure = self.on_proxy_failure,
on_connect_failure = self.connect_to_next_host) on_connect_failure = self.connect_to_next_type)
self.last_connection = con self.last_connection = con
# increase default timeout for server responses # 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
con.set_idlequeue(gajim.idlequeue) con.set_idlequeue(gajim.idlequeue)
host = self.select_next_host(self._hosts)
self._current_host = host
self._hosts.remove(host)
# FIXME: this is a hack; need a better way # FIXME: this is a hack; need a better way
if self.on_connect_success == self._on_new_account: if self.on_connect_success == self._on_new_account:
con.RegisterDisconnectHandler(self._on_new_account) con.RegisterDisconnectHandler(self._on_new_account)
log.info("Connecting to %s: [%s:%d]", self.name, host['host'], host['port']) if self._current_type == 'ssl':
con.connect((host['host'], host['port']), proxy = self._proxy, port = self._current_host['ssl_port']
secure = self._secure) secur = 1
else:
port = self._current_host['port']
if self._current_type == 'plain':
secur = 0
else:
secur = None
log.info('Connecting to %s: [%s:%d]', self.name,
self._current_host['host'], port)
con.connect((self._current_host['host'], port), proxy=self._proxy,
secure = secur)
else:
self.connect_to_next_host(retry)
def connect_to_next_host(self, retry = False):
if len(self._hosts):
# No config option exist when creating a new account
if self.name in gajim.config.get_per('accounts'):
self._connection_types = gajim.config.get_per('accounts', self.name,
'connection_types').split()
else:
self._connection_types = ['tls', 'ssl', 'plain']
host = self.select_next_host(self._hosts)
self._current_host = host
self._hosts.remove(host)
self.connect_to_next_type()
else: else:
if not retry and self.retrycount == 0: if not retry and self.retrycount == 0:
log.debug("Out of hosts, giving up connecting to %s", self.name) log.debug("Out of hosts, giving up connecting to %s", self.name)
@ -527,15 +590,26 @@ class Connection(ConnectionHandlers):
if not self.connected: # We went offline during connecting process if not self.connected: # We went offline during connecting process
# FIXME - not possible, maybe it was when we used threads # FIXME - not possible, maybe it was when we used threads
return return
_con_type = con_type
# xmpp returns 'tcp', but we set 'plain' in connection_types in config
if _con_type == 'tcp':
_con_type = 'plain'
if _con_type != self._current_type:
self.connect_to_next_type()
return
if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
'warn_when_insecure_connection'):
self.dispatch('PLAIN_CONNECTION', (con,))
return True
return self.connection_accepted(con, con_type)
def connection_accepted(self, con, con_type):
self.hosts = [] self.hosts = []
if not con_type:
log.debug('Could not connect to %s:%s' % (self._current_host['host'],
self._current_host['port']))
self.connected_hostname = self._current_host['host'] self.connected_hostname = self._current_host['host']
self.on_connect_failure = None self.on_connect_failure = None
con.RegisterDisconnectHandler(self._disconnectedReconnCB) con.RegisterDisconnectHandler(self._disconnectedReconnCB)
log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'], log.debug('Connected to server %s:%s with %s' % (
self._current_host['port'], con_type)) self._current_host['host'], self._current_host['port'], con_type))
name = gajim.config.get_per('accounts', self.name, 'name') name = gajim.config.get_per('accounts', self.name, 'name')
hostname = gajim.config.get_per('accounts', self.name, 'hostname') hostname = gajim.config.get_per('accounts', self.name, 'hostname')
@ -548,10 +622,10 @@ class Connection(ConnectionHandlers):
text = _('The authenticity of the %s certificate could be invalid.') %\ text = _('The authenticity of the %s certificate could be invalid.') %\
hostname hostname
if errnum in ssl_error: if errnum in ssl_error:
text += _('\nSSL Error: %s') % ssl_error[errnum] text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
else: else:
text += _('\nUnknown SSL error: %d') % errnum text += _('\nUnknown SSL error: %d') % errnum
self.dispatch('SSL_ERROR', (text, con.Connection.ssl_cert_pem, self.dispatch('SSL_ERROR', (text, errnum, con.Connection.ssl_cert_pem,
con.Connection.ssl_fingerprint_sha1)) con.Connection.ssl_fingerprint_sha1))
return True return True
if hasattr(con.Connection, 'ssl_fingerprint_sha1'): if hasattr(con.Connection, 'ssl_fingerprint_sha1'):
@ -565,11 +639,17 @@ class Connection(ConnectionHandlers):
self._register_handlers(con, con_type) self._register_handlers(con, con_type)
con.auth(name, self.password, self.server_resource, 1, self.__on_auth) con.auth(name, self.password, self.server_resource, 1, self.__on_auth)
def ssl_certificate_accepted(self): def ssl_certificate_accepted(self):
name = gajim.config.get_per('accounts', self.name, 'name') name = gajim.config.get_per('accounts', self.name, 'name')
self._register_handlers(self.connection, 'ssl') self._register_handlers(self.connection, 'ssl')
self.connection.auth(name, self.password, self.server_resource, 1, self.__on_auth) self.connection.auth(name, self.password, self.server_resource, 1,
self.__on_auth)
def plain_connection_accepted(self):
name = gajim.config.get_per('accounts', self.name, 'name')
self._register_handlers(self.connection, 'tcp')
self.connection.auth(name, self.password, self.server_resource, 1,
self.__on_auth)
def _register_handlers(self, con, con_type): def _register_handlers(self, con, con_type):
self.peerhost = con.get_peerhost() self.peerhost = con.get_peerhost()
@ -699,6 +779,17 @@ class Connection(ConnectionHandlers):
def send_invisible_presence(self, msg, signed, initial = False): def send_invisible_presence(self, msg, signed, initial = False):
if not self.connection: if not self.connection:
return return
# If we are already connected, and privacy rules are supported, send
# offline presence first as it's required by XEP-0126
if self.connected > 1 and self.privacy_rules_supported:
self.on_purpose = True
p = common.xmpp.Presence(typ = 'unavailable')
p = self.add_sha(p, False)
if msg:
p.setStatus(msg)
self.remove_all_transfers()
self.connection.send(p)
# try to set the privacy rule # try to set the privacy rule
iq = self.build_privacy_rule('invisible', 'deny') iq = self.build_privacy_rule('invisible', 'deny')
self.connection.SendAndCallForResponse(iq, self._continue_invisible, self.connection.SendAndCallForResponse(iq, self._continue_invisible,
@ -707,8 +798,7 @@ class Connection(ConnectionHandlers):
def _continue_invisible(self, con, iq_obj, msg, signed, initial): def _continue_invisible(self, con, iq_obj, msg, signed, initial):
ptype = '' ptype = ''
show = '' show = ''
# FIXME: JEP 126 need some modifications (see http://lists.jabber.ru/pipermail/ejabberd/2005-July/001252.html). So I disable it for the moment if iq_obj.getType() == 'error': # server doesn't support privacy lists
if 1 or iq_obj.getType() == 'error': #server doesn't support privacy lists
# We use the old way which is not xmpp complient # We use the old way which is not xmpp complient
ptype = 'invisible' ptype = 'invisible'
show = 'invisible' show = 'invisible'
@ -760,7 +850,7 @@ class Connection(ConnectionHandlers):
callback is the function to call when user give the passphrase''' callback is the function to call when user give the passphrase'''
signed = '' signed = ''
keyID = gajim.config.get_per('accounts', self.name, 'keyid') keyID = gajim.config.get_per('accounts', self.name, 'keyid')
if keyID and self.gpg: if keyID and self.USE_GPG:
use_gpg_agent = gajim.config.get('use_gpg_agent') use_gpg_agent = gajim.config.get('use_gpg_agent')
if self.gpg.passphrase is None and not use_gpg_agent: if self.gpg.passphrase is None and not use_gpg_agent:
# We didn't set a passphrase # We didn't set a passphrase
@ -768,7 +858,7 @@ class Connection(ConnectionHandlers):
if self.gpg.passphrase is not None or use_gpg_agent: if self.gpg.passphrase is not None or use_gpg_agent:
signed = self.gpg.sign(msg, keyID) signed = self.gpg.sign(msg, keyID)
if signed == 'BAD_PASSPHRASE': if signed == 'BAD_PASSPHRASE':
self.gpg = None self.USE_GPG = False
signed = '' signed = ''
self.dispatch('BAD_PASSPHRASE', ()) self.dispatch('BAD_PASSPHRASE', ())
return signed return signed
@ -845,7 +935,8 @@ class Connection(ConnectionHandlers):
safe_substitute({ safe_substitute({
'hostname': socket.gethostname() 'hostname': socket.gethostname()
}) })
if USE_GPG: if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
self.connect_and_init(show, msg, sign_msg) self.connect_and_init(show, msg, sign_msg)
@ -922,9 +1013,15 @@ class Connection(ConnectionHandlers):
fjid += '/' + resource fjid += '/' + resource
msgtxt = msg msgtxt = msg
msgenc = '' msgenc = ''
if keyID and self.gpg:
#encrypt if keyID and self.USE_GPG:
msgenc, error = self.gpg.encrypt(msg, [keyID]) if keyID == 'UNKNOWN':
error = _('Neither the remote presence is signed, nor a key was assigned.')
elif keyID[8:] == 'MISMATCH':
error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8])
else:
#encrypt
msgenc, error = self.gpg.encrypt(msg, [keyID])
if msgenc and not error: if msgenc and not error:
msgtxt = '[This message is encrypted]' msgtxt = '[This message is encrypted]'
lang = os.getenv('LANG') lang = os.getenv('LANG')
@ -1109,7 +1206,7 @@ class Connection(ConnectionHandlers):
def send_new_account_infos(self, form, is_form): def send_new_account_infos(self, form, is_form):
if is_form: if is_form:
# Get username and password and put them in new_account_info # Get username and password and put them in new_account_info
for field in self._data_form.iter_fields(): for field in form.iter_fields():
if field.var == 'username': if field.var == 'username':
self.new_account_info['name'] = field.value self.new_account_info['name'] = field.value
if field.var == 'password': if field.var == 'password':

View file

@ -35,15 +35,20 @@ from calendar import timegm
import socks5 import socks5
import common.xmpp import common.xmpp
from common import GnuPG
from common import helpers from common import helpers
from common import gajim from common import gajim
from common import atom from common import atom
from common import pep
from common import exceptions from common import exceptions
from common.commands import ConnectionCommands from common.commands import ConnectionCommands
from common.pubsub import ConnectionPubSub from common.pubsub import ConnectionPubSub
from common.caps import ConnectionCaps from common.caps import ConnectionCaps
from common import dbus_support
if dbus_support.supported:
import dbus
from music_track_listener import MusicTrackListener
from common.stanza_session import EncryptedStanzaSession from common.stanza_session import EncryptedStanzaSession
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
@ -54,6 +59,7 @@ VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed' AGENT_REMOVED = 'agent_removed'
METACONTACTS_ARRIVED = 'metacontacts_arrived' METACONTACTS_ARRIVED = 'metacontacts_arrived'
PRIVACY_ARRIVED = 'privacy_arrived' PRIVACY_ARRIVED = 'privacy_arrived'
PEP_ACCESS_MODEL = 'pep_access_model'
HAS_IDLE = True HAS_IDLE = True
try: try:
import idle import idle
@ -182,7 +188,7 @@ class ConnectionBytestream:
ft_add_hosts_to_send = map(lambda e:e.strip(), ft_add_hosts_to_send = map(lambda e:e.strip(),
ft_add_hosts_to_send.split(',')) ft_add_hosts_to_send.split(','))
for ft_host in ft_add_hosts_to_send: for ft_host in ft_add_hosts_to_send:
ft_add_hosts.append(ft_host) ft_add_hosts.append(ft_host)
listener = gajim.socks5queue.start_listener(port, listener = gajim.socks5queue.start_listener(port,
sha_str, self._result_socks5_sid, file_props['sid']) sha_str, self._result_socks5_sid, file_props['sid'])
if listener == None: if listener == None:
@ -750,6 +756,13 @@ class ConnectionDisco:
q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC})
q.addChild('feature', attrs = {'var': common.xmpp.NS_COMMANDS}) q.addChild('feature', attrs = {'var': common.xmpp.NS_COMMANDS})
q.addChild('feature', attrs = {'var': common.xmpp.NS_DISCO_INFO}) q.addChild('feature', attrs = {'var': common.xmpp.NS_DISCO_INFO})
if gajim.config.get('use_pep'):
q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY})
q.addChild('feature', attrs = {'var': common.xmpp.NS_ACTIVITY + '+notify'})
q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE})
q.addChild('feature', attrs = {'var': common.xmpp.NS_TUNE + '+notify'})
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD})
q.addChild('feature', attrs = {'var': common.xmpp.NS_MOOD + '+notify'})
q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT}) q.addChild('feature', attrs = {'var': common.xmpp.NS_ESESSION_INIT})
if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': if (node is None or extension == 'cstates') and gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
@ -821,6 +834,12 @@ class ConnectionDisco:
if identity['category'] == 'pubsub' and identity['type'] == \ if identity['category'] == 'pubsub' and identity['type'] == \
'pep': 'pep':
self.pep_supported = True self.pep_supported = True
if dbus_support.supported:
listener = MusicTrackListener.get()
track = listener.get_playing_track()
if gajim.config.get('publish_tune'):
gajim.interface.roster._music_track_changed(listener,
track, self.name)
break break
if features.__contains__(common.xmpp.NS_BYTESTREAM): if features.__contains__(common.xmpp.NS_BYTESTREAM):
gajim.proxy65_manager.resolve(jid, self.connection, self.name) gajim.proxy65_manager.resolve(jid, self.connection, self.name)
@ -860,7 +879,7 @@ class ConnectionVcard:
ext.append('xhtml') ext.append('xhtml')
if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled': if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
ext.append('cstates') ext.append('cstates')
if len(ext): if len(ext):
c.setAttr('ext', ' '.join(ext)) c.setAttr('ext', ' '.join(ext))
c.setAttr('ver', gajim.version.split('-', 1)[0]) c.setAttr('ver', gajim.version.split('-', 1)[0])
@ -1099,6 +1118,16 @@ class ConnectionVcard:
self.get_privacy_list('block') self.get_privacy_list('block')
# Ask metacontacts before roster # Ask metacontacts before roster
self.get_metacontacts() self.get_metacontacts()
elif self.awaiting_answers[id][0] == PEP_ACCESS_MODEL:
conf = iq_obj.getTag('pubsub').getTag('configure')
node = conf.getAttr('node')
form_tag = conf.getTag('x', namespace=common.xmpp.NS_DATA)
if form_tag:
form = common.dataforms.ExtendForm(node=form_tag)
for field in form.iter_fields():
if field.var == 'pubsub#access_model':
self.dispatch('PEP_ACCESS_MODEL', (node, field.value))
break
del self.awaiting_answers[id] del self.awaiting_answers[id]
@ -1251,7 +1280,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
reply.setType('error') reply.setType('error')
reply.addChild(feature) reply.addChild(feature)
reply.addChild(node=xmpp.ErrorNode('service-unavailable', typ='cancel')) reply.addChild(node=common.xmpp.ErrorNode('service-unavailable', typ='cancel'))
con.send(reply) con.send(reply)
@ -1419,7 +1448,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
iq_obj = iq_obj.buildReply('result') iq_obj = iq_obj.buildReply('result')
qp = iq_obj.getTag('query') qp = iq_obj.getTag('query')
qp.setTagData('utc', strftime('%Y%m%dT%T', gmtime())) qp.setTagData('utc', strftime('%Y%m%dT%T', gmtime()))
qp.setTagData('tz', tzname[daylight]) qp.setTagData('tz', helpers.decode_string(tzname[daylight]))
qp.setTagData('display', helpers.decode_string(strftime('%c', qp.setTagData('display', helpers.decode_string(strftime('%c',
localtime()))) localtime())))
self.connection.send(iq_obj) self.connection.send(iq_obj)
@ -1599,7 +1628,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
if not user_nick: if not user_nick:
user_nick = '' user_nick = ''
if encTag and GnuPG.USE_GPG: if encTag and self.USE_GPG:
#decrypt #decrypt
encmsg = encTag.getData() encmsg = encTag.getData()
@ -1770,8 +1799,22 @@ returns the session that we last sent a message to.'''
''' Called when we receive <message/> with pubsub event. ''' ''' Called when we receive <message/> with pubsub event. '''
# TODO: Logging? (actually services where logging would be useful, should # TODO: Logging? (actually services where logging would be useful, should
# TODO: allow to access archives remotely...) # TODO: allow to access archives remotely...)
jid = helpers.get_full_jid_from_iq(msg)
event = msg.getTag('event') event = msg.getTag('event')
# XEP-0107: User Mood
items = event.getTag('items', {'node': common.xmpp.NS_MOOD})
if items: pep.user_mood(items, self.name, jid)
# XEP-0118: User Tune
items = event.getTag('items', {'node': common.xmpp.NS_TUNE})
if items: pep.user_tune(items, self.name, jid)
# XEP-0080: User Geolocation
items = event.getTag('items', {'node': common.xmpp.NS_GEOLOC})
if items: pep.user_geoloc(items, self.name, jid)
# XEP-0108: User Activity
items = event.getTag('items', {'node': common.xmpp.NS_ACTIVITY})
if items: pep.user_activity(items, self.name, jid)
items = event.getTag('items') items = event.getTag('items')
if items is None: return if items is None: return
@ -1857,7 +1900,7 @@ returns the session that we last sent a message to.'''
except: except:
prio = 0 prio = 0
keyID = '' keyID = ''
if sigTag and self.gpg: if sigTag and self.USE_GPG:
# verify # verify
sigmsg = sigTag.getData() sigmsg = sigTag.getData()
keyID = self.gpg.verify(status, sigmsg) keyID = self.gpg.verify(status, sigmsg)
@ -2109,6 +2152,21 @@ returns the session that we last sent a message to.'''
raw_roster = roster.getRaw() raw_roster = roster.getRaw()
roster = {} roster = {}
our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
if self.connected > 1 and self.continue_connect_info:
msg = self.continue_connect_info[1]
sign_msg = self.continue_connect_info[2]
signed = ''
send_first_presence = True
if sign_msg:
signed = self.get_signed_presence(msg, self._send_first_presence)
if signed is None:
self.dispatch('GPG_PASSWORD_REQUIRED',
(self._send_first_presence,))
# _send_first_presence will be called when user enter passphrase
send_first_presence = False
if send_first_presence:
self._send_first_presence(signed)
for jid in raw_roster: for jid in raw_roster:
try: try:
j = helpers.parse_jid(jid) j = helpers.parse_jid(jid)
@ -2131,20 +2189,6 @@ returns the session that we last sent a message to.'''
self.dispatch('ROSTER', roster) self.dispatch('ROSTER', roster)
# continue connection
if self.connected > 1 and self.continue_connect_info:
msg = self.continue_connect_info[1]
sign_msg = self.continue_connect_info[2]
signed = ''
if sign_msg:
signed = self.get_signed_presence(msg, self._send_first_presence)
if signed is None:
self.dispatch('GPG_PASSWORD_REQUIRED',
(self._send_first_presence,))
# _send_first_presence will be called when user enter passphrase
return
self._send_first_presence(signed)
def _send_first_presence(self, signed = ''): def _send_first_presence(self, signed = ''):
show = self.continue_connect_info[0] show = self.continue_connect_info[0]
msg = self.continue_connect_info[1] msg = self.continue_connect_info[1]
@ -2155,6 +2199,7 @@ returns the session that we last sent a message to.'''
self.dispatch('ERROR', (_('OpenPGP passphrase was not given'), self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
#%s is the account name here #%s is the account name here
_('You will be connected to %s without OpenPGP.') % self.name)) _('You will be connected to %s without OpenPGP.') % self.name))
self.USE_GPG = False
signed = '' signed = ''
self.connected = STATUS_LIST.index(show) self.connected = STATUS_LIST.index(show)
sshow = helpers.get_xmpp_show(show) sshow = helpers.get_xmpp_show(show)

View file

@ -27,7 +27,8 @@ class Contact:
'''Information concerning each contact''' '''Information concerning each contact'''
def __init__(self, jid='', name='', groups=[], show='', status='', sub='', def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
ask='', resource='', priority=0, keyID='', our_chatstate=None, ask='', resource='', priority=0, keyID='', our_chatstate=None,
chatstate=None, last_status_time=None, msg_id = None, composing_xep = None): chatstate=None, last_status_time=None, msg_id = None, composing_xep = None,
mood={}, tune={}, activity={}):
self.jid = jid self.jid = jid
self.name = name self.name = name
self.contact_name = '' # nick choosen by contact self.contact_name = '' # nick choosen by contact
@ -63,6 +64,10 @@ class Contact:
self.chatstate = chatstate self.chatstate = chatstate
self.last_status_time = last_status_time self.last_status_time = last_status_time
self.mood = mood.copy()
self.tune = tune.copy()
self.activity = activity.copy()
def get_full_jid(self): def get_full_jid(self):
if self.resource: if self.resource:
return self.jid + '/' + self.resource return self.jid + '/' + self.resource
@ -162,15 +167,16 @@ class Contacts:
def create_contact(self, jid='', name='', groups=[], show='', status='', def create_contact(self, jid='', name='', groups=[], show='', status='',
sub='', ask='', resource='', priority=0, keyID='', our_chatstate=None, sub='', ask='', resource='', priority=0, keyID='', our_chatstate=None,
chatstate=None, last_status_time=None, composing_xep=None): chatstate=None, last_status_time=None, composing_xep=None,
mood={}, tune={}, activity={}):
return Contact(jid, name, groups, show, status, sub, ask, resource, return Contact(jid, name, groups, show, status, sub, ask, resource,
priority, keyID, our_chatstate, chatstate, last_status_time, priority, keyID, our_chatstate, chatstate, last_status_time,
composing_xep) None, composing_xep, mood, tune, activity)
def copy_contact(self, contact): def copy_contact(self, contact):
return self.create_contact(jid = contact.jid, name = contact.name, return self.create_contact(jid = contact.jid, name = contact.name,
groups = contact.groups, show = contact.show, status = contact.status, groups = contact.groups, show = contact.show, status =
sub = contact.sub, ask = contact.ask, resource = contact.resource, contact.status, sub = contact.sub, ask = contact.ask, resource = contact.resource,
priority = contact.priority, keyID = contact.keyID, priority = contact.priority, keyID = contact.keyID,
our_chatstate = contact.our_chatstate, chatstate = contact.chatstate, our_chatstate = contact.our_chatstate, chatstate = contact.chatstate,
last_status_time = contact.last_status_time) last_status_time = contact.last_status_time)
@ -469,6 +475,15 @@ class Contacts:
return 1 return 1
if show2 > show1: if show2 > show1:
return -1 return -1
server1 = common.gajim.get_server_from_jid(jid1)
server2 = common.gajim.get_server_from_jid(jid2)
myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
if server1 == myserver1:
if server2 != myserver2:
return 1
elif server2 == myserver2:
return -1
if jid1 > jid2: if jid1 > jid2:
return 1 return 1
if jid2 > jid1: if jid2 > jid1:

View file

@ -2,7 +2,7 @@ docdir = '../'
datadir = '../' datadir = '../'
version = '0.11.4.0-svn' version = '0.11.4.2-svn'
import sys, os.path import sys, os.path
for base in ('.', 'common'): for base in ('.', 'common'):

View file

@ -138,9 +138,6 @@ SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
# zeroconf account name # zeroconf account name
ZEROCONF_ACC_NAME = 'Local' ZEROCONF_ACC_NAME = 'Local'
priority_dict = {}
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
priority_dict[status] = config.get('autopriority' + status)
HAVE_PYCRYPTO = True HAVE_PYCRYPTO = True
try: try:
@ -154,6 +151,17 @@ try:
except ImportError: except ImportError:
HAVE_PYSEXY = False HAVE_PYSEXY = False
HAVE_GPG = True
try:
import GnuPGInterface
except ImportError:
HAVE_GPG = False
else:
import os
status = os.system('gpg -h >/dev/null 2>&1')
if status != 0:
HAVE_GPG = False
def get_nick_from_jid(jid): def get_nick_from_jid(jid):
pos = jid.find('@') pos = jid.find('@')
return jid[:pos] return jid[:pos]
@ -260,13 +268,14 @@ def account_is_disconnected(account):
def get_number_of_securely_connected_accounts(): def get_number_of_securely_connected_accounts():
'''returns the number of the accounts that are SSL/TLS connected''' '''returns the number of the accounts that are SSL/TLS connected'''
num_of_secured = 0 num_of_secured = 0
for account in connections: for account in connections.keys():
if account_is_securely_connected(account): if account_is_securely_connected(account):
num_of_secured += 1 num_of_secured += 1
return num_of_secured return num_of_secured
def account_is_securely_connected(account): def account_is_securely_connected(account):
if account in con_types and con_types[account] in ('tls', 'ssl'): if account_is_connected(account) and \
account in con_types and con_types[account] in ('tls', 'ssl'):
return True return True
else: else:
return False return False

View file

@ -429,8 +429,8 @@ def launch_browser_mailer(kind, uri):
command = 'kfmclient exec' command = 'kfmclient exec'
elif gajim.config.get('openwith') == 'exo-open': elif gajim.config.get('openwith') == 'exo-open':
command = 'exo-open' command = 'exo-open'
elif ((sys.platform == 'darwin') and elif ((sys.platform == 'darwin') and\
(gajim.config.get('openwith') == 'open')): (gajim.config.get('openwith') == 'open')):
command = 'open' command = 'open'
elif gajim.config.get('openwith') == 'custom': elif gajim.config.get('openwith') == 'custom':
if kind == 'url': if kind == 'url':
@ -459,8 +459,8 @@ def launch_file_manager(path_to_open):
command = 'kfmclient exec' command = 'kfmclient exec'
elif gajim.config.get('openwith') == 'exo-open': elif gajim.config.get('openwith') == 'exo-open':
command = 'exo-open' command = 'exo-open'
elif ((sys.platform == 'darwin') and elif ((sys.platform == 'darwin') and\
(gajim.config.get('openwith') == 'open')): (gajim.config.get('openwith') == 'open')):
command = 'open' command = 'open'
elif gajim.config.get('openwith') == 'custom': elif gajim.config.get('openwith') == 'custom':
command = gajim.config.get('custom_file_manager') command = gajim.config.get('custom_file_manager')
@ -784,7 +784,8 @@ def get_os_info():
'sourcemage') or not\ 'sourcemage') or not\
os.path.basename(path_to_file).startswith('slackware'): os.path.basename(path_to_file).startswith('slackware'):
text = distro_name + ' ' + text text = distro_name + ' ' + text
elif path_to_file.endswith('aurox-release'): elif path_to_file.endswith('aurox-release') or \
path_to_file.endswith('arch-release'):
# file doesn't have version # file doesn't have version
text = distro_name text = distro_name
elif path_to_file.endswith('lfs-release'): # file just has version elif path_to_file.endswith('lfs-release'): # file just has version
@ -792,7 +793,7 @@ def get_os_info():
return text return text
# our last chance, ask uname and strip it # our last chance, ask uname and strip it
uname_output = get_output_of_command('uname -a | cut -d" " -f1,3') uname_output = get_output_of_command('uname -sr')
if uname_output is not None: if uname_output is not None:
return uname_output[0] # only first line return uname_output[0] # only first line
return 'N/A' return 'N/A'
@ -1092,3 +1093,38 @@ def get_transport_path(transport):
elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports', elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
transport)): transport)):
return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport) return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
# No transport folder found, use default jabber one
return get_iconset_path(gajim.config.get('iconset'))
def prepare_and_validate_gpg_keyID(account, jid, keyID):
'''Returns an eight char long keyID that can be used with for GPG encryption with this contact.
If the given keyID is None, return UNKNOWN; if the key does not match the assigned key
XXXXXXXXMISMATCH is returned. If the key is trusted and not yet assigned, assign it'''
if gajim.connections[account].USE_GPG:
if keyID and len(keyID) == 16:
keyID = keyID[8:]
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
if jid in attached_keys and keyID:
attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
if attachedkeyID != keyID:
# Mismatch! Another gpg key was expected
keyID += 'MISMATCH'
elif jid in attached_keys:
# An unsigned presence, just use the assigned key
keyID = attached_keys[attached_keys.index(jid) + 1]
elif keyID:
public_keys = gajim.connections[account].ask_gpg_keys()
# Assign the corresponding key, if we have it in our keyring
if public_keys.has_key(keyID):
for u in gajim.contacts.get_contacts(account, jid):
u.keyID = keyID
keys_str = gajim.config.get_per('accounts', account, 'attached_gpg_keys')
keys_str += jid + ' ' + keyID + ' '
gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str)
elif keyID is None:
keyID = 'UNKNOWN'
return keyID

View file

@ -174,8 +174,10 @@ class OptionsParser:
self.update_config_to_01115() self.update_config_to_01115()
if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]: if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]:
self.update_config_to_01121() self.update_config_to_01121()
if old < [0, 11, 2, 2] and new >= [0, 11, 2, 2]: if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]:
self.update_config_to_01122() self.update_config_to_01141()
if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]:
self.update_config_to_01142()
gajim.logger.init_vars() gajim.logger.init_vars()
gajim.config.set('version', new_version) gajim.config.set('version', new_version)
@ -503,7 +505,7 @@ class OptionsParser:
gajim.config.set('version', '0.11.2.1') gajim.config.set('version', '0.11.2.1')
def update_config_to_01122(self): def update_config_to_01141(self):
back = os.getcwd() back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER) os.chdir(logger.LOG_DB_FOLDER)
con = sqlite.connect(logger.LOG_DB_FILE) con = sqlite.connect(logger.LOG_DB_FILE)
@ -524,5 +526,20 @@ class OptionsParser:
except sqlite.OperationalError, e: except sqlite.OperationalError, e:
pass pass
con.close() con.close()
gajim.config.set('version', '0.11.2.2') gajim.config.set('version', '0.11.4.1')
def update_config_to_01142(self):
'''next_message_received sound event is splittedin 2 events'''
gajim.config.add_per('soundevents', 'next_message_received_focused')
gajim.config.add_per('soundevents', 'next_message_received_unfocused')
if gajim.config.get_per('soundevents', 'next_message_received'):
enabled = gajim.config.get_per('soundevents', 'next_message_received',
'enabled')
path = gajim.config.get_per('soundevents', 'next_message_received',
'path')
gajim.config.del_per('soundevents', 'next_message_received')
gajim.config.set_per('soundevents', 'next_message_received_focused',
'enabled', enabled)
gajim.config.set_per('soundevents', 'next_message_received_focused',
'path', path)
gajim.config.set('version', '0.11.1.2')

220
src/common/pep.py Normal file
View file

@ -0,0 +1,220 @@
from common import gajim, xmpp
def user_mood(items, name, jid):
has_child = False
mood = None
text = None
for item in items.getTags('item'):
child = item.getTag('mood')
if child is not None:
has_child = True
for ch in child.getChildren():
if ch.getName() != 'text':
mood = ch.getName()
else:
text = ch.getData()
if jid == gajim.get_jid_from_account(name):
acc = gajim.connections[name]
if has_child:
if acc.mood.has_key('mood'):
del acc.mood['mood']
if acc.mood.has_key('text'):
del acc.mood['text']
if mood != None:
acc.mood['mood'] = mood
if text != None:
acc.mood['text'] = text
(user, resource) = gajim.get_room_and_nick_from_fjid(jid)
contact = gajim.contacts.get_contact(name, user, resource=resource)
if not contact:
return
if has_child:
if contact.mood.has_key('mood'):
del contact.mood['mood']
if contact.mood.has_key('text'):
del contact.mood['text']
if mood != None:
contact.mood['mood'] = mood
if text != None:
contact.mood['text'] = text
def user_tune(items, name, jid):
has_child = False
artist = None
title = None
source = None
track = None
length = None
for item in items.getTags('item'):
child = item.getTag('tune')
if child is not None:
has_child = True
for ch in child.getChildren():
if ch.getName() == 'artist':
artist = ch.getData()
elif ch.getName() == 'title':
title = ch.getData()
elif ch.getName() == 'source':
source = ch.getData()
elif ch.getName() == 'track':
track = ch.getData()
elif ch.getName() == 'length':
length = ch.getData()
if jid == gajim.get_jid_from_account(name):
acc = gajim.connections[name]
if has_child:
if acc.tune.has_key('artist'):
del acc.tune['artist']
if acc.tune.has_key('title'):
del acc.tune['title']
if acc.tune.has_key('source'):
del acc.tune['source']
if acc.tune.has_key('track'):
del acc.tune['track']
if acc.tune.has_key('length'):
del acc.tune['length']
if artist != None:
acc.tune['artist'] = artist
if title != None:
acc.tune['title'] = title
if source != None:
acc.tune['source'] = source
if track != None:
acc.tune['track'] = track
if length != None:
acc.tune['length'] = length
(user, resource) = gajim.get_room_and_nick_from_fjid(jid)
contact = gajim.contacts.get_contact(name, user, resource=resource)
if not contact:
return
if has_child:
if contact.tune.has_key('artist'):
del contact.tune['artist']
if contact.tune.has_key('title'):
del contact.tune['title']
if contact.tune.has_key('source'):
del contact.tune['source']
if contact.tune.has_key('track'):
del contact.tune['track']
if contact.tune.has_key('length'):
del contact.tune['length']
if artist != None:
contact.tune['artist'] = artist
if title != None:
contact.tune['title'] = title
if source != None:
contact.tune['source'] = source
if track != None:
contact.tune['track'] = track
if length != None:
contact.tune['length'] = length
def user_geoloc(items, name, jid):
pass
def user_activity(items, name, jid):
has_child = False
activity = None
subactivity = None
text = None
for item in items.getTags('item'):
child = item.getTag('activity')
if child is not None:
has_child = True
for ch in child.getChildren():
if ch.getName() != 'text':
activity = ch.getName()
for chi in ch.getChildren():
subactivity = chi.getName()
else:
text = ch.getData()
if jid == gajim.get_jid_from_account(name):
acc = gajim.connections[name]
if has_child:
if acc.activity.has_key('activity'):
del acc.activity['activity']
if acc.activity.has_key('subactivity'):
del acc.activity['subactivity']
if acc.activity.has_key('text'):
del acc.activity['text']
if activity != None:
acc.activity['activity'] = activity
if subactivity != None:
acc.activity['subactivity'] = subactivity
if text != None:
acc.activity['text'] = text
(user, resource) = gajim.get_room_and_nick_from_fjid(jid)
contact = gajim.contacts.get_contact(name, user, resource=resource)
if not contact:
return
if has_child:
if contact.activity.has_key('activity'):
del contact.activity['activity']
if contact.activity.has_key('subactivity'):
del contact.activity['subactivity']
if contact.activity.has_key('text'):
del contact.activity['text']
if activity != None:
contact.activity['activity'] = activity
if subactivity != None:
contact.activity['subactivity'] = subactivity
if text != None:
contact.activity['text'] = text
def user_send_mood(account, mood, message = ''):
if gajim.config.get('publish_mood') == False:
return
item = xmpp.Node('mood', {'xmlns': xmpp.NS_MOOD})
if mood != '':
item.addChild(mood)
if message != '':
i = item.addChild('text')
i.addData(message)
gajim.connections[account].send_pb_publish('', xmpp.NS_MOOD, item, '0')
def user_send_activity(account, activity, subactivity = '', message = ''):
if gajim.config.get('publish_activity') == False:
return
item = xmpp.Node('activity', {'xmlns': xmpp.NS_ACTIVITY})
if activity != '':
i = item.addChild(activity)
if subactivity != '':
i.addChild(subactivity)
if message != '':
i = item.addChild('text')
i.addData(message)
gajim.connections[account].send_pb_publish('', xmpp.NS_ACTIVITY, item, '0')
def user_send_tune(account, artist = '', title = '', source = '', track = 0,length = 0, items = None):
if (gajim.config.get('publish_tune') == False) or \
(gajim.connections[account].pep_supported == False):
return
item = xmpp.Node('tune', {'xmlns': xmpp.NS_TUNE})
if artist != '':
i = item.addChild('artist')
i.addData(artist)
if title != '':
i = item.addChild('title')
i.addData(title)
if source != '':
i = item.addChild('source')
i.addData(source)
if track != 0:
i = item.addChild('track')
i.addData(track)
if length != 0:
i = item.addChild('length')
i.addData(length)
if items is not None:
item.addChild(payload=items)
gajim.connections[account].send_pb_publish('', xmpp.NS_TUNE, item, '0')

View file

@ -1,5 +1,6 @@
import xmpp import xmpp
import gajim import gajim
import connection_handlers
class ConnectionPubSub: class ConnectionPubSub:
def __init__(self): def __init__(self):
@ -43,9 +44,61 @@ class ConnectionPubSub:
self.connection.send(query) self.connection.send(query)
def send_pb_delete(self, jid, node):
'''Deletes node.'''
query = xmpp.Iq('set', to=jid)
d = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
d = d.addChild('delete', {'node': node})
self.connection.send(query)
def send_pb_create(self, jid, node, configure = False, configure_form = None):
'''Creates new node.'''
query = xmpp.Iq('set', to=jid)
c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
c = c.addChild('create', {'node': node})
if configure:
conf = c.addChild('configure')
if configure_form is not None:
conf.addChild(node=configure_form)
self.connection.send(query)
def send_pb_configure(self, jid, node, cb, *cbargs, **cbkwargs):
query = xmpp.Iq('set', to=jid)
c = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
c = c.addChild('configure', {'node': node})
id = self.connection.send(query)
def on_configure(self, connection, query):
try:
filledform = cb(stanza['pubsub']['configure']['x'], *cbargs, **cbkwargs)
#TODO: Build a form
#TODO: Send it
except CancelConfigure:
cancel = xmpp.Iq('set', to=jid)
ca = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
ca = ca.addChild('configure', {'node': node})
#ca = ca.addChild('x', namespace=xmpp.NS_DATA, {'type': 'cancel'})
self.connection.send(cancel)
self.__callbacks[id] = (on_configure, (), {})
def _PubSubCB(self, conn, stanza): def _PubSubCB(self, conn, stanza):
try: try:
cb, args, kwargs = self.__callbacks.pop(stanza.getID()) cb, args, kwargs = self.__callbacks.pop(stanza.getID())
cb(conn, stanza, *args, **kwargs) cb(conn, stanza, *args, **kwargs)
except: except:
pass pass
def request_pb_configuration(self, jid, node):
query = xmpp.Iq('get', to=jid)
e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB_OWNER)
e = e.addChild('configure', {'node': node})
id = self.connection.getAnID()
query.setID(id)
self.awaiting_answers[id] = (connection_handlers.PEP_ACCESS_MODEL,)
self.connection.send(query)

View file

@ -932,7 +932,7 @@ class Socks5Receiver(Socks5, IdleObject):
elif self.state == 3: # send 'connect' request elif self.state == 3: # send 'connect' request
self.send_raw(self._get_request_buff(self._get_sha1_auth())) self.send_raw(self._get_request_buff(self._get_sha1_auth()))
elif self.file_props['type'] != 'r': elif self.file_props['type'] != 'r':
if self.file_props['paused'] == True: if self.file_props['paused']:
self.idlequeue.plug_idle(self, False, False) self.idlequeue.plug_idle(self, False, False)
return return
result = self.write_next() result = self.write_next()

View file

@ -1,7 +1,6 @@
from common import gajim from common import gajim
from common import xmpp from common import xmpp
from common import helpers
from common import exceptions from common import exceptions
import random import random
@ -571,9 +570,9 @@ class EncryptedStanzaSession(StanzaSession):
self.d = crypto.powmod(g, self.y, p) self.d = crypto.powmod(g, self.y, p)
to_add = { 'my_nonce': self.n_s, to_add = { 'my_nonce': self.n_s,
'dhkeys': crypto.encode_mpi(self.d), 'dhkeys': crypto.encode_mpi(self.d),
'counter': crypto.encode_mpi(self.c_o), 'counter': crypto.encode_mpi(self.c_o),
'nonce': self.n_o } 'nonce': self.n_o }
for name in to_add: for name in to_add:
b64ed = base64.b64encode(to_add[name]) b64ed = base64.b64encode(to_add[name])

View file

@ -27,11 +27,14 @@ NS_AGENTS ='jabber:iq:agents'
NS_AMP ='http://jabber.org/protocol/amp' NS_AMP ='http://jabber.org/protocol/amp'
NS_AMP_ERRORS =NS_AMP+'#errors' NS_AMP_ERRORS =NS_AMP+'#errors'
NS_AUTH ='jabber:iq:auth' NS_AUTH ='jabber:iq:auth'
NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
NS_BROWSE ='jabber:iq:browse' NS_BROWSE ='jabber:iq:browse'
NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # XEP-0065 NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
NS_CAPS ='http://jabber.org/protocol/caps' # XEP-0115 NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # XEP-0085 NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
NS_CLIENT ='jabber:client' NS_CLIENT ='jabber:client'
NS_COMMANDS ='http://jabber.org/protocol/commands' NS_COMMANDS ='http://jabber.org/protocol/commands'
NS_COMPONENT_ACCEPT='jabber:component:accept' NS_COMPONENT_ACCEPT='jabber:component:accept'
@ -48,8 +51,9 @@ NS_ENCRYPTED ='jabber:x:encrypted' # XEP-00
NS_ESESSION_INIT='http://www.xmpp.org/extensions/xep-0116.html#ns-init' # XEP-0116 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_FEATURE ='http://jabber.org/protocol/feature-neg'
NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # XEP-0096 NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
NS_GEOLOC ='http://jabber.org/protocol/geoloc' # XEP-0080 NS_GAMING ='http://jabber.org/protocol/gaming' # XEP-0196
NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
NS_GROUPCHAT ='gc-1.0' NS_GROUPCHAT ='gc-1.0'
NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070 NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # XEP-0070
NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124 NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-0124
@ -72,6 +76,7 @@ NS_PRIVACY ='jabber:iq:privacy'
NS_PRIVATE ='jabber:iq:private' NS_PRIVATE ='jabber:iq:private'
NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154 NS_PROFILE ='http://jabber.org/protocol/profile' # XEP-0154
NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060 NS_PUBSUB ='http://jabber.org/protocol/pubsub' # XEP-0060
NS_PUBSUB_OWNER ='http://jabber.org/protocol/pubsub#owner' # JEP-0060
NS_REGISTER ='jabber:iq:register' NS_REGISTER ='jabber:iq:register'
NS_ROSTER ='jabber:iq:roster' NS_ROSTER ='jabber:iq:roster'
NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144 NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
@ -90,12 +95,14 @@ NS_STREAMS ='http://etherx.jabber.org/streams'
NS_TIME ='jabber:iq:time' # XEP-0900 NS_TIME ='jabber:iq:time' # XEP-0900
NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202 NS_TIME_REVISED ='urn:xmpp:time' # XEP-0202
NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls' NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
NS_TUNE ='http://jabber.org/protocol/tune' # XEP-0118
NS_VACATION ='http://jabber.org/protocol/vacation' NS_VACATION ='http://jabber.org/protocol/vacation'
NS_VCARD ='vcard-temp' NS_VCARD ='vcard-temp'
NS_GMAILNOTIFY ='google:mail:notify' NS_GMAILNOTIFY ='google:mail:notify'
NS_GTALKSETTING ='google:setting' NS_GTALKSETTING ='google:setting'
NS_VCARD_UPDATE =NS_VCARD+':x:update' NS_VCARD_UPDATE =NS_VCARD+':x:update'
NS_VERSION ='jabber:iq:version' NS_VERSION ='jabber:iq:version'
NS_VIEWING ='http://jabber.org/protocol/viewing' # XEP--197
NS_PING ='urn:xmpp:ping' # XEP-0199 NS_PING ='urn:xmpp:ping' # XEP-0199
NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130 NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # XEP-0130
NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071 NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # XEP-0071

View file

@ -87,7 +87,11 @@ class Roster(PlugIn):
def PresenceHandler(self,dis,pres): def PresenceHandler(self,dis,pres):
""" Presence tracker. Used internally for setting items' resources state in """ Presence tracker. Used internally for setting items' resources state in
internal roster representation. """ internal roster representation. """
jid=JID(pres.getFrom()) jid=pres.getFrom()
if not jid:
# If no from attribue, it's from server
jid=self._owner.Server
jid=JID(jid)
if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}} if not self._data.has_key(jid.getStripped()): self._data[jid.getStripped()]={'name':None,'ask':None,'subscription':'none','groups':['Not in roster'],'resources':{}}
if type(self._data[jid.getStripped()]['resources'])!=type(dict()): if type(self._data[jid.getStripped()]['resources'])!=type(dict()):
self._data[jid.getStripped()]['resources']={} self._data[jid.getStripped()]['resources']={}

View file

@ -761,15 +761,18 @@ class NonBlockingTLS(PlugIn):
for line in lines: for line in lines:
if 'BEGIN CERTIFICATE' in line: if 'BEGIN CERTIFICATE' in line:
begin = i begin = i
continue
elif 'END CERTIFICATE' in line and begin > -1: elif 'END CERTIFICATE' in line and begin > -1:
cert = ''.join(lines[begin:i+2]) cert = ''.join(lines[begin:i+2])
try: try:
X509cert = OpenSSL.crypto.load_certificate( X509cert = OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert) OpenSSL.crypto.FILETYPE_PEM, cert)
store.add_cert(X509cert) store.add_cert(X509cert)
except OpenSSL.crypto.Error, exception_obj:
log.warning('Unable to load a certificate from file %s: %s' %\
(gajim.MY_CACERTS, exception_obj.args[0][0][2]))
except: except:
log.warning('Unable to load a certificate from file %s' % \ log.warning(
'Unknown error while loading certificate from file %s' % \
gajim.MY_CACERTS) gajim.MY_CACERTS)
begin = -1 begin = -1
i += 1 i += 1
@ -787,7 +790,8 @@ class NonBlockingTLS(PlugIn):
try: try:
self.starttls='in progress' self.starttls='in progress'
tcpsock._sslObj.do_handshake() tcpsock._sslObj.do_handshake()
except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: # Errors are handeled in _do_receive function
except:
pass pass
tcpsock._sslObj.setblocking(False) tcpsock._sslObj.setblocking(False)
log.debug("Synchronous handshake completed") log.debug("Synchronous handshake completed")
@ -1027,7 +1031,7 @@ class NBSOCKS5PROXYsocket(NonBlockingTcp):
# use the IPv4 address request even if remote resolving was specified. # use the IPv4 address request even if remote resolving was specified.
try: try:
self.ipaddr = socket.inet_aton(self.server[0]) self.ipaddr = socket.inet_aton(self.server[0])
req = req + "\x01" + ipaddr req = req + "\x01" + self.ipaddr
except socket.error: except socket.error:
# Well it's not an IP number, so it's probably a DNS name. # Well it's not an IP number, so it's probably a DNS name.
# if self.__proxy[3]==True: # if self.__proxy[3]==True:

View file

@ -32,7 +32,6 @@ from calendar import timegm
from common import socks5 from common import socks5
import common.xmpp import common.xmpp
from common import GnuPG
from common import helpers from common import helpers
from common import gajim from common import gajim
from common.zeroconf import zeroconf from common.zeroconf import zeroconf
@ -726,13 +725,15 @@ class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
if not user_nick: if not user_nick:
user_nick = '' user_nick = ''
if encTag and GnuPG.USE_GPG: if encTag and self.USE_GPG:
#decrypt #decrypt
encmsg = encTag.getData() encmsg = encTag.getData()
keyID = gajim.config.get_per('accounts', self.name, 'keyid') keyID = gajim.config.get_per('accounts', self.name, 'keyid')
if keyID: if keyID:
decmsg = self.gpg.decrypt(encmsg, keyID) decmsg = self.gpg.decrypt(encmsg, keyID)
# \x00 chars are not allowed in C (so in GTK)
decmsg = decmsg.replace('\x00', '')
if decmsg: if decmsg:
msgtxt = decmsg msgtxt = decmsg
encrypted = True encrypted = True

View file

@ -49,8 +49,6 @@ from common.zeroconf import client_zeroconf
from common.zeroconf import zeroconf from common.zeroconf import zeroconf
from connection_handlers_zeroconf import * from connection_handlers_zeroconf import *
USE_GPG = GnuPG.USE_GPG
class ConnectionZeroconf(ConnectionHandlersZeroconf): class ConnectionZeroconf(ConnectionHandlersZeroconf):
'''Connection class''' '''Connection class'''
def __init__(self, name): def __init__(self, name):
@ -62,7 +60,9 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
self.connected = 0 # offline self.connected = 0 # offline
self.connection = None self.connection = None
self.gpg = None self.gpg = None
if USE_GPG: self.USE_GPG = False
if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
self.is_zeroconf = True self.is_zeroconf = True
self.privacy_rules_supported = False self.privacy_rules_supported = False
@ -71,9 +71,9 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
self.status = '' self.status = ''
self.old_show = '' self.old_show = ''
self.priority = 0 self.priority = 0
self.call_resolve_timeout = False self.call_resolve_timeout = False
self.time_to_reconnect = None self.time_to_reconnect = None
#self.new_account_info = None #self.new_account_info = None
self.bookmarks = [] self.bookmarks = []
@ -86,21 +86,25 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
self.no_log_for = False self.no_log_for = False
self.pep_supported = False self.pep_supported = False
self.mood = {}
self.tune = {}
self.activity = {}
# Do we continue connection when we get roster (send presence,get vcard...) # Do we continue connection when we get roster (send presence,get vcard...)
self.continue_connect_info = None self.continue_connect_info = None
if USE_GPG: if gajim.HAVE_GPG:
self.USE_GPG = True
self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent')) self.gpg = GnuPG.GnuPG(gajim.config.get('use_gpg_agent'))
self.get_config_values_or_default() self.get_config_values_or_default()
self.muc_jid = {} # jid of muc server for each transport type self.muc_jid = {} # jid of muc server for each transport type
self.vcard_supported = False self.vcard_supported = False
self.private_storage_supported = False self.private_storage_supported = False
def get_config_values_or_default(self): def get_config_values_or_default(self):
''' get name, host, port from config, or ''' get name, host, port from config, or
create zeroconf account with default values''' create zeroconf account with default values'''
if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
gajim.log.debug('Creating zeroconf account') gajim.log.debug('Creating zeroconf account')
gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
@ -146,7 +150,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def quit(self, kill_core): def quit(self, kill_core):
if kill_core and self.connected > 1: if kill_core and self.connected > 1:
self.disconnect() self.disconnect()
def disable_account(self): def disable_account(self):
self.disconnect() self.disconnect()
@ -160,17 +164,19 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def get_signed_msg(self, msg): def get_signed_msg(self, msg):
signed = '' signed = ''
keyID = gajim.config.get_per('accounts', self.name, 'keyid') keyID = gajim.config.get_per('accounts', self.name, 'keyid')
if keyID and USE_GPG: if keyID and self.USE_GPG:
use_gpg_agent = gajim.config.get('use_gpg_agent') use_gpg_agent = gajim.config.get('use_gpg_agent')
if self.connected < 2 and self.gpg.passphrase is None and \ if self.connected < 2 and self.gpg.passphrase is None and \
not use_gpg_agent: not use_gpg_agent:
# We didn't set a passphrase # We didn't set a passphrase
self.dispatch('ERROR', (_('OpenPGP passphrase was not given'), self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
#%s is the account name here #%s is the account name here
_('You will be connected to %s without OpenPGP.') % self.name)) _('You will be connected to %s without OpenPGP.') % self.name))
self.USE_GPG = False
elif self.gpg.passphrase is not None or use_gpg_agent: elif self.gpg.passphrase is not None or use_gpg_agent:
signed = self.gpg.sign(msg, keyID) signed = self.gpg.sign(msg, keyID)
if signed == 'BAD_PASSPHRASE': if signed == 'BAD_PASSPHRASE':
self.USE_GPG = False
signed = '' signed = ''
if self.connected < 2: if self.connected < 2:
self.dispatch('BAD_PASSPHRASE', ()) self.dispatch('BAD_PASSPHRASE', ())
@ -189,12 +195,12 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
#XXX open chat windows don't get refreshed (full name), add that #XXX open chat windows don't get refreshed (full name), add that
return self.call_resolve_timeout return self.call_resolve_timeout
# callbacks called from zeroconf # callbacks called from zeroconf
def _on_new_service(self,jid): def _on_new_service(self,jid):
self.roster.setItem(jid) self.roster.setItem(jid)
self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid))) self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0, None)) self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0, None))
def _on_remove_service(self, jid): def _on_remove_service(self, jid):
self.roster.delItem(jid) self.roster.delItem(jid)
# 'NOTIFY' (account, (jid, status, status message, resource, priority, # 'NOTIFY' (account, (jid, status, status message, resource, priority,
@ -326,7 +332,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
elif show == 'offline' and self.connected: elif show == 'offline' and self.connected:
self.disconnect() self.disconnect()
self.time_to_reconnect = None self.time_to_reconnect = None
# update status # update status
elif show != 'offline' and self.connected: elif show != 'offline' and self.connected:
was_invisible = self.connected == STATUS_LIST.index('invisible') was_invisible = self.connected == STATUS_LIST.index('invisible')
@ -344,18 +350,18 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
if check: if check:
self.dispatch('STATUS', show) self.dispatch('STATUS', show)
else: else:
# show notification that avahi or system bus is down # show notification that avahi or system bus is down
self.dispatch('STATUS', 'offline') self.dispatch('STATUS', 'offline')
self.status = 'offline' self.status = 'offline'
self.dispatch('CONNECTION_LOST', self.dispatch('CONNECTION_LOST',
(_('Could not change status of account "%s"') % self.name, (_('Could not change status of account "%s"') % self.name,
_('Please check if avahi-daemon is running.'))) _('Please check if avahi-daemon is running.')))
def get_status(self): def get_status(self):
return STATUS_LIST[self.connected] return STATUS_LIST[self.connected]
def send_message(self, jid, msg, keyID, type = 'chat', subject='', def send_message(self, jid, msg, keyID, type = 'chat', subject='',
chatstate = None, msg_id = None, composing_xep = None, resource = None, chatstate = None, msg_id = None, composing_xep = None, resource = None,
user_nick = None, session=None): user_nick = None, session=None):
fjid = jid fjid = jid
@ -370,9 +376,14 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
msgtxt = msg msgtxt = msg
msgenc = '' msgenc = ''
if keyID and USE_GPG: if keyID and self.USE_GPG:
# encrypt if keyID == 'UNKNOWN':
msgenc, error = self.gpg.encrypt(msg, [keyID]) error = _('Neither the remote presence is signed, nor a key was assigned.')
elif keyID[8:] == 'MISMATCH':
error = _('The contact\'s key (%s) does not match the key assigned in Gajim.' % keyID[:8])
else:
# encrypt
msgenc, error = self.gpg.encrypt(msg, [keyID])
if msgenc and not error: if msgenc and not error:
msgtxt = '[This message is encrypted]' msgtxt = '[This message is encrypted]'
lang = os.getenv('LANG') lang = os.getenv('LANG')
@ -414,8 +425,8 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
msg_id = '' msg_id = ''
chatstate_node.setTagData('id', msg_id) chatstate_node.setTagData('id', msg_id)
# when msgtxt, requests JEP-0022 composing notification # when msgtxt, requests JEP-0022 composing notification
if chatstate is 'composing' or msgtxt: if chatstate is 'composing' or msgtxt:
chatstate_node.addChild(name = 'composing') chatstate_node.addChild(name = 'composing')
if session: if session:
session.last_send = time.time() session.last_send = time.time()
@ -440,15 +451,15 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
else: else:
kind = 'single_msg_sent' kind = 'single_msg_sent'
gajim.logger.write(kind, jid, log_msg) gajim.logger.write(kind, jid, log_msg)
self.dispatch('MSGSENT', (jid, msg, keyID)) self.dispatch('MSGSENT', (jid, msg, keyID))
def send_stanza(self, stanza): def send_stanza(self, stanza):
# send a stanza untouched # send a stanza untouched
if not self.connection: if not self.connection:
return return
self.connection.send(stanza) self.connection.send(stanza)
def ack_subscribed(self, jid): def ack_subscribed(self, jid):
gajim.log.debug('This should not happen (ack_subscribed)') gajim.log.debug('This should not happen (ack_subscribed)')
@ -471,11 +482,11 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def unsubscribe_agent(self, agent): def unsubscribe_agent(self, agent):
gajim.log.debug('This should not happen (unsubscribe_agent)') gajim.log.debug('This should not happen (unsubscribe_agent)')
def update_contact(self, jid, name, groups): def update_contact(self, jid, name, groups):
if self.connection: if self.connection:
self.connection.getRoster().setItem(jid = jid, name = name, self.connection.getRoster().setItem(jid = jid, name = name,
groups = groups) groups = groups)
def new_account(self, name, config, sync = False): def new_account(self, name, config, sync = False):
gajim.log.debug('This should not happen (new_account)') gajim.log.debug('This should not happen (new_account)')
@ -496,18 +507,18 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
def get_bookmarks(self): def get_bookmarks(self):
gajim.log.debug('This should not happen (get_bookmarks)') gajim.log.debug('This should not happen (get_bookmarks)')
def store_bookmarks(self): def store_bookmarks(self):
gajim.log.debug('This should not happen (store_bookmarks)') gajim.log.debug('This should not happen (store_bookmarks)')
def get_metacontacts(self): def get_metacontacts(self):
gajim.log.debug('This should not happen (get_metacontacts)') gajim.log.debug('This should not happen (get_metacontacts)')
def send_agent_status(self, agent, ptype): def send_agent_status(self, agent, ptype):
gajim.log.debug('This should not happen (send_agent_status)') gajim.log.debug('This should not happen (send_agent_status)')
def gpg_passphrase(self, passphrase): def gpg_passphrase(self, passphrase):
if USE_GPG: if self.gpg:
use_gpg_agent = gajim.config.get('use_gpg_agent') use_gpg_agent = gajim.config.get('use_gpg_agent')
if use_gpg_agent: if use_gpg_agent:
self.gpg.passphrase = None self.gpg.passphrase = None
@ -515,13 +526,13 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf):
self.gpg.passphrase = passphrase self.gpg.passphrase = passphrase
def ask_gpg_keys(self): def ask_gpg_keys(self):
if USE_GPG: if self.gpg:
keys = self.gpg.get_keys() keys = self.gpg.get_keys()
return keys return keys
return None return None
def ask_gpg_secrete_keys(self): def ask_gpg_secrete_keys(self):
if USE_GPG: if self.gpg:
keys = self.gpg.get_secret_keys() keys = self.gpg.get_secret_keys()
return keys return keys
return None return None

File diff suppressed because it is too large Load diff

View file

@ -61,8 +61,8 @@ class TextViewImage(gtk.Image):
self.anchor = anchor self.anchor = anchor
self._selected = False self._selected = False
self._disconnect_funcs = [] self._disconnect_funcs = []
self.connect("parent-set", self.on_parent_set) self.connect('parent-set', self.on_parent_set)
self.connect("expose-event", self.on_expose) self.connect('expose-event', self.on_expose)
def _get_selected(self): def _get_selected(self):
parent = self.get_parent() parent = self.get_parent()
@ -74,7 +74,7 @@ class TextViewImage(gtk.Image):
return True return True
else: else:
return False return False
def get_state(self): def get_state(self):
parent = self.get_parent() parent = self.get_parent()
if not parent: if not parent:
@ -110,22 +110,22 @@ class TextViewImage(gtk.Image):
self._disconnect_signals() self._disconnect_signals()
return return
self._do_connect(parent, "style-set", self.do_queue_draw) self._do_connect(parent, 'style-set', self.do_queue_draw)
self._do_connect(parent, "focus-in-event", self.do_queue_draw) self._do_connect(parent, 'focus-in-event', self.do_queue_draw)
self._do_connect(parent, "focus-out-event", self.do_queue_draw) self._do_connect(parent, 'focus-out-event', self.do_queue_draw)
textbuf = parent.get_buffer() textbuf = parent.get_buffer()
self._do_connect(textbuf, "mark-set", self.on_mark_set) self._do_connect(textbuf, 'mark-set', self.on_mark_set)
self._do_connect(textbuf, "mark-deleted", self.on_mark_deleted) self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted)
def do_queue_draw(self, *args): def do_queue_draw(self, *args):
self.queue_draw() self.queue_draw()
return False return False
def on_mark_set(self, buf, iterat, mark): def on_mark_set(self, buf, iterat, mark):
self.on_mark_modified(mark) self.on_mark_modified(mark)
return False return False
def on_mark_deleted(self, buf, mark): def on_mark_deleted(self, buf, mark):
self.on_mark_modified(mark) self.on_mark_modified(mark)
return False return False
@ -142,12 +142,12 @@ class TextViewImage(gtk.Image):
widget.window.draw_rectangle(gc, True, area.x, area.y, widget.window.draw_rectangle(gc, True, area.x, area.y,
area.width, area.height) area.width, area.height)
return False return False
class ConversationTextview: class ConversationTextview:
'''Class for the conversation textview (where user reads already said messages) '''Class for the conversation textview (where user reads already said messages)
for chat/groupchat windows''' for chat/groupchat windows'''
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(path_to_file) FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(path_to_file)
@ -159,7 +159,7 @@ class ConversationTextview:
'''if used_in_history_window is True, then we do not show '''if used_in_history_window is True, then we do not show
Clear menuitem in context menu''' Clear menuitem in context menu'''
self.used_in_history_window = used_in_history_window self.used_in_history_window = used_in_history_window
# no need to inherit TextView, use it as atrribute is safer # no need to inherit TextView, use it as atrribute is safer
self.tv = HtmlTextView() self.tv = HtmlTextView()
self.tv.html_hyperlink_handler = self.html_hyperlink_handler self.tv.html_hyperlink_handler = self.html_hyperlink_handler
@ -185,8 +185,8 @@ class ConversationTextview:
id = self.tv.connect('button_press_event', id = self.tv.connect('button_press_event',
self.on_textview_button_press_event) self.on_textview_button_press_event)
self.handlers[id] = self.tv self.handlers[id] = self.tv
id = self.tv.connect("expose-event", id = self.tv.connect('expose-event',
self.on_textview_expose_event) self.on_textview_expose_event)
self.handlers[id] = self.tv self.handlers[id] = self.tv
@ -213,10 +213,9 @@ class ConversationTextview:
colors = gajim.config.get('gc_nicknames_colors') colors = gajim.config.get('gc_nicknames_colors')
colors = colors.split(':') colors = colors.split(':')
for color in xrange(len(colors)): for i,color in enumerate(colors):
tagname = 'gc_nickname_color_' + str(color) tagname = 'gc_nickname_color_' + str(i)
tag = buffer.create_tag(tagname) tag = buffer.create_tag(tagname)
color = colors[color]
tag.set_property('foreground', color) tag.set_property('foreground', color)
tag = buffer.create_tag('marked') tag = buffer.create_tag('marked')
@ -282,7 +281,7 @@ class ConversationTextview:
self.tv.destroy() self.tv.destroy()
#FIXME: #FIXME:
# self.line_tooltip.destroy() # self.line_tooltip.destroy()
def update_tags(self): def update_tags(self):
self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
@ -315,7 +314,7 @@ class ConversationTextview:
self.smooth_id = None self.smooth_id = None
self.smooth_scroll_timer.cancel() self.smooth_scroll_timer.cancel()
return False return False
return True return True
def smooth_scroll_timeout(self): def smooth_scroll_timeout(self):
gobject.idle_add(self.do_smooth_scroll_timeout) gobject.idle_add(self.do_smooth_scroll_timeout)
@ -336,9 +335,9 @@ class ConversationTextview:
if None != self.smooth_id: # already scrolling if None != self.smooth_id: # already scrolling
return False return False
self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY,
self.smooth_scroll) self.smooth_scroll)
self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
self.smooth_scroll_timeout) self.smooth_scroll_timeout)
self.smooth_scroll_timer.start() self.smooth_scroll_timer.start()
return False return False
@ -353,9 +352,8 @@ class ConversationTextview:
adjustment.set_value(0) adjustment.set_value(0)
return False # when called in an idle_add, just do it once return False # when called in an idle_add, just do it once
def bring_scroll_to_end(self, diff_y = 0,\ def bring_scroll_to_end(self, diff_y = 0,
use_smooth =\ use_smooth=gajim.config.get('use_smooth_scrolling')):
gajim.config.get('use_smooth_scrolling')):
''' scrolls to the end of textview if end is not visible ''' ''' scrolls to the end of textview if end is not visible '''
buffer = self.tv.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
@ -418,12 +416,13 @@ class ConversationTextview:
# add the new focus out line # add the new focus out line
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
buffer.insert(end_iter, '\n') buffer.insert(end_iter, '\n')
buffer.insert_pixbuf(end_iter, buffer.insert_pixbuf(end_iter,
ConversationTextview.FOCUS_OUT_LINE_PIXBUF) ConversationTextview.FOCUS_OUT_LINE_PIXBUF)
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
before_img_iter = end_iter.copy() before_img_iter = end_iter.copy()
before_img_iter.backward_char() # one char back (an image also takes one char) # one char back (an image also takes one char)
before_img_iter.backward_char()
buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
self.allow_focus_out_line = False self.allow_focus_out_line = False
@ -452,7 +451,8 @@ class ConversationTextview:
# check if the current pointer is still over the line # check if the current pointer is still over the line
position = self.tv.window.get_origin() position = self.tv.window.get_origin()
self.line_tooltip.show_tooltip(_('Text below this line is what has ' self.line_tooltip.show_tooltip(_('Text below this line is what has '
'been said since the last time you paid attention to this group chat'), 8, position[1] + pointer[1]) 'been said since the last time you paid attention to this group chat'),
8, position[1] + pointer[1])
def on_textview_expose_event(self, widget, event): def on_textview_expose_event(self, widget, event):
expalloc = event.area expalloc = event.area
@ -460,7 +460,7 @@ class ConversationTextview:
exp_y0 = expalloc.y exp_y0 = expalloc.y
exp_x1 = exp_x0 + expalloc.width exp_x1 = exp_x0 + expalloc.width
exp_y1 = exp_y0 + expalloc.height exp_y1 = exp_y0 + expalloc.height
try: try:
tryfirst = [self.image_cache[(exp_x0, exp_y0)]] tryfirst = [self.image_cache[(exp_x0, exp_y0)]]
except KeyError: except KeyError:
@ -582,7 +582,8 @@ class ConversationTextview:
else: else:
if dict_link.find('%s') == -1: if dict_link.find('%s') == -1:
# we must have %s in the url if not WIKTIONARY # we must have %s in the url if not WIKTIONARY
item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY')) item = gtk.MenuItem(_(
'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
item.set_property('sensitive', False) item.set_property('sensitive', False)
else: else:
link = dict_link % self.selected_phrase link = dict_link % self.selected_phrase
@ -603,7 +604,7 @@ class ConversationTextview:
id = item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item self.handlers[id] = item
submenu.append(item) submenu.append(item)
item = gtk.MenuItem(_('Open as _Link')) item = gtk.MenuItem(_('Open as _Link'))
id = item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item self.handlers[id] = item
@ -639,7 +640,8 @@ class ConversationTextview:
if return_val: # if sth was selected when we right-clicked if return_val: # if sth was selected when we right-clicked
# get the selected text # get the selected text
start_sel, finish_sel = return_val[0], return_val[1] start_sel, finish_sel = return_val[0], return_val[1]
self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode('utf-8') self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode(
'utf-8')
def on_open_link_activate(self, widget, kind, text): def on_open_link_activate(self, widget, kind, text):
helpers.launch_browser_mailer(kind, text) helpers.launch_browser_mailer(kind, text)
@ -673,7 +675,8 @@ class ConversationTextview:
if kind == 'url': if kind == 'url':
id = childs[0].connect('activate', self.on_copy_link_activate, text) id = childs[0].connect('activate', self.on_copy_link_activate, text)
self.handlers[id] = childs[0] self.handlers[id] = childs[0]
id = childs[1].connect('activate', self.on_open_link_activate, kind, text) id = childs[1].connect('activate', self.on_open_link_activate, kind,
text)
self.handlers[id] = childs[1] self.handlers[id] = childs[1]
childs[2].hide() # copy mail address childs[2].hide() # copy mail address
childs[3].hide() # open mail composer childs[3].hide() # open mail composer
@ -685,13 +688,14 @@ class ConversationTextview:
# load muc icon # load muc icon
join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem')
muc_icon = gajim.interface.roster.load_icon('muc_active') muc_icon = gajim.interface.roster.load_icon('muc_active')
if muc_icon: if muc_icon:
join_group_chat_menuitem.set_image(muc_icon) join_group_chat_menuitem.set_image(muc_icon)
text = text.lower() text = text.lower()
id = childs[2].connect('activate', self.on_copy_link_activate, text) id = childs[2].connect('activate', self.on_copy_link_activate, text)
self.handlers[id] = childs[2] self.handlers[id] = childs[2]
id = childs[3].connect('activate', self.on_open_link_activate, kind, text) id = childs[3].connect('activate', self.on_open_link_activate, kind,
text)
self.handlers[id] = childs[3] self.handlers[id] = childs[3]
id = childs[5].connect('activate', self.on_start_chat_activate, text) id = childs[5].connect('activate', self.on_start_chat_activate, text)
self.handlers[id] = childs[5] self.handlers[id] = childs[5]
@ -708,7 +712,8 @@ class ConversationTextview:
allow_add = True allow_add = True
if allow_add: if allow_add:
id = childs[7].connect('activate', self.on_add_to_roster_activate, text) id = childs[7].connect('activate', self.on_add_to_roster_activate,
text)
self.handlers[id] = childs[7] self.handlers[id] = childs[7]
childs[7].show() # show add to roster menuitem childs[7].show() # show add to roster menuitem
else: else:
@ -729,7 +734,8 @@ class ConversationTextview:
# we get the end of the tag # we get the end of the tag
while not end_iter.ends_tag(texttag): while not end_iter.ends_tag(texttag):
end_iter.forward_char() end_iter.forward_char()
word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode('utf-8') word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode(
'utf-8')
if event.button == 3: # right click if event.button == 3: # right click
self.make_link_menu(event, kind, word) self.make_link_menu(event, kind, word)
else: else:
@ -786,16 +792,16 @@ class ConversationTextview:
exitcode = 0 exitcode = 0
# some latex commands are really bad # some latex commands are really bad
blacklist = ["\\def", "\\let", "\\futurelet", blacklist = ['\\def', '\\let', '\\futurelet',
"\\newcommand", "\\renewcomment", "\\else", "\\fi", "\\write", '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write',
"\\input", "\\include", "\\chardef", "\\catcode", "\\makeatletter", '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter',
"\\noexpand", "\\toksdef", "\\every", "\\errhelp", "\\errorstopmode", '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode',
"\\scrollmode", "\\nonstopmode", "\\batchmode", "\\read", "\\csname", '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname',
"\\newhelp", "\\relax", "\\afterground", "\\afterassignment", '\\newhelp', '\\relax', '\\afterground', '\\afterassignment',
"\\expandafter", "\\noexpand", "\\special", "\\command", "\\loop", '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop',
"\\repeat", "\\toks", "\\output", "\\line", "\\mathcode", "\\name", '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name',
"\\item", "\\section", "\\mbox", "\\DeclareRobustCommand", "\\[", '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[',
"\\]"] '\\]']
str = str[2:len(str)-2] str = str[2:len(str)-2]
@ -807,16 +813,18 @@ class ConversationTextview:
if exitcode == 0: if exitcode == 0:
random.seed() random.seed()
tmpfile = os.path.join(gettempdir(), "gajimtex_" + random.randint(0, tmpfile = os.path.join(gettempdir(), 'gajimtex_' + random.randint(0,
100).__str__()) 100).__str__())
# build latex string # build latex string
texstr = "\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}\\usepackage{amsmath}\\usepackage{amssymb}\\pagestyle{empty}" texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}'
texstr += "\\begin{document}\\begin{large}\\begin{gather*}" texstr += '\\usepackage{amsmath}\\usepackage{amssymb}'
texstr += '\\pagestyle{empty}'
texstr += '\\begin{document}\\begin{large}\\begin{gather*}'
texstr += str texstr += str
texstr += "\\end{gather*}\\end{large}\\end{document}" texstr += '\\end{gather*}\\end{large}\\end{document}'
file = open(os.path.join(tmpfile + ".tex"), "w+") file = open(os.path.join(tmpfile + '.tex'), 'w+')
file.write(texstr) file.write(texstr)
file.flush() file.flush()
file.close() file.close()
@ -825,17 +833,17 @@ class ConversationTextview:
cwd=gettempdir()) cwd=gettempdir())
exitcode = p.wait() exitcode = p.wait()
if exitcode == 0: if exitcode == 0:
p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'], p = Popen(['dvips', '-E', '-o', tmpfile + '.ps', tmpfile + '.dvi'],
cwd=gettempdir()) cwd=gettempdir())
exitcode = p.wait() exitcode = p.wait()
if exitcode == 0: if exitcode == 0:
p = Popen(['convert', tmpfile + '.ps', tmpfile + '.png'], p = Popen(['convert', '-alpha', 'off', tmpfile + '.ps',
cwd=gettempdir()) tmpfile + '.png'], cwd=gettempdir())
exitcode = p.wait() exitcode = p.wait()
extensions = [".tex", ".log", ".aux", ".dvi", ".ps"] extensions = ['.tex', '.log', '.aux', '.dvi', '.ps']
for ext in extensions: for ext in extensions:
try: try:
os.remove(tmpfile + ext) os.remove(tmpfile + ext)
@ -895,11 +903,13 @@ class ConversationTextview:
use_other_tags = False use_other_tags = False
elif special_text.startswith('*'): # it's a bold text elif special_text.startswith('*'): # it's a bold text
tags.append('bold') tags.append('bold')
if special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic if special_text[1] == '/' and special_text[-2] == '/' and\
len(special_text) > 4: # it's also italic
tags.append('italic') tags.append('italic')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove */ /* special_text = special_text[2:-2] # remove */ /*
elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined elif special_text[1] == '_' and special_text[-2] == '_' and \
len(special_text) > 4: # it's also underlined
tags.append('underline') tags.append('underline')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove *_ _* special_text = special_text[2:-2] # remove *_ _*
@ -908,11 +918,13 @@ class ConversationTextview:
special_text = special_text[1:-1] # remove * * special_text = special_text[1:-1] # remove * *
elif special_text.startswith('/'): # it's an italic text elif special_text.startswith('/'): # it's an italic text
tags.append('italic') tags.append('italic')
if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold if special_text[1] == '*' and special_text[-2] == '*' and \
len(special_text) > 4: # it's also bold
tags.append('bold') tags.append('bold')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /* */ special_text = special_text[2:-2] # remove /* */
elif special_text[1] == '_' and special_text[-2] == '_' and len(special_text) > 4: # it's also underlined elif special_text[1] == '_' and special_text[-2] == '_' and \
len(special_text) > 4: # it's also underlined
tags.append('underline') tags.append('underline')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove /_ _/ special_text = special_text[2:-2] # remove /_ _/
@ -921,11 +933,13 @@ class ConversationTextview:
special_text = special_text[1:-1] # remove / / special_text = special_text[1:-1] # remove / /
elif special_text.startswith('_'): # it's an underlined text elif special_text.startswith('_'): # it's an underlined text
tags.append('underline') tags.append('underline')
if special_text[1] == '*' and special_text[-2] == '*' and len(special_text) > 4: # it's also bold if special_text[1] == '*' and special_text[-2] == '*' and \
len(special_text) > 4: # it's also bold
tags.append('bold') tags.append('bold')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _* *_ special_text = special_text[2:-2] # remove _* *_
elif special_text[1] == '/' and special_text[-2] == '/' and len(special_text) > 4: # it's also italic elif special_text[1] == '/' and special_text[-2] == '/' and \
len(special_text) > 4: # it's also italic
tags.append('italic') tags.append('italic')
if not show_ascii_formatting_chars: if not show_ascii_formatting_chars:
special_text = special_text[2:-2] # remove _/ /_ special_text = special_text[2:-2] # remove _/ /_
@ -1123,8 +1137,8 @@ class ConversationTextview:
self.tv.display_html(xhtml.encode('utf-8')) self.tv.display_html(xhtml.encode('utf-8'))
return return
except Exception, e: except Exception, e:
gajim.log.debug(str("Error processing xhtml")+str(e)) gajim.log.debug(str('Error processing xhtml')+str(e))
gajim.log.debug(str("with |"+xhtml+"|")) gajim.log.debug(str('with |'+xhtml+'|'))
buffer = self.tv.get_buffer() buffer = self.tv.get_buffer()
# /me is replaced by name if name is given # /me is replaced by name if name is given
@ -1136,4 +1150,3 @@ class ConversationTextview:
# add the rest of text located in the index and after # add the rest of text located in the index and after
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(end_iter, text[index:], *text_tags) buffer.insert_with_tags_by_name(end_iter, text[index:], *text_tags)

View file

@ -342,7 +342,7 @@ class SingleForm(gtk.Table, object):
for value, label in field.iter_options(): for value, label in field.iter_options():
radio = gtk.RadioButton(first_radio, label=label) radio = gtk.RadioButton(first_radio, label=label)
radio.connect('toggled', radio.connect('toggled',
self.on_list_single_radiobutton_toggled, field, value) self.on_list_single_radiobutton_toggled, field, value)
if first_radio is None: if first_radio is None:
first_radio = radio first_radio = radio
if field.value == '': # TODO: is None when done if field.value == '': # TODO: is None when done

View file

@ -9,7 +9,7 @@
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za> ## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net> ## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
## Julien Pivotto <roidelapluie@gmail.com> ## Julien Pivotto <roidelapluie@gmail.com>
## Stephan Erb <steve-e@h3c.de> ## Stephan Erb <steve-e@h3c.de>
## ##
## This file is part of Gajim. ## This file is part of Gajim.
## ##
@ -207,7 +207,7 @@ class EditGroupsDialog:
for group in groups: for group in groups:
if group not in helpers.special_groups or groups[group] > 0: if group not in helpers.special_groups or groups[group] > 0:
group_list.append(group) group_list.append(group)
group_list.sort() group_list.sort()
for group in group_list: for group in group_list:
iter = store.append() iter = store.append()
store.set(iter, 0, group) # Group name store.set(iter, 0, group) # Group name
@ -256,7 +256,7 @@ class PassphraseDialog:
self.window.destroy() self.window.destroy()
return passphrase, checked return passphrase, checked
def __init__(self, titletext, labeltext, checkbuttontext=None, is_modal = True, def __init__(self, titletext, labeltext, checkbuttontext=None, is_modal=True,
ok_handler = None, cancel_handler = None): ok_handler = None, cancel_handler = None):
self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade') self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade')
self.window = self.xml.get_widget('passphrase_dialog') self.window = self.xml.get_widget('passphrase_dialog')
@ -278,14 +278,14 @@ class PassphraseDialog:
self.xml.signal_autoconnect(self) self.xml.signal_autoconnect(self)
self.window.show_all() self.window.show_all()
self.check = bool(checkbuttontext) self.check = bool(checkbuttontext)
checkbutton = self.xml.get_widget('save_passphrase_checkbutton') checkbutton = self.xml.get_widget('save_passphrase_checkbutton')
if self.check: if self.check:
checkbutton.set_label(checkbuttontext) checkbutton.set_label(checkbuttontext)
else: else:
checkbutton.hide() checkbutton.hide()
def on_okbutton_clicked(self, widget): def on_okbutton_clicked(self, widget):
passph = self.passphrase_entry.get_text().decode('utf-8') passph = self.passphrase_entry.get_text().decode('utf-8')
@ -351,9 +351,9 @@ class ChooseGPGKeyDialog:
def run(self): def run(self):
rep = self.window.run() rep = self.window.run()
if rep == gtk.RESPONSE_OK: selection = self.keys_treeview.get_selection()
selection = self.keys_treeview.get_selection() (model, iter) = selection.get_selected()
(model, iter) = selection.get_selected() if iter and rep == gtk.RESPONSE_OK:
keyID = [ model[iter][0].decode('utf-8'), keyID = [ model[iter][0].decode('utf-8'),
model[iter][1].decode('utf-8') ] model[iter][1].decode('utf-8') ]
else: else:
@ -370,6 +370,125 @@ class ChooseGPGKeyDialog:
self.keys_treeview.set_cursor(path) self.keys_treeview.set_cursor(path)
class ChangeActivityDialog:
activities = [_('doing_chores'), _('drinking'), _('eating'),
_('excercising'), _('grooming'), _('having_appointment'),
_('inactive'), _('relaxing'), _('talking'), _('traveling'),
_('working'), ]
subactivities = [_('at_the_spa'), _('brushing_teeth'),
_('buying_groceries'), _('cleaning'), _('coding'),
_('commuting'), _('cooking'), _('cycling'), _('day_off'),
_('doing_maintenance'), _('doing_the_dishes'),
_('doing_the_laundry'), _('driving'), _('gaming'),
_('gardening'), _('getting_a_haircut'), _('going_out'),
_('hanging_out'), _('having_a_beer'), _('having_a_snack'),
_('having_breakfast'), _('having_coffee'),
_('having_dinner'), _('having_lunch'), _('having_tea'),
_('hiking'), _('in_a_car'), _('in_a_meeting'),
_('in_real_life'), _('jogging'), _('on_a_bus'),
_('on_a_plane'), _('on_a_train'), _('on_a_trip'),
_('on_the_phone'), _('on_vacation'), _('other'),
_('partying'), _('playing_sports'), _('reading'),
_('rehearsing'), _('running'), _('running_an_errand'),
_('scheduled_holiday'), _('shaving'), _('shopping'),
_('skiing'), _('sleeping'), _('socializing'),
_('studying'), _('sunbathing'), _('swimming'),
_('taking_a_bath'), _('taking_a_shower'), _('walking'),
_('walking_the_dog'), _('watching_tv'),
_('watching_a_movie'), _('working_out'), _('writing'), ]
def __init__(self, account):
self.account = account
self.xml = gtkgui_helpers.get_glade('change_activity_dialog.glade')
self.window = self.xml.get_widget('change_activity_dialog')
self.window.set_transient_for(gajim.interface.roster.window)
self.window.set_title(_('Activity'))
self.entry = self.xml.get_widget('entry')
self.combo1 = self.xml.get_widget('combobox1')
self.liststore1 = gtk.ListStore(str)
self.combo1.set_model(self.liststore1)
for activity in self.activities:
self.liststore1.append((activity,))
cellrenderertext = gtk.CellRendererText()
self.combo1.pack_start(cellrenderertext, True)
self.combo1.add_attribute(cellrenderertext, 'text', 0)
self.combo2 = self.xml.get_widget('combobox2')
self.liststore2 = gtk.ListStore(str)
self.combo2.set_model(self.liststore2)
for subactivity in self.subactivities:
self.liststore2.append((subactivity,))
cellrenderertext = gtk.CellRendererText()
self.combo2.pack_start(cellrenderertext, True)
self.combo2.add_attribute(cellrenderertext, 'text', 0)
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_ok_button_clicked(self, widget):
'''Return activity and messsage (None if no activity selected)'''
activity = None
subactivity = None
message = None
active1 = self.combo1.get_active()
active2 = self.combo2.get_active()
if active1 > -1:
activity = self.liststore1[active1][0].decode('utf-8')
if active2 > -1:
subactivity = self.liststore2[active2][0].decode('utf-8')
message = self.entry.get_text().decode('utf-8')
from common import pep
pep.user_send_activity(self.account, activity,
subactivity, message)
self.window.destroy()
def on_cancel_button_clicked(self, widget):
self.window.destroy()
class ChangeMoodDialog:
moods = [_('afraid'), _('amazed'), _('angry'), _('annoyed'), _('anxious'), _('aroused'), _('ashamed'), _('bored'), _('brave'), _('calm'), _('cold'), _('confused'), _('contented'), _('cranky'), _('curious'), _('depressed'), _('disappointed'), _('disgusted'), _('distracted'), _('embarrassed'), _('excited'), _('flirtatious'), _('frustrated'), _('grumpy'), _('guilty'), _('happy'), _('hot'), _('humbled'), _('humiliated'), _('hungry'), _('hurt'), _('impressed'), _('in_awe'), _('in_love'), _('indignant'), _('interested'), _('intoxicated'), _('invincible'), _('jealous'), _('lonely'), _('mean'), _('moody'), _('nervous'), _('neutral'), _('offended'), _('playful'), _('proud'), _('relieved'), _('remorseful'), _('restless'), _('sad'), _('sarcastic'), _('serious'), _('shocked'), _('shy'), _('sick'), _('sleepy'), _('stressed'), _('surprised'), _('thirsty'), _('worried')]
def __init__(self, account):
self.account = account
self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade')
self.window = self.xml.get_widget('change_mood_dialog')
self.window.set_transient_for(gajim.interface.roster.window)
self.window.set_title(_('Mood'))
self.entry = self.xml.get_widget('entry')
self.combo = self.xml.get_widget('combobox')
self.liststore = gtk.ListStore(str)
self.combo.set_model(self.liststore)
for mood in self.moods:
self.liststore.append((mood,))
cellrenderertext = gtk.CellRendererText()
self.combo.pack_start(cellrenderertext, True)
self.combo.add_attribute(cellrenderertext, 'text', 0)
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_ok_button_clicked(self, widget):
'''Return mood and messsage (None if no mood selected)'''
mood = None
message = None
active = self.combo.get_active()
if active > -1:
mood = self.liststore[active][0].decode('utf-8')
message = self.entry.get_text().decode('utf-8')
from common import pep
pep.user_send_mood(self.account, mood, message)
self.window.destroy()
def on_cancel_button_clicked(self, widget):
self.window.destroy()
class ChangeStatusMessageDialog: class ChangeStatusMessageDialog:
def __init__(self, show = None): def __init__(self, show = None):
self.show = show self.show = show
@ -502,7 +621,7 @@ class ChangeStatusMessageDialog:
self.preset_messages_dict[msg_name] = msg_text self.preset_messages_dict[msg_name] = msg_text
iter_ = self.message_liststore.append((msg_name,)) iter_ = self.message_liststore.append((msg_name,))
gajim.config.add_per('statusmsg', msg_name) gajim.config.add_per('statusmsg', msg_name)
# select in combobox the one we just saved # select in combobox the one we just saved
self.message_combobox.set_active_iter(iter_) self.message_combobox.set_active_iter(iter_)
gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l) gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l)
@ -578,7 +697,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
self.agents[type_].append(jid_) self.agents[type_].append(jid_)
self.available_types.append(type_) self.available_types.append(type_)
liststore = gtk.ListStore(str) liststore = gtk.ListStore(str)
self.group_comboboxentry.set_model(liststore) self.group_comboboxentry.set_model(liststore)
# Combobox with transport/jabber icons # Combobox with transport/jabber icons
liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str) liststore = gtk.ListStore(str, gtk.gdk.Pixbuf, str)
cell = gtk.CellRendererPixbuf() cell = gtk.CellRendererPixbuf()
@ -828,7 +947,7 @@ class AboutDialog:
text = open(copying_file_path).read() text = open(copying_file_path).read()
dlg.set_license(text) dlg.set_license(text)
dlg.set_comments('%s\n%s %s\n%s %s' dlg.set_comments('%s\n%s %s\n%s %s'
% (_('A GTK+ jabber client'), \ % (_('A GTK+ jabber client'), \
_('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \ _('GTK+ Version:'), self.tuple2str(gtk.gtk_version), \
_('PyGTK Version:'), self.tuple2str(gtk.pygtk_version))) _('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
@ -872,7 +991,7 @@ class AboutDialog:
dlg.props.wrap_license = True dlg.props.wrap_license = True
pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join( pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(
gajim.DATA_DIR, 'pixmaps', 'gajim_about.png')) gajim.DATA_DIR, 'pixmaps', 'gajim_about.png'))
dlg.set_logo(pixbuf) dlg.set_logo(pixbuf)
#here you write your name in the form Name FamilyName <someone@somewhere> #here you write your name in the form Name FamilyName <someone@somewhere>
@ -918,11 +1037,11 @@ class HigDialog(gtk.MessageDialog):
def __init__(self, parent, type, buttons, pritext, sectext, def __init__(self, parent, type, buttons, pritext, sectext,
on_response_ok = None, on_response_cancel = None, on_response_yes = None, on_response_ok = None, on_response_cancel = None, on_response_yes = None,
on_response_no = None): on_response_no = None):
gtk.MessageDialog.__init__(self, parent, gtk.MessageDialog.__init__(self, parent,
gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
type, buttons, message_format = pritext) type, buttons, message_format = pritext)
self.format_secondary_text(sectext) self.format_secondary_markup(sectext)
buttons = self.action_area.get_children() buttons = self.action_area.get_children()
possible_responses = {gtk.STOCK_OK: on_response_ok, possible_responses = {gtk.STOCK_OK: on_response_ok,
@ -969,7 +1088,7 @@ class FileChooserDialog(gtk.FileChooserDialog):
select_multiple = False, current_folder = None, on_response_ok = None, select_multiple = False, current_folder = None, on_response_ok = None,
on_response_cancel = None): on_response_cancel = None):
gtk.FileChooserDialog.__init__(self, title = title_text, gtk.FileChooserDialog.__init__(self, title = title_text,
action = action, buttons = buttons) action = action, buttons = buttons)
self.set_default_response(default_response) self.set_default_response(default_response)
@ -1027,7 +1146,7 @@ class ConfirmationDialog(HigDialog):
on_response_cancel = None): on_response_cancel = None):
self.user_response_ok = on_response_ok self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel self.user_response_cancel = on_response_cancel
HigDialog.__init__(self, None, HigDialog.__init__(self, None,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
self.on_response_ok, self.on_response_cancel) self.on_response_ok, self.on_response_cancel)
self.popup() self.popup()
@ -1054,7 +1173,7 @@ class NonModalConfirmationDialog(HigDialog):
on_response_cancel = None): on_response_cancel = None):
self.user_response_ok = on_response_ok self.user_response_ok = on_response_ok
self.user_response_cancel = on_response_cancel self.user_response_cancel = on_response_cancel
HigDialog.__init__(self, None, HigDialog.__init__(self, None,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext,
self.on_response_ok, self.on_response_cancel) self.on_response_ok, self.on_response_cancel)
self.set_modal(False) self.set_modal(False)
@ -1078,7 +1197,7 @@ class NonModalConfirmationDialog(HigDialog):
class WarningDialog(HigDialog): class WarningDialog(HigDialog):
def __init__(self, pritext, sectext=''): def __init__(self, pritext, sectext=''):
'''HIG compliant warning dialog.''' '''HIG compliant warning dialog.'''
HigDialog.__init__( self, None, HigDialog.__init__( self, None,
gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext) gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
self.set_modal(False) self.set_modal(False)
self.set_transient_for(gajim.interface.roster.window) self.set_transient_for(gajim.interface.roster.window)
@ -1087,7 +1206,7 @@ class WarningDialog(HigDialog):
class InformationDialog(HigDialog): class InformationDialog(HigDialog):
def __init__(self, pritext, sectext=''): def __init__(self, pritext, sectext=''):
'''HIG compliant info dialog.''' '''HIG compliant info dialog.'''
HigDialog.__init__( self, None, HigDialog.__init__( self, None,
gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext) gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext)
self.set_modal(False) self.set_modal(False)
self.set_transient_for(gajim.interface.roster.window) self.set_transient_for(gajim.interface.roster.window)
@ -1096,20 +1215,52 @@ class InformationDialog(HigDialog):
class ErrorDialog(HigDialog): class ErrorDialog(HigDialog):
def __init__(self, pritext, sectext=''): def __init__(self, pritext, sectext=''):
'''HIG compliant error dialog.''' '''HIG compliant error dialog.'''
HigDialog.__init__( self, None, HigDialog.__init__( self, None,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext) gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext)
self.popup() self.popup()
class YesNoDialog(HigDialog): class YesNoDialog(HigDialog):
def __init__(self, pritext, sectext='', on_response_yes = None, def __init__(self, pritext, sectext='', checktext='', on_response_yes=None,
on_response_no = None): on_response_no=None):
'''HIG compliant YesNo dialog.''' '''HIG compliant YesNo dialog.'''
HigDialog.__init__( self, None, self.user_response_yes = on_response_yes
self.user_response_no = on_response_no
HigDialog.__init__( self, None,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, pritext, sectext,
on_response_yes = on_response_yes, on_response_no = on_response_no) on_response_yes=self.on_response_yes,
on_response_no=self.on_response_no)
if checktext:
self.checkbutton = gtk.CheckButton(checktext)
self.vbox.pack_start(self.checkbutton, expand=False, fill=True)
else:
self.checkbutton = None
self.set_modal(False) self.set_modal(False)
self.popup() self.popup()
def on_response_yes(self, widget):
if self.user_response_yes:
if isinstance(self.user_response_yes, tuple):
self.user_response_yes[0](self.is_checked(),
*self.user_response_yes[1:])
else:
self.user_response_yes(self.is_checked())
self.destroy()
def on_response_no(self, widget):
if self.user_response_no:
if isinstance(self.user_response_no, tuple):
self.user_response_no[0](*self.user_response_no[1:])
else:
self.user_response_no()
self.destroy()
def is_checked(self):
''' Get active state of the checkbutton '''
if not self.checkbutton:
return False
return self.checkbutton.get_active()
class ConfirmationDialogCheck(ConfirmationDialog): class ConfirmationDialogCheck(ConfirmationDialog):
'''HIG compliant confirmation dialog with checkbutton.''' '''HIG compliant confirmation dialog with checkbutton.'''
def __init__(self, pritext, sectext='', checktext = '', def __init__(self, pritext, sectext='', checktext = '',
@ -1145,7 +1296,7 @@ class ConfirmationDialogCheck(ConfirmationDialog):
def on_response_cancel(self, widget): def on_response_cancel(self, widget):
if self.user_response_cancel: if self.user_response_cancel:
if isinstance(self.user_response_cancel, tuple): if isinstance(self.user_response_cancel, tuple):
self.user_response_cancel[0](*self.user_response_ok[1:]) self.user_response_cancel[0](*self.user_response_cancel[1:])
else: else:
self.user_response_cancel() self.user_response_cancel()
self.destroy() self.destroy()
@ -1765,7 +1916,7 @@ class PopupNotificationWindow:
bg_color = 'yellowgreen' bg_color = 'yellowgreen'
elif event_type == _('Groupchat Invitation'): elif event_type == _('Groupchat Invitation'):
bg_color = 'tan1' bg_color = 'tan1'
elif event_type == _('Contact Changed Status'): elif event_type == _('Contact Changed Status'):
bg_color = 'thistle2' bg_color = 'thistle2'
else: # Unknown event! Shouldn't happen but deal with it else: # Unknown event! Shouldn't happen but deal with it
bg_color = 'white' bg_color = 'white'
@ -1773,7 +1924,7 @@ class PopupNotificationWindow:
close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color) close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color) eventbox.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
event_description_label.set_markup( event_description_label.set_markup(
'<span foreground="black">%s</span>' % text) '<span foreground="black">%s</span>' % text)
# set the image # set the image
image.set_from_file(path_to_image) image.set_from_file(path_to_image)
@ -1922,14 +2073,14 @@ class SingleMessageWindow:
self.completion_dict = {} self.completion_dict = {}
self.xml.signal_autoconnect(self) self.xml.signal_autoconnect(self)
if gajim.config.get('saveposition'): # get window position and size from config
# get window position and size from config gtkgui_helpers.resize_window(self.window,
gtkgui_helpers.move_window(self.window, gajim.config.get('single-msg-width'),
gajim.config.get('single-msg-x-position'), gajim.config.get('single-msg-height'))
gajim.config.get('single-msg-y-position')) gtkgui_helpers.move_window(self.window,
gtkgui_helpers.resize_window(self.window, gajim.config.get('single-msg-x-position'),
gajim.config.get('single-msg-width'), gajim.config.get('single-msg-y-position'))
gajim.config.get('single-msg-height'))
self.window.show_all() self.window.show_all()
def on_single_message_window_destroy(self, widget): def on_single_message_window_destroy(self, widget):
@ -1940,15 +2091,14 @@ class SingleMessageWindow:
self.message_tv_buffer.place_cursor(end_iter) self.message_tv_buffer.place_cursor(end_iter)
def save_pos(self): def save_pos(self):
if gajim.config.get('saveposition'): # save the window size and position
# save the window size and position x, y = self.window.get_position()
x, y = self.window.get_position() gajim.config.set('single-msg-x-position', x)
gajim.config.set('single-msg-x-position', x) gajim.config.set('single-msg-y-position', y)
gajim.config.set('single-msg-y-position', y) width, height = self.window.get_size()
width, height = self.window.get_size() gajim.config.set('single-msg-width', width)
gajim.config.set('single-msg-width', width) gajim.config.set('single-msg-height', height)
gajim.config.set('single-msg-height', height) gajim.interface.save_config()
gajim.interface.save_config()
def on_single_message_window_delete_event(self, window, ev): def on_single_message_window_delete_event(self, window, ev):
self.save_pos() self.save_pos()
@ -1999,7 +2149,7 @@ class SingleMessageWindow:
if self.message: if self.message:
self.conversation_textview.print_real_text(self.message) self.conversation_textview.print_real_text(self.message)
fjid = self.from_whom fjid = self.from_whom
if self.resource: if self.resource:
fjid += '/' + self.resource # Full jid of sender (with resource) fjid += '/' + self.resource # Full jid of sender (with resource)
self.from_entry.set_text(fjid) self.from_entry.set_text(fjid)
@ -2009,16 +2159,16 @@ class SingleMessageWindow:
self.cancel_button.hide() self.cancel_button.hide()
self.close_button.show() self.close_button.show()
elif action == 'form': # prepare UI for Receiving elif action == 'form': # prepare UI for Receiving
title = _('Form %s') % title title = _('Form %s') % title
self.send_button.show() self.send_button.show()
self.send_and_close_button.show() self.send_and_close_button.show()
self.to_label.show() self.to_label.show()
self.to_entry.show() self.to_entry.show()
self.reply_button.hide() self.reply_button.hide()
self.from_label.hide() self.from_label.hide()
self.from_entry.hide() self.from_entry.hide()
self.conversation_scrolledwindow.hide() self.conversation_scrolledwindow.hide()
self.message_scrolledwindow.hide() self.message_scrolledwindow.hide()
self.window.set_title(title) self.window.set_title(title)
@ -2266,7 +2416,7 @@ class PrivacyListWindow:
# set jabber id completion # set jabber id completion
jids_list_store = gtk.ListStore(gobject.TYPE_STRING) jids_list_store = gtk.ListStore(gobject.TYPE_STRING)
for jid in gajim.contacts.get_jid_list(self.account): for jid in gajim.contacts.get_jid_list(self.account):
jids_list_store.append([jid]) jids_list_store.append([jid])
jid_entry_completion = gtk.EntryCompletion() jid_entry_completion = gtk.EntryCompletion()
jid_entry_completion.set_text_column(0) jid_entry_completion.set_text_column(0)
jid_entry_completion.set_model(jids_list_store) jid_entry_completion.set_model(jids_list_store)
@ -2280,7 +2430,7 @@ class PrivacyListWindow:
self.list_of_groups[group] = count self.list_of_groups[group] = count
count += 1 count += 1
self.edit_type_group_combobox.append_text(group) self.edit_type_group_combobox.append_text(group)
self.edit_type_group_combobox.set_active(0) self.edit_type_group_combobox.set_active(0)
self.window.set_title(title) self.window.set_title(title)
@ -2302,7 +2452,7 @@ class PrivacyListWindow:
if a_d_dict['default'] == self.privacy_list_name: if a_d_dict['default'] == self.privacy_list_name:
self.privacy_list_default_checkbutton.set_active(True) self.privacy_list_default_checkbutton.set_active(True)
else: else:
self.privacy_list_default_checkbutton.set_active(False) self.privacy_list_default_checkbutton.set_active(False)
def privacy_list_received(self, rules): def privacy_list_received(self, rules):
self.list_of_rules_combobox.get_model().clear() self.list_of_rules_combobox.get_model().clear()
@ -2437,7 +2587,7 @@ class PrivacyListWindow:
self.edit_queries_send_checkbutton.set_active(False) self.edit_queries_send_checkbutton.set_active(False)
self.edit_view_status_checkbutton.set_active(False) self.edit_view_status_checkbutton.set_active(False)
self.edit_send_status_checkbutton.set_active(False) self.edit_send_status_checkbutton.set_active(False)
self.edit_order_spinbutton.set_value(1) self.edit_order_spinbutton.set_value(1)
self.edit_type_group_combobox.set_active(0) self.edit_type_group_combobox.set_active(0)
self.edit_type_subscription_combobox.set_active(0) self.edit_type_subscription_combobox.set_active(0)
self.add_edit_rule_label.set_label( self.add_edit_rule_label.set_label(
@ -2519,7 +2669,7 @@ class PrivacyListsWindow:
or edit an already there one''' or edit an already there one'''
def __init__(self, account): def __init__(self, account):
self.account = account self.account = account
self.privacy_lists_save = [] self.privacy_lists_save = []
self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade') self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade')
@ -2529,7 +2679,7 @@ class PrivacyListsWindow:
'new_privacy_list_button', 'new_privacy_list_entry', 'new_privacy_list_button', 'new_privacy_list_entry',
'privacy_lists_refresh_button', 'close_privacy_lists_window_button']: 'privacy_lists_refresh_button', 'close_privacy_lists_window_button']:
self.__dict__[widget_to_add] = self.xml.get_widget( self.__dict__[widget_to_add] = self.xml.get_widget(
widget_to_add) widget_to_add)
self.draw_privacy_lists_in_combobox([]) self.draw_privacy_lists_in_combobox([])
self.privacy_lists_refresh() self.privacy_lists_refresh()
@ -2967,8 +3117,8 @@ class AdvancedNotificationsWindow:
self.window.show_all() self.window.show_all()
def initiate_rule_state(self): def initiate_rule_state(self):
'''Set values for all widgets''' '''Set values for all widgets'''
if self.active_num < 0: if self.active_num < 0:
return return
# event # event
@ -3209,7 +3359,7 @@ class AdvancedNotificationsWindow:
for st in ['online', 'away', 'xa', 'dnd', 'invisible']: for st in ['online', 'away', 'xa', 'dnd', 'invisible']:
self.__dict__[st + '_cb'].hide() self.__dict__[st + '_cb'].hide()
self.special_status_rb.show() self.special_status_rb.show()
else: else:
self.set_status_config() self.set_status_config()
# 'special status' clicked # 'special status' clicked
@ -3417,6 +3567,15 @@ class TransformChatToMUC:
self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.guests_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
# All contacts beside the following can be invited:
# transports, zeroconf contacts, minimized groupchats
invitable = lambda contact, contact_transport = None:\
contact.jid not in self.auto_jids and\
contact.jid != gajim.get_jid_from_account(self.account) and\
contact.jid not in gajim.interface.minimized_controls[account] and\
not contact.is_transport() and\
not contact_transport
# set jabber id and pseudos # set jabber id and pseudos
for account in gajim.contacts.get_accounts(): for account in gajim.contacts.get_accounts():
if gajim.connections[account].is_zeroconf: if gajim.connections[account].is_zeroconf:
@ -3425,24 +3584,20 @@ class TransformChatToMUC:
contact = \ contact = \
gajim.contacts.get_contact_with_highest_priority(account, jid) gajim.contacts.get_contact_with_highest_priority(account, jid)
contact_transport = gajim.get_transport_name_from_jid(jid) contact_transport = gajim.get_transport_name_from_jid(jid)
# do not add transports, zeroconf contacs, minimized groupchats # Add contact if it can be invited
# and selfjid to list of invitable jids if invitable(contact, contact_transport) and \
if contact.jid not in self.auto_jids and contact.jid != \ contact.show not in ('offline', 'error'):
gajim.get_jid_from_account(self.account) and not contact_transport \ img = gajim.interface.roster.jabber_state_images['16'][
and not contact.is_transport() and contact.jid not in \ contact.show]
gajim.interface.minimized_controls[account]: name = contact.name
if contact.show not in ('offline', 'error'): if name == '':
img = gajim.interface.roster.jabber_state_images['16'][ name = jid.split('@')[0]
contact.show] iter = self.store.append([img.get_pixbuf(), name, jid])
name = contact.name # preselect treeview rows
if name == '': if self.preselected_jids and jid in self.preselected_jids:
name = jid.split('@')[0] path = self.store.get_path(iter)
iter = self.store.append([img.get_pixbuf(), name, jid]) self.guests_treeview.get_selection().\
# preselect treeview rows select_path(path)
if self.preselected_jids and jid in self.preselected_jids:
path = self.store.get_path(iter)
self.guests_treeview.get_selection().\
select_path(path)
# show all # show all
self.window.show_all() self.window.show_all()
@ -3460,7 +3615,6 @@ class TransformChatToMUC:
server = self.server_list_comboboxentry.get_active_text() server = self.server_list_comboboxentry.get_active_text()
if server == '': if server == '':
return return
room_id = gajim.nicks[self.account] + str(randrange(9999999))
gajim.connections[self.account].check_unique_room_id_support(server, self) gajim.connections[self.account].check_unique_room_id_support(server, self)
def unique_room_id_supported(self, server, room_id): def unique_room_id_supported(self, server, room_id):
@ -3473,7 +3627,7 @@ class TransformChatToMUC:
guest_list.append(guest) guest_list.append(guest)
room_jid = room_id + '@' + server room_jid = room_id + '@' + server
gajim.automatic_rooms[self.account][room_jid] = {} gajim.automatic_rooms[self.account][room_jid] = {}
gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list
gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True
gajim.interface.roster.join_gc_room(self.account, room_jid, gajim.interface.roster.join_gc_room(self.account, room_jid,
gajim.nicks[self.account], None, is_continued=True) gajim.nicks[self.account], None, is_continued=True)

View file

@ -46,7 +46,6 @@ import inspect
import weakref import weakref
import gobject import gobject
import gtk import gtk
import gobject
import pango import pango
import dialogs import dialogs

View file

@ -21,7 +21,6 @@
import os import os
import sys import sys
import gtk import gtk
import gobject
import gtkgui_helpers import gtkgui_helpers
import dialogs import dialogs
@ -53,7 +52,7 @@ class FeaturesWindow:
_('Requires python-avahi.'), _('Requires python-avahi.'),
_('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')),
_('gajim-remote'): (self.dbus_available, _('gajim-remote'): (self.dbus_available,
_('A script to controle gajim via commandline.'), _('A script to controle Gajim via commandline.'),
_('Requires python-dbus.'), _('Requires python-dbus.'),
_('Feature not available under Windows.')), _('Feature not available under Windows.')),
_('OpenGPG'): (self.gpg_available, _('OpenGPG'): (self.gpg_available,
@ -86,14 +85,14 @@ class FeaturesWindow:
_('Feature not available under Windows.')), _('Feature not available under Windows.')),
_('Trayicon'): (self.trayicon_available, _('Trayicon'): (self.trayicon_available,
_('A icon in systemtray reflecting the current presence.'), _('A icon in systemtray reflecting the current presence.'),
_('Requires python-gnome2-extras or compiled trayicon module from Gajim sources.'), _('Requires python-gnome2-extras or compiled trayicon module from Gajim sources.'),
_('Requires PyGTK >= 2.10.')), _('Requires PyGTK >= 2.10.')),
_('Idle'): (self.idle_available, _('Idle'): (self.idle_available,
_('Ability to measure idle time, in order to set auto status.'), _('Ability to measure idle time, in order to set auto status.'),
_('Requires compilation of the idle module from Gajim sources.'), _('Requires compilation of the idle module from Gajim sources.'),
_('Requires compilation of the idle module from Gajim sources.')), _('Requires compilation of the idle module from Gajim sources.')),
_('LaTeX'): (self.latex_available, _('LaTeX'): (self.latex_available,
_('Transform LaTeX espressions between $$ $$.'), _('Transform LaTeX expressions between $$ $$.'),
_('Requires texlive-latex-base, dvips and imagemagick. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'), _('Requires texlive-latex-base, dvips and imagemagick. You have to set \'use_latex\' to True in the Advanced Configuration Editor.'),
_('Feature not available under Windows.')), _('Feature not available under Windows.')),
_('End to end encryption'): (self.pycrypto_available, _('End to end encryption'): (self.pycrypto_available,
@ -141,6 +140,8 @@ class FeaturesWindow:
def on_features_treeview_cursor_changed(self, widget): def on_features_treeview_cursor_changed(self, widget):
selection = widget.get_selection() selection = widget.get_selection()
if not selection:
return
path = selection.get_selected_rows()[1][0] path = selection.get_selected_rows()[1][0]
available = self.model[path][1] available = self.model[path][1]
feature = self.model[path][0].decode('utf-8') feature = self.model[path][0].decode('utf-8')
@ -178,8 +179,8 @@ class FeaturesWindow:
def gpg_available(self): def gpg_available(self):
if os.name == 'nt': if os.name == 'nt':
return False return False
from common import GnuPG from common import gajim
return GnuPG.USE_GPG return gajim.HAVE_GPG
def network_manager_available(self): def network_manager_available(self):
if os.name == 'nt': if os.name == 'nt':

View file

@ -44,7 +44,6 @@ if os.name == 'nt':
# os.environ['GTK_BASEPATH'] = 'gtk' # os.environ['GTK_BASEPATH'] = 'gtk'
import sys import sys
import urllib
import logging import logging
consoleloghandler = logging.StreamHandler() consoleloghandler = logging.StreamHandler()
@ -222,7 +221,6 @@ import gobject
import re import re
import signal import signal
import getopt
import time import time
import math import math
@ -457,10 +455,13 @@ class Interface:
def handle_event_http_auth(self, account, data): def handle_event_http_auth(self, account, data):
#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg)) #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
def response(widget, account, iq_obj, answer): def response(account, iq_obj, answer):
self.dialog.destroy() self.dialog.destroy()
gajim.connections[account].build_http_auth_answer(iq_obj, answer) gajim.connections[account].build_http_auth_answer(iq_obj, answer)
def on_yes(is_checked, account, iq_obj):
response(account, iq_obj, 'yes')
sec_msg = _('Do you accept this request?') sec_msg = _('Do you accept this request?')
if gajim.get_number_of_connected_accounts() > 1: if gajim.get_number_of_connected_accounts() > 1:
sec_msg = _('Do you accept this request on account %s?') % account sec_msg = _('Do you accept this request on account %s?') % account
@ -468,8 +469,8 @@ class Interface:
sec_msg = data[4] + '\n' + sec_msg sec_msg = data[4] + '\n' + sec_msg
self.dialog = dialogs.YesNoDialog(_('HTTP (%s) Authorization for %s (id: %s)') \ self.dialog = dialogs.YesNoDialog(_('HTTP (%s) Authorization for %s (id: %s)') \
% (data[0], data[1], data[2]), sec_msg, % (data[0], data[1], data[2]), sec_msg,
on_response_yes = (response, account, data[3], 'yes'), on_response_yes=(on_yes, account, data[3]),
on_response_no = (response, account, data[3], 'no')) on_response_no=(response, account, data[3], 'no'))
def handle_event_error_answer(self, account, array): def handle_event_error_answer(self, account, array):
#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode)) #('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
@ -581,10 +582,11 @@ class Interface:
jid = array[0].split('/')[0] jid = array[0].split('/')[0]
keyID = array[5] keyID = array[5]
contact_nickname = array[7] contact_nickname = array[7]
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split() # Get the proper keyID
if jid in attached_keys: keyID = helpers.prepare_and_validate_gpg_keyID(account,
keyID = attached_keys[attached_keys.index(jid) + 1] jid, keyID)
resource = array[3] resource = array[3]
if not resource: if not resource:
resource = '' resource = ''
@ -723,7 +725,7 @@ class Interface:
# remove in 2007 # remove in 2007
# It's maybe a GC_NOTIFY (specialy for MSN gc) # It's maybe a GC_NOTIFY (specialy for MSN gc)
self.handle_event_gc_notify(account, (jid, array[1], status_message, self.handle_event_gc_notify(account, (jid, array[1], status_message,
array[3], None, None, None, None, None, None, None, None)) array[3], None, None, None, None, None, [], None, None))
def handle_event_msg(self, account, array): def handle_event_msg(self, account, array):
@ -848,8 +850,14 @@ class Interface:
msg = message msg = message
if subject: if subject:
msg = _('Subject: %s') % subject + '\n' + msg msg = _('Subject: %s') % subject + '\n' + msg
focused = False
if chat_control:
parent_win = chat_control.parent_win
if chat_control == parent_win.get_active_control() and \
parent_win.window.has_focus:
focused = True
notify.notify('new_message', jid_of_control, account, [msg_type, notify.notify('new_message', jid_of_control, account, [msg_type,
first, nickname, msg], advanced_notif_num) first, nickname, msg, focused], advanced_notif_num)
if self.remote_ctrl: if self.remote_ctrl:
self.remote_ctrl.raise_signal('NewMessage', (account, array)) self.remote_ctrl.raise_signal('NewMessage', (account, array))
@ -949,13 +957,22 @@ class Interface:
self.remote_ctrl.raise_signal('Subscribed', (account, array)) self.remote_ctrl.raise_signal('Subscribed', (account, array))
def handle_event_unsubscribed(self, account, jid): def handle_event_unsubscribed(self, account, jid):
dialogs.InformationDialog(_('Contact "%s" removed subscription from you')\
% jid, _('You will always see him or her as offline.'))
# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
gajim.connections[account].ack_unsubscribed(jid) gajim.connections[account].ack_unsubscribed(jid)
if self.remote_ctrl: if self.remote_ctrl:
self.remote_ctrl.raise_signal('Unsubscribed', (account, jid)) self.remote_ctrl.raise_signal('Unsubscribed', (account, jid))
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if not contact:
return
def on_yes(is_checked, list_):
self.roster.on_req_usub(None, list_)
list_ = [(contact, account)]
dialogs.YesNoDialog(
_('Contact "%s" removed subscription from you') % jid,
_('You will always see him or her as offline.\nDo you want to remove him or her from your contact list?'),
on_response_yes=(on_yes, list_))
# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
def handle_event_agent_info_error(self, account, agent): def handle_event_agent_info_error(self, account, agent):
#('AGENT_ERROR_INFO', account, (agent)) #('AGENT_ERROR_INFO', account, (agent))
try: try:
@ -999,6 +1016,11 @@ class Interface:
def handle_event_agent_info_items(self, account, array): def handle_event_agent_info_items(self, account, array):
#('AGENT_INFO_ITEMS', account, (agent, node, items)) #('AGENT_INFO_ITEMS', account, (agent, node, items))
our_jid = gajim.get_jid_from_account(account)
if gajim.interface.instances[account].has_key('pep_services') and \
array[0] == our_jid:
gajim.interface.instances[account]['pep_services'].items_received(
array[2])
try: try:
gajim.connections[account].services_cache.agent_items(array[0], gajim.connections[account].services_cache.agent_items(array[0],
array[1], array[2]) array[1], array[2])
@ -1014,11 +1036,11 @@ class Interface:
return return
def handle_event_new_acc_connected(self, account, array): def handle_event_new_acc_connected(self, account, array):
#('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_cert, #('NEW_ACC_CONNECTED', account, (infos, is_form, ssl_msg, ssl_err,
# ssl_fingerprint)) # ssl_cert, ssl_fingerprint))
if self.instances.has_key('account_creation_wizard'): if self.instances.has_key('account_creation_wizard'):
self.instances['account_creation_wizard'].new_acc_connected(array[0], self.instances['account_creation_wizard'].new_acc_connected(array[0],
array[1], array[2], array[3], array[4]) array[1], array[2], array[3], array[4], array[5])
def handle_event_new_acc_not_connected(self, account, array): def handle_event_new_acc_not_connected(self, account, array):
#('NEW_ACC_NOT_CONNECTED', account, (reason)) #('NEW_ACC_NOT_CONNECTED', account, (reason))
@ -1407,14 +1429,19 @@ class Interface:
gajim.connections[account].gpg_passphrase(self.gpg_passphrase[keyid]) gajim.connections[account].gpg_passphrase(self.gpg_passphrase[keyid])
callback() callback()
return return
if self.gpg_dialog:
# A GPG dialog is already open, retry in 0.5 second
gobject.timeout_add(500, self.handle_event_gpg_password_required,
account, array)
return
password_ok = False password_ok = False
count = 0 count = 0
title = _('Passphrase Required') title = _('Passphrase Required')
second = _('Enter GPG key passphrase for account %s.') % account second = _('Enter GPG key passphrase for account %s.') % account
while not password_ok and count < 3: while not password_ok and count < 3:
count += 1 count += 1
w = dialogs.PassphraseDialog(title, second, '') self.gpg_dialog = dialogs.PassphraseDialog(title, second, '')
passphrase, save = w.run() passphrase, save = self.gpg_dialog.run()
if passphrase == -1: if passphrase == -1:
# User pressed cancel # User pressed cancel
passphrase = None passphrase = None
@ -1424,6 +1451,7 @@ class Interface:
test_gpg_passphrase(passphrase) test_gpg_passphrase(passphrase)
title = _('Wrong Passphrase') title = _('Wrong Passphrase')
second = _('Please retype your GPG passphrase or press Cancel.') second = _('Please retype your GPG passphrase or press Cancel.')
self.gpg_dialog = None
if passphrase != None: if passphrase != None:
self.gpg_passphrase[keyid] = passphrase self.gpg_passphrase[keyid] = passphrase
gobject.timeout_add(30000, self.forget_gpg_passphrase, keyid) gobject.timeout_add(30000, self.forget_gpg_passphrase, keyid)
@ -1857,6 +1885,7 @@ class Interface:
# block signed in notifications for 30 seconds # block signed in notifications for 30 seconds
gajim.block_signed_in_notifications[account] = True gajim.block_signed_in_notifications[account] = True
self.roster.set_actions_menu_needs_rebuild() self.roster.set_actions_menu_needs_rebuild()
self.roster.draw_account(account)
if self.sleeper.getState() != common.sleepy.STATE_UNKNOWN and \ if self.sleeper.getState() != common.sleepy.STATE_UNKNOWN and \
gajim.connections[account].connected in (2, 3): gajim.connections[account].connected in (2, 3):
# we go online or free for chat, so we activate auto status # we go online or free for chat, so we activate auto status
@ -1922,12 +1951,12 @@ class Interface:
negotiated, not_acceptable, ask_user = session.verify_options_bob(form) negotiated, not_acceptable, ask_user = session.verify_options_bob(form)
if ask_user: if ask_user:
def accept_nondefault_options(widget): def accept_nondefault_options(is_checked):
self.dialog.destroy() self.dialog.destroy()
negotiated.update(ask_user) negotiated.update(ask_user)
session.respond_e2e_bob(form, negotiated, not_acceptable) session.respond_e2e_bob(form, negotiated, not_acceptable)
def reject_nondefault_options(widget): def reject_nondefault_options():
self.dialog.destroy() self.dialog.destroy()
for key in ask_user.keys(): for key in ask_user.keys():
not_acceptable.append(key) not_acceptable.append(key)
@ -1939,8 +1968,8 @@ class Interface:
%s %s
Are these options acceptable?''') % (negotiation.describe_features(ask_user)), Are these options acceptable?''') % (negotiation.describe_features(ask_user)),
on_response_yes = accept_nondefault_options, on_response_yes=accept_nondefault_options,
on_response_no = reject_nondefault_options) on_response_no=reject_nondefault_options)
else: else:
session.respond_e2e_bob(form, negotiated, not_acceptable) session.respond_e2e_bob(form, negotiated, not_acceptable)
@ -1963,7 +1992,7 @@ class Interface:
session.check_identity = _cb session.check_identity = _cb
if ask_user: if ask_user:
def accept_nondefault_options(widget): def accept_nondefault_options(is_checked):
dialog.destroy() dialog.destroy()
negotiated.update(ask_user) negotiated.update(ask_user)
@ -1973,7 +2002,7 @@ class Interface:
except exceptions.NegotiationError, details: except exceptions.NegotiationError, details:
session.fail_bad_negotiation(details) session.fail_bad_negotiation(details)
def reject_nondefault_options(widget): def reject_nondefault_options():
session.reject_negotiation() session.reject_negotiation()
dialog.destroy() dialog.destroy()
@ -2169,6 +2198,11 @@ class Interface:
_('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource, _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource,
is_modal = False, ok_handler = on_ok) is_modal = False, ok_handler = on_ok)
def handle_event_pep_access_model(self, account, data):
# ('PEP_ACCESS_MODEL', account, (node, model))
if self.instances[account].has_key('pep_services'):
self.instances[account]['pep_services'].new_service(data[0], data[1])
def handle_event_unique_room_id_supported(self, account, data): def handle_event_unique_room_id_supported(self, account, data):
'''Receive confirmation that unique_room_id are supported''' '''Receive confirmation that unique_room_id are supported'''
# ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id) # ('UNIQUE_ROOM_ID_SUPPORTED', server, instance, room_id)
@ -2181,44 +2215,76 @@ class Interface:
instance.unique_room_id_error(data[0]) instance.unique_room_id_error(data[0])
def handle_event_ssl_error(self, account, data): def handle_event_ssl_error(self, account, data):
# ('SSL_ERROR', account, (text, cert, sha1_fingerprint)) # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint))
server = gajim.config.get_per('accounts', account, 'hostname') server = gajim.config.get_per('accounts', account, 'hostname')
def on_ok(is_checked): def on_ok(is_checked=False):
if is_checked: if is_checked:
f = open(gajim.MY_CACERTS, 'a') # Check if cert is already in file
f.write(server + '\n') certs = ''
f.write(data[1] + '\n\n') if os.path.isfile(gajim.MY_CACERTS):
f.close() f = open(gajim.MY_CACERTS)
certs = f.read()
f.close()
if data[2] in certs:
dialogs.ErrorDialog(_('Certificate Already in File'),
_('This certificate is already in file %s, so it\'s not added again.') % gajim.MY_CACERTS)
else:
f = open(gajim.MY_CACERTS, 'a')
f.write(server + '\n')
f.write(data[2] + '\n\n')
f.close()
gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
data[2]) data[3])
gajim.connections[account].ssl_certificate_accepted() gajim.connections[account].ssl_certificate_accepted()
def on_cancel(): def on_cancel():
gajim.connections[account].disconnect(on_purpose=True) gajim.connections[account].disconnect(on_purpose=True)
self.handle_event_status(account, 'offline') self.handle_event_status(account, 'offline')
pritext = _('Error verifying SSL certificate') pritext = _('Error verifying SSL certificate')
sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]} sectext = _('There was an error verifying the SSL certificate of your jabber server: %(error)s\nDo you still want to connect to this server?') % {'error': data[0]}
checktext = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[2] if data[1] in (18, 27):
dialogs.ConfirmationDialogCheck(pritext, sectext, checktext, checktext = _('Add this certificate to the list of trusted certificates.\nSHA1 fingerprint of the certificate:\n%s') % data[3]
on_response_ok=on_ok, on_response_cancel=on_cancel) dialogs.ConfirmationDialogCheck(pritext, sectext, checktext,
on_response_ok=on_ok, on_response_cancel=on_cancel)
else:
dialogs.ConfirmationDialog(pritext, sectext,
on_response_ok=on_ok, on_response_cancel=on_cancel)
def handle_event_fingerprint_error(self, account, data): def handle_event_fingerprint_error(self, account, data):
# ('FINGERPRINT_ERROR', account, (fingerprint,)) # ('FINGERPRINT_ERROR', account, (new_fingerprint,))
def on_yes(widget): def on_yes(is_checked):
dialog.destroy()
gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1', gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
data[0]) data[0])
gajim.connections[account].ssl_certificate_accepted() gajim.connections[account].ssl_certificate_accepted()
def on_no(widget): def on_no():
dialog.destroy()
gajim.connections[account].disconnect(on_purpose=True) gajim.connections[account].disconnect(on_purpose=True)
self.handle_event_status(account, 'offline') self.handle_event_status(account, 'offline')
pritext = _('SSL certificate error') pritext = _('SSL certificate error')
sectext = _('It seems SSL certificate has changed or your connection is ' sectext = _('It seems the SSL certificate has changed or your connection '
'being hacked. Do you still want to connect and update the fingerprint' 'is being hacked.\nOld fingerprint: %s\nNew fingerprint: %s\n\nDo you '
'of the certificate?') 'still want to connect and update the fingerprint of the certificate?'\
) % (gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha1'),
data[0])
dialog = dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes, dialog = dialogs.YesNoDialog(pritext, sectext, on_response_yes=on_yes,
on_response_no=on_no) on_response_no=on_no)
def handle_event_plain_connection(self, account, data):
# ('PLAIN_CONNECTION', account, (connection))
server = gajim.config.get_per('accounts', account, 'hostname')
def on_yes(is_checked):
if is_checked:
gajim.config.set_per('accounts', account,
'warn_when_insecure_connection', False)
gajim.connections[account].connection_accepted(data[0], 'tcp')
def on_no():
gajim.connections[account].disconnect(on_purpose=True)
self.handle_event_status(account, 'offline')
pritext = _('Insecure connection')
sectext = _('You are about to send your password on an insecure '
'conection. Are you sure you want to do that?')
checktext = _('Do _not ask me again')
dialog = dialogs.YesNoDialog(pritext, sectext, checktext,
on_response_yes=on_yes, on_response_no=on_no)
def read_sleepy(self): def read_sleepy(self):
'''Check idle status and change that status if needed''' '''Check idle status and change that status if needed'''
if not self.sleeper.poll(): if not self.sleeper.poll():
@ -2328,7 +2394,7 @@ class Interface:
#FIXME: recognize xmpp: and treat it specially #FIXME: recognize xmpp: and treat it specially
links = r'\b(%s)\S*[\w\/\=]|' % prefixes links = r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]"
#2nd one: at_least_one_char@at_least_one_char.at_least_one_char #2nd one: at_least_one_char@at_least_one_char.at_least_one_char
mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]' mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
@ -2340,7 +2406,7 @@ class Interface:
latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$' latex = r'|\$\$[^$\\]*?([\]\[0-9A-Za-z()|+*/-]|[\\][\]\[0-9A-Za-z()|{}$])(.*?[^\\])?\$\$'
basic_pattern = links + mail basic_pattern = links + '|' + mail
if gajim.config.get('use_latex'): if gajim.config.get('use_latex'):
basic_pattern += latex basic_pattern += latex
@ -2551,6 +2617,7 @@ class Interface:
'SEARCH_FORM': self.handle_event_search_form, 'SEARCH_FORM': self.handle_event_search_form,
'SEARCH_RESULT': self.handle_event_search_result, 'SEARCH_RESULT': self.handle_event_search_result,
'RESOURCE_CONFLICT': self.handle_event_resource_conflict, 'RESOURCE_CONFLICT': self.handle_event_resource_conflict,
'PEP_ACCESS_MODEL': self.handle_event_pep_access_model,
'UNIQUE_ROOM_ID_UNSUPPORTED': \ 'UNIQUE_ROOM_ID_UNSUPPORTED': \
self.handle_event_unique_room_id_unsupported, self.handle_event_unique_room_id_unsupported,
'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported, 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported,
@ -2558,6 +2625,7 @@ class Interface:
'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required, 'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required,
'SSL_ERROR': self.handle_event_ssl_error, 'SSL_ERROR': self.handle_event_ssl_error,
'FINGERPRINT_ERROR': self.handle_event_fingerprint_error, 'FINGERPRINT_ERROR': self.handle_event_fingerprint_error,
'PLAIN_CONNECTION': self.handle_event_plain_connection,
} }
gajim.handlers = self.handlers gajim.handlers = self.handlers
@ -2674,6 +2742,7 @@ class Interface:
self.status_sent_to_users = {} self.status_sent_to_users = {}
self.status_sent_to_groups = {} self.status_sent_to_groups = {}
self.gpg_passphrase = {} self.gpg_passphrase = {}
self.gpg_dialog = None
self.default_colors = { self.default_colors = {
'inmsgcolor': gajim.config.get('inmsgcolor'), 'inmsgcolor': gajim.config.get('inmsgcolor'),
'outmsgcolor': gajim.config.get('outmsgcolor'), 'outmsgcolor': gajim.config.get('outmsgcolor'),
@ -2903,7 +2972,7 @@ if __name__ == '__main__':
print >> sys.stderr, _('Session Management support not available (missing gnome.ui module)') print >> sys.stderr, _('Session Management support not available (missing gnome.ui module)')
else: else:
def die_cb(cli): def die_cb(cli):
gtk.main_quit() gajim.interface.roster.quit_gtkgui_interface()
gnome.program_init('gajim', gajim.version) gnome.program_init('gajim', gajim.version)
cli = gnome.ui.master_client() cli = gnome.ui.master_client()
cli.connect('die', die_cb) cli.connect('die', die_cb)
@ -2928,4 +2997,7 @@ if __name__ == '__main__':
osx.init() osx.init()
Interface() Interface()
gtk.main() try:
gtk.main()
except KeyboardInterrupt:
print >> sys.stderr, 'KeyboardInterrupt'

View file

@ -72,12 +72,19 @@ def tree_cell_data_func(column, renderer, model, iter, tv=None):
# reference to GroupchatControl instance (self) # reference to GroupchatControl instance (self)
theme = gajim.config.get('roster_theme') theme = gajim.config.get('roster_theme')
# allocate space for avatar only if needed # allocate space for avatar only if needed
parent_iter = model.iter_parent(iter)
if isinstance(renderer, gtk.CellRendererPixbuf): if isinstance(renderer, gtk.CellRendererPixbuf):
if model[iter][C_AVATAR]: avatar_position = gajim.config.get('avatar_position_in_roster')
if avatar_position == 'right':
renderer.set_property('xalign', 1) # align pixbuf to the right
else:
renderer.set_property('xalign', 0.5)
if parent_iter and (model[iter][C_AVATAR] or avatar_position == 'left'):
renderer.set_property('visible', True) renderer.set_property('visible', True)
renderer.set_property('width', gajim.config.get('roster_avatar_width'))
else: else:
renderer.set_property('visible', False) renderer.set_property('visible', False)
if model.iter_parent(iter): if parent_iter:
bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
if bgcolor: if bgcolor:
renderer.set_property('cell-background', bgcolor) renderer.set_property('cell-background', bgcolor)
@ -228,6 +235,12 @@ class GroupchatControl(ChatControlBase):
self.tooltip = tooltips.GCTooltip() self.tooltip = tooltips.GCTooltip()
# nickname coloring
self.gc_count_nicknames_colors = 0
self.gc_custom_colors = {}
self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
split(':'))
# connect the menuitems to their respective functions # connect the menuitems to their respective functions
xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade') xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
@ -301,6 +314,16 @@ class GroupchatControl(ChatControlBase):
# first one img, second one text, third is sec pixbuf # first one img, second one text, third is sec pixbuf
column = gtk.TreeViewColumn() column = gtk.TreeViewColumn()
def add_avatar_renderer():
renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image
column.pack_start(renderer_pixbuf, expand = False)
column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
self.list_treeview)
if gajim.config.get('avatar_position_in_roster') == 'left':
add_avatar_renderer()
renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img renderer_image = cell_renderer_image.CellRendererImage(0, 0) # status img
renderer_image.set_property('width', 26) renderer_image.set_property('width', 26)
column.pack_start(renderer_image, expand = False) column.pack_start(renderer_image, expand = False)
@ -315,12 +338,8 @@ class GroupchatControl(ChatControlBase):
column.set_cell_data_func(renderer_text, tree_cell_data_func, column.set_cell_data_func(renderer_text, tree_cell_data_func,
self.list_treeview) self.list_treeview)
renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image if gajim.config.get('avatar_position_in_roster') == 'right':
column.pack_start(renderer_pixbuf, expand = False) add_avatar_renderer()
column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
self.list_treeview)
renderer_pixbuf.set_property('xalign', 1) # align pixbuf to the right
self.list_treeview.append_column(column) self.list_treeview.append_column(column)
@ -639,9 +658,6 @@ class GroupchatControl(ChatControlBase):
fin = True fin = True
return None return None
gc_count_nicknames_colors = 0
gc_custom_colors = {}
def print_old_conversation(self, text, contact = '', tim = None, def print_old_conversation(self, text, contact = '', tim = None,
xhtml = None): xhtml = None):
if isinstance(text, str): if isinstance(text, str):
@ -692,9 +708,7 @@ class GroupchatControl(ChatControlBase):
str(self.gc_custom_colors[contact])) str(self.gc_custom_colors[contact]))
else: else:
self.gc_count_nicknames_colors += 1 self.gc_count_nicknames_colors += 1
number_of_colors = len(gajim.config.get('gc_nicknames_colors').\ if self.gc_count_nicknames_colors == self.number_of_colors:
split(':'))
if self.gc_count_nicknames_colors == number_of_colors:
self.gc_count_nicknames_colors = 0 self.gc_count_nicknames_colors = 0
self.gc_custom_colors[contact] = \ self.gc_custom_colors[contact] = \
self.gc_count_nicknames_colors self.gc_count_nicknames_colors
@ -813,6 +827,13 @@ class GroupchatControl(ChatControlBase):
return False return False
else: # Special word == word, no char after in word else: # Special word == word, no char after in word
return True return True
for special_word in special_words:
if special_word.find(' ') > -1:
# There is a space in this special word, do a global search
# without splitting by words as previously
# We don't search this in all cases so we don't loose time
if text.find(special_word) > -1:
return True
return False return False
def set_subject(self, subject): def set_subject(self, subject):
@ -1979,7 +2000,8 @@ class GroupchatControl(ChatControlBase):
self.handlers[id] = item self.handlers[id] = item
item = xml.get_widget('add_to_roster_menuitem') item = xml.get_widget('add_to_roster_menuitem')
if not jid: our_jid = gajim.get_jid_from_account(self.account)
if not jid or jid == our_jid:
item.set_sensitive(False) item.set_sensitive(False)
else: else:
id = item.connect('activate', self.on_add_to_roster, jid) id = item.connect('activate', self.on_add_to_roster, jid)
@ -2079,12 +2101,7 @@ class GroupchatControl(ChatControlBase):
if not nick in gajim.contacts.get_nick_list(self.account, if not nick in gajim.contacts.get_nick_list(self.account,
self.room_jid): self.room_jid):
# it's a group # it's a group
col = widget.get_column(0) if x < 27:
avatar_cell = col.get_cell_renderers()[0]
(pos, avatar_size) = col.cell_get_position(avatar_cell)
status_cell = col.get_cell_renderers()[1]
(pos, status_size) = col.cell_get_position(status_cell)
if x > avatar_size and x < avatar_size + status_size:
if (widget.row_expanded(path)): if (widget.row_expanded(path)):
widget.collapse_row(path) widget.collapse_row(path)
else: else:
@ -2142,6 +2159,9 @@ class GroupchatControl(ChatControlBase):
self.tooltip.hide_tooltip() self.tooltip.hide_tooltip()
def show_tooltip(self, contact): def show_tooltip(self, contact):
if not self.list_treeview.window:
# control has been destroyed since tooltip was requested
return
pointer = self.list_treeview.get_pointer() pointer = self.list_treeview.get_pointer()
props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1]) props = self.list_treeview.get_path_at_pos(pointer[0], pointer[1])
# check if the current pointer is at the same path # check if the current pointer is at the same path

View file

@ -247,6 +247,11 @@ def move_window(window, x, y):
x = 0 x = 0
if y < 0: if y < 0:
y = 0 y = 0
w, h = window.get_size()
if x + w > screen_w:
x = screen_w - w
if y + h > screen_h:
y = screen_h - h
window.move(x, y) window.move(x, y)
def resize_window(window, w, h): def resize_window(window, w, h):

View file

@ -21,25 +21,25 @@
## NOTE: some method names may match those of logger.py but that's it ## NOTE: some method names may match those of logger.py but that's it
## someday (TM) should have common class that abstracts db connections and helpers on it ## someday (TM) should have common class that abstracts db connections and helpers on it
## the same can be said for history_window.py ## the same can be said for history_window.py
import os import os
if os.name == 'nt': if os.name == 'nt':
import warnings import warnings
warnings.filterwarnings(action='ignore') warnings.filterwarnings(action='ignore')
# Used to create windows installer with GTK included # Used to create windows installer with GTK included
# paths = os.environ['PATH'] # paths = os.environ['PATH']
# list_ = paths.split(';') # list_ = paths.split(';')
# new_list = [] # new_list = []
# for p in list_: # for p in list_:
# if p.find('gtk') < 0 and p.find('GTK') < 0: # if p.find('gtk') < 0 and p.find('GTK') < 0:
# new_list.append(p) # new_list.append(p)
# new_list.insert(0, 'gtk/lib') # new_list.insert(0, 'gtk/lib')
# new_list.insert(0, 'gtk/bin') # new_list.insert(0, 'gtk/bin')
# os.environ['PATH'] = ';'.join(new_list) # os.environ['PATH'] = ';'.join(new_list)
# os.environ['GTK_BASEPATH'] = 'gtk' # os.environ['GTK_BASEPATH'] = 'gtk'
import sys import sys
import signal import signal
import gtk import gtk
@ -100,7 +100,7 @@ class HistoryManager:
self.logs_scrolledwindow = xml.get_widget('logs_scrolledwindow') self.logs_scrolledwindow = xml.get_widget('logs_scrolledwindow')
self.search_results_scrolledwindow = xml.get_widget( self.search_results_scrolledwindow = xml.get_widget(
'search_results_scrolledwindow') 'search_results_scrolledwindow')
self.welcome_label = xml.get_widget('welcome_label') self.welcome_vbox = xml.get_widget('welcome_vbox')
self.jids_already_in = [] # holds jids that we already have in DB self.jids_already_in = [] # holds jids that we already have in DB
self.AT_LEAST_ONE_DELETION_DONE = False self.AT_LEAST_ONE_DELETION_DONE = False
@ -236,7 +236,7 @@ class HistoryManager:
self.logs_liststore.clear() # clear the store self.logs_liststore.clear() # clear the store
self.welcome_label.hide() self.welcome_vbox.hide()
self.search_results_scrolledwindow.hide() self.search_results_scrolledwindow.hide()
self.logs_scrolledwindow.show() self.logs_scrolledwindow.show()
@ -579,7 +579,7 @@ class HistoryManager:
if text == '': if text == '':
return return
self.welcome_label.hide() self.welcome_vbox.hide()
self.logs_scrolledwindow.hide() self.logs_scrolledwindow.hide()
self.search_results_scrolledwindow.show() self.search_results_scrolledwindow.show()

View file

@ -25,7 +25,7 @@ __version__ = '$Revision: 64 $'
from urllib import urlopen from urllib import urlopen
from xml.dom import minidom from xml.dom import minidom
from time import time, strftime from time import time
class LastFM: class LastFM:
# Where to fetch the played song information # Where to fetch the played song information

View file

@ -1,6 +1,6 @@
## message_control.py ## message_control.py
## ##
## Copyright (C) 2006 Travis Shirk <travis@pobox.com> ## Copyright (C) 2006-2007 Travis Shirk <travis@pobox.com>
## Copyright (C) 2007 Stephan Erb <steve-e@h3c.de> ## Copyright (C) 2007 Stephan Erb <steve-e@h3c.de>
## ##
## This file is part of Gajim. ## This file is part of Gajim.
@ -34,7 +34,7 @@ class MessageControl:
def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None): def __init__(self, type_id, parent_win, widget_name, contact, account, resource = None):
# dict { cb id : widget} # dict { cb id : widget}
# keep all registered callbacks of widgets, created by self.xml # keep all registered callbacks of widgets, created by self.xml
self.handlers = {} self.handlers = {}
self.type_id = type_id self.type_id = type_id
self.parent_win = parent_win self.parent_win = parent_win
self.widget_name = widget_name self.widget_name = widget_name
@ -86,7 +86,7 @@ class MessageControl:
pass # NOTE: Derived classes SHOULD implement this pass # NOTE: Derived classes SHOULD implement this
def get_tab_label(self, chatstate): def get_tab_label(self, chatstate):
'''Return a suitable the tab label string. Returns a tuple such as: '''Return a suitable tab label string. Returns a tuple such as:
(label_str, color) either of which can be None (label_str, color) either of which can be None
if chatstate is given that means we have HE SENT US a chatstate and if chatstate is given that means we have HE SENT US a chatstate and
we want it displayed''' we want it displayed'''
@ -126,8 +126,8 @@ class MessageControl:
if self.session.enable_encryption: if self.session.enable_encryption:
was_encrypted = True was_encrypted = True
print "starting a new session, dropping the old one!" gajim.connections[self.account].delete_session(self.session.jid,
gajim.connections[self.account].delete_session(self.session.jid, self.session.thread_id) self.session.thread_id)
self.session = session self.session = session

View file

@ -6,10 +6,9 @@
## Vincent Hanquez <tab@snarc.org> ## Vincent Hanquez <tab@snarc.org>
## Nikos Kouremenos <kourem@gmail.com> ## Nikos Kouremenos <kourem@gmail.com>
## Dimitur Kirov <dkirov@gmail.com> ## Dimitur Kirov <dkirov@gmail.com>
## Travis Shirk <travis@pobox.com>
## Norman Rasmussen <norman@rasmussen.co.za> ## Norman Rasmussen <norman@rasmussen.co.za>
## Copyright (C) 2006 Travis Shirk <travis@pobox.com> ## Copyright (C) 2005-2007 Travis Shirk <travis@pobox.com>
## Geobert Quach <geobert@gmail.com> ## Copyright (C) 2006 Geobert Quach <geobert@gmail.com>
## Copyright (C) 2007 Stephan Erb <steve-e@h3c.de> ## Copyright (C) 2007 Stephan Erb <steve-e@h3c.de>
## ##
## This file is part of Gajim. ## This file is part of Gajim.
@ -39,7 +38,7 @@ from common import gajim
#################### ####################
class MessageWindow: class MessageWindow(object):
'''Class for windows which contain message like things; chats, '''Class for windows which contain message like things; chats,
groupchats, etc.''' groupchats, etc.'''
@ -53,8 +52,8 @@ class MessageWindow:
CLOSE_COMMAND, CLOSE_COMMAND,
CLOSE_CTRL_KEY CLOSE_CTRL_KEY
) = range(5) ) = range(5)
def __init__(self, acct, type): def __init__(self, acct, type, parent_window=None, parent_paned=None):
# A dictionary of dictionaries where _contacts[account][jid] == A MessageControl # A dictionary of dictionaries where _contacts[account][jid] == A MessageControl
self._controls = {} self._controls = {}
# If None, the window is not tied to any specific account # If None, the window is not tied to any specific account
@ -68,6 +67,18 @@ class MessageWindow:
self.widget_name = 'message_window' self.widget_name = 'message_window'
self.xml = gtkgui_helpers.get_glade('%s.glade' % self.widget_name) self.xml = gtkgui_helpers.get_glade('%s.glade' % self.widget_name)
self.window = self.xml.get_widget(self.widget_name) self.window = self.xml.get_widget(self.widget_name)
self.notebook = self.xml.get_widget('notebook')
self.parent_paned = None
if parent_window:
orig_window = self.window
self.window = parent_window
self.parent_paned = parent_paned
self.notebook.reparent(self.parent_paned)
self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
orig_window.destroy()
del orig_window
id = self.window.connect('delete-event', self._on_window_delete) id = self.window.connect('delete-event', self._on_window_delete)
self.handlers[id] = self.window self.handlers[id] = self.window
id = self.window.connect('destroy', self._on_window_destroy) id = self.window.connect('destroy', self._on_window_destroy)
@ -91,7 +102,6 @@ class MessageWindow:
self.window.add_events(gtk.gdk.POINTER_MOTION_MASK) self.window.add_events(gtk.gdk.POINTER_MOTION_MASK)
self.alignment = self.xml.get_widget('alignment') self.alignment = self.xml.get_widget('alignment')
self.notebook = self.xml.get_widget('notebook')
id = self.notebook.connect('switch-page', id = self.notebook.connect('switch-page',
self._on_notebook_switch_page) self._on_notebook_switch_page)
self.handlers[id] = self.notebook self.handlers[id] = self.notebook
@ -144,6 +154,9 @@ class MessageWindow:
n += len(dict) n += len(dict)
return n return n
def resize(self, width, height):
gtkgui_helpers.resize_window(self.window, width, height)
def _on_window_focus(self, widget, event): def _on_window_focus(self, widget, event):
# window received focus, so if we had urgency REMOVE IT # window received focus, so if we had urgency REMOVE IT
# NOTE: we do not have to read the message (it maybe in a bg tab) # NOTE: we do not have to read the message (it maybe in a bg tab)
@ -179,6 +192,8 @@ class MessageWindow:
for ctrl in self.controls(): for ctrl in self.controls():
ctrl.shutdown() ctrl.shutdown()
self._controls.clear() self._controls.clear()
# Clean up handlers connected to the parent window, this is important since
# self.window may be the RosterWindow
for i in self.handlers.keys(): for i in self.handlers.keys():
if self.handlers[i].handler_is_connected(i): if self.handlers[i].handler_is_connected(i):
self.handlers[i].disconnect(i) self.handlers[i].disconnect(i)
@ -210,8 +225,9 @@ class MessageWindow:
widget = xml.get_widget('tab_close_button') widget = xml.get_widget('tab_close_button')
id = widget.connect('clicked', self._on_close_button_clicked, control) id = widget.connect('clicked', self._on_close_button_clicked, control)
control.handlers[id] = widget control.handlers[id] = widget
id = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event, control.widget) id = tab_label_box.connect('button-press-event', self.on_tab_eventbox_button_press_event,
control.widget)
control.handlers[id] = tab_label_box control.handlers[id] = tab_label_box
self.notebook.append_page(control.widget, tab_label_box) self.notebook.append_page(control.widget, tab_label_box)
@ -222,7 +238,10 @@ class MessageWindow:
self.setup_tab_dnd(control.widget) self.setup_tab_dnd(control.widget)
self.redraw_tab(control) self.redraw_tab(control)
self.window.show_all() if self.parent_paned:
self.notebook.show_all()
else:
self.window.show_all()
# NOTE: we do not call set_control_active(True) since we don't know whether # NOTE: we do not call set_control_active(True) since we don't know whether
# the tab is the active one. # the tab is the active one.
self.show_title() self.show_title()
@ -263,7 +282,7 @@ class MessageWindow:
if not control: if not control:
# No more control in this window # No more control in this window
return return
# CTRL mask # CTRL mask
if modifier & gtk.gdk.CONTROL_MASK: if modifier & gtk.gdk.CONTROL_MASK:
if keyval == gtk.keysyms.h: if keyval == gtk.keysyms.h:
@ -286,7 +305,7 @@ class MessageWindow:
# Tab switch bindings # Tab switch bindings
if keyval == gtk.keysyms.Right: # ALT + RIGHT if keyval == gtk.keysyms.Right: # ALT + RIGHT
new = self.notebook.get_current_page() + 1 new = self.notebook.get_current_page() + 1
if new >= self.notebook.get_n_pages(): if new >= self.notebook.get_n_pages():
new = 0 new = 0
self.notebook.set_current_page(new) self.notebook.set_current_page(new)
elif keyval == gtk.keysyms.Left: # ALT + LEFT elif keyval == gtk.keysyms.Left: # ALT + LEFT
@ -307,7 +326,7 @@ class MessageWindow:
'''When close button is pressed: close a tab''' '''When close button is pressed: close a tab'''
self.remove_tab(control, self.CLOSE_CLOSE_BUTTON) self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
def show_title(self, urgent = True, control = None): def show_title(self, urgent=True, control=None):
'''redraw the window's title''' '''redraw the window's title'''
if not control: if not control:
control = self.get_active_control() control = self.get_active_control()
@ -341,10 +360,7 @@ class MessageWindow:
name += '/' + control.resource name += '/' + control.resource
window_mode = gajim.interface.msg_win_mgr.mode window_mode = gajim.interface.msg_win_mgr.mode
if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
if self.get_num_controls() == 1:
label = name
elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
# Show the plural form since number of tabs > 1 # Show the plural form since number of tabs > 1
if self.type == 'chat': if self.type == 'chat':
label = _('Chats') label = _('Chats')
@ -352,9 +368,16 @@ class MessageWindow:
label = _('Group Chats') label = _('Group Chats')
else: else:
label = _('Private Chats') label = _('Private Chats')
elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
label = None
elif self.get_num_controls() == 1:
label = name
else: else:
label = _('Messages') label = _('Messages')
title = _('%s - Gajim') % label
title = 'Gajim'
if label:
title = _('%s - %s') % (label, title)
if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT: if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
title = title + ": " + control.account title = title + ": " + control.account
@ -370,7 +393,7 @@ class MessageWindow:
ctrl = self._controls[acct][jid] ctrl = self._controls[acct][jid]
ctrl_page = self.notebook.page_num(ctrl.widget) ctrl_page = self.notebook.page_num(ctrl.widget)
self.notebook.set_current_page(ctrl_page) self.notebook.set_current_page(ctrl_page)
def remove_tab(self, ctrl, method, reason = None, force = False): def remove_tab(self, ctrl, method, reason = None, force = False):
'''reason is only for gc (offline status message) '''reason is only for gc (offline status message)
if force is True, do not ask any confirmation''' if force is True, do not ask any confirmation'''
@ -413,7 +436,12 @@ class MessageWindow:
gajim.interface.msg_win_mgr._on_window_destroy(self.window) gajim.interface.msg_win_mgr._on_window_destroy(self.window)
# dnd clean up # dnd clean up
self.notebook.drag_dest_unset() self.notebook.drag_dest_unset()
self.window.destroy() if self.parent_paned:
# Don't close parent window, just remove the child
child = self.parent_paned.get_child2()
self.parent_paned.remove(child)
else:
self.window.destroy()
return # don't show_title, we are dead return # don't show_title, we are dead
elif self.get_num_controls() == 1: # we are going from two tabs to one elif self.get_num_controls() == 1: # we are going from two tabs to one
show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') show_tabs_if_one_tab = gajim.config.get('tabs_always_visible')
@ -542,7 +570,7 @@ class MessageWindow:
first_composing_ind = -1 # id of first composing ctrl to switch to first_composing_ind = -1 # id of first composing ctrl to switch to
# if no others controls have awaiting events # if no others controls have awaiting events
# loop until finding an unread tab or having done a complete cycle # loop until finding an unread tab or having done a complete cycle
while True: while True:
if forward == True: # look for the first unread tab on the right if forward == True: # look for the first unread tab on the right
ind = ind + 1 ind = ind + 1
if ind >= self.notebook.get_n_pages(): if ind >= self.notebook.get_n_pages():
@ -590,7 +618,7 @@ class MessageWindow:
if old_no >= 0: if old_no >= 0:
old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no)) old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no))
old_ctrl.set_control_active(False) old_ctrl.set_control_active(False)
new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num)) new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
new_ctrl.set_control_active(True) new_ctrl.set_control_active(True)
self.show_title(control = new_ctrl) self.show_title(control = new_ctrl)
@ -598,8 +626,8 @@ class MessageWindow:
def _on_notebook_key_press(self, widget, event): def _on_notebook_key_press(self, widget, event):
control = self.get_active_control() control = self.get_active_control()
# Ctrl+PageUP / DOWN has to be handled by notebook # Ctrl+PageUP / DOWN has to be handled by notebook
if event.state & gtk.gdk.CONTROL_MASK and event.keyval in ( if (event.state & gtk.gdk.CONTROL_MASK and
gtk.keysyms.Page_Down, gtk.keysyms.Page_Up): event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.Page_Up)):
return False return False
if isinstance(control, ChatControlBase): if isinstance(control, ChatControlBase):
# we forwarded it to message textview # we forwarded it to message textview
@ -609,7 +637,7 @@ class MessageWindow:
def setup_tab_dnd(self, child): def setup_tab_dnd(self, child):
'''Set tab label as drag source and connect the drag_data_get signal''' '''Set tab label as drag source and connect the drag_data_get signal'''
tab_label = self.notebook.get_tab_label(child) tab_label = self.notebook.get_tab_label(child)
tab_label.dnd_handler = tab_label.connect('drag_data_get', tab_label.dnd_handler = tab_label.connect('drag_data_get',
self.on_tab_label_drag_data_get_cb) self.on_tab_label_drag_data_get_cb)
self.handlers[tab_label.dnd_handler] = tab_label self.handlers[tab_label.dnd_handler] = tab_label
tab_label.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS, tab_label.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS,
@ -630,11 +658,11 @@ class MessageWindow:
source_child = self.notebook.get_nth_page(source_page_num) source_child = self.notebook.get_nth_page(source_page_num)
if dest_page_num != source_page_num: if dest_page_num != source_page_num:
self.notebook.reorder_child(source_child, dest_page_num) self.notebook.reorder_child(source_child, dest_page_num)
def get_tab_at_xy(self, x, y): def get_tab_at_xy(self, x, y):
'''Thanks to Gaim '''Thanks to Gaim
Return the tab under xy and Return the tab under xy and
if its nearer from left or right side of the tab if its nearer from left or right side of the tab
''' '''
page_num = -1 page_num = -1
to_right = False to_right = False
@ -655,7 +683,7 @@ class MessageWindow:
if (y >= tab_alloc.y) and \ if (y >= tab_alloc.y) and \
(y <= (tab_alloc.y + tab_alloc.height)): (y <= (tab_alloc.y + tab_alloc.height)):
page_num = i page_num = i
if y > tab_alloc.y + (tab_alloc.height / 2.0): if y > tab_alloc.y + (tab_alloc.height / 2.0):
to_right = True to_right = True
break break
@ -679,36 +707,53 @@ class MessageWindow:
tab_label.disconnect(tab_label.dnd_handler) tab_label.disconnect(tab_label.dnd_handler)
################################################################################ ################################################################################
class MessageWindowMgr: class MessageWindowMgr(gobject.GObject):
'''A manager and factory for MessageWindow objects''' '''A manager and factory for MessageWindow objects'''
__gsignals__ = {
'window-delete': (gobject.SIGNAL_RUN_LAST, None, (object,)),
}
# These constants map to common.config.opt_one_window_types indices # These constants map to common.config.opt_one_window_types indices
( (
ONE_MSG_WINDOW_NEVER, ONE_MSG_WINDOW_NEVER,
ONE_MSG_WINDOW_ALWAYS, ONE_MSG_WINDOW_ALWAYS,
ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER,
ONE_MSG_WINDOW_PERACCT, ONE_MSG_WINDOW_PERACCT,
ONE_MSG_WINDOW_PERTYPE ONE_MSG_WINDOW_PERTYPE,
) = range(4) ) = range(5)
# A key constant for the main window for all messages # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode
MAIN_WIN = 'main' MAIN_WIN = 'main'
# A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode
ROSTER_MAIN_WIN = 'roster'
def __init__(self): def __init__(self, parent_window, parent_paned):
''' A dictionary of windows; the key depends on the config: ''' A dictionary of windows; the key depends on the config:
ONE_MSG_WINDOW_NEVER: The key is the contact JID ONE_MSG_WINDOW_NEVER: The key is the contact JID
ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN
ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN
ONE_MSG_WINDOW_PERACCT: The key is the account name ONE_MSG_WINDOW_PERACCT: The key is the account name
ONE_MSG_WINDOW_PERTYPE: The key is a message type constant''' ONE_MSG_WINDOW_PERTYPE: The key is a message type constant'''
gobject.GObject.__init__(self)
self._windows = {} self._windows = {}
# Map the mode to a int constant for frequent compares # Map the mode to a int constant for frequent compares
mode = gajim.config.get('one_message_window') mode = gajim.config.get('one_message_window')
self.mode = common.config.opt_one_window_types.index(mode) self.mode = common.config.opt_one_window_types.index(mode)
self.parent_win = parent_window
self.parent_paned = parent_paned
def change_account_name(self, old_name, new_name): def change_account_name(self, old_name, new_name):
for win in self.windows(): for win in self.windows():
win.change_account_name(old_name, new_name) win.change_account_name(old_name, new_name)
def _new_window(self, acct, type): def _new_window(self, acct, type):
win = MessageWindow(acct, type) parent_win = None
parent_paned = None
if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
parent_win = self.parent_win
parent_paned = self.parent_paned
win = MessageWindow(acct, type, parent_win, parent_paned)
# we track the lifetime of this window # we track the lifetime of this window
win.window.connect('delete-event', self._on_window_delete) win.window.connect('delete-event', self._on_window_delete)
win.window.connect('destroy', self._on_window_destroy) win.window.connect('destroy', self._on_window_destroy)
@ -729,7 +774,7 @@ class MessageWindowMgr:
def has_window(self, jid, acct): def has_window(self, jid, acct):
return self.get_window(jid, acct) != None return self.get_window(jid, acct) != None
def one_window_opened(self, contact, acct, type): def one_window_opened(self, contact=None, acct=None, type=None):
try: try:
return self._windows[self._mode_to_key(contact, acct, type)] != None return self._windows[self._mode_to_key(contact, acct, type)] != None
except KeyError: except KeyError:
@ -737,12 +782,13 @@ class MessageWindowMgr:
def _resize_window(self, win, acct, type): def _resize_window(self, win, acct, type):
'''Resizes window according to config settings''' '''Resizes window according to config settings'''
if not gajim.config.get('saveposition'): if self.mode in (self.ONE_MSG_WINDOW_ALWAYS,
return self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER):
if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
size = (gajim.config.get('msgwin-width'), size = (gajim.config.get('msgwin-width'),
gajim.config.get('msgwin-height')) gajim.config.get('msgwin-height'))
if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
parent_size = win.window.get_size()
size = (parent_size[0] + size[0], size[1])
elif self.mode == self.ONE_MSG_WINDOW_PERACCT: elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
size = (gajim.config.get_per('accounts', acct, 'msgwin-width'), size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
gajim.config.get_per('accounts', acct, 'msgwin-height')) gajim.config.get_per('accounts', acct, 'msgwin-height'))
@ -754,13 +800,12 @@ class MessageWindowMgr:
size = (gajim.config.get(opt_width), gajim.config.get(opt_height)) size = (gajim.config.get(opt_width), gajim.config.get(opt_height))
else: else:
return return
win.resize(size[0], size[1])
gtkgui_helpers.resize_window(win.window, size[0], size[1])
def _position_window(self, win, acct, type): def _position_window(self, win, acct, type):
'''Moves window according to config settings''' '''Moves window according to config settings'''
if not gajim.config.get('saveposition') or\ if (self.mode in [self.ONE_MSG_WINDOW_NEVER,
self.mode == self.ONE_MSG_WINDOW_NEVER: self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]):
return return
if self.mode == self.ONE_MSG_WINDOW_ALWAYS: if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
@ -782,21 +827,22 @@ class MessageWindowMgr:
key = acct + contact.jid key = acct + contact.jid
if resource: if resource:
key += '/' + resource key += '/' + resource
return key
elif self.mode == self.ONE_MSG_WINDOW_ALWAYS: elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
key = self.MAIN_WIN return self.MAIN_WIN
elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
return self.ROSTER_MAIN_WIN
elif self.mode == self.ONE_MSG_WINDOW_PERACCT: elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
key = acct return acct
elif self.mode == self.ONE_MSG_WINDOW_PERTYPE: elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
key = type return type
return key
def create_window(self, contact, acct, type, resource = None): def create_window(self, contact, acct, type, resource = None):
key = None
win_acct = None win_acct = None
win_type = None win_type = None
win_role = 'messages' win_role = None # X11 window role
key = self._mode_to_key(contact, acct, type, resource) win_key = self._mode_to_key(contact, acct, type, resource)
if self.mode == self.ONE_MSG_WINDOW_PERACCT: if self.mode == self.ONE_MSG_WINDOW_PERACCT:
win_acct = acct win_acct = acct
win_role = acct win_role = acct
@ -806,21 +852,24 @@ class MessageWindowMgr:
elif self.mode == self.ONE_MSG_WINDOW_NEVER: elif self.mode == self.ONE_MSG_WINDOW_NEVER:
win_type = type win_type = type
win_role = contact.jid win_role = contact.jid
elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
win_role = 'messages'
win = None win = None
try: try:
win = self._windows[key] win = self._windows[win_key]
except KeyError: except KeyError:
win = self._new_window(win_acct, win_type) win = self._new_window(win_acct, win_type)
win.window.set_role(win_role) if win_role:
win.window.set_role(win_role)
# Position and size window based on saved state and window mode # Position and size window based on saved state and window mode
if not self.one_window_opened(contact, acct, type): if not self.one_window_opened(contact, acct, type):
self._position_window(win, acct, type)
self._resize_window(win, acct, type) self._resize_window(win, acct, type)
self._position_window(win, acct, type)
self._windows[key] = win self._windows[win_key] = win
return win return win
def change_key(self, old_jid, new_jid, acct): def change_key(self, old_jid, new_jid, acct):
@ -842,6 +891,7 @@ class MessageWindowMgr:
def _on_window_destroy(self, win): def _on_window_destroy(self, win):
for k in self._windows.keys(): for k in self._windows.keys():
if self._windows[k].window == win: if self._windows[k].window == win:
self.emit('window-delete', self._windows[k])
del self._windows[k] del self._windows[k]
return return
@ -870,17 +920,16 @@ class MessageWindowMgr:
for c in w.controls(): for c in w.controls():
yield c yield c
def shutdown(self): def shutdown(self, width_adjust=0):
for w in self.windows(): for w in self.windows():
self.save_state(w) self.save_state(w, width_adjust)
w.window.hide() if not w.parent_paned:
w.window.destroy() w.window.hide()
w.window.destroy()
gajim.interface.save_config() gajim.interface.save_config()
def save_state(self, msg_win): def save_state(self, msg_win, width_adjust=0):
if not gajim.config.get('saveposition'):
return
# Save window size and position # Save window size and position
pos_x_key = 'msgwin-x-position' pos_x_key = 'msgwin-x-position'
pos_y_key = 'msgwin-y-position' pos_y_key = 'msgwin-y-position'
@ -907,6 +956,9 @@ class MessageWindowMgr:
type = msg_win.type type = msg_win.type
size_width_key = type + '-msgwin-width' size_width_key = type + '-msgwin-width'
size_height_key = type + '-msgwin-height' size_height_key = type + '-msgwin-height'
elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
# Ignore any hpaned width
width = msg_win.notebook.allocation.width
if acct: if acct:
gajim.config.set_per('accounts', acct, size_width_key, width) gajim.config.set_per('accounts', acct, size_width_key, width)
@ -915,11 +967,12 @@ class MessageWindowMgr:
if self.mode != self.ONE_MSG_WINDOW_NEVER: if self.mode != self.ONE_MSG_WINDOW_NEVER:
gajim.config.set_per('accounts', acct, pos_x_key, x) gajim.config.set_per('accounts', acct, pos_x_key, x)
gajim.config.set_per('accounts', acct, pos_y_key, y) gajim.config.set_per('accounts', acct, pos_y_key, y)
else: else:
width += width_adjust
gajim.config.set(size_width_key, width) gajim.config.set(size_width_key, width)
gajim.config.set(size_height_key, height) gajim.config.set(size_height_key, height)
if self.mode != self.ONE_MSG_WINDOW_NEVER: if self.mode != self.ONE_MSG_WINDOW_NEVER:
gajim.config.set(pos_x_key, x) gajim.config.set(pos_x_key, x)
gajim.config.set(pos_y_key, y) gajim.config.set(pos_y_key, y)
@ -937,17 +990,24 @@ class MessageWindowMgr:
controls = [] controls = []
for w in self.windows(): for w in self.windows():
w.window.hide() # Note, we are taking care not to hide/delete the roster window when the
# MessageWindow is embedded.
if not w.parent_paned:
w.window.hide()
while w.notebook.get_n_pages(): while w.notebook.get_n_pages():
page = w.notebook.get_nth_page(0) page = w.notebook.get_nth_page(0)
ctrl = w._widget_to_control(page) ctrl = w._widget_to_control(page)
w.notebook.remove_page(0) w.notebook.remove_page(0)
page.unparent() page.unparent()
controls.append(ctrl) controls.append(ctrl)
# Must clear _controls from window to prevent # Must clear _controls from window to prevent MessageControl.shutdown calls
# MessageControl.shutdown calls
w._controls = {} w._controls = {}
w.window.destroy() if not w.parent_paned:
w.window.destroy()
else:
# Don't close parent window, just remove the child
child = w.parent_paned.get_child2()
w.parent_paned.remove(child)
self._windows = {} self._windows = {}

View file

@ -18,6 +18,7 @@
## You should have received a copy of the GNU General Public License ## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>. ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
## ##
import os
import gobject import gobject
if __name__ == '__main__': if __name__ == '__main__':
# install _() func before importing dbus_support # install _() func before importing dbus_support
@ -51,6 +52,13 @@ class MusicTrackListener(gobject.GObject):
bus = dbus.SessionBus() bus = dbus.SessionBus()
## MPRIS
bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
'org.freedesktop.MediaPlayer')
bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
'org.freedesktop.MediaPlayer')
## Muine ## Muine
bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
'org.gnome.Muine.Player') 'org.gnome.Muine.Player')
@ -60,30 +68,32 @@ class MusicTrackListener(gobject.GObject):
'org.gnome.Muine.Player') 'org.gnome.Muine.Player')
## Rhythmbox ## Rhythmbox
bus.add_signal_receiver(self._rhythmbox_music_track_change_cb,
'playingUriChanged', 'org.gnome.Rhythmbox.Player')
bus.add_signal_receiver(self._player_name_owner_changed, bus.add_signal_receiver(self._player_name_owner_changed,
'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
bus.add_signal_receiver(self._player_playing_changed_cb, bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
'playingChanged', 'org.gnome.Rhythmbox.Player') 'playingChanged', 'org.gnome.Rhythmbox.Player')
bus.add_signal_receiver(self._player_playing_song_property_changed_cb, bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
## Banshee ## Banshee
banshee_bus = dbus.SessionBus() # Banshee sucks because it only supports polling.
dubus = banshee_bus.get_object('org.freedesktop.DBus', # Thus, we only register this is we are very sure that it's
'/org/freedesktop/dbus') # installed.
self.dubus_methods = dbus.Interface(dubus, 'org.freedesktop.DBus') if os.name == 'posix' and os.system('which banshee >/dev/null 2>&1') == 0:
self.current_banshee_title = '' banshee_bus = dbus.SessionBus()
self.banshee_paused_before = False dubus = banshee_bus.get_object('org.freedesktop.DBus',
self.banshee_is_here = False '/org/freedesktop/dbus')
gobject.timeout_add(10000, self._check_if_banshee_bus) self.dubus_methods = dbus.Interface(dubus, 'org.freedesktop.DBus')
if self.dubus_methods.NameHasOwner('org.gnome.Banshee'): self.current_banshee_title = ''
self._get_banshee_bus() self.banshee_paused_before = False
self.banshee_is_here = True self.banshee_is_here = False
# Otherwise, it opens Banshee! gobject.timeout_add(10000, self._check_if_banshee_bus)
self.banshee_props ={} if self.dubus_methods.NameHasOwner('org.gnome.Banshee'):
gobject.timeout_add(1000, self._banshee_check_track_status) self._get_banshee_bus()
self.banshee_is_here = True
# Otherwise, it opens Banshee!
self.banshee_props ={}
gobject.timeout_add(1000, self._banshee_check_track_status)
def _check_if_banshee_bus(self): def _check_if_banshee_bus(self):
if self.dubus_methods.NameHasOwner('org.gnome.Banshee'): if self.dubus_methods.NameHasOwner('org.gnome.Banshee'):
@ -116,6 +126,40 @@ class MusicTrackListener(gobject.GObject):
if b == 'rb:stream-song-title': if b == 'rb:stream-song-title':
self.emit('music-track-changed', self._last_playing_music) self.emit('music-track-changed', self._last_playing_music)
def _mpris_properties_extract(self, song):
info = MusicTrackInfo()
if song.has_key('title'):
info.title = song['title']
else:
info.title = ''
if song.has_key('album'):
info.album = song['album']
else:
info.album = ''
if song.has_key('artist'):
info.artist = song['artist']
else:
info.artist = ''
if song.has_key('length'):
info.duration = int(song['length'])
else:
info.duration = 0
return info
def _mpris_playing_changed_cb(self, playing):
if playing == 0:
self.emit('music-track-changed', self._last_playing_music)
else:
self.emit('music-track-changed', None)
def _mpris_music_track_change_cb(self, arg):
self._last_playing_music = self._mpris_properties_extract(arg)
def _muine_properties_extract(self, song_string): def _muine_properties_extract(self, song_string):
d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \
song_string.split('\n')) song_string.split('\n'))
@ -131,6 +175,13 @@ class MusicTrackListener(gobject.GObject):
info = self._muine_properties_extract(arg) info = self._muine_properties_extract(arg)
self.emit('music-track-changed', info) self.emit('music-track-changed', info)
def _rhythmbox_playing_changed_cb(self, playing):
if playing:
info = self.get_playing_track()
self.emit('music-track-changed', info)
else:
self.emit('music-track-changed', None)
def _rhythmbox_properties_extract(self, props): def _rhythmbox_properties_extract(self, props):
info = MusicTrackInfo() info = MusicTrackInfo()
info.title = props['title'] info.title = props['title']
@ -139,17 +190,6 @@ class MusicTrackListener(gobject.GObject):
info.duration = int(props['duration']) info.duration = int(props['duration'])
info.track_number = int(props['track-number']) info.track_number = int(props['track-number'])
return info return info
def _rhythmbox_music_track_change_cb(self, uri):
if not uri:
return
bus = dbus.SessionBus()
rbshellobj = bus.get_object('org.gnome.Rhythmbox',
'/org/gnome/Rhythmbox/Shell')
rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
props = rbshell.getSongProperties(uri)
info = self._rhythmbox_properties_extract(props)
self.emit('music-track-changed', info)
def _banshee_check_track_status(self): def _banshee_check_track_status(self):
if self.dubus_methods.NameHasOwner('org.gnome.Banshee') and \ if self.dubus_methods.NameHasOwner('org.gnome.Banshee') and \

View file

@ -170,17 +170,25 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
nickname = parameters[2] nickname = parameters[2]
if gajim.config.get('notification_preview_message'): if gajim.config.get('notification_preview_message'):
message = parameters[3] message = parameters[3]
if message.startswith('/me ') or message.startswith('/me\n'):
message = '* ' + nickname + message[3:]
else: else:
# We don't want message preview, do_preview = False # We don't want message preview, do_preview = False
message = '' message = ''
focused = parameters[4]
if helpers.allow_showing_notification(account, 'notify_on_new_message', if helpers.allow_showing_notification(account, 'notify_on_new_message',
advanced_notif_num, is_first_message): advanced_notif_num, is_first_message):
do_popup = True do_popup = True
if is_first_message and helpers.allow_sound_notification( if is_first_message and helpers.allow_sound_notification(
'first_message_received', advanced_notif_num): 'first_message_received', advanced_notif_num):
do_sound = True do_sound = True
elif not is_first_message and helpers.allow_sound_notification( elif not is_first_message and focused and \
'next_message_received', advanced_notif_num): helpers.allow_sound_notification('next_message_received_focused',
advanced_notif_num):
do_sound = True
elif not is_first_message and not focused and \
helpers.allow_sound_notification('next_message_received_unfocused',
advanced_notif_num):
do_sound = True do_sound = True
else: else:
print '*Event not implemeted yet*' print '*Event not implemeted yet*'
@ -283,8 +291,10 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
pass # do not set snd_event pass # do not set snd_event
elif is_first_message: elif is_first_message:
snd_event = 'first_message_received' snd_event = 'first_message_received'
elif focused:
snd_event = 'next_message_received_focused'
else: else:
snd_event = 'next_message_received' snd_event = 'next_message_received_unfocused'
elif event in ('contact_connected', 'contact_disconnected'): elif event in ('contact_connected', 'contact_disconnected'):
snd_event = event snd_event = event
if snd_file: if snd_file:

View file

@ -1 +0,0 @@
# dummy

View file

@ -1 +0,0 @@
# dummy

View file

@ -1 +0,0 @@
# dummy

View file

@ -1 +0,0 @@
# dummy

View file

@ -114,6 +114,7 @@ static PyObject * idle_getIdleSec(PyObject *self, PyObject *args)
else else
{ {
printf("Couldn't grab properties of system\n"); printf("Couldn't grab properties of system\n");
return NULL;
} }
if (obj) if (obj)

View file

@ -1 +0,0 @@
# dummy

View file

@ -1 +0,0 @@
# dummy

View file

@ -6,6 +6,7 @@
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com> ## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net> ## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com> ## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com>
## Copyright (C) 2007 Travis Shirk <travis@pobox.com>
## ##
## This file is part of Gajim. ## This file is part of Gajim.
## ##
@ -373,7 +374,6 @@ class SignalObject(dbus.service.Object):
if not specified status is changed for all accounts. ''' if not specified status is changed for all accounts. '''
if status not in ('offline', 'online', 'chat', if status not in ('offline', 'online', 'chat',
'away', 'xa', 'dnd', 'invisible'): 'away', 'xa', 'dnd', 'invisible'):
raise InvalidArgument
return DBUS_BOOLEAN(False) return DBUS_BOOLEAN(False)
if account: if account:
gobject.idle_add(gajim.interface.roster.send_status, account, gobject.idle_add(gajim.interface.roster.send_status, account,
@ -615,7 +615,10 @@ class SignalObject(dbus.service.Object):
for contact in contacts: for contact in contacts:
resource_props = dbus.Struct((DBUS_STRING(contact.resource), resource_props = dbus.Struct((DBUS_STRING(contact.resource),
dbus.Int32(contact.priority), DBUS_STRING(contact.status))) dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
contact_dict['resources'].append(resource_props) contact_dict['resources'].append(resource_props)
contact_dict['groups'] = dbus.Array([], signature='s')
for group in prim_contact.groups:
contact_dict['groups'].append(DBUS_STRING(group))
return contact_dict return contact_dict
@dbus.service.method(INTERFACE, in_signature='', out_signature='s') @dbus.service.method(INTERFACE, in_signature='', out_signature='s')
@ -654,4 +657,4 @@ class SignalObject(dbus.service.Object):
gajim.interface.instances[account]['join_gc'] = \ gajim.interface.instances[account]['join_gc'] = \
JoinGroupchatWindow(account, room_jid, nick) JoinGroupchatWindow(account, room_jid, nick)
else: else:
gajim.connections[account].join_gc(nick, room_jid, password) gajim.interface.roster.join_gc_room(account, room_jid, nick, password)

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,6 @@
import sys import sys
import gtk import gtk
import systray import systray
import gobject
from common import gajim from common import gajim
from common import helpers from common import helpers
@ -60,7 +59,7 @@ class StatusIcon(systray.Systray):
self.unsubscribe_events() self.unsubscribe_events()
def on_status_icon_left_clicked(self, widget): def on_status_icon_left_clicked(self, widget):
gobject.idle_add(self.on_left_click) self.on_left_click()
def set_img(self): def set_img(self):
'''apart from image, we also update tooltip text here''' '''apart from image, we also update tooltip text here'''

View file

@ -247,21 +247,17 @@ class Systray:
sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on')) sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
if os.name == 'nt': if os.name == 'nt':
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0): if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
if self.added_hide_menuitem is False: if self.added_hide_menuitem is False:
self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
item = gtk.MenuItem(_('Hide this menu')) item = gtk.MenuItem(_('Hide this menu'))
self.systray_context_menu.prepend(item) self.systray_context_menu.prepend(item)
self.added_hide_menuitem = True self.added_hide_menuitem = True
self.systray_context_menu.popup(None, None,
gtk.status_icon_position_menu, event_button,
event_time, self.status_icon)
else: # GNU and Unices
self.systray_context_menu.popup(None, None, None, event_button,
event_time)
self.systray_context_menu.show_all() self.systray_context_menu.show_all()
self.systray_context_menu.popup(None, None, None, event_button,
event_time)
def on_show_all_events_menuitem_activate(self, widget): def on_show_all_events_menuitem_activate(self, widget):
events = gajim.events.get_systray_events() events = gajim.events.get_systray_events()

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
## tooltips.py ## tooltips.py
## ##
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com> ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
@ -463,6 +464,10 @@ class RosterTooltip(NotificationAreaTooltip):
contact.last_status_time) contact.last_status_time)
properties.append((self.table, None)) properties.append((self.table, None))
else: # only one resource else: # only one resource
#FIXME: User {Mood, Activity, Tune} not shown if there are
#multiple resources
#FIXME: User {Mood, Activity, Tune} not shown for self
if contact.show: if contact.show:
show = helpers.get_uf_show(contact.show) show = helpers.get_uf_show(contact.show)
if contact.last_status_time: if contact.last_status_time:
@ -494,6 +499,52 @@ class RosterTooltip(NotificationAreaTooltip):
show = '<i>' + show + '</i>' show = '<i>' + show + '</i>'
# we append show below # we append show below
if contact.mood.has_key('mood'):
mood = contact.mood['mood'].strip()
mood = gobject.markup_escape_text(mood)
mood_string = _('Mood:') + ' <b>%s</b>' % mood
if contact.mood.has_key('text') and contact.mood['text'] != '':
mood_text = contact.mood['text'].strip()
mood_text = gobject.markup_escape_text(mood_text)
mood_string += ' (%s)' % mood_text
properties.append((mood_string, None))
if contact.activity.has_key('activity'):
activity = contact.activity['activity'].strip()
activity = gobject.markup_escape_text(activity)
activity_string = _('Activity:') + ' <b>%s' % activity
if contact.activity.has_key('subactivity'):
activity_sub = contact.activity['subactivity'].strip()
activity_sub = gobject.markup_escape_text(activity_sub)
activity_string += ' (%s)</b>' % activity_sub
else:
activity_string += '</b>'
if contact.activity.has_key('text'):
activity_text = contact.activity['text'].strip()
activity_text = gobject.markup_escape_text(activity_text)
activity_string += ' (%s)' % activity_text
properties.append((activity_string, None))
if contact.tune.has_key('artist') or contact.tune.has_key('title'):
if contact.tune.has_key('artist'):
artist = contact.tune['artist'].strip()
artist = gobject.markup_escape_text(artist)
else:
artist = _('Unknown Artist')
if contact.tune.has_key('title'):
title = contact.tune['title'].strip()
title = gobject.markup_escape_text(title)
else:
title = _('Unknown Title')
if contact.tune.has_key('source'):
source = contact.tune['source'].strip()
source = gobject.markup_escape_text(source)
else:
source = _('Unknown Source')
tune_string = _('Tune:') + ' ' + _('<b>"%(title)s"</b> by <i>%(artist)s</i>\nfrom <i>%(source)s</i>' %\
{'title': title, 'artist': artist, 'source': source})
properties.append((tune_string, None))
if contact.status: if contact.status:
status = contact.status.strip() status = contact.status.strip()
if status: if status:
@ -551,7 +602,7 @@ class RosterTooltip(NotificationAreaTooltip):
vertical_fill, 0, 0) vertical_fill, 0, 0)
else: else:
if isinstance(property[0], (unicode, str)): #FIXME: rm unicode? if isinstance(property[0], (unicode, str)): #FIXME: rm unicode?
label.set_markup(property[0]) label.set_markup(property[0])
label.set_line_wrap(True) label.set_line_wrap(True)
else: else:
label = property[0] label = property[0]

View file

@ -178,8 +178,6 @@ class VcardWindow:
pass pass
def set_values(self, vcard): def set_values(self, vcard):
if not 'PHOTO' in vcard:
self.xml.get_widget('no_user_avatar_label').show()
for i in vcard.keys(): for i in vcard.keys():
if i == 'PHOTO' and self.xml.get_widget('information_notebook').\ if i == 'PHOTO' and self.xml.get_widget('information_notebook').\
get_n_pages() > 4: get_n_pages() > 4:
@ -187,6 +185,7 @@ class VcardWindow:
get_avatar_pixbuf_encoded_mime(vcard[i]) get_avatar_pixbuf_encoded_mime(vcard[i])
image = self.xml.get_widget('PHOTO_image') image = self.xml.get_widget('PHOTO_image')
image.show() image.show()
self.xml.get_widget('user_avatar_label').show()
if not pixbuf: if not pixbuf:
image.set_from_icon_name('stock_person', image.set_from_icon_name('stock_person',
gtk.ICON_SIZE_DIALOG) gtk.ICON_SIZE_DIALOG)