merge plugin system to trunk
This commit is contained in:
commit
05a1af2c55
|
@ -19,7 +19,7 @@
|
||||||
<object class="GtkHBox" id="hbox3024">
|
<object class="GtkHBox" id="hbox3024">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage" id="gc_banner_status_image">
|
<object class="GtkImage" id="banner_status_image">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="stock">gtk-missing-image</property>
|
<property name="stock">gtk-missing-image</property>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -0,0 +1,644 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="2.16"/>
|
||||||
|
<!-- interface-naming-policy toplevel-contextual -->
|
||||||
|
<object class="GtkWindow" id="plugins_window">
|
||||||
|
<property name="width_request">650</property>
|
||||||
|
<property name="height_request">500</property>
|
||||||
|
<property name="border_width">6</property>
|
||||||
|
<property name="title" translatable="yes">Plugins</property>
|
||||||
|
<property name="default_width">650</property>
|
||||||
|
<property name="default_height">500</property>
|
||||||
|
<signal name="destroy" handler="on_plugins_window_destroy"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebook" id="plugins_notebook">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHPaned" id="hpaned1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="position">250</property>
|
||||||
|
<property name="position_set">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="border_width">6</property>
|
||||||
|
<property name="hscrollbar_policy">automatic</property>
|
||||||
|
<property name="vscrollbar_policy">automatic</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="installed_plugins_treeview">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">False</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">6</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_name_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">&lt;empty&gt;</property>
|
||||||
|
<property name="use_markup">True</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Version:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_version_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes"><empty></property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Authors:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_authors_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="yalign">0</property>
|
||||||
|
<property name="label" translatable="yes"><empty></property>
|
||||||
|
<property name="wrap_mode">word-char</property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Homepage:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLinkButton" id="plugin_homepage_linkbutton">
|
||||||
|
<property name="label" translatable="yes">homepage url</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="focus_on_click">False</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Descrition:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="plugin_description_textview">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="pixels_above_lines">6</property>
|
||||||
|
<property name="editable">False</property>
|
||||||
|
<property name="wrap_mode">word</property>
|
||||||
|
<property name="left_margin">6</property>
|
||||||
|
<property name="right_margin">6</property>
|
||||||
|
<property name="indent">1</property>
|
||||||
|
<property name="buffer">textbuffer1</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHButtonBox" id="hbuttonbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
|
<property name="layout_style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="uninstall_plugin_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_uninstall_plugin_button_clicked"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox11">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="image1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="stock">gtk-cancel</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="uninstall_plugin_button_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Uninstall</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="configure_plugin_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_configure_plugin_button_clicked"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox12">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="image2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="stock">gtk-preferences</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="configure_plugin_button_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Configure</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">True</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<object class="GtkLabel" id="label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Installed</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="tab_fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHPaned" id="hpaned2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow" id="scrolledwindow2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="hscrollbar_policy">automatic</property>
|
||||||
|
<property name="vscrollbar_policy">automatic</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="treeview2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">False</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox6">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_name_label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><empty></property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox7">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Version:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_version_label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><empty></property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label8">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Authors:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="plugin_authors_label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes"><empty></property>
|
||||||
|
<property name="selectable">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox9">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label9">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Homepage:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLinkButton" id="plugin_homepage_linkbutton1">
|
||||||
|
<property name="label" translatable="yes">button</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<property name="focus_on_click">False</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox5">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox10">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label10">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Descrition:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment4">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="plugin_description_textview1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHButtonBox" id="hbuttonbox3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="layout_style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="uninstall_plugin_button1">
|
||||||
|
<property name="label" translatable="yes">button</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="configure_plugin_button1">
|
||||||
|
<property name="label" translatable="yes">button</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="resize">True</property>
|
||||||
|
<property name="shrink">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<object class="GtkLabel" id="label2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="label" translatable="yes">Available</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
<property name="tab_fill">False</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHButtonBox" id="hbuttonbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="spacing">15</property>
|
||||||
|
<property name="layout_style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="close_button">
|
||||||
|
<property name="label">gtk-close</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="use_stock">True</property>
|
||||||
|
<signal name="clicked" handler="on_close_button_clicked"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<object class="GtkTextBuffer" id="textbuffer1">
|
||||||
|
<property name="text" translatable="yes">Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization.</property>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -54,8 +54,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="join_gc_menuitem">
|
<object class="GtkImageMenuItem" id="join_gc_menuitem">
|
||||||
<property name="label" translatable="yes">Join _Group Chat...</property>
|
<property name="label" translatable="yes">Join _Group Chat...</property>
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
<property name="image">image3</property>
|
<property name="image">image3</property>
|
||||||
<property name="use_stock">False</property>
|
<property name="use_stock">False</property>
|
||||||
<property name="accel_group">accelgroup1</property>
|
<property name="accel_group">accelgroup1</property>
|
||||||
|
@ -69,8 +69,8 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem" id="add_new_contact_menuitem">
|
<object class="GtkImageMenuItem" id="add_new_contact_menuitem">
|
||||||
<property name="label" translatable="yes">Add _Contact...</property>
|
<property name="label" translatable="yes">Add _Contact...</property>
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
<property name="image">image4</property>
|
<property name="image">image4</property>
|
||||||
<property name="use_stock">False</property>
|
<property name="use_stock">False</property>
|
||||||
<property name="accel_group">accelgroup1</property>
|
<property name="accel_group">accelgroup1</property>
|
||||||
|
@ -159,6 +159,16 @@
|
||||||
<signal name="activate" handler="on_preferences_menuitem_activate"/>
|
<signal name="activate" handler="on_preferences_menuitem_activate"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImageMenuItem" id="plugins_menuitem">
|
||||||
|
<property name="label" translatable="yes">P_lugins</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="image">image13</property>
|
||||||
|
<property name="use_stock">False</property>
|
||||||
|
<signal name="activate" handler="on_plugins_menuitem_activate"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -437,4 +447,9 @@
|
||||||
<property name="stock">gtk-properties</property>
|
<property name="stock">gtk-properties</property>
|
||||||
<property name="icon-size">1</property>
|
<property name="icon-size">1</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkImage" id="image13">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="stock">gtk-disconnect</property>
|
||||||
|
<property name="icon-size">1</property>
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Acronyms expander plugin.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 9th June 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
import gobject
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log, log_calls
|
||||||
|
|
||||||
|
class AcronymsExpanderPlugin(GajimPlugin):
|
||||||
|
name = u'Acronyms Expander'
|
||||||
|
short_name = u'acronyms_expander'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('AcronymsExpanderPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
|
||||||
|
self.gui_extension_points = {
|
||||||
|
'chat_control_base': (self.connect_with_chat_control_base,
|
||||||
|
self.disconnect_from_chat_control_base)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.config_default_values = {
|
||||||
|
'INVOKER': (' ', _('')),
|
||||||
|
'ACRONYMS': ({'RTFM': 'Read The Friendly Manual',
|
||||||
|
'/slap': '/me slaps',
|
||||||
|
'PS-': 'plug-in system',
|
||||||
|
'G-': 'Gajim',
|
||||||
|
'GNT-': 'http://trac.gajim.org/newticket',
|
||||||
|
'GW-': 'http://trac.gajim.org/',
|
||||||
|
'GTS-': 'http://trac.gajim.org/report',
|
||||||
|
},
|
||||||
|
_('')),
|
||||||
|
}
|
||||||
|
|
||||||
|
@log_calls('AcronymsExpanderPlugin')
|
||||||
|
def textbuffer_live_acronym_expander(self, tb):
|
||||||
|
"""
|
||||||
|
@param tb gtk.TextBuffer
|
||||||
|
"""
|
||||||
|
#assert isinstance(tb,gtk.TextBuffer)
|
||||||
|
ACRONYMS = self.config['ACRONYMS']
|
||||||
|
INVOKER = self.config['INVOKER']
|
||||||
|
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
|
||||||
|
#log.debug('%s %d'%(t, len(t)))
|
||||||
|
if t and t[-1] == INVOKER:
|
||||||
|
#log.debug('changing msg text')
|
||||||
|
base, sep, head=t[:-1].rpartition(INVOKER)
|
||||||
|
log.debug('%s | %s | %s'%(base, sep, head))
|
||||||
|
if head in ACRONYMS:
|
||||||
|
head = ACRONYMS[head]
|
||||||
|
#log.debug('head: %s'%(head))
|
||||||
|
t = ''.join((base, sep, head, INVOKER))
|
||||||
|
#log.debug("setting text: '%s'"%(t))
|
||||||
|
gobject.idle_add(tb.set_text, t)
|
||||||
|
|
||||||
|
@log_calls('AcronymsExpanderPlugin')
|
||||||
|
def connect_with_chat_control_base(self, chat_control):
|
||||||
|
d = {}
|
||||||
|
tv = chat_control.msg_textview
|
||||||
|
tb = tv.get_buffer()
|
||||||
|
h_id = tb.connect('changed', self.textbuffer_live_acronym_expander)
|
||||||
|
d['h_id'] = h_id
|
||||||
|
|
||||||
|
chat_control.acronyms_expander_plugin_data = d
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@log_calls('AcronymsExpanderPlugin')
|
||||||
|
def disconnect_from_chat_control_base(self, chat_control):
|
||||||
|
d = chat_control.acronyms_expander_plugin_data
|
||||||
|
tv = chat_control.msg_textview
|
||||||
|
tv.get_buffer().disconnect(d['h_id'])
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
from plugin import BannerTweaksPlugin
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="2.16"/>
|
||||||
|
<!-- interface-naming-policy toplevel-contextual -->
|
||||||
|
<object class="GtkWindow" id="window1">
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="banner_tweaks_config_vbox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">9</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="show_banner_image_checkbutton">
|
||||||
|
<property name="label" translatable="yes">Display status icon</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">If checked, status icon will be displayed in chat window banner.</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_show_banner_image_checkbutton_toggled"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="show_banner_online_msg_checkbutton">
|
||||||
|
<property name="label" translatable="yes">Display status message of contact</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">If checked, status message of contact will be displayed in chat window banner.</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_show_banner_online_msg_checkbutton_toggled"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="show_banner_resource_checkbutton">
|
||||||
|
<property name="label" translatable="yes">Display resource name of contact</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">If checked, resource name of contact will be displayed in chat window banner.</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_show_banner_resource_checkbutton_toggled"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="banner_small_fonts_checkbutton">
|
||||||
|
<property name="label" translatable="yes">Use small fonts for contact name and resource name</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">If checked, smaller font will be used to display resource name and contact name in chat window banner.</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="on_banner_small_fonts_checkbutton_toggled"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,205 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Adjustable chat window banner.
|
||||||
|
|
||||||
|
Includes tweaks to make it compact.
|
||||||
|
|
||||||
|
Based on patch by pb in ticket #4133:
|
||||||
|
http://trac.gajim.org/attachment/ticket/4133/gajim-chatbanneroptions-svn10008.patch
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 30 July 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
import gobject
|
||||||
|
import message_control
|
||||||
|
from common import i18n
|
||||||
|
from common import gajim
|
||||||
|
from common import helpers
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log, log_calls
|
||||||
|
from plugins.gui import GajimPluginConfigDialog
|
||||||
|
|
||||||
|
class BannerTweaksPlugin(GajimPlugin):
|
||||||
|
name = u'Banner Tweaks'
|
||||||
|
short_name = u'banner_tweaks'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Allows user to tweak chat window banner appearance (eg. make it compact).
|
||||||
|
|
||||||
|
Based on patch by pb in ticket #4133:
|
||||||
|
http://trac.gajim.org/attachment/ticket/4133'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('BannerTweaksPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = BannerTweaksPluginConfigDialog(self)
|
||||||
|
|
||||||
|
self.gui_extension_points = {
|
||||||
|
'chat_control_base_draw_banner': (self.chat_control_base_draw_banner_called,
|
||||||
|
self.chat_control_base_draw_banner_deactivation)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.config_default_values = {
|
||||||
|
'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')),
|
||||||
|
'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')),
|
||||||
|
'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')),
|
||||||
|
'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')),
|
||||||
|
'old_chat_avatar_height': (52, _('chat_avatar_height value before plugin was activated')),
|
||||||
|
}
|
||||||
|
|
||||||
|
@log_calls('BannerTweaksPlugin')
|
||||||
|
def activate(self):
|
||||||
|
self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height')
|
||||||
|
#gajim.config.set('chat_avatar_height', 28)
|
||||||
|
|
||||||
|
@log_calls('BannerTweaksPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height'])
|
||||||
|
|
||||||
|
@log_calls('BannerTweaksPlugin')
|
||||||
|
def chat_control_base_draw_banner_called(self, chat_control):
|
||||||
|
if not self.config['show_banner_online_msg']:
|
||||||
|
chat_control.banner_status_label.hide()
|
||||||
|
chat_control.banner_status_label.set_no_show_all(True)
|
||||||
|
status_text = ''
|
||||||
|
chat_control.banner_status_label.set_markup(status_text)
|
||||||
|
|
||||||
|
if not self.config['show_banner_image']:
|
||||||
|
banner_status_img = chat_control.xml.get_object('banner_status_image')
|
||||||
|
banner_status_img.clear()
|
||||||
|
|
||||||
|
# TODO: part below repeats a lot of code from ChatControl.draw_banner_text()
|
||||||
|
# This could be rewritten using re module: getting markup text from
|
||||||
|
# banner_name_label and replacing some elements based on plugin config.
|
||||||
|
# Would it be faster?
|
||||||
|
if self.config['show_banner_resource'] or self.config['banner_small_fonts']:
|
||||||
|
banner_name_label = chat_control.xml.get_object('banner_name_label')
|
||||||
|
label_text = banner_name_label.get_label()
|
||||||
|
|
||||||
|
contact = chat_control.contact
|
||||||
|
jid = contact.jid
|
||||||
|
|
||||||
|
name = contact.get_shown_name()
|
||||||
|
if chat_control.resource:
|
||||||
|
name += '/' + chat_control.resource
|
||||||
|
elif contact.resource and self.config['show_banner_resource']:
|
||||||
|
name += '/' + contact.resource
|
||||||
|
|
||||||
|
if chat_control.TYPE_ID == message_control.TYPE_PM:
|
||||||
|
name = _('%(nickname)s from group chat %(room_name)s') %\
|
||||||
|
{'nickname': name, 'room_name': chat_control.room_name}
|
||||||
|
name = gobject.markup_escape_text(name)
|
||||||
|
|
||||||
|
# We know our contacts nick, but if another contact has the same nick
|
||||||
|
# in another account we need to also display the account.
|
||||||
|
# except if we are talking to two different resources of the same contact
|
||||||
|
acct_info = ''
|
||||||
|
for account in gajim.contacts.get_accounts():
|
||||||
|
if account == chat_control.account:
|
||||||
|
continue
|
||||||
|
if acct_info: # We already found a contact with same nick
|
||||||
|
break
|
||||||
|
for jid in gajim.contacts.get_jid_list(account):
|
||||||
|
other_contact_ = \
|
||||||
|
gajim.contacts.get_first_contact_from_jid(account, jid)
|
||||||
|
if other_contact_.get_shown_name() == chat_control.contact.get_shown_name():
|
||||||
|
acct_info = ' (%s)' % \
|
||||||
|
gobject.markup_escape_text(chat_control.account)
|
||||||
|
break
|
||||||
|
|
||||||
|
font_attrs, font_attrs_small = chat_control.get_font_attrs()
|
||||||
|
if self.config['banner_small_fonts']:
|
||||||
|
font_attrs = font_attrs_small
|
||||||
|
|
||||||
|
st = gajim.config.get('displayed_chat_state_notifications')
|
||||||
|
cs = contact.chatstate
|
||||||
|
if cs and st in ('composing_only', 'all'):
|
||||||
|
if contact.show == 'offline':
|
||||||
|
chatstate = ''
|
||||||
|
elif contact.composing_xep == 'XEP-0085':
|
||||||
|
if st == 'all' or cs == 'composing':
|
||||||
|
chatstate = helpers.get_uf_chatstate(cs)
|
||||||
|
else:
|
||||||
|
chatstate = ''
|
||||||
|
elif contact.composing_xep == 'XEP-0022':
|
||||||
|
if cs in ('composing', 'paused'):
|
||||||
|
# only print composing, paused
|
||||||
|
chatstate = helpers.get_uf_chatstate(cs)
|
||||||
|
else:
|
||||||
|
chatstate = ''
|
||||||
|
else:
|
||||||
|
# When does that happen ? See [7797] and [7804]
|
||||||
|
chatstate = helpers.get_uf_chatstate(cs)
|
||||||
|
|
||||||
|
label_text = '<span %s>%s</span><span %s>%s %s</span>' % \
|
||||||
|
(font_attrs, name, font_attrs_small, acct_info, chatstate)
|
||||||
|
else:
|
||||||
|
# weight="heavy" size="x-large"
|
||||||
|
label_text = '<span %s>%s</span><span %s>%s</span>' % \
|
||||||
|
(font_attrs, name, font_attrs_small, acct_info)
|
||||||
|
|
||||||
|
banner_name_label.set_markup(label_text)
|
||||||
|
|
||||||
|
@log_calls('BannerTweaksPlugin')
|
||||||
|
def chat_control_base_draw_banner_deactivation(self, chat_control):
|
||||||
|
pass
|
||||||
|
#chat_control.draw_banner()
|
||||||
|
|
||||||
|
class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog):
|
||||||
|
def init(self):
|
||||||
|
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
|
||||||
|
'config_dialog.ui')
|
||||||
|
self.xml = gtk.Builder()
|
||||||
|
self.xml.set_translation_domain(i18n.APP)
|
||||||
|
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
|
||||||
|
['banner_tweaks_config_vbox'])
|
||||||
|
self.config_vbox = self.xml.get_object('banner_tweaks_config_vbox')
|
||||||
|
self.child.pack_start(self.config_vbox)
|
||||||
|
|
||||||
|
self.show_banner_image_checkbutton = self.xml.get_object('show_banner_image_checkbutton')
|
||||||
|
self.show_banner_online_msg_checkbutton = self.xml.get_object('show_banner_online_msg_checkbutton')
|
||||||
|
self.show_banner_resource_checkbutton = self.xml.get_object('show_banner_resource_checkbutton')
|
||||||
|
self.banner_small_fonts_checkbutton = self.xml.get_object('banner_small_fonts_checkbutton')
|
||||||
|
|
||||||
|
self.xml.connect_signals(self)
|
||||||
|
|
||||||
|
def on_run(self):
|
||||||
|
self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image'])
|
||||||
|
self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg'])
|
||||||
|
self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource'])
|
||||||
|
self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts'])
|
||||||
|
|
||||||
|
def on_show_banner_image_checkbutton_toggled(self, button):
|
||||||
|
self.plugin.config['show_banner_image'] = button.get_active()
|
||||||
|
|
||||||
|
def on_show_banner_online_msg_checkbutton_toggled(self, button):
|
||||||
|
self.plugin.config['show_banner_online_msg'] = button.get_active()
|
||||||
|
|
||||||
|
def on_show_banner_resource_checkbutton_toggled(self, button):
|
||||||
|
self.plugin.config['show_banner_resource'] = button.get_active()
|
||||||
|
|
||||||
|
def on_banner_small_fonts_checkbutton_toggled(self, button):
|
||||||
|
self.plugin.config['banner_small_fonts'] = button.get_active()
|
|
@ -0,0 +1 @@
|
||||||
|
from plugin import DBusPlugin
|
|
@ -0,0 +1,738 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## Copyright (C) 2005-2006 Yann Leboulanger <asterix@lagaule.org>
|
||||||
|
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||||
|
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
|
||||||
|
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
|
||||||
|
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
|
||||||
|
## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com>
|
||||||
|
## Copyright (C) 2007 Travis Shirk <travis@pobox.com>
|
||||||
|
## Copyright (C) 2008 Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
'''
|
||||||
|
D-BUS Support plugin.
|
||||||
|
|
||||||
|
Based on src/remote_control.py
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 8th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
import os
|
||||||
|
import new
|
||||||
|
|
||||||
|
import gobject
|
||||||
|
|
||||||
|
|
||||||
|
from common import dbus_support
|
||||||
|
if dbus_support.supported:
|
||||||
|
import dbus
|
||||||
|
if dbus_support:
|
||||||
|
INTERFACE = 'org.gajim.dbusplugin.RemoteInterface'
|
||||||
|
OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject'
|
||||||
|
SERVICE = 'org.gajim.dbusplugin'
|
||||||
|
|
||||||
|
import dbus.service
|
||||||
|
import dbus.glib
|
||||||
|
# type mapping
|
||||||
|
|
||||||
|
# in most cases it is a utf-8 string
|
||||||
|
DBUS_STRING = dbus.String
|
||||||
|
|
||||||
|
# general type (for use in dicts, where all values should have the same type)
|
||||||
|
DBUS_BOOLEAN = dbus.Boolean
|
||||||
|
DBUS_DOUBLE = dbus.Double
|
||||||
|
DBUS_INT32 = dbus.Int32
|
||||||
|
# dictionary with string key and binary value
|
||||||
|
DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv")
|
||||||
|
# dictionary with string key and value
|
||||||
|
DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
|
||||||
|
# empty type (there is no equivalent of None on D-Bus, but historically gajim
|
||||||
|
# used 0 instead)
|
||||||
|
DBUS_NONE = lambda : dbus.Int32(0)
|
||||||
|
|
||||||
|
def get_dbus_struct(obj):
|
||||||
|
''' recursively go through all the items and replace
|
||||||
|
them with their casted dbus equivalents
|
||||||
|
'''
|
||||||
|
if obj is None:
|
||||||
|
return DBUS_NONE()
|
||||||
|
if isinstance(obj, (unicode, str)):
|
||||||
|
return DBUS_STRING(obj)
|
||||||
|
if isinstance(obj, int):
|
||||||
|
return DBUS_INT32(obj)
|
||||||
|
if isinstance(obj, float):
|
||||||
|
return DBUS_DOUBLE(obj)
|
||||||
|
if isinstance(obj, bool):
|
||||||
|
return DBUS_BOOLEAN(obj)
|
||||||
|
if isinstance(obj, (list, tuple)):
|
||||||
|
result = dbus.Array([get_dbus_struct(i) for i in obj],
|
||||||
|
signature='v')
|
||||||
|
if result == []:
|
||||||
|
return DBUS_NONE()
|
||||||
|
return result
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = DBUS_DICT_SV()
|
||||||
|
for key, value in obj.items():
|
||||||
|
result[DBUS_STRING(key)] = get_dbus_struct(value)
|
||||||
|
if result == {}:
|
||||||
|
return DBUS_NONE()
|
||||||
|
return result
|
||||||
|
# unknown type
|
||||||
|
return DBUS_NONE()
|
||||||
|
|
||||||
|
class SignalObject(dbus.service.Object):
|
||||||
|
''' Local object definition for /org/gajim/dbus/RemoteObject.
|
||||||
|
(This docstring is not be visible, because the clients can access only the remote object.)'''
|
||||||
|
|
||||||
|
def __init__(self, bus_name):
|
||||||
|
self.first_show = True
|
||||||
|
self.vcard_account = None
|
||||||
|
|
||||||
|
# register our dbus API
|
||||||
|
dbus.service.Object.__init__(self, bus_name, OBJ_PATH)
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def Roster(self, account_and_data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def AccountPresence(self, status_and_account):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def ContactPresence(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def ContactAbsence(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def ContactStatus(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def NewMessage(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def Subscribe(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def Subscribed(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def Unsubscribed(self, account_and_jid):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def NewAccount(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def VcardInfo(self, account_and_vcard):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def LastStatusTime(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def OsInfo(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def GCPresence(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def GCMessage(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def RosterInfo(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dbus.service.signal(INTERFACE, signature='av')
|
||||||
|
def NewGmail(self, account_and_array):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def raise_signal(self, signal, arg):
|
||||||
|
'''raise a signal, with a single argument of unspecified type
|
||||||
|
Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).'''
|
||||||
|
getattr(self, signal)(arg)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
|
||||||
|
def get_status(self, account):
|
||||||
|
'''Returns status (show to be exact) which is the global one
|
||||||
|
unless account is given'''
|
||||||
|
if not account:
|
||||||
|
# If user did not ask for account, returns the global status
|
||||||
|
return DBUS_STRING(helpers.get_global_show())
|
||||||
|
# return show for the given account
|
||||||
|
index = gajim.connections[account].connected
|
||||||
|
return DBUS_STRING(gajim.SHOW_LIST[index])
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
|
||||||
|
def get_status_message(self, account):
|
||||||
|
'''Returns status which is the global one
|
||||||
|
unless account is given'''
|
||||||
|
if not account:
|
||||||
|
# If user did not ask for account, returns the global status
|
||||||
|
return DBUS_STRING(str(helpers.get_global_status()))
|
||||||
|
# return show for the given account
|
||||||
|
status = gajim.connections[account].status
|
||||||
|
return DBUS_STRING(status)
|
||||||
|
|
||||||
|
def _get_account_and_contact(self, account, jid):
|
||||||
|
'''get the account (if not given) and contact instance from jid'''
|
||||||
|
connected_account = None
|
||||||
|
contact = None
|
||||||
|
accounts = gajim.contacts.get_accounts()
|
||||||
|
# if there is only one account in roster, take it as default
|
||||||
|
# if user did not ask for account
|
||||||
|
if not account and len(accounts) == 1:
|
||||||
|
account = accounts[0]
|
||||||
|
if account:
|
||||||
|
if gajim.connections[account].connected > 1: # account is connected
|
||||||
|
connected_account = account
|
||||||
|
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||||
|
jid)
|
||||||
|
else:
|
||||||
|
for account in accounts:
|
||||||
|
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||||
|
jid)
|
||||||
|
if contact and gajim.connections[account].connected > 1:
|
||||||
|
# account is connected
|
||||||
|
connected_account = account
|
||||||
|
break
|
||||||
|
if not contact:
|
||||||
|
contact = jid
|
||||||
|
|
||||||
|
return connected_account, contact
|
||||||
|
|
||||||
|
def _get_account_for_groupchat(self, account, room_jid):
|
||||||
|
'''get the account which is connected to groupchat (if not given)
|
||||||
|
or check if the given account is connected to the groupchat'''
|
||||||
|
connected_account = None
|
||||||
|
accounts = gajim.contacts.get_accounts()
|
||||||
|
# if there is only one account in roster, take it as default
|
||||||
|
# if user did not ask for account
|
||||||
|
if not account and len(accounts) == 1:
|
||||||
|
account = accounts[0]
|
||||||
|
if account:
|
||||||
|
if gajim.connections[account].connected > 1 and \
|
||||||
|
room_jid in gajim.gc_connected[account] and \
|
||||||
|
gajim.gc_connected[account][room_jid]:
|
||||||
|
# account and groupchat are connected
|
||||||
|
connected_account = account
|
||||||
|
else:
|
||||||
|
for account in accounts:
|
||||||
|
if gajim.connections[account].connected > 1 and \
|
||||||
|
room_jid in gajim.gc_connected[account] and \
|
||||||
|
gajim.gc_connected[account][room_jid]:
|
||||||
|
# account and groupchat are connected
|
||||||
|
connected_account = account
|
||||||
|
break
|
||||||
|
return connected_account
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
|
||||||
|
def send_file(self, file_path, jid, account):
|
||||||
|
'''send file, located at 'file_path' to 'jid', using account
|
||||||
|
(optional) 'account' '''
|
||||||
|
jid = self._get_real_jid(jid, account)
|
||||||
|
connected_account, contact = self._get_account_and_contact(account, jid)
|
||||||
|
|
||||||
|
if connected_account:
|
||||||
|
if file_path[:7] == 'file://':
|
||||||
|
file_path=file_path[7:]
|
||||||
|
if os.path.isfile(file_path): # is it file?
|
||||||
|
gajim.interface.instances['file_transfers'].send_file(
|
||||||
|
connected_account, contact, file_path)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
def _send_message(self, jid, message, keyID, account, type = 'chat',
|
||||||
|
subject = None):
|
||||||
|
'''can be called from send_chat_message (default when send_message)
|
||||||
|
or send_single_message'''
|
||||||
|
if not jid or not message:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
if not keyID:
|
||||||
|
keyID = ''
|
||||||
|
|
||||||
|
connected_account, contact = self._get_account_and_contact(account, jid)
|
||||||
|
if connected_account:
|
||||||
|
connection = gajim.connections[connected_account]
|
||||||
|
connection.send_message(jid, message, keyID, type, subject)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b')
|
||||||
|
def send_chat_message(self, jid, message, keyID, account):
|
||||||
|
'''Send chat 'message' to 'jid', using account (optional) 'account'.
|
||||||
|
if keyID is specified, encrypt the message with the pgp key '''
|
||||||
|
jid = self._get_real_jid(jid, account)
|
||||||
|
return self._send_message(jid, message, keyID, account)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b')
|
||||||
|
def send_single_message(self, jid, subject, message, keyID, account):
|
||||||
|
'''Send single 'message' to 'jid', using account (optional) 'account'.
|
||||||
|
if keyID is specified, encrypt the message with the pgp key '''
|
||||||
|
jid = self._get_real_jid(jid, account)
|
||||||
|
return self._send_message(jid, message, keyID, account, type, subject)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
|
||||||
|
def send_groupchat_message(self, room_jid, message, account):
|
||||||
|
'''Send 'message' to groupchat 'room_jid',
|
||||||
|
using account (optional) 'account'.'''
|
||||||
|
if not room_jid or not message:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
connected_account = self._get_account_for_groupchat(account, room_jid)
|
||||||
|
if connected_account:
|
||||||
|
connection = gajim.connections[connected_account]
|
||||||
|
connection.send_gc_message(room_jid, message)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
|
||||||
|
def open_chat(self, jid, account):
|
||||||
|
'''Shows the tabbed window for new message to 'jid', using account
|
||||||
|
(optional) 'account' '''
|
||||||
|
if not jid:
|
||||||
|
raise MissingArgument
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
jid = self._get_real_jid(jid, account)
|
||||||
|
try:
|
||||||
|
jid = helpers.parse_jid(jid)
|
||||||
|
except:
|
||||||
|
# Jid is not conform, ignore it
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
if account:
|
||||||
|
accounts = [account]
|
||||||
|
else:
|
||||||
|
accounts = gajim.connections.keys()
|
||||||
|
if len(accounts) == 1:
|
||||||
|
account = accounts[0]
|
||||||
|
connected_account = None
|
||||||
|
first_connected_acct = None
|
||||||
|
for acct in accounts:
|
||||||
|
if gajim.connections[acct].connected > 1: # account is online
|
||||||
|
contact = gajim.contacts.get_first_contact_from_jid(acct, jid)
|
||||||
|
if gajim.interface.msg_win_mgr.has_window(jid, acct):
|
||||||
|
connected_account = acct
|
||||||
|
break
|
||||||
|
# jid is in roster
|
||||||
|
elif contact:
|
||||||
|
connected_account = acct
|
||||||
|
break
|
||||||
|
# we send the message to jid not in roster, because account is
|
||||||
|
# specified, or there is only one account
|
||||||
|
elif account:
|
||||||
|
connected_account = acct
|
||||||
|
elif first_connected_acct is None:
|
||||||
|
first_connected_acct = acct
|
||||||
|
|
||||||
|
# if jid is not a conntact, open-chat with first connected account
|
||||||
|
if connected_account is None and first_connected_acct:
|
||||||
|
connected_account = first_connected_acct
|
||||||
|
|
||||||
|
if connected_account:
|
||||||
|
gajim.interface.new_chat_from_jid(connected_account, jid)
|
||||||
|
# preserve the 'steal focus preservation'
|
||||||
|
win = gajim.interface.msg_win_mgr.get_window(jid,
|
||||||
|
connected_account).window
|
||||||
|
if win.get_property('visible'):
|
||||||
|
win.window.focus()
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
|
||||||
|
def change_status(self, status, message, account):
|
||||||
|
''' change_status(status, message, account). account is optional -
|
||||||
|
if not specified status is changed for all accounts. '''
|
||||||
|
if status not in ('offline', 'online', 'chat',
|
||||||
|
'away', 'xa', 'dnd', 'invisible'):
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
if account:
|
||||||
|
gobject.idle_add(gajim.interface.roster.send_status, account,
|
||||||
|
status, message)
|
||||||
|
else:
|
||||||
|
# account not specified, so change the status of all accounts
|
||||||
|
for acc in gajim.contacts.get_accounts():
|
||||||
|
if not gajim.config.get_per('accounts', acc,
|
||||||
|
'sync_with_global_status'):
|
||||||
|
continue
|
||||||
|
gobject.idle_add(gajim.interface.roster.send_status, acc,
|
||||||
|
status, message)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
|
||||||
|
def show_next_pending_event(self):
|
||||||
|
'''Show the window(s) with next pending event in tabbed/group chats.'''
|
||||||
|
if gajim.events.get_nb_events():
|
||||||
|
gajim.interface.systray.handle_first_event()
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}')
|
||||||
|
def contact_info(self, jid):
|
||||||
|
'''get vcard info for a contact. Return cached value of the vcard.
|
||||||
|
'''
|
||||||
|
if not isinstance(jid, unicode):
|
||||||
|
jid = unicode(jid)
|
||||||
|
if not jid:
|
||||||
|
raise MissingArgument
|
||||||
|
return DBUS_DICT_SV()
|
||||||
|
jid = self._get_real_jid(jid)
|
||||||
|
|
||||||
|
cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
|
||||||
|
if cached_vcard:
|
||||||
|
return get_dbus_struct(cached_vcard)
|
||||||
|
|
||||||
|
# return empty dict
|
||||||
|
return DBUS_DICT_SV()
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='as')
|
||||||
|
def list_accounts(self):
|
||||||
|
'''list register accounts'''
|
||||||
|
result = gajim.contacts.get_accounts()
|
||||||
|
result_array = dbus.Array([], signature='s')
|
||||||
|
if result and len(result) > 0:
|
||||||
|
for account in result:
|
||||||
|
result_array.append(DBUS_STRING(account))
|
||||||
|
return result_array
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}')
|
||||||
|
def account_info(self, account):
|
||||||
|
'''show info on account: resource, jid, nick, prio, message'''
|
||||||
|
result = DBUS_DICT_SS()
|
||||||
|
if gajim.connections.has_key(account):
|
||||||
|
# account is valid
|
||||||
|
con = gajim.connections[account]
|
||||||
|
index = con.connected
|
||||||
|
result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
|
||||||
|
result['name'] = DBUS_STRING(con.name)
|
||||||
|
result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name))
|
||||||
|
result['message'] = DBUS_STRING(con.status)
|
||||||
|
result['priority'] = DBUS_STRING(unicode(con.priority))
|
||||||
|
result['resource'] = DBUS_STRING(unicode(gajim.config.get_per(
|
||||||
|
'accounts', con.name, 'resource')))
|
||||||
|
return result
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}')
|
||||||
|
def list_contacts(self, account):
|
||||||
|
'''list all contacts in the roster. If the first argument is specified,
|
||||||
|
then return the contacts for the specified account'''
|
||||||
|
result = dbus.Array([], signature='aa{sv}')
|
||||||
|
accounts = gajim.contacts.get_accounts()
|
||||||
|
if len(accounts) == 0:
|
||||||
|
return result
|
||||||
|
if account:
|
||||||
|
accounts_to_search = [account]
|
||||||
|
else:
|
||||||
|
accounts_to_search = accounts
|
||||||
|
for acct in accounts_to_search:
|
||||||
|
if acct in accounts:
|
||||||
|
for jid in gajim.contacts.get_jid_list(acct):
|
||||||
|
item = self._contacts_as_dbus_structure(
|
||||||
|
gajim.contacts.get_contacts(acct, jid))
|
||||||
|
if item:
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
|
||||||
|
def toggle_roster_appearance(self):
|
||||||
|
''' shows/hides the roster window '''
|
||||||
|
win = gajim.interface.roster.window
|
||||||
|
if win.get_property('visible'):
|
||||||
|
gobject.idle_add(win.hide)
|
||||||
|
else:
|
||||||
|
win.present()
|
||||||
|
# preserve the 'steal focus preservation'
|
||||||
|
if self._is_first():
|
||||||
|
win.window.focus()
|
||||||
|
else:
|
||||||
|
win.window.focus(long(time()))
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
|
||||||
|
def toggle_ipython(self):
|
||||||
|
''' shows/hides the ipython window '''
|
||||||
|
win = gajim.ipython_window
|
||||||
|
if win:
|
||||||
|
if win.window.is_visible():
|
||||||
|
gobject.idle_add(win.hide)
|
||||||
|
else:
|
||||||
|
win.show_all()
|
||||||
|
win.present()
|
||||||
|
else:
|
||||||
|
gajim.interface.create_ipython_window()
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}')
|
||||||
|
def prefs_list(self):
|
||||||
|
prefs_dict = DBUS_DICT_SS()
|
||||||
|
def get_prefs(data, name, path, value):
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
key = ''
|
||||||
|
if path is not None:
|
||||||
|
for node in path:
|
||||||
|
key += node + '#'
|
||||||
|
key += name
|
||||||
|
prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
|
||||||
|
gajim.config.foreach(get_prefs)
|
||||||
|
return prefs_dict
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='b')
|
||||||
|
def prefs_store(self):
|
||||||
|
try:
|
||||||
|
gajim.interface.save_config()
|
||||||
|
except Exception, e:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
|
||||||
|
def prefs_del(self, key):
|
||||||
|
if not key:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
key_path = key.split('#', 2)
|
||||||
|
if len(key_path) != 3:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
if key_path[2] == '*':
|
||||||
|
gajim.config.del_per(key_path[0], key_path[1])
|
||||||
|
else:
|
||||||
|
gajim.config.del_per(key_path[0], key_path[1], key_path[2])
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
|
||||||
|
def prefs_put(self, key):
|
||||||
|
if not key:
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
key_path = key.split('#', 2)
|
||||||
|
if len(key_path) < 3:
|
||||||
|
subname, value = key.split('=', 1)
|
||||||
|
gajim.config.set(subname, value)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
subname, value = key_path[2].split('=', 1)
|
||||||
|
gajim.config.set_per(key_path[0], key_path[1], subname, value)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
|
||||||
|
def add_contact(self, jid, account):
|
||||||
|
if account:
|
||||||
|
if account in gajim.connections and \
|
||||||
|
gajim.connections[account].connected > 1:
|
||||||
|
# if given account is active, use it
|
||||||
|
AddNewContactWindow(account = account, jid = jid)
|
||||||
|
else:
|
||||||
|
# wrong account
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
else:
|
||||||
|
# if account is not given, show account combobox
|
||||||
|
AddNewContactWindow(account = None, jid = jid)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
|
||||||
|
def remove_contact(self, jid, account):
|
||||||
|
jid = self._get_real_jid(jid, account)
|
||||||
|
accounts = gajim.contacts.get_accounts()
|
||||||
|
|
||||||
|
# if there is only one account in roster, take it as default
|
||||||
|
if account:
|
||||||
|
accounts = [account]
|
||||||
|
contact_exists = False
|
||||||
|
for account in accounts:
|
||||||
|
contacts = gajim.contacts.get_contacts(account, jid)
|
||||||
|
if contacts:
|
||||||
|
gajim.connections[account].unsubscribe(jid)
|
||||||
|
for contact in contacts:
|
||||||
|
gajim.interface.roster.remove_contact(contact, account)
|
||||||
|
gajim.contacts.remove_jid(account, jid)
|
||||||
|
contact_exists = True
|
||||||
|
return DBUS_BOOLEAN(contact_exists)
|
||||||
|
|
||||||
|
def _is_first(self):
|
||||||
|
if self.first_show:
|
||||||
|
self.first_show = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_real_jid(self, jid, account = None):
|
||||||
|
'''get the real jid from the given one: removes xmpp: or get jid from nick
|
||||||
|
if account is specified, search only in this account
|
||||||
|
'''
|
||||||
|
if account:
|
||||||
|
accounts = [account]
|
||||||
|
else:
|
||||||
|
accounts = gajim.connections.keys()
|
||||||
|
if jid.startswith('xmpp:'):
|
||||||
|
return jid[5:] # len('xmpp:') = 5
|
||||||
|
nick_in_roster = None # Is jid a nick ?
|
||||||
|
for account in accounts:
|
||||||
|
# Does jid exists in roster of one account ?
|
||||||
|
if gajim.contacts.get_contacts(account, jid):
|
||||||
|
return jid
|
||||||
|
if not nick_in_roster:
|
||||||
|
# look in all contact if one has jid as nick
|
||||||
|
for jid_ in gajim.contacts.get_jid_list(account):
|
||||||
|
c = gajim.contacts.get_contacts(account, jid_)
|
||||||
|
if c[0].name == jid:
|
||||||
|
nick_in_roster = jid_
|
||||||
|
break
|
||||||
|
if nick_in_roster:
|
||||||
|
# We have not found jid in roster, but we found is as a nick
|
||||||
|
return nick_in_roster
|
||||||
|
# We have not found it as jid nor as nick, probably a not in roster jid
|
||||||
|
return jid
|
||||||
|
|
||||||
|
def _contacts_as_dbus_structure(self, contacts):
|
||||||
|
''' get info from list of Contact objects and create dbus dict '''
|
||||||
|
if not contacts:
|
||||||
|
return None
|
||||||
|
prim_contact = None # primary contact
|
||||||
|
for contact in contacts:
|
||||||
|
if prim_contact is None or contact.priority > prim_contact.priority:
|
||||||
|
prim_contact = contact
|
||||||
|
contact_dict = DBUS_DICT_SV()
|
||||||
|
contact_dict['name'] = DBUS_STRING(prim_contact.name)
|
||||||
|
contact_dict['show'] = DBUS_STRING(prim_contact.show)
|
||||||
|
contact_dict['jid'] = DBUS_STRING(prim_contact.jid)
|
||||||
|
if prim_contact.keyID:
|
||||||
|
keyID = None
|
||||||
|
if len(prim_contact.keyID) == 8:
|
||||||
|
keyID = prim_contact.keyID
|
||||||
|
elif len(prim_contact.keyID) == 16:
|
||||||
|
keyID = prim_contact.keyID[8:]
|
||||||
|
if keyID:
|
||||||
|
contact_dict['openpgp'] = keyID
|
||||||
|
contact_dict['resources'] = dbus.Array([], signature='(sis)')
|
||||||
|
for contact in contacts:
|
||||||
|
resource_props = dbus.Struct((DBUS_STRING(contact.resource),
|
||||||
|
dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
|
||||||
|
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
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='', out_signature='s')
|
||||||
|
def get_unread_msgs_number(self):
|
||||||
|
return DBUS_STRING(str(gajim.events.get_nb_events()))
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
|
||||||
|
def start_chat(self, account):
|
||||||
|
if not account:
|
||||||
|
# error is shown in gajim-remote check_arguments(..)
|
||||||
|
return DBUS_BOOLEAN(False)
|
||||||
|
NewChatDialog(account)
|
||||||
|
return DBUS_BOOLEAN(True)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
|
||||||
|
def send_xml(self, xml, account):
|
||||||
|
if account:
|
||||||
|
gajim.connections[account].send_stanza(xml)
|
||||||
|
else:
|
||||||
|
for acc in gajim.contacts.get_accounts():
|
||||||
|
gajim.connections[acc].send_stanza(xml)
|
||||||
|
|
||||||
|
@dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
|
||||||
|
def join_room(self, room_jid, nick, password, account):
|
||||||
|
if not account:
|
||||||
|
# get the first connected account
|
||||||
|
accounts = gajim.connections.keys()
|
||||||
|
for acct in accounts:
|
||||||
|
if gajim.account_is_connected(acct):
|
||||||
|
account = acct
|
||||||
|
break
|
||||||
|
if not account:
|
||||||
|
return
|
||||||
|
if not nick:
|
||||||
|
nick = ''
|
||||||
|
gajim.interface.instances[account]['join_gc'] = \
|
||||||
|
JoinGroupchatWindow(account, room_jid, nick)
|
||||||
|
else:
|
||||||
|
gajim.interface.join_gc_room(account, room_jid, nick, password)
|
||||||
|
|
||||||
|
from common import gajim
|
||||||
|
from common import helpers
|
||||||
|
from time import time
|
||||||
|
from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from common import ged
|
||||||
|
|
||||||
|
class DBusPlugin(GajimPlugin):
|
||||||
|
name = u'D-Bus Support'
|
||||||
|
short_name = u'dbus'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''D-Bus support. Based on remote_control module from
|
||||||
|
Gajim core but uses new events handling system.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('DBusPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
#self.gui_extension_points = {}
|
||||||
|
#self.config_default_values = {}
|
||||||
|
|
||||||
|
self.events_names = ['Roster', 'AccountPresence', 'ContactPresence',
|
||||||
|
'ContactAbsence', 'ContactStatus', 'NewMessage',
|
||||||
|
'Subscribe', 'Subscribed', 'Unsubscribed',
|
||||||
|
'NewAccount', 'VcardInfo', 'LastStatusTime',
|
||||||
|
'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo',
|
||||||
|
'NewGmail']
|
||||||
|
|
||||||
|
self.signal_object = None
|
||||||
|
|
||||||
|
self.events_handlers = {}
|
||||||
|
self._set_handling_methods()
|
||||||
|
|
||||||
|
@log_calls('DBusPlugin')
|
||||||
|
def activate(self):
|
||||||
|
session_bus = dbus_support.session_bus.SessionBus()
|
||||||
|
|
||||||
|
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||||
|
self.signal_object = SignalObject(bus_name)
|
||||||
|
|
||||||
|
@log_calls('DBusPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
self.signal_object.remove_from_connection()
|
||||||
|
self.signal_object = None
|
||||||
|
|
||||||
|
@log_calls('DBusPlugin')
|
||||||
|
def _set_handling_methods(self):
|
||||||
|
for event_name in self.events_names:
|
||||||
|
setattr(self, event_name,
|
||||||
|
new.instancemethod(
|
||||||
|
self._generate_handling_method(event_name),
|
||||||
|
self,
|
||||||
|
DBusPlugin))
|
||||||
|
self.events_handlers[event_name] = (ged.POSTCORE,
|
||||||
|
getattr(self, event_name))
|
||||||
|
|
||||||
|
def _generate_handling_method(self, event_name):
|
||||||
|
def handler(self, arg):
|
||||||
|
#print "Handler of event %s called"%(event_name)
|
||||||
|
if self.signal_object:
|
||||||
|
getattr(self.signal_object, event_name)(get_dbus_struct(arg))
|
||||||
|
|
||||||
|
return handler
|
|
@ -0,0 +1 @@
|
||||||
|
from plugin import EventsDumpPlugin
|
|
@ -0,0 +1,129 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
'''
|
||||||
|
Events Dump plugin.
|
||||||
|
|
||||||
|
Dumps info about selected events to console.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 10th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import new
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from common import ged
|
||||||
|
|
||||||
|
class EventsDumpPlugin(GajimPlugin):
|
||||||
|
name = u'Events Dump'
|
||||||
|
short_name = u'events_dump'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Dumps info about selected events to console.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('EventsDumpPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
#self.gui_extension_points = {}
|
||||||
|
#self.config_default_values = {}
|
||||||
|
events_from_old_dbus_support = [
|
||||||
|
'Roster', 'AccountPresence', 'ContactPresence',
|
||||||
|
'ContactAbsence', 'ContactStatus', 'NewMessage',
|
||||||
|
'Subscribe', 'Subscribed', 'Unsubscribed',
|
||||||
|
'NewAccount', 'VcardInfo', 'LastStatusTime',
|
||||||
|
'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo',
|
||||||
|
'NewGmail']
|
||||||
|
|
||||||
|
events_from_src_gajim = [
|
||||||
|
'ROSTER', 'WARNING', 'ERROR',
|
||||||
|
'INFORMATION', 'ERROR_ANSWER', 'STATUS',
|
||||||
|
'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT',
|
||||||
|
'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE',
|
||||||
|
'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS',
|
||||||
|
'AGENT_REMOVED', 'REGISTER_AGENT_INFO',
|
||||||
|
'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO',
|
||||||
|
'QUIT', 'NEW_ACC_CONNECTED',
|
||||||
|
'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK',
|
||||||
|
'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO',
|
||||||
|
'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG',
|
||||||
|
'GC_CONFIG_CHANGE', 'GC_INVITATION',
|
||||||
|
'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED',
|
||||||
|
'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS',
|
||||||
|
'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST',
|
||||||
|
'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR',
|
||||||
|
'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT',
|
||||||
|
'HTTP_AUTH', 'VCARD_PUBLISHED',
|
||||||
|
'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN',
|
||||||
|
'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT',
|
||||||
|
'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED',
|
||||||
|
'PRIVACY_LISTS_ACTIVE_DEFAULT',
|
||||||
|
'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT',
|
||||||
|
'PING_SENT', 'PING_REPLY', 'PING_ERROR',
|
||||||
|
'SEARCH_FORM', 'SEARCH_RESULT',
|
||||||
|
'RESOURCE_CONFLICT', 'PEP_CONFIG',
|
||||||
|
'UNIQUE_ROOM_ID_UNSUPPORTED',
|
||||||
|
'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG',
|
||||||
|
'GPG_PASSWORD_REQUIRED', 'SSL_ERROR',
|
||||||
|
'FINGERPRINT_ERROR', 'PLAIN_CONNECTION',
|
||||||
|
'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED']
|
||||||
|
|
||||||
|
network_events_from_core = ['raw-message-received',
|
||||||
|
'raw-iq-received',
|
||||||
|
'raw-pres-received']
|
||||||
|
|
||||||
|
network_events_generated_in_nec = [
|
||||||
|
'customized-message-received',
|
||||||
|
'more-customized-message-received',
|
||||||
|
'modify-only-message-received',
|
||||||
|
'enriched-chat-message-received']
|
||||||
|
|
||||||
|
self.events_names = []
|
||||||
|
self.events_names += network_events_from_core
|
||||||
|
self.events_names += network_events_generated_in_nec
|
||||||
|
|
||||||
|
self.events_handlers = {}
|
||||||
|
self._set_handling_methods()
|
||||||
|
|
||||||
|
@log_calls('EventsDumpPlugin')
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('EventsDumpPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('EventsDumpPlugin')
|
||||||
|
def _set_handling_methods(self):
|
||||||
|
for event_name in self.events_names:
|
||||||
|
setattr(self, event_name,
|
||||||
|
new.instancemethod(
|
||||||
|
self._generate_handling_method(event_name),
|
||||||
|
self,
|
||||||
|
EventsDumpPlugin))
|
||||||
|
self.events_handlers[event_name] = (ged.POSTCORE,
|
||||||
|
getattr(self, event_name))
|
||||||
|
|
||||||
|
def _generate_handling_method(self, event_name):
|
||||||
|
def handler(self, *args):
|
||||||
|
print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args))
|
||||||
|
|
||||||
|
return handler
|
|
@ -0,0 +1 @@
|
||||||
|
from plugin import GoogleTranslationPlugin
|
|
@ -0,0 +1,118 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
'''
|
||||||
|
Google Translation plugin.
|
||||||
|
|
||||||
|
Translates (currently only incoming) messages using Google Translate.
|
||||||
|
|
||||||
|
:note: consider this as proof-of-concept
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 25th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import urllib2
|
||||||
|
import new
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from common import helpers
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from common import ged
|
||||||
|
from common import nec
|
||||||
|
|
||||||
|
class GoogleTranslationPlugin(GajimPlugin):
|
||||||
|
name = u'Google Translation'
|
||||||
|
short_name = u'google_translation'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Translates (currently only incoming) messages using Google Translate.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('GoogleTranslationPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
#self.gui_extension_points = {}
|
||||||
|
self.config_default_values = {'from_lang' : (u'en', _(u'Language of text to be translated')),
|
||||||
|
'to_lang' : (u'fr', _(u'Language to which translation will be made')),
|
||||||
|
'user_agent' : (u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11',
|
||||||
|
_(u'User Agent data to be used with urllib2 when connecting to Google Translate service'))}
|
||||||
|
|
||||||
|
#self.events_handlers = {}
|
||||||
|
|
||||||
|
self.events = [GoogleTranslateMessageReceivedEvent]
|
||||||
|
|
||||||
|
self.translated_text_re = \
|
||||||
|
re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P<text>[^"]*)"}, 200, null, 200\)')
|
||||||
|
|
||||||
|
@log_calls('GoogleTranslationPlugin')
|
||||||
|
def translate_text(self, text, from_lang, to_lang):
|
||||||
|
text = self.prepare_text_for_url(text)
|
||||||
|
headers = { 'User-Agent' : self.config['user_agent'] }
|
||||||
|
translation_url = u'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals()
|
||||||
|
|
||||||
|
request = urllib2.Request(translation_url, headers=headers)
|
||||||
|
response = urllib2.urlopen(request)
|
||||||
|
results = response.read()
|
||||||
|
|
||||||
|
translated_text = self.translated_text_re.search(results).group('text')
|
||||||
|
|
||||||
|
return translated_text
|
||||||
|
|
||||||
|
@log_calls('GoogleTranslationPlugin')
|
||||||
|
def prepare_text_for_url(self, text):
|
||||||
|
'''
|
||||||
|
Converts text so it can be used within URL as query to Google Translate.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# There should be more replacements for plugin to work in any case:
|
||||||
|
char_replacements = { ' ' : '%20',
|
||||||
|
'+' : '%2B'}
|
||||||
|
|
||||||
|
for char, replacement in char_replacements.iteritems():
|
||||||
|
text = text.replace(char, replacement)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
@log_calls('GoogleTranslationPlugin')
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('GoogleTranslationPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'google-translate-message-received'
|
||||||
|
base_network_events = ['raw-message-received']
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
|
||||||
|
if msg_type == u'chat':
|
||||||
|
msg_text = "".join(self.base_event.xmpp_msg.kids[0].data)
|
||||||
|
if msg_text:
|
||||||
|
from_lang = self.plugin.config['from_lang']
|
||||||
|
to_lang = self.plugin.config['to_lang']
|
||||||
|
self.base_event.xmpp_msg.kids[0].setData(
|
||||||
|
self.plugin.translate_text(msg_text, from_lang, to_lang))
|
||||||
|
|
||||||
|
return False # We only want to modify old event, not emit another,
|
||||||
|
# so we return False here.
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
from length_notifier import LengthNotifierPlugin
|
|
@ -0,0 +1,152 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="2.16"/>
|
||||||
|
<!-- interface-naming-policy toplevel-contextual -->
|
||||||
|
<object class="GtkWindow" id="window1">
|
||||||
|
<child>
|
||||||
|
<object class="GtkTable" id="length_notifier_config_table">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="border_width">9</property>
|
||||||
|
<property name="n_rows">3</property>
|
||||||
|
<property name="n_columns">2</property>
|
||||||
|
<property name="column_spacing">7</property>
|
||||||
|
<property name="row_spacing">5</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Message length:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Notification color:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label3">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">JabberIDs to include:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="bottom_attach">3</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="jids_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
|
||||||
|
<signal name="editing_done" handler="on_jids_entry_editing_done"/>
|
||||||
|
<signal name="changed" handler="on_jids_entry_changed"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
<property name="bottom_attach">3</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkColorButton" id="notification_colorbutton">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="title" translatable="yes">Pick a color for notification</property>
|
||||||
|
<property name="color">#000000000000</property>
|
||||||
|
<signal name="color_set" handler="on_notification_colorbutton_color_set"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="bottom_attach">2</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options"></property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSpinButton" id="message_length_spinbutton">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
|
||||||
|
<property name="width_chars">6</property>
|
||||||
|
<property name="snap_to_ticks">True</property>
|
||||||
|
<property name="numeric">True</property>
|
||||||
|
<signal name="value_changed" handler="on_message_length_spinbutton_value_changed"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="alignment2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<child>
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="right_attach">2</property>
|
||||||
|
<property name="x_options">GTK_FILL</property>
|
||||||
|
<property name="y_options">GTK_FILL</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,161 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Message length notifier plugin.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 1st June 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
from common import i18n
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log, log_calls
|
||||||
|
from plugins.gui import GajimPluginConfigDialog
|
||||||
|
|
||||||
|
class LengthNotifierPlugin(GajimPlugin):
|
||||||
|
name = u'Message Length Notifier'
|
||||||
|
short_name = u'length_notifier'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Highlights message entry field in chat window when given length of message is exceeded.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = LengthNotifierPluginConfigDialog(self)
|
||||||
|
|
||||||
|
self.gui_extension_points = {
|
||||||
|
'chat_control' : (self.connect_with_chat_control,
|
||||||
|
self.disconnect_from_chat_control)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')),
|
||||||
|
'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')),
|
||||||
|
'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPlugin')
|
||||||
|
def textview_length_warning(self, tb, chat_control):
|
||||||
|
tv = chat_control.msg_textview
|
||||||
|
d = chat_control.length_notifier_plugin_data
|
||||||
|
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
|
||||||
|
if t:
|
||||||
|
len_t = len(t)
|
||||||
|
#print("len_t: %d"%(len_t))
|
||||||
|
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
|
||||||
|
if not d['prev_color']:
|
||||||
|
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
|
||||||
|
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
|
||||||
|
elif d['prev_color']:
|
||||||
|
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
|
||||||
|
d['prev_color'] = None
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPlugin')
|
||||||
|
def connect_with_chat_control(self, chat_control):
|
||||||
|
jid = chat_control.contact.jid
|
||||||
|
if self.jid_is_ok(jid):
|
||||||
|
d = {'prev_color' : None}
|
||||||
|
tv = chat_control.msg_textview
|
||||||
|
tb = tv.get_buffer()
|
||||||
|
h_id = tb.connect('changed', self.textview_length_warning, chat_control)
|
||||||
|
d['h_id'] = h_id
|
||||||
|
|
||||||
|
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
|
||||||
|
if t:
|
||||||
|
len_t = len(t)
|
||||||
|
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
|
||||||
|
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
|
||||||
|
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
|
||||||
|
|
||||||
|
chat_control.length_notifier_plugin_data = d
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPlugin')
|
||||||
|
def disconnect_from_chat_control(self, chat_control):
|
||||||
|
try:
|
||||||
|
d = chat_control.length_notifier_plugin_data
|
||||||
|
tv = chat_control.msg_textview
|
||||||
|
tv.get_buffer().disconnect(d['h_id'])
|
||||||
|
if d['prev_color']:
|
||||||
|
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
|
||||||
|
except AttributeError, error:
|
||||||
|
pass
|
||||||
|
#log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error))
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPlugin')
|
||||||
|
def jid_is_ok(self, jid):
|
||||||
|
if jid in self.config['JIDS'] or not self.config['JIDS']:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog):
|
||||||
|
def init(self):
|
||||||
|
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
|
||||||
|
'config_dialog.ui')
|
||||||
|
self.xml = gtk.Builder()
|
||||||
|
self.xml.set_translation_domain(i18n.APP)
|
||||||
|
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
|
||||||
|
['length_notifier_config_table'])
|
||||||
|
self.config_table = self.xml.get_object('length_notifier_config_table')
|
||||||
|
self.child.pack_start(self.config_table)
|
||||||
|
|
||||||
|
self.message_length_spinbutton = self.xml.get_object(
|
||||||
|
'message_length_spinbutton')
|
||||||
|
self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1,
|
||||||
|
10, 0)
|
||||||
|
self.notification_colorbutton = self.xml.get_object(
|
||||||
|
'notification_colorbutton')
|
||||||
|
self.jids_entry = self.xml.get_object('jids_entry')
|
||||||
|
|
||||||
|
self.xml.connect_signals(self)
|
||||||
|
|
||||||
|
def on_run(self):
|
||||||
|
self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH'])
|
||||||
|
self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR']))
|
||||||
|
#self.jids_entry.set_text(self.plugin.config['JIDS'])
|
||||||
|
self.jids_entry.set_text(','.join(self.plugin.config['JIDS']))
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPluginConfigDialog')
|
||||||
|
def on_message_length_spinbutton_value_changed(self, spinbutton):
|
||||||
|
self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value()
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPluginConfigDialog')
|
||||||
|
def on_notification_colorbutton_color_set(self, colorbutton):
|
||||||
|
self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string()
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPluginConfigDialog')
|
||||||
|
def on_jids_entry_changed(self, entry):
|
||||||
|
text = entry.get_text()
|
||||||
|
if len(text)>0:
|
||||||
|
self.plugin.config['JIDS'] = entry.get_text().split(',')
|
||||||
|
else:
|
||||||
|
self.plugin.config['JIDS'] = []
|
||||||
|
|
||||||
|
@log_calls('LengthNotifierPluginConfigDialog')
|
||||||
|
def on_jids_entry_editing_done(self, entry):
|
||||||
|
pass
|
|
@ -0,0 +1 @@
|
||||||
|
from plugin import NewEventsExamplePlugin
|
|
@ -0,0 +1,147 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
'''
|
||||||
|
New Events Example plugin.
|
||||||
|
|
||||||
|
Demonstrates how to use Network Events Controller to generate new events
|
||||||
|
based on existing one.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 15th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import new
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from common import helpers
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from common import ged
|
||||||
|
from common import nec
|
||||||
|
|
||||||
|
class NewEventsExamplePlugin(GajimPlugin):
|
||||||
|
name = u'New Events Example'
|
||||||
|
short_name = u'new_events_example'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Shows how to generate new network events based on existing one using Network Events Controller.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('NewEventsExamplePlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
#self.gui_extension_points = {}
|
||||||
|
#self.config_default_values = {}
|
||||||
|
|
||||||
|
self.events_handlers = {'raw-message-received' :
|
||||||
|
(ged.POSTCORE,
|
||||||
|
self.raw_message_received),
|
||||||
|
'customized-message-received' :
|
||||||
|
(ged.POSTCORE,
|
||||||
|
self.customized_message_received),
|
||||||
|
'enriched-chat-message-received' :
|
||||||
|
(ged.POSTCORE,
|
||||||
|
self.enriched_chat_message_received)}
|
||||||
|
|
||||||
|
self.events = [CustomizedMessageReceivedEvent,
|
||||||
|
MoreCustomizedMessageReceivedEvent,
|
||||||
|
ModifyOnlyMessageReceivedEvent,
|
||||||
|
EnrichedChatMessageReceivedEvent]
|
||||||
|
|
||||||
|
def enriched_chat_message_received(self, event_object):
|
||||||
|
pass
|
||||||
|
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
|
||||||
|
#event_object)
|
||||||
|
|
||||||
|
def raw_message_received(self, event_object):
|
||||||
|
pass
|
||||||
|
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
|
||||||
|
#event_object)
|
||||||
|
|
||||||
|
def customized_message_received(self, event_object):
|
||||||
|
pass
|
||||||
|
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
|
||||||
|
#event_object
|
||||||
|
|
||||||
|
@log_calls('NewEventsExamplePlugin')
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('NewEventsExamplePlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CustomizedMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'customized-message-received'
|
||||||
|
base_network_events = ['raw-message-received']
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
class MoreCustomizedMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
'''
|
||||||
|
Shows chain of custom created events.
|
||||||
|
|
||||||
|
This one is based on custom 'customized-messsage-received'.
|
||||||
|
'''
|
||||||
|
name = 'more-customized-message-received'
|
||||||
|
base_network_events = ['customized-message-received']
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
class ModifyOnlyMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'modify-only-message-received'
|
||||||
|
base_network_events = ['raw-message-received']
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
|
||||||
|
if msg_type == u'chat':
|
||||||
|
msg_text = "".join(self.base_event.xmpp_msg.kids[0].data)
|
||||||
|
self.base_event.xmpp_msg.kids[0].setData(
|
||||||
|
u'%s [MODIFIED BY CUSTOM NETWORK EVENT]'%(msg_text))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
'''
|
||||||
|
Generates more friendly (in use by handlers) network event for
|
||||||
|
received chat message.
|
||||||
|
'''
|
||||||
|
name = 'enriched-chat-message-received'
|
||||||
|
base_network_events = ['raw-message-received']
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
|
||||||
|
if msg_type == u'chat':
|
||||||
|
self.xmpp_msg = self.base_event.xmpp_msg
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg)
|
||||||
|
self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid)
|
||||||
|
self.account = self.base_event.account
|
||||||
|
self.from_nickname = gajim.get_contact_name_from_jid(
|
||||||
|
self.account,
|
||||||
|
self.from_jid_without_resource)
|
||||||
|
self.msg_text = "".join(self.xmpp_msg.kids[0].data)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
__all__ = ['RosterButtonsPlugin']
|
||||||
|
|
||||||
|
from plugin import RosterButtonsPlugin
|
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Roster buttons plug-in.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 14th June 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import gtk
|
||||||
|
from common import i18n
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log, log_calls
|
||||||
|
|
||||||
|
class RosterButtonsPlugin(GajimPlugin):
|
||||||
|
name = u'Roster Buttons'
|
||||||
|
short_name = u'roster_buttons'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Adds quick action buttons to roster window.'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.GTK_BUILDER_FILE_PATH = self.local_file_path('roster_buttons.ui')
|
||||||
|
self.roster_vbox = gajim.interface.roster.xml.get_object('roster_vbox2')
|
||||||
|
self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_object('show_offline_contacts_menuitem')
|
||||||
|
|
||||||
|
self.config_dialog = None
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def activate(self):
|
||||||
|
self.xml = gtk.Builder()
|
||||||
|
self.xml.set_translation_domain(i18n.APP)
|
||||||
|
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
|
||||||
|
['roster_buttons_buttonbox'])
|
||||||
|
self.buttonbox = self.xml.get_object('roster_buttons_buttonbox')
|
||||||
|
|
||||||
|
self.roster_vbox.pack_start(self.buttonbox, expand=False)
|
||||||
|
self.roster_vbox.reorder_child(self.buttonbox, 0)
|
||||||
|
self.xml.connect_signals(self)
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
self.roster_vbox.remove(self.buttonbox)
|
||||||
|
|
||||||
|
self.buttonbox = None
|
||||||
|
self.xml = None
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def on_roster_button_1_clicked(self, button):
|
||||||
|
#gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None)
|
||||||
|
self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active())
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def on_roster_button_2_clicked(self, button):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def on_roster_button_3_clicked(self, button):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('RosterButtonsPlugin')
|
||||||
|
def on_roster_button_4_clicked(self, button):
|
||||||
|
pass
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="2.16"/>
|
||||||
|
<!-- interface-naming-policy toplevel-contextual -->
|
||||||
|
<object class="GtkWindow" id="window1">
|
||||||
|
<child>
|
||||||
|
<object class="GtkHButtonBox" id="roster_buttons_buttonbox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="homogeneous">True</property>
|
||||||
|
<property name="layout_style">spread</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="roster_button_1">
|
||||||
|
<property name="label" translatable="yes">1</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_roster_button_1_clicked"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="roster_button_2">
|
||||||
|
<property name="label" translatable="yes">2</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_roster_button_2_clicked"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="roster_button_3">
|
||||||
|
<property name="label" translatable="yes">3</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_roster_button_3_clicked"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="roster_button_4">
|
||||||
|
<property name="label" translatable="yes">4</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<signal name="clicked" handler="on_roster_button_4_clicked"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">False</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -0,0 +1,772 @@
|
||||||
|
"""
|
||||||
|
A python version of the main functions to use Snarl
|
||||||
|
(http://www.fullphat.net/snarl)
|
||||||
|
|
||||||
|
Version 1.0
|
||||||
|
|
||||||
|
This module can be used in two ways. One is the normal way
|
||||||
|
the other snarl interfaces work. This means you can call snShowMessage
|
||||||
|
and get an ID back for manipulations.
|
||||||
|
|
||||||
|
The other way is there is a class this module exposes called SnarlMessage.
|
||||||
|
This allows you to keep track of the message as a python object. If you
|
||||||
|
use the send without specifying False as the argument it will set the ID
|
||||||
|
to what the return of the last SendMessage was. This is of course only
|
||||||
|
useful for the SHOW message.
|
||||||
|
|
||||||
|
Requires one of:
|
||||||
|
pywin32 extensions from http://pywin32.sourceforge.net
|
||||||
|
ctypes (included in Python 2.5, downloadable for earlier versions)
|
||||||
|
|
||||||
|
Creator: Sam Listopad II (samlii@users.sourceforge.net)
|
||||||
|
|
||||||
|
Copyright 2006-2008 Samuel Listopad II
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy
|
||||||
|
of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
|
||||||
|
by applicable law or agreed to in writing, software distributed under the
|
||||||
|
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
||||||
|
OF ANY KIND, either express or implied. See the License for the specific
|
||||||
|
language governing permissions and limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import array, struct
|
||||||
|
|
||||||
|
def LOWORD(dword):
|
||||||
|
"""Return the low WORD of the passed in integer"""
|
||||||
|
return dword & 0x0000ffff
|
||||||
|
#get the hi word
|
||||||
|
def HIWORD(dword):
|
||||||
|
"""Return the high WORD of the passed in integer"""
|
||||||
|
return dword >> 16
|
||||||
|
|
||||||
|
class Win32FuncException(Exception):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self.value)
|
||||||
|
|
||||||
|
class Win32Funcs:
|
||||||
|
"""Just a little class to hide the details of finding and using the
|
||||||
|
correct win32 functions. The functions may throw a UnicodeEncodeError if
|
||||||
|
there is not a unicode version and it is sent a unicode string that cannot
|
||||||
|
be converted to ASCII."""
|
||||||
|
WM_USER = 0x400
|
||||||
|
WM_COPYDATA = 0x4a
|
||||||
|
#Type of String the functions are expecting.
|
||||||
|
#Used like function(myWin32Funcs.strType(param)).
|
||||||
|
__strType = str
|
||||||
|
#FindWindow function to use
|
||||||
|
__FindWindow = None
|
||||||
|
#FindWindow function to use
|
||||||
|
__FindWindowEx = None
|
||||||
|
#SendMessage function to use
|
||||||
|
__SendMessage = None
|
||||||
|
#SendMessageTimeout function to use
|
||||||
|
__SendMessageTimeout = None
|
||||||
|
#IsWindow function to use
|
||||||
|
__IsWindow = None
|
||||||
|
#RegisterWindowMessage to use
|
||||||
|
__RegisterWindowMessage = None
|
||||||
|
#GetWindowText to use
|
||||||
|
__GetWindowText = None
|
||||||
|
|
||||||
|
def FindWindow(self, lpClassName, lpWindowName):
|
||||||
|
"""Wraps the windows API call of FindWindow"""
|
||||||
|
if lpClassName is not None:
|
||||||
|
lpClassName = self.__strType(lpClassName)
|
||||||
|
if lpWindowName is not None:
|
||||||
|
lpWindowName = self.__strType(lpWindowName)
|
||||||
|
return self.__FindWindow(lpClassName, lpWindowName)
|
||||||
|
|
||||||
|
def FindWindowEx(self, hwndParent, hwndChildAfter, lpClassName, lpWindowName):
|
||||||
|
"""Wraps the windows API call of FindWindow"""
|
||||||
|
if lpClassName is not None:
|
||||||
|
lpClassName = self.__strType(lpClassName)
|
||||||
|
if lpWindowName is not None:
|
||||||
|
lpWindowName = self.__strType(lpWindowName)
|
||||||
|
return self.__FindWindowEx(hwndParent, hwndChildAfter, lpClassName, lpWindowName)
|
||||||
|
|
||||||
|
def SendMessage(self, hWnd, Msg, wParam, lParam):
|
||||||
|
"""Wraps the windows API call of SendMessage"""
|
||||||
|
return self.__SendMessage(hWnd, Msg, wParam, lParam)
|
||||||
|
|
||||||
|
def SendMessageTimeout(self, hWnd, Msg,
|
||||||
|
wParam, lParam, fuFlags,
|
||||||
|
uTimeout, lpdwResult = None):
|
||||||
|
"""Wraps the windows API call of SendMessageTimeout"""
|
||||||
|
idToRet = None
|
||||||
|
try:
|
||||||
|
idFromMsg = array.array('I', [0])
|
||||||
|
result = idFromMsg.buffer_info()[0]
|
||||||
|
response = self.__SendMessageTimeout(hWnd, Msg, wParam,
|
||||||
|
lParam, fuFlags,
|
||||||
|
uTimeout, result)
|
||||||
|
if response == 0:
|
||||||
|
raise Win32FuncException, "SendMessageTimeout TimedOut"
|
||||||
|
|
||||||
|
idToRet = idFromMsg[0]
|
||||||
|
except TypeError:
|
||||||
|
idToRet = self.__SendMessageTimeout(hWnd, Msg, wParam,
|
||||||
|
lParam, fuFlags,
|
||||||
|
uTimeout)
|
||||||
|
|
||||||
|
if lpdwResult is not None and lpdwResult.typecode == 'I':
|
||||||
|
lpdwResult[0] = idToRet
|
||||||
|
|
||||||
|
return idToRet
|
||||||
|
|
||||||
|
def IsWindow(self, hWnd):
|
||||||
|
"""Wraps the windows API call of IsWindow"""
|
||||||
|
return self.__IsWindow(hWnd)
|
||||||
|
|
||||||
|
def RegisterWindowMessage(self, lpString):
|
||||||
|
"""Wraps the windows API call of RegisterWindowMessage"""
|
||||||
|
return self.__RegisterWindowMessage(self.__strType(lpString))
|
||||||
|
|
||||||
|
def GetWindowText(self, hWnd, lpString = None, nMaxCount = None):
|
||||||
|
"""Wraps the windows API call of SendMessageTimeout"""
|
||||||
|
text = ''
|
||||||
|
if hWnd == 0:
|
||||||
|
return text
|
||||||
|
|
||||||
|
if nMaxCount is None:
|
||||||
|
nMaxCount = 1025
|
||||||
|
|
||||||
|
try:
|
||||||
|
arrayType = 'c'
|
||||||
|
if self.__strType == unicode:
|
||||||
|
arrayType = 'u'
|
||||||
|
path_string = array.array(arrayType, self.__strType('\x00') * nMaxCount)
|
||||||
|
path_buffer = path_string.buffer_info()[0]
|
||||||
|
result = self.__GetWindowText(hWnd,
|
||||||
|
path_buffer,
|
||||||
|
nMaxCount)
|
||||||
|
if result > 0:
|
||||||
|
if self.__strType == unicode:
|
||||||
|
text = path_string[0:result].tounicode()
|
||||||
|
else:
|
||||||
|
text = path_string[0:result].tostring()
|
||||||
|
except TypeError:
|
||||||
|
text = self.__GetWindowText(hWnd)
|
||||||
|
|
||||||
|
if lpString is not None and lpString.typecode == 'c':
|
||||||
|
lpdwResult[0:len(text)] = array.array('c', str(text));
|
||||||
|
|
||||||
|
if lpString is not None and lpString.typecode == 'u':
|
||||||
|
lpdwResult[0:len(text)] = array.array('u', unicode(text));
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Load up my needed functions"""
|
||||||
|
# First see if they already have win32gui imported. If so use it.
|
||||||
|
# This has to be checked first since the auto check looks for ctypes
|
||||||
|
# first.
|
||||||
|
try:
|
||||||
|
self.__FindWindow = win32gui.FindWindow
|
||||||
|
self.__FindWindowEx = win32gui.FindWindowEx
|
||||||
|
self.__GetWindowText = win32gui.GetWindowText
|
||||||
|
self.__IsWindow = win32gui.IsWindow
|
||||||
|
self.__SendMessage = win32gui.SendMessage
|
||||||
|
self.__SendMessageTimeout = win32gui.SendMessageTimeout
|
||||||
|
self.__RegisterWindowMessage = win32gui.RegisterWindowMessage
|
||||||
|
self.__strType = unicode
|
||||||
|
|
||||||
|
#Something threw a NameError, most likely the win32gui lines
|
||||||
|
#so do auto check
|
||||||
|
except NameError:
|
||||||
|
try:
|
||||||
|
from ctypes import windll
|
||||||
|
self.__FindWindow = windll.user32.FindWindowW
|
||||||
|
self.__FindWindowEx = windll.user32.FindWindowExW
|
||||||
|
self.__GetWindowText = windll.user32.GetWindowTextW
|
||||||
|
self.__IsWindow = windll.user32.IsWindow
|
||||||
|
self.__SendMessage = windll.user32.SendMessageW
|
||||||
|
self.__SendMessageTimeout = windll.user32.SendMessageTimeoutW
|
||||||
|
self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageW
|
||||||
|
self.__strType = unicode
|
||||||
|
|
||||||
|
#FindWindowW wasn't found, look for FindWindowA
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
self.__FindWindow = windll.user32.FindWindowA
|
||||||
|
self.__FindWindowEx = windll.user32.FindWindowExA
|
||||||
|
self.__GetWindowText = windll.user32.GetWindowTextA
|
||||||
|
self.__IsWindow = windll.user32.IsWindow
|
||||||
|
self.__SendMessage = windll.user32.SendMessageA
|
||||||
|
self.__SendMessageTimeout = windll.user32.SendMessageTimeoutA
|
||||||
|
self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageA
|
||||||
|
# Couldn't find either so Die and tell user why.
|
||||||
|
except AttributeError:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("Your Windows TM setup seems to be corrupt."+
|
||||||
|
" No FindWindow found in user32.\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import win32gui
|
||||||
|
self.__FindWindow = win32gui.FindWindow
|
||||||
|
self.__FindWindowEx = win32gui.FindWindowEx
|
||||||
|
self.__GetWindowText = win32gui.GetWindowText
|
||||||
|
self.__IsWindow = win32gui.IsWindow
|
||||||
|
self.__SendMessage = win32gui.SendMessage
|
||||||
|
self.__SendMessageTimeout = win32gui.SendMessageTimeout
|
||||||
|
self.__RegisterWindowMessage = win32gui.RegisterWindowMessage
|
||||||
|
self.__strType = unicode
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("You need to have either"+
|
||||||
|
" ctypes or pywin32 installed.\n")
|
||||||
|
sys.stderr.flush()
|
||||||
|
#sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
myWin32Funcs = Win32Funcs()
|
||||||
|
|
||||||
|
|
||||||
|
SHOW = 1
|
||||||
|
HIDE = 2
|
||||||
|
UPDATE = 3
|
||||||
|
IS_VISIBLE = 4
|
||||||
|
GET_VERSION = 5
|
||||||
|
REGISTER_CONFIG_WINDOW = 6
|
||||||
|
REVOKE_CONFIG_WINDOW = 7
|
||||||
|
REGISTER_ALERT = 8
|
||||||
|
REVOKE_ALERT = 9
|
||||||
|
REGISTER_CONFIG_WINDOW_2 = 10
|
||||||
|
GET_VERSION_EX = 11
|
||||||
|
SET_TIMEOUT = 12
|
||||||
|
|
||||||
|
EX_SHOW = 32
|
||||||
|
|
||||||
|
GLOBAL_MESSAGE = "SnarlGlobalMessage"
|
||||||
|
GLOBAL_MSG = "SnarlGlobalEvent"
|
||||||
|
|
||||||
|
#Messages That may be received from Snarl
|
||||||
|
SNARL_LAUNCHED = 1
|
||||||
|
SNARL_QUIT = 2
|
||||||
|
SNARL_ASK_APPLET_VER = 3
|
||||||
|
SNARL_SHOW_APP_UI = 4
|
||||||
|
|
||||||
|
SNARL_NOTIFICATION_CLICKED = 32 #notification was right-clicked by user
|
||||||
|
SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED #Name clarified
|
||||||
|
SNARL_NOTIFICATION_TIMED_OUT = 33
|
||||||
|
SNARL_NOTIFICATION_ACK = 34 #notification was left-clicked by user
|
||||||
|
|
||||||
|
#Snarl Test Message
|
||||||
|
WM_SNARLTEST = myWin32Funcs.WM_USER + 237
|
||||||
|
|
||||||
|
M_ABORTED = 0x80000007L
|
||||||
|
M_ACCESS_DENIED = 0x80000009L
|
||||||
|
M_ALREADY_EXISTS = 0x8000000CL
|
||||||
|
M_BAD_HANDLE = 0x80000006L
|
||||||
|
M_BAD_POINTER = 0x80000005L
|
||||||
|
M_FAILED = 0x80000008L
|
||||||
|
M_INVALID_ARGS = 0x80000003L
|
||||||
|
M_NO_INTERFACE = 0x80000004L
|
||||||
|
M_NOT_FOUND = 0x8000000BL
|
||||||
|
M_NOT_IMPLEMENTED = 0x80000001L
|
||||||
|
M_OK = 0x00000000L
|
||||||
|
M_OUT_OF_MEMORY = 0x80000002L
|
||||||
|
M_TIMED_OUT = 0x8000000AL
|
||||||
|
|
||||||
|
ErrorCodeRev = {
|
||||||
|
0x80000007L : "M_ABORTED",
|
||||||
|
0x80000009L : "M_ACCESS_DENIED",
|
||||||
|
0x8000000CL : "M_ALREADY_EXISTS",
|
||||||
|
0x80000006L : "M_BAD_HANDLE",
|
||||||
|
0x80000005L : "M_BAD_POINTER",
|
||||||
|
0x80000008L : "M_FAILED",
|
||||||
|
0x80000003L : "M_INVALID_ARGS",
|
||||||
|
0x80000004L : "M_NO_INTERFACE",
|
||||||
|
0x8000000BL : "M_NOT_FOUND",
|
||||||
|
0x80000001L : "M_NOT_IMPLEMENTED",
|
||||||
|
0x00000000L : "M_OK",
|
||||||
|
0x80000002L : "M_OUT_OF_MEMORY",
|
||||||
|
0x8000000AL : "M_TIMED_OUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnarlMessage(object):
|
||||||
|
"""The main Snarl interface object.
|
||||||
|
|
||||||
|
ID = Snarl Message ID for most operations. See SDK for more info
|
||||||
|
as to other values to put here.
|
||||||
|
type = Snarl Message Type. Valid values are : SHOW, HIDE, UPDATE,
|
||||||
|
IS_VISIBLE, GET_VERSION, REGISTER_CONFIG_WINDOW, REVOKE_CONFIG_WINDOW
|
||||||
|
all which are constants in the PySnarl module.
|
||||||
|
timeout = Timeout in seconds for the Snarl Message
|
||||||
|
data = Snarl Message data. This is dependant upon message type. See SDK
|
||||||
|
title = Snarl Message title.
|
||||||
|
text = Snarl Message text.
|
||||||
|
icon = Path to the icon to display in the Snarl Message.
|
||||||
|
"""
|
||||||
|
__msgType = 0
|
||||||
|
__msgID = 0
|
||||||
|
__msgTimeout = 0
|
||||||
|
__msgData = 0
|
||||||
|
__msgTitle = ""
|
||||||
|
__msgText = ""
|
||||||
|
__msgIcon = ""
|
||||||
|
__msgClass = ""
|
||||||
|
__msgExtra = ""
|
||||||
|
__msgExtra2 = ""
|
||||||
|
__msgRsvd1 = 0
|
||||||
|
__msgRsvd2 = 0
|
||||||
|
__msgHWnd = 0
|
||||||
|
|
||||||
|
lastKnownHWnd = 0
|
||||||
|
|
||||||
|
def getType(self):
|
||||||
|
"""Type Attribute getter."""
|
||||||
|
return self.__msgType
|
||||||
|
def setType(self, value):
|
||||||
|
"""Type Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgType = value
|
||||||
|
type = property(getType, setType, doc="The Snarl Message Type")
|
||||||
|
|
||||||
|
def getID(self):
|
||||||
|
"""ID Attribute getter."""
|
||||||
|
return self.__msgID
|
||||||
|
def setID(self, value):
|
||||||
|
"""ID Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgID = value
|
||||||
|
ID = property(getID, setID, doc="The Snarl Message ID")
|
||||||
|
|
||||||
|
def getTimeout(self):
|
||||||
|
"""Timeout Attribute getter."""
|
||||||
|
return self.__msgTimeout
|
||||||
|
def updateTimeout(self, value):
|
||||||
|
"""Timeout Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgTimeout = value
|
||||||
|
timeout = property(getTimeout, updateTimeout,
|
||||||
|
doc="The Snarl Message Timeout")
|
||||||
|
|
||||||
|
def getData(self):
|
||||||
|
"""Data Attribute getter."""
|
||||||
|
return self.__msgData
|
||||||
|
def setData(self, value):
|
||||||
|
"""Data Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgData = value
|
||||||
|
data = property(getData, setData, doc="The Snarl Message Data")
|
||||||
|
|
||||||
|
def getTitle(self):
|
||||||
|
"""Title Attribute getter."""
|
||||||
|
return self.__msgTitle
|
||||||
|
def setTitle(self, value):
|
||||||
|
"""Title Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgTitle = value
|
||||||
|
title = property(getTitle, setTitle, doc="The Snarl Message Title")
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
"""Text Attribute getter."""
|
||||||
|
return self.__msgText
|
||||||
|
def setText(self, value):
|
||||||
|
"""Text Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgText = value
|
||||||
|
text = property(getText, setText, doc="The Snarl Message Text")
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
"""Icon Attribute getter."""
|
||||||
|
return self.__msgIcon
|
||||||
|
def setIcon(self, value):
|
||||||
|
"""Icon Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgIcon = value
|
||||||
|
icon = property(getIcon, setIcon, doc="The Snarl Message Icon")
|
||||||
|
|
||||||
|
def getClass(self):
|
||||||
|
"""Class Attribute getter."""
|
||||||
|
return self.__msgClass
|
||||||
|
def setClass(self, value):
|
||||||
|
"""Class Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgClass = value
|
||||||
|
msgclass = property(getClass, setClass, doc="The Snarl Message Class")
|
||||||
|
|
||||||
|
def getExtra(self):
|
||||||
|
"""Extra Attribute getter."""
|
||||||
|
return self.__msgExtra
|
||||||
|
def setExtra(self, value):
|
||||||
|
"""Extra Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgExtra = value
|
||||||
|
extra = property(getExtra, setExtra, doc="Extra Info for the Snarl Message")
|
||||||
|
|
||||||
|
def getExtra2(self):
|
||||||
|
"""Extra2 Attribute getter."""
|
||||||
|
return self.__msgExtra2
|
||||||
|
def setExtra2(self, value):
|
||||||
|
"""Extra2 Attribute setter."""
|
||||||
|
if( isinstance(value, basestring) ):
|
||||||
|
self.__msgExtra2 = value
|
||||||
|
extra2 = property(getExtra2, setExtra2,
|
||||||
|
doc="More Extra Info for the Snarl Message")
|
||||||
|
|
||||||
|
def getRsvd1(self):
|
||||||
|
"""Rsvd1 Attribute getter."""
|
||||||
|
return self.__msgRsvd1
|
||||||
|
def setRsvd1(self, value):
|
||||||
|
"""Rsvd1 Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgRsvd1 = value
|
||||||
|
rsvd1 = property(getRsvd1, setRsvd1, doc="The Snarl Message Field Rsvd1")
|
||||||
|
|
||||||
|
def getRsvd2(self):
|
||||||
|
"""Rsvd2 Attribute getter."""
|
||||||
|
return self.__msgRsvd2
|
||||||
|
def setRsvd2(self, value):
|
||||||
|
"""Rsvd2 Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgRsvd2 = value
|
||||||
|
rsvd2 = property(getRsvd2, setRsvd2, doc="The Snarl Message Field Rsvd2")
|
||||||
|
|
||||||
|
def getHwnd(self):
|
||||||
|
"""hWnd Attribute getter."""
|
||||||
|
return self.__msgHWnd
|
||||||
|
def setHwnd(self, value):
|
||||||
|
"""hWnd Attribute setter."""
|
||||||
|
if( isinstance(value, (int, long)) ):
|
||||||
|
self.__msgHWnd = value
|
||||||
|
|
||||||
|
hWnd = property(getHwnd, setHwnd, doc="The hWnd of the window this message is being sent from")
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, title="", text="", icon="", msg_type=1, msg_id=0):
|
||||||
|
self.__msgTimeout = 0
|
||||||
|
self.__msgData = 0
|
||||||
|
self.__msgClass = ""
|
||||||
|
self.__msgExtra = ""
|
||||||
|
self.__msgExtra2 = ""
|
||||||
|
self.__msgRsvd1 = 0
|
||||||
|
self.__msgRsvd2 = 0
|
||||||
|
self.__msgType = msg_type
|
||||||
|
self.__msgText = text
|
||||||
|
self.__msgTitle = title
|
||||||
|
self.__msgIcon = icon
|
||||||
|
self.__msgID = msg_id
|
||||||
|
|
||||||
|
def createCopyStruct(self):
|
||||||
|
"""Creates the struct to send as the copyData in the message."""
|
||||||
|
return struct.pack("ILLL1024s1024s1024s1024s1024s1024sLL",
|
||||||
|
self.__msgType,
|
||||||
|
self.__msgID,
|
||||||
|
self.__msgTimeout,
|
||||||
|
self.__msgData,
|
||||||
|
self.__msgTitle.encode('utf-8'),
|
||||||
|
self.__msgText.encode('utf-8'),
|
||||||
|
self.__msgIcon.encode('utf-8'),
|
||||||
|
self.__msgClass.encode('utf-8'),
|
||||||
|
self.__msgExtra.encode('utf-8'),
|
||||||
|
self.__msgExtra2.encode('utf-8'),
|
||||||
|
self.__msgRsvd1,
|
||||||
|
self.__msgRsvd2
|
||||||
|
)
|
||||||
|
__lpData = None
|
||||||
|
__cds = None
|
||||||
|
|
||||||
|
def packData(self, dwData):
|
||||||
|
"""This packs the data in the necessary format for a
|
||||||
|
WM_COPYDATA message."""
|
||||||
|
self.__lpData = None
|
||||||
|
self.__cds = None
|
||||||
|
item = self.createCopyStruct()
|
||||||
|
self.__lpData = array.array('c', item)
|
||||||
|
lpData_ad = self.__lpData.buffer_info()[0]
|
||||||
|
cbData = self.__lpData.buffer_info()[1]
|
||||||
|
self.__cds = array.array('c',
|
||||||
|
struct.pack("IIP",
|
||||||
|
dwData,
|
||||||
|
cbData,
|
||||||
|
lpData_ad)
|
||||||
|
)
|
||||||
|
cds_ad = self.__cds.buffer_info()[0]
|
||||||
|
return cds_ad
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset this SnarlMessage to the default state."""
|
||||||
|
self.__msgType = 0
|
||||||
|
self.__msgID = 0
|
||||||
|
self.__msgTimeout = 0
|
||||||
|
self.__msgData = 0
|
||||||
|
self.__msgTitle = ""
|
||||||
|
self.__msgText = ""
|
||||||
|
self.__msgIcon = ""
|
||||||
|
self.__msgClass = ""
|
||||||
|
self.__msgExtra = ""
|
||||||
|
self.__msgExtra2 = ""
|
||||||
|
self.__msgRsvd1 = 0
|
||||||
|
self.__msgRsvd2 = 0
|
||||||
|
|
||||||
|
|
||||||
|
def send(self, setid=True):
|
||||||
|
"""Send this SnarlMessage to the Snarl window.
|
||||||
|
Args:
|
||||||
|
setid - Boolean defining whether or not to set the ID
|
||||||
|
of this SnarlMessage to the return value of
|
||||||
|
the SendMessage call. Default is True to
|
||||||
|
make simple case of SHOW easy.
|
||||||
|
"""
|
||||||
|
hwnd = myWin32Funcs.FindWindow(None, "Snarl")
|
||||||
|
if myWin32Funcs.IsWindow(hwnd):
|
||||||
|
if self.type == REGISTER_CONFIG_WINDOW or self.type == REGISTER_CONFIG_WINDOW_2:
|
||||||
|
self.hWnd = self.data
|
||||||
|
try:
|
||||||
|
response = myWin32Funcs.SendMessageTimeout(hwnd,
|
||||||
|
myWin32Funcs.WM_COPYDATA,
|
||||||
|
self.hWnd, self.packData(2),
|
||||||
|
2, 500)
|
||||||
|
except Win32FuncException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
idFromMsg = response
|
||||||
|
if setid:
|
||||||
|
self.ID = idFromMsg
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return idFromMsg
|
||||||
|
print "No snarl window found"
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
"""Hide this message. Type will revert to type before calling hide
|
||||||
|
to allow for better reuse of object."""
|
||||||
|
oldType = self.__msgType
|
||||||
|
self.__msgType = HIDE
|
||||||
|
retVal = bool(self.send(False))
|
||||||
|
self.__msgType = oldType
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
def isVisible(self):
|
||||||
|
"""Is this message visible. Type will revert to type before calling
|
||||||
|
hide to allow for better reuse of object."""
|
||||||
|
oldType = self.__msgType
|
||||||
|
self.__msgType = IS_VISIBLE
|
||||||
|
retVal = bool(self.send(False))
|
||||||
|
self.__msgType = oldType
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
def update(self, title=None, text=None, icon=None):
|
||||||
|
"""Update this message with given title and text. Type will revert
|
||||||
|
to type before calling hide to allow for better reuse of object."""
|
||||||
|
oldType = self.__msgType
|
||||||
|
self.__msgType = UPDATE
|
||||||
|
if text:
|
||||||
|
self.__msgText = text
|
||||||
|
if title:
|
||||||
|
self.__msgTitle = title
|
||||||
|
if icon:
|
||||||
|
self.__msgIcon = icon
|
||||||
|
retVal = self.send(False)
|
||||||
|
self.__msgType = oldType
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
def setTimeout(self, timeout):
|
||||||
|
"""Set the timeout in seconds of the message"""
|
||||||
|
oldType = self.__msgType
|
||||||
|
oldData = self.__msgData
|
||||||
|
self.__msgType = SET_TIMEOUT
|
||||||
|
#self.timeout = timeout
|
||||||
|
#self.__msgData = self.__msgTimeout
|
||||||
|
self.__msgData = timeout
|
||||||
|
retVal = self.send(False)
|
||||||
|
self.__msgType = oldType
|
||||||
|
self.__msgData = oldData
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
def show(self, timeout=None, title=None,
|
||||||
|
text=None, icon=None,
|
||||||
|
replyWindow=None, replyMsg=None, msgclass=None, soundPath=None):
|
||||||
|
"""Show a message"""
|
||||||
|
oldType = self.__msgType
|
||||||
|
oldTimeout = self.__msgTimeout
|
||||||
|
self.__msgType = SHOW
|
||||||
|
if text:
|
||||||
|
self.__msgText = text
|
||||||
|
if title:
|
||||||
|
self.__msgTitle = title
|
||||||
|
if timeout:
|
||||||
|
self.__msgTimeout = timeout
|
||||||
|
if icon:
|
||||||
|
self.__msgIcon = icon
|
||||||
|
if replyWindow:
|
||||||
|
self.__msgID = replyMsg
|
||||||
|
if replyMsg:
|
||||||
|
self.__msgData = replyWindow
|
||||||
|
if soundPath:
|
||||||
|
self.__msgExtra = soundPath
|
||||||
|
if msgclass:
|
||||||
|
self.__msgClass = msgclass
|
||||||
|
|
||||||
|
if ((self.__msgClass and self.__msgClass != "") or
|
||||||
|
(self.__msgExtra and self.__msgExtra != "")):
|
||||||
|
self.__msgType = EX_SHOW
|
||||||
|
|
||||||
|
|
||||||
|
retVal = bool(self.send())
|
||||||
|
self.__msgType = oldType
|
||||||
|
self.__msgTimeout = oldTimeout
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
|
||||||
|
def snGetVersion():
|
||||||
|
""" Get the version of Snarl that is running as a tuple. (Major, Minor)
|
||||||
|
|
||||||
|
If Snarl is not running or there was an error it will
|
||||||
|
return False."""
|
||||||
|
msg = SnarlMessage(msg_type=GET_VERSION)
|
||||||
|
version = msg.send(False)
|
||||||
|
if not version:
|
||||||
|
return False
|
||||||
|
return (HIWORD(version), LOWORD(version))
|
||||||
|
|
||||||
|
def snGetVersionEx():
|
||||||
|
""" Get the internal version of Snarl that is running.
|
||||||
|
|
||||||
|
If Snarl is not running or there was an error it will
|
||||||
|
return False."""
|
||||||
|
sm = SnarlMessage(msg_type=GET_VERSION_EX)
|
||||||
|
verNum = sm.send(False)
|
||||||
|
if not verNum:
|
||||||
|
return False
|
||||||
|
return verNum
|
||||||
|
|
||||||
|
def snGetGlobalMessage():
|
||||||
|
"""Get the Snarl global message id from windows."""
|
||||||
|
return myWin32Funcs.RegisterWindowMessage(GLOBAL_MSG)
|
||||||
|
|
||||||
|
def snShowMessage(title, text, timeout=0, iconPath="",
|
||||||
|
replyWindow=0, replyMsg=0):
|
||||||
|
"""Show a message using Snarl and return its ID. See SDK for arguments."""
|
||||||
|
sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg)
|
||||||
|
sm.data = replyWindow
|
||||||
|
if sm.show(timeout):
|
||||||
|
return sm.ID
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def snShowMessageEx(msgClass, title, text, timeout=0, iconPath="",
|
||||||
|
replyWindow=0, replyMsg=0, soundFile=None, hWndFrom=None):
|
||||||
|
"""Show a message using Snarl and return its ID. See SDK for arguments.
|
||||||
|
One added argument is hWndFrom that allows one to make the messages appear
|
||||||
|
to come from a specific window. This window should be the one you registered
|
||||||
|
earlier with RegisterConfig"""
|
||||||
|
sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg)
|
||||||
|
sm.data = replyWindow
|
||||||
|
if hWndFrom is not None:
|
||||||
|
sm.hWnd = hWndFrom
|
||||||
|
else:
|
||||||
|
sm.hWnd = SnarlMessage.lastKnownHWnd
|
||||||
|
if sm.show(timeout, msgclass=msgClass, soundPath=soundFile):
|
||||||
|
return sm.ID
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def snUpdateMessage(msgId, msgTitle, msgText, icon=None):
|
||||||
|
"""Update a message"""
|
||||||
|
sm = SnarlMessage(msg_id=msgId)
|
||||||
|
if icon:
|
||||||
|
sm.icon = icon
|
||||||
|
return sm.update(msgTitle, msgText)
|
||||||
|
|
||||||
|
def snHideMessage(msgId):
|
||||||
|
"""Hide a message"""
|
||||||
|
return SnarlMessage(msg_id=msgId).hide()
|
||||||
|
|
||||||
|
def snSetTimeout(msgId, timeout):
|
||||||
|
"""Update the timeout of a message already shown."""
|
||||||
|
sm = SnarlMessage(msg_id=msgId)
|
||||||
|
return sm.setTimeout(timeout)
|
||||||
|
|
||||||
|
def snIsMessageVisible(msgId):
|
||||||
|
"""Returns True if the message is visible False otherwise."""
|
||||||
|
return SnarlMessage(msg_id=msgId).isVisible()
|
||||||
|
|
||||||
|
def snRegisterConfig(replyWnd, appName, replyMsg):
|
||||||
|
"""Register a config window. See SDK for more info."""
|
||||||
|
global lastRegisteredSnarlMsg
|
||||||
|
sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW,
|
||||||
|
title=appName,
|
||||||
|
msg_id=replyMsg)
|
||||||
|
sm.data = replyWnd
|
||||||
|
SnarlMessage.lastKnownHWnd = replyWnd
|
||||||
|
|
||||||
|
return sm.send(False)
|
||||||
|
|
||||||
|
def snRegisterConfig2(replyWnd, appName, replyMsg, icon):
|
||||||
|
"""Register a config window. See SDK for more info."""
|
||||||
|
global lastRegisteredSnarlMsg
|
||||||
|
sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW_2,
|
||||||
|
title=appName,
|
||||||
|
msg_id=replyMsg,
|
||||||
|
icon=icon)
|
||||||
|
sm.data = replyWnd
|
||||||
|
SnarlMessage.lastKnownHWnd = replyWnd
|
||||||
|
return sm.send(False)
|
||||||
|
|
||||||
|
def snRegisterAlert(appName, classStr) :
|
||||||
|
"""Register an alert for an already registered config. See SDK for more info."""
|
||||||
|
sm = SnarlMessage(msg_type=REGISTER_ALERT,
|
||||||
|
title=appName,
|
||||||
|
text=classStr)
|
||||||
|
return sm.send(False)
|
||||||
|
|
||||||
|
def snRevokeConfig(replyWnd):
|
||||||
|
"""Revoke a config window"""
|
||||||
|
sm = SnarlMessage(msg_type=REVOKE_CONFIG_WINDOW)
|
||||||
|
sm.data = replyWnd
|
||||||
|
if replyWnd == SnarlMessage.lastKnownHWnd:
|
||||||
|
SnarlMessage.lastKnownHWnd = 0
|
||||||
|
return sm.send(False)
|
||||||
|
|
||||||
|
def snGetSnarlWindow():
|
||||||
|
"""Returns the hWnd of the snarl window"""
|
||||||
|
return myWin32Funcs.FindWindow(None, "Snarl")
|
||||||
|
|
||||||
|
def snGetAppPath():
|
||||||
|
"""Returns the application path of the currently running snarl window"""
|
||||||
|
app_path = None
|
||||||
|
snarl_handle = snGetSnarlWindow()
|
||||||
|
if snarl_handle != 0:
|
||||||
|
pathwin_handle = myWin32Funcs.FindWindowEx(snarl_handle,
|
||||||
|
0,
|
||||||
|
"static",
|
||||||
|
None)
|
||||||
|
if pathwin_handle != 0:
|
||||||
|
try:
|
||||||
|
result = myWin32Funcs.GetWindowText(pathwin_handle)
|
||||||
|
app_path = result
|
||||||
|
except Win32FuncException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
return app_path
|
||||||
|
|
||||||
|
def snGetIconsPath():
|
||||||
|
"""Returns the path to the icons of the program"""
|
||||||
|
s = snGetAppPath()
|
||||||
|
if s is None:
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return s + "etc\\icons\\"
|
||||||
|
|
||||||
|
def snSendTestMessage(data=None):
|
||||||
|
"""Sends a test message to Snarl. Used to make sure the
|
||||||
|
api is connecting"""
|
||||||
|
param = 0
|
||||||
|
command = 0
|
||||||
|
if data:
|
||||||
|
param = struct.pack("I", data)
|
||||||
|
command = 1
|
||||||
|
myWin32Funcs.SendMessage(snGetSnarlWindow(), WM_SNARLTEST, command, param)
|
|
@ -0,0 +1 @@
|
||||||
|
from plugin import SnarlNotificationsPlugin
|
|
@ -0,0 +1,90 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
'''
|
||||||
|
Events notifications using Snarl
|
||||||
|
|
||||||
|
Fancy events notifications under Windows using Snarl infrastructure.
|
||||||
|
|
||||||
|
:note: plugin is at proof-of-concept state.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 15th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import new
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
#import PySnarl
|
||||||
|
|
||||||
|
from common import gajim
|
||||||
|
from plugins import GajimPlugin
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from common import ged
|
||||||
|
|
||||||
|
class SnarlNotificationsPlugin(GajimPlugin):
|
||||||
|
name = u'Snarl Notifications'
|
||||||
|
short_name = u'snarl_notifications'
|
||||||
|
version = u'0.1'
|
||||||
|
description = u'''Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system.
|
||||||
|
PySnarl bindings are used (http://code.google.com/p/pysnarl/).'''
|
||||||
|
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
|
||||||
|
homepage = u'http://blog.bilinski.it'
|
||||||
|
|
||||||
|
@log_calls('SnarlNotificationsPlugin')
|
||||||
|
def init(self):
|
||||||
|
self.config_dialog = None
|
||||||
|
#self.gui_extension_points = {}
|
||||||
|
#self.config_default_values = {}
|
||||||
|
|
||||||
|
self.events_handlers = {'NewMessage' : (ged.POSTCORE, self.newMessage)}
|
||||||
|
|
||||||
|
@log_calls('SnarlNotificationsPlugin')
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('SnarlNotificationsPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('SnarlNotificationsPlugin')
|
||||||
|
def newMessage(self, args):
|
||||||
|
event_name = "NewMessage"
|
||||||
|
data = args
|
||||||
|
account = data[0]
|
||||||
|
jid = data[1][0]
|
||||||
|
jid_without_resource = gajim.get_jid_without_resource(jid)
|
||||||
|
msg = data[1][1]
|
||||||
|
msg_type = data[1][4]
|
||||||
|
if msg_type == 'chat':
|
||||||
|
nickname = gajim.get_contact_name_from_jid(account,
|
||||||
|
jid_without_resource)
|
||||||
|
elif msg_type == 'pm':
|
||||||
|
nickname = gajim.get_resource_from_jid(jid)
|
||||||
|
|
||||||
|
print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args))
|
||||||
|
print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%(
|
||||||
|
event_name, account, jid, msg, nickname)
|
||||||
|
|
||||||
|
|
||||||
|
#if PySnarl.snGetVersion() != False:
|
||||||
|
#(major, minor) = PySnarl.snGetVersion()
|
||||||
|
#print "Found Snarl version",str(major)+"."+str(minor),"running."
|
||||||
|
#PySnarl.snShowMessage(nickname, msg[:20]+'...')
|
||||||
|
#else:
|
||||||
|
#print "Sorry Snarl does not appear to be running"
|
|
@ -155,6 +155,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
"""
|
"""
|
||||||
self.draw_banner_text()
|
self.draw_banner_text()
|
||||||
self._update_banner_state_image()
|
self._update_banner_state_image()
|
||||||
|
gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
|
||||||
|
self)
|
||||||
|
|
||||||
def draw_banner_text(self):
|
def draw_banner_text(self):
|
||||||
"""
|
"""
|
||||||
|
@ -409,6 +411,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
self.command_hits = []
|
self.command_hits = []
|
||||||
self.last_key_tabs = False
|
self.last_key_tabs = False
|
||||||
|
|
||||||
|
# PluginSystem: adding GUI extension point for ChatControlBase
|
||||||
|
# instance object (also subclasses, eg. ChatControl or GroupchatControl)
|
||||||
|
gajim.plugin_manager.gui_extension_point('chat_control_base', self)
|
||||||
|
|
||||||
def set_speller(self):
|
def set_speller(self):
|
||||||
# now set the one the user selected
|
# now set the one the user selected
|
||||||
per_type = 'contacts'
|
per_type = 'contacts'
|
||||||
|
@ -444,6 +450,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
||||||
i += 1
|
i += 1
|
||||||
menu.show_all()
|
menu.show_all()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
# PluginSystem: removing GUI extension points connected with ChatControlBase
|
||||||
|
# instance object
|
||||||
|
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
|
||||||
|
gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self)
|
||||||
|
|
||||||
def on_msg_textview_populate_popup(self, textview, menu):
|
def on_msg_textview_populate_popup(self, textview, menu):
|
||||||
"""
|
"""
|
||||||
Override the default context menu and we prepend an option to switch
|
Override the default context menu and we prepend an option to switch
|
||||||
|
@ -1585,6 +1597,10 @@ class ChatControl(ChatControlBase):
|
||||||
else:
|
else:
|
||||||
img.hide()
|
img.hide()
|
||||||
|
|
||||||
|
# PluginSystem: adding GUI extension point for this ChatControl
|
||||||
|
# instance object
|
||||||
|
gajim.plugin_manager.gui_extension_point('chat_control', self)
|
||||||
|
|
||||||
def _update_jingle(self, jingle_type):
|
def _update_jingle(self, jingle_type):
|
||||||
if jingle_type not in ('audio', 'video'):
|
if jingle_type not in ('audio', 'video'):
|
||||||
return
|
return
|
||||||
|
@ -2455,7 +2471,13 @@ class ChatControl(ChatControlBase):
|
||||||
self.reset_kbd_mouse_timeout_vars()
|
self.reset_kbd_mouse_timeout_vars()
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
# Send 'gone' chatstate
|
# PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove
|
||||||
|
# it's GUI extension points
|
||||||
|
super(ChatControl, self).shutdown()
|
||||||
|
# PluginSystem: removing GUI extension points connected with ChatControl
|
||||||
|
# instance object
|
||||||
|
gajim.plugin_manager.remove_gui_extension_point('chat_control', self) # Send 'gone' chatstate
|
||||||
|
|
||||||
self.send_chatstate('gone', self.contact)
|
self.send_chatstate('gone', self.contact)
|
||||||
self.contact.chatstate = None
|
self.contact.chatstate = None
|
||||||
self.contact.our_chatstate = None
|
self.contact.our_chatstate = None
|
||||||
|
|
|
@ -268,6 +268,8 @@ def check_and_possibly_create_paths():
|
||||||
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
||||||
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
||||||
|
|
||||||
|
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
|
||||||
|
|
||||||
if not os.path.exists(MY_DATA):
|
if not os.path.exists(MY_DATA):
|
||||||
create_path(MY_DATA)
|
create_path(MY_DATA)
|
||||||
elif os.path.isfile(MY_DATA):
|
elif os.path.isfile(MY_DATA):
|
||||||
|
@ -333,6 +335,13 @@ def check_and_possibly_create_paths():
|
||||||
print _('Gajim will now exit')
|
print _('Gajim will now exit')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
if not os.path.exists(PLUGINS_CONFIG_PATH):
|
||||||
|
create_path(PLUGINS_CONFIG_PATH)
|
||||||
|
elif os.path.isfile(PLUGINS_CONFIG_PATH):
|
||||||
|
print _('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH
|
||||||
|
print _('Gajim will now exit')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
def create_path(directory):
|
def create_path(directory):
|
||||||
head, tail = os.path.split(directory)
|
head, tail = os.path.split(directory)
|
||||||
if not os.path.exists(head):
|
if not os.path.exists(head):
|
||||||
|
|
|
@ -455,6 +455,9 @@ class Config:
|
||||||
'roster': [opt_str, '', _("'yes', 'no' or ''")],
|
'roster': [opt_str, '', _("'yes', 'no' or ''")],
|
||||||
'urgency_hint': [opt_bool, False],
|
'urgency_hint': [opt_bool, False],
|
||||||
}, {}),
|
}, {}),
|
||||||
|
'plugins': ({
|
||||||
|
'active': [opt_bool, False, _('State whether plugins should be activated on exit (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')],
|
||||||
|
},{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
statusmsg_default = {
|
statusmsg_default = {
|
||||||
|
|
|
@ -140,7 +140,8 @@ class ConfigPaths:
|
||||||
|
|
||||||
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
||||||
'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
|
'MY_EMOTS': u'emoticons', 'MY_ICONSETS': u'iconsets',
|
||||||
'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities'}
|
'MY_MOOD_ICONSETS': u'moods', 'MY_ACTIVITY_ICONSETS': u'activities',
|
||||||
|
'PLUGINS_USER': u'plugins'}
|
||||||
for name in d:
|
for name in d:
|
||||||
self.add(name, TYPE_DATA, windowsify(d[name]))
|
self.add(name, TYPE_DATA, windowsify(d[name]))
|
||||||
|
|
||||||
|
@ -155,6 +156,8 @@ class ConfigPaths:
|
||||||
self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
|
self.add('DATA', None, os.path.join(basedir, windowsify(u'data')))
|
||||||
self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons')))
|
self.add('ICONS', None, os.path.join(basedir, windowsify(u'icons')))
|
||||||
self.add('HOME', None, fse(os.path.expanduser('~')))
|
self.add('HOME', None, fse(os.path.expanduser('~')))
|
||||||
|
self.add('PLUGINS_BASE', None, os.path.join(basedir,
|
||||||
|
windowsify(u'plugins')))
|
||||||
try:
|
try:
|
||||||
self.add('TMP', None, fse(tempfile.gettempdir()))
|
self.add('TMP', None, fse(tempfile.gettempdir()))
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
|
@ -172,14 +175,17 @@ class ConfigPaths:
|
||||||
conffile = windowsify(u'config')
|
conffile = windowsify(u'config')
|
||||||
pidfile = windowsify(u'gajim')
|
pidfile = windowsify(u'gajim')
|
||||||
secretsfile = windowsify(u'secrets')
|
secretsfile = windowsify(u'secrets')
|
||||||
|
pluginsconfdir = windowsify(u'pluginsconfig')
|
||||||
|
|
||||||
if len(profile) > 0:
|
if len(profile) > 0:
|
||||||
conffile += u'.' + profile
|
conffile += u'.' + profile
|
||||||
pidfile += u'.' + profile
|
pidfile += u'.' + profile
|
||||||
secretsfile += u'.' + profile
|
secretsfile += u'.' + profile
|
||||||
|
pluginsconfdir += u'.' + profile
|
||||||
pidfile += u'.pid'
|
pidfile += u'.pid'
|
||||||
self.add('CONFIG_FILE', TYPE_CONFIG, conffile)
|
self.add('CONFIG_FILE', TYPE_CONFIG, conffile)
|
||||||
self.add('PID_FILE', TYPE_CACHE, pidfile)
|
self.add('PID_FILE', TYPE_CACHE, pidfile)
|
||||||
self.add('SECRETS_FILE', TYPE_DATA, secretsfile)
|
self.add('SECRETS_FILE', TYPE_DATA, secretsfile)
|
||||||
|
self.add('PLUGINS_CONFIG_DIR', TYPE_CONFIG, pluginsconfdir)
|
||||||
|
|
||||||
gajimpaths = ConfigPaths()
|
gajimpaths = ConfigPaths()
|
||||||
|
|
|
@ -41,6 +41,7 @@ from calendar import timegm
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import common.xmpp
|
import common.xmpp
|
||||||
|
import common.caps_cache as capscache
|
||||||
|
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from common import gajim
|
from common import gajim
|
||||||
|
@ -50,7 +51,10 @@ from common.pubsub import ConnectionPubSub
|
||||||
from common.pep import ConnectionPEP
|
from common.pep import ConnectionPEP
|
||||||
from common.protocol.caps import ConnectionCaps
|
from common.protocol.caps import ConnectionCaps
|
||||||
from common.protocol.bytestream import ConnectionSocks5Bytestream
|
from common.protocol.bytestream import ConnectionSocks5Bytestream
|
||||||
import common.caps_cache as capscache
|
from common import ged
|
||||||
|
from common import nec
|
||||||
|
from common.nec import NetworkEvent
|
||||||
|
from plugins import GajimPlugin
|
||||||
if gajim.HAVE_FARSIGHT:
|
if gajim.HAVE_FARSIGHT:
|
||||||
from common.jingle import ConnectionJingle
|
from common.jingle import ConnectionJingle
|
||||||
else:
|
else:
|
||||||
|
@ -552,6 +556,9 @@ class ConnectionVcard:
|
||||||
def _IqCB(self, con, iq_obj):
|
def _IqCB(self, con, iq_obj):
|
||||||
id_ = iq_obj.getID()
|
id_ = iq_obj.getID()
|
||||||
|
|
||||||
|
gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received',
|
||||||
|
conn=con, xmpp_iq=iq_obj))
|
||||||
|
|
||||||
# Check if we were waiting a timeout for this id
|
# Check if we were waiting a timeout for this id
|
||||||
found_tim = None
|
found_tim = None
|
||||||
for tim in self.awaiting_timeouts:
|
for tim in self.awaiting_timeouts:
|
||||||
|
@ -808,33 +815,16 @@ class ConnectionHandlersBase:
|
||||||
|
|
||||||
def _ErrorCB(self, con, iq_obj):
|
def _ErrorCB(self, con, iq_obj):
|
||||||
log.debug('ErrorCB')
|
log.debug('ErrorCB')
|
||||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
|
||||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from)
|
|
||||||
id_ = unicode(iq_obj.getID())
|
id_ = unicode(iq_obj.getID())
|
||||||
if id_ in self.last_ids:
|
if id_ in self.last_ids:
|
||||||
self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, ''))
|
gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
|
||||||
self.last_ids.remove(id_)
|
conn=self, iq_obj=iq_obj))
|
||||||
return
|
return True
|
||||||
|
|
||||||
def _LastResultCB(self, con, iq_obj):
|
def _LastResultCB(self, con, iq_obj):
|
||||||
log.debug('LastResultCB')
|
log.debug('LastResultCB')
|
||||||
qp = iq_obj.getTag('query')
|
gajim.nec.push_incoming_event(LastResultReceivedEvent(None, conn=self,
|
||||||
seconds = qp.getAttr('seconds')
|
iq_obj=iq_obj))
|
||||||
status = qp.getData()
|
|
||||||
try:
|
|
||||||
seconds = int(seconds)
|
|
||||||
except Exception:
|
|
||||||
return
|
|
||||||
id_ = iq_obj.getID()
|
|
||||||
if id_ in self.groupchat_jids:
|
|
||||||
who = self.groupchat_jids[id_]
|
|
||||||
del self.groupchat_jids[id_]
|
|
||||||
else:
|
|
||||||
who = helpers.get_full_jid_from_iq(iq_obj)
|
|
||||||
if id_ in self.last_ids:
|
|
||||||
self.last_ids.remove(id_)
|
|
||||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
|
||||||
self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, seconds, status))
|
|
||||||
|
|
||||||
def get_sessions(self, jid):
|
def get_sessions(self, jid):
|
||||||
"""
|
"""
|
||||||
|
@ -1004,6 +994,9 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
self.gmail_last_tid = None
|
self.gmail_last_tid = None
|
||||||
self.gmail_last_time = None
|
self.gmail_last_time = None
|
||||||
|
|
||||||
|
gajim.ged.register_event_handler('http-auth-received', ged.CORE,
|
||||||
|
self._nec_http_auth_received)
|
||||||
|
|
||||||
def build_http_auth_answer(self, iq_obj, answer):
|
def build_http_auth_answer(self, iq_obj, answer):
|
||||||
if not self.connection or self.connected < 2:
|
if not self.connection or self.connected < 2:
|
||||||
return
|
return
|
||||||
|
@ -1018,33 +1011,33 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
common.xmpp.protocol.ERR_NOT_AUTHORIZED)
|
common.xmpp.protocol.ERR_NOT_AUTHORIZED)
|
||||||
self.connection.send(err)
|
self.connection.send(err)
|
||||||
|
|
||||||
|
def _nec_http_auth_received(self, obj):
|
||||||
|
if obj.conn.name != self.name:
|
||||||
|
return
|
||||||
|
if obj.opt in ('yes', 'no'):
|
||||||
|
obj.conn.build_http_auth_answer(obj.iq_obj, obj.opt)
|
||||||
|
return True
|
||||||
|
|
||||||
def _HttpAuthCB(self, con, iq_obj):
|
def _HttpAuthCB(self, con, iq_obj):
|
||||||
log.debug('HttpAuthCB')
|
log.debug('HttpAuthCB')
|
||||||
opt = gajim.config.get_per('accounts', self.name, 'http_auth')
|
gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None, conn=self,
|
||||||
if opt in ('yes', 'no'):
|
iq_obj=iq_obj))
|
||||||
self.build_http_auth_answer(iq_obj, opt)
|
|
||||||
else:
|
|
||||||
id_ = iq_obj.getTagAttr('confirm', 'id')
|
|
||||||
method = iq_obj.getTagAttr('confirm', 'method')
|
|
||||||
url = iq_obj.getTagAttr('confirm', 'url')
|
|
||||||
msg = iq_obj.getTagData('body') # In case it's a message with a body
|
|
||||||
self.dispatch('HTTP_AUTH', (method, url, id_, iq_obj, msg))
|
|
||||||
raise common.xmpp.NodeProcessed
|
raise common.xmpp.NodeProcessed
|
||||||
|
|
||||||
def _ErrorCB(self, con, iq_obj):
|
def _ErrorCB(self, con, iq_obj):
|
||||||
log.debug('ErrorCB')
|
log.debug('ErrorCB')
|
||||||
ConnectionHandlersBase._ErrorCB(self, con, iq_obj)
|
if ConnectionHandlersBase._ErrorCB(self, con, iq_obj):
|
||||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
return
|
||||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from)
|
|
||||||
id_ = unicode(iq_obj.getID())
|
id_ = unicode(iq_obj.getID())
|
||||||
if id_ in self.version_ids:
|
if id_ in self.version_ids:
|
||||||
self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
|
gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
|
||||||
self.version_ids.remove(id_)
|
conn=self, iq_obj=iq_obj))
|
||||||
return
|
return
|
||||||
if id_ in self.entity_time_ids:
|
if id_ in self.entity_time_ids:
|
||||||
self.dispatch('ENTITY_TIME', (jid_stripped, resource, ''))
|
gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
|
||||||
self.entity_time_ids.remove(id_)
|
conn=self, iq_obj=iq_obj))
|
||||||
return
|
return
|
||||||
|
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||||
errmsg = iq_obj.getErrorMsg()
|
errmsg = iq_obj.getErrorMsg()
|
||||||
errcode = iq_obj.getErrorCode()
|
errcode = iq_obj.getErrorCode()
|
||||||
self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
|
self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
|
||||||
|
@ -1210,25 +1203,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
|
|
||||||
def _VersionResultCB(self, con, iq_obj):
|
def _VersionResultCB(self, con, iq_obj):
|
||||||
log.debug('VersionResultCB')
|
log.debug('VersionResultCB')
|
||||||
client_info = ''
|
gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
|
||||||
os_info = ''
|
conn=self, iq_obj=iq_obj))
|
||||||
qp = iq_obj.getTag('query')
|
|
||||||
if qp.getTag('name'):
|
|
||||||
client_info += qp.getTag('name').getData()
|
|
||||||
if qp.getTag('version'):
|
|
||||||
client_info += ' ' + qp.getTag('version').getData()
|
|
||||||
if qp.getTag('os'):
|
|
||||||
os_info += qp.getTag('os').getData()
|
|
||||||
id_ = iq_obj.getID()
|
|
||||||
if id_ in self.groupchat_jids:
|
|
||||||
who = self.groupchat_jids[id_]
|
|
||||||
del self.groupchat_jids[id_]
|
|
||||||
else:
|
|
||||||
who = helpers.get_full_jid_from_iq(iq_obj)
|
|
||||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
|
||||||
if id_ in self.version_ids:
|
|
||||||
self.version_ids.remove(id_)
|
|
||||||
self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info))
|
|
||||||
|
|
||||||
def _TimeCB(self, con, iq_obj):
|
def _TimeCB(self, con, iq_obj):
|
||||||
log.debug('TimeCB')
|
log.debug('TimeCB')
|
||||||
|
@ -1260,50 +1236,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
|
|
||||||
def _TimeRevisedResultCB(self, con, iq_obj):
|
def _TimeRevisedResultCB(self, con, iq_obj):
|
||||||
log.debug('TimeRevisedResultCB')
|
log.debug('TimeRevisedResultCB')
|
||||||
time_info = ''
|
gajim.nec.push_incoming_event(TimeResultReceivedEvent(None,
|
||||||
qp = iq_obj.getTag('time')
|
conn=self, iq_obj=iq_obj))
|
||||||
if not qp:
|
|
||||||
# wrong answer
|
|
||||||
return
|
|
||||||
tzo = qp.getTag('tzo').getData()
|
|
||||||
if tzo.lower() == 'z':
|
|
||||||
tzo = '0:0'
|
|
||||||
tzoh, tzom = tzo.split(':')
|
|
||||||
utc_time = qp.getTag('utc').getData()
|
|
||||||
ZERO = datetime.timedelta(0)
|
|
||||||
class UTC(datetime.tzinfo):
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return ZERO
|
|
||||||
def tzname(self, dt):
|
|
||||||
return "UTC"
|
|
||||||
def dst(self, dt):
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
class contact_tz(datetime.tzinfo):
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
|
|
||||||
def tzname(self, dt):
|
|
||||||
return "remote timezone"
|
|
||||||
def dst(self, dt):
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
try:
|
|
||||||
t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
t = t.replace(tzinfo=UTC())
|
|
||||||
time_info = t.astimezone(contact_tz()).strftime('%c')
|
|
||||||
except ValueError, e:
|
|
||||||
log.info('Wrong time format: %s' % str(e))
|
|
||||||
|
|
||||||
id_ = iq_obj.getID()
|
|
||||||
if id_ in self.groupchat_jids:
|
|
||||||
who = self.groupchat_jids[id_]
|
|
||||||
del self.groupchat_jids[id_]
|
|
||||||
else:
|
|
||||||
who = helpers.get_full_jid_from_iq(iq_obj)
|
|
||||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
|
||||||
if id_ in self.entity_time_ids:
|
|
||||||
self.entity_time_ids.remove(id_)
|
|
||||||
self.dispatch('ENTITY_TIME', (jid_stripped, resource, time_info))
|
|
||||||
|
|
||||||
def _gMailNewMailCB(self, con, gm):
|
def _gMailNewMailCB(self, con, gm):
|
||||||
"""
|
"""
|
||||||
|
@ -1329,54 +1263,15 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
self.connection.send(iq)
|
self.connection.send(iq)
|
||||||
raise common.xmpp.NodeProcessed
|
raise common.xmpp.NodeProcessed
|
||||||
|
|
||||||
def _gMailQueryCB(self, con, gm):
|
def _gMailQueryCB(self, con, iq_obj):
|
||||||
"""
|
"""
|
||||||
Called when we receive results from Querying the server for mail messages
|
Called when we receive results from Querying the server for mail messages
|
||||||
in gmail account
|
in gmail account
|
||||||
"""
|
"""
|
||||||
if not gm.getTag('mailbox'):
|
log.debug('gMailQueryCB')
|
||||||
return
|
gajim.nec.push_incoming_event(GMailQueryReceivedEvent(None,
|
||||||
self.gmail_url = gm.getTag('mailbox').getAttr('url')
|
conn=self, iq_obj=iq_obj))
|
||||||
if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
|
raise common.xmpp.NodeProcessed
|
||||||
newmsgs = gm.getTag('mailbox').getAttr('total-matched')
|
|
||||||
if newmsgs != '0':
|
|
||||||
# there are new messages
|
|
||||||
gmail_messages_list = []
|
|
||||||
if gm.getTag('mailbox').getTag('mail-thread-info'):
|
|
||||||
gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
|
|
||||||
for gmessage in gmail_messages:
|
|
||||||
unread_senders = []
|
|
||||||
for sender in gmessage.getTag('senders').getTags('sender'):
|
|
||||||
if sender.getAttr('unread') != '1':
|
|
||||||
continue
|
|
||||||
if sender.getAttr('name'):
|
|
||||||
unread_senders.append(sender.getAttr('name') + '< ' + \
|
|
||||||
sender.getAttr('address') + '>')
|
|
||||||
else:
|
|
||||||
unread_senders.append(sender.getAttr('address'))
|
|
||||||
|
|
||||||
if not unread_senders:
|
|
||||||
continue
|
|
||||||
gmail_subject = gmessage.getTag('subject').getData()
|
|
||||||
gmail_snippet = gmessage.getTag('snippet').getData()
|
|
||||||
tid = int(gmessage.getAttr('tid'))
|
|
||||||
if not self.gmail_last_tid or tid > self.gmail_last_tid:
|
|
||||||
self.gmail_last_tid = tid
|
|
||||||
gmail_messages_list.append({ \
|
|
||||||
'From': unread_senders, \
|
|
||||||
'Subject': gmail_subject, \
|
|
||||||
'Snippet': gmail_snippet, \
|
|
||||||
'url': gmessage.getAttr('url'), \
|
|
||||||
'participation': gmessage.getAttr('participation'), \
|
|
||||||
'messages': gmessage.getAttr('messages'), \
|
|
||||||
'date': gmessage.getAttr('date')})
|
|
||||||
self.gmail_last_time = int(gm.getTag('mailbox').getAttr(
|
|
||||||
'result-time'))
|
|
||||||
|
|
||||||
jid = gajim.get_jid_from_account(self.name)
|
|
||||||
log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
|
|
||||||
self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
|
|
||||||
raise common.xmpp.NodeProcessed
|
|
||||||
|
|
||||||
def _rosterItemExchangeCB(self, con, msg):
|
def _rosterItemExchangeCB(self, con, msg):
|
||||||
"""
|
"""
|
||||||
|
@ -1427,6 +1322,10 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
Called when we receive a message
|
Called when we receive a message
|
||||||
"""
|
"""
|
||||||
log.debug('MessageCB')
|
log.debug('MessageCB')
|
||||||
|
|
||||||
|
gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
|
||||||
|
conn=con, xmpp_msg=msg, account=self.name))
|
||||||
|
|
||||||
mtype = msg.getType()
|
mtype = msg.getType()
|
||||||
|
|
||||||
# check if the message is a roster item exchange (XEP-0144)
|
# check if the message is a roster item exchange (XEP-0144)
|
||||||
|
@ -1782,6 +1681,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
"""
|
"""
|
||||||
Called when we receive a presence
|
Called when we receive a presence
|
||||||
"""
|
"""
|
||||||
|
gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
|
||||||
|
conn=con, xmpp_pres=prs))
|
||||||
ptype = prs.getType()
|
ptype = prs.getType()
|
||||||
if ptype == 'available':
|
if ptype == 'available':
|
||||||
ptype = None
|
ptype = None
|
||||||
|
@ -2449,3 +2350,213 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
||||||
con.RegisterHandler('presence', self._StanzaArrivedCB)
|
con.RegisterHandler('presence', self._StanzaArrivedCB)
|
||||||
con.RegisterHandler('message', self._StanzaArrivedCB)
|
con.RegisterHandler('message', self._StanzaArrivedCB)
|
||||||
con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
|
con.RegisterHandler('unknown', self._StreamCB, 'urn:ietf:params:xml:ns:xmpp-streams', xmlns='http://etherx.jabber.org/streams')
|
||||||
|
|
||||||
|
class HelperEvent:
|
||||||
|
def get_jid_resource(self):
|
||||||
|
if self.id_ in self.conn.groupchat_jids:
|
||||||
|
who = self.conn.groupchat_jids[self.id_]
|
||||||
|
del self.conn.groupchat_jids[self.id_]
|
||||||
|
else:
|
||||||
|
who = helpers.get_full_jid_from_iq(self.iq_obj)
|
||||||
|
self.jid, self.resource = gajim.get_room_and_nick_from_fjid(who)
|
||||||
|
|
||||||
|
def get_id(self):
|
||||||
|
self.id_ = self.iq_obj.getID()
|
||||||
|
|
||||||
|
class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'http-auth-received'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
if not self.conn:
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
if not self.iq_obj:
|
||||||
|
self.iq_obj = self.base_event.xmpp_iq
|
||||||
|
|
||||||
|
self.opt = gajim.config.get_per('accounts', self.conn.name, 'http_auth')
|
||||||
|
self.iq_id = self.iq_obj.getTagAttr('confirm', 'id')
|
||||||
|
self.method = self.iq_obj.getTagAttr('confirm', 'method')
|
||||||
|
self.url = self.iq_obj.getTagAttr('confirm', 'url')
|
||||||
|
# In case it's a message with a body
|
||||||
|
self.msg = self.iq_obj.getTagData('body')
|
||||||
|
return True
|
||||||
|
|
||||||
|
class LastResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
|
name = 'last-result-received'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
if not self.conn:
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
if not self.iq_obj:
|
||||||
|
self.iq_obj = self.base_event.xmpp_iq
|
||||||
|
|
||||||
|
self.get_id()
|
||||||
|
self.get_jid_resource()
|
||||||
|
if self.id_ in self.conn.last_ids:
|
||||||
|
self.conn.last_ids.remove(self.id_)
|
||||||
|
|
||||||
|
self.status = ''
|
||||||
|
self.seconds = -1
|
||||||
|
|
||||||
|
if self.iq_obj.getType() == 'error':
|
||||||
|
return True
|
||||||
|
|
||||||
|
qp = self.iq_obj.getTag('query')
|
||||||
|
sec = qp.getAttr('seconds')
|
||||||
|
self.status = qp.getData()
|
||||||
|
try:
|
||||||
|
self.seconds = int(sec)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
class VersionResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
|
name = 'version-result-received'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
if not self.conn:
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
if not self.iq_obj:
|
||||||
|
self.iq_obj = self.base_event.xmpp_iq
|
||||||
|
|
||||||
|
self.get_id()
|
||||||
|
self.get_jid_resource()
|
||||||
|
if self.id_ in self.conn.version_ids:
|
||||||
|
self.conn.version_ids.remove(self.id_)
|
||||||
|
|
||||||
|
self.client_info = ''
|
||||||
|
self.os_info = ''
|
||||||
|
|
||||||
|
if self.iq_obj.getType() == 'error':
|
||||||
|
return True
|
||||||
|
|
||||||
|
qp = self.iq_obj.getTag('query')
|
||||||
|
if qp.getTag('name'):
|
||||||
|
self.client_info += qp.getTag('name').getData()
|
||||||
|
if qp.getTag('version'):
|
||||||
|
self.client_info += ' ' + qp.getTag('version').getData()
|
||||||
|
if qp.getTag('os'):
|
||||||
|
self.os_info += qp.getTag('os').getData()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
class TimeResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
|
||||||
|
name = 'version-result-received'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
if not self.conn:
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
if not self.iq_obj:
|
||||||
|
self.iq_obj = self.base_event.xmpp_iq
|
||||||
|
|
||||||
|
self.get_id()
|
||||||
|
self.get_jid_resource()
|
||||||
|
if self.id_ in self.conn.entity_time_ids:
|
||||||
|
self.conn.entity_time_ids.remove(self.id_)
|
||||||
|
|
||||||
|
self.time_info = ''
|
||||||
|
|
||||||
|
if self.iq_obj.getType() == 'error':
|
||||||
|
return True
|
||||||
|
|
||||||
|
qp = self.iq_obj.getTag('time')
|
||||||
|
if not qp:
|
||||||
|
# wrong answer
|
||||||
|
return
|
||||||
|
tzo = qp.getTag('tzo').getData()
|
||||||
|
if tzo.lower() == 'z':
|
||||||
|
tzo = '0:0'
|
||||||
|
tzoh, tzom = tzo.split(':')
|
||||||
|
utc_time = qp.getTag('utc').getData()
|
||||||
|
ZERO = datetime.timedelta(0)
|
||||||
|
class UTC(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return ZERO
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "UTC"
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
class contact_tz(datetime.tzinfo):
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return datetime.timedelta(hours=int(tzoh), minutes=int(tzom))
|
||||||
|
def tzname(self, dt):
|
||||||
|
return "remote timezone"
|
||||||
|
def dst(self, dt):
|
||||||
|
return ZERO
|
||||||
|
|
||||||
|
try:
|
||||||
|
t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
t = t.replace(tzinfo=UTC())
|
||||||
|
self.time_info = t.astimezone(contact_tz()).strftime('%c')
|
||||||
|
except ValueError, e:
|
||||||
|
log.info('Wrong time format: %s' % str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
class GMailQueryReceivedEvent(nec.NetworkIncomingEvent):
|
||||||
|
name = 'gmail-notify'
|
||||||
|
base_network_events = []
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
if not self.conn:
|
||||||
|
self.conn = self.base_event.conn
|
||||||
|
if not self.iq_obj:
|
||||||
|
self.iq_obj = self.base_event.xmpp_iq
|
||||||
|
|
||||||
|
if not self.iq_obj.getTag('mailbox'):
|
||||||
|
return
|
||||||
|
mb = self.iq_obj.getTag('mailbox')
|
||||||
|
if not mb.getAttr('url'):
|
||||||
|
return
|
||||||
|
self.conn.gmail_url = mb.getAttr('url')
|
||||||
|
if mb.getNamespace() != common.xmpp.NS_GMAILNOTIFY:
|
||||||
|
return
|
||||||
|
self.newmsgs = mb.getAttr('total-matched')
|
||||||
|
if not self.newmsgs:
|
||||||
|
return
|
||||||
|
if self.newmsgs == '0':
|
||||||
|
return
|
||||||
|
# there are new messages
|
||||||
|
self.gmail_messages_list = []
|
||||||
|
if mb.getTag('mail-thread-info'):
|
||||||
|
gmail_messages = mb.getTags('mail-thread-info')
|
||||||
|
for gmessage in gmail_messages:
|
||||||
|
unread_senders = []
|
||||||
|
for sender in gmessage.getTag('senders').getTags(
|
||||||
|
'sender'):
|
||||||
|
if sender.getAttr('unread') != '1':
|
||||||
|
continue
|
||||||
|
if sender.getAttr('name'):
|
||||||
|
unread_senders.append(sender.getAttr('name') + \
|
||||||
|
'< ' + sender.getAttr('address') + '>')
|
||||||
|
else:
|
||||||
|
unread_senders.append(sender.getAttr('address'))
|
||||||
|
|
||||||
|
if not unread_senders:
|
||||||
|
continue
|
||||||
|
gmail_subject = gmessage.getTag('subject').getData()
|
||||||
|
gmail_snippet = gmessage.getTag('snippet').getData()
|
||||||
|
tid = int(gmessage.getAttr('tid'))
|
||||||
|
if not self.conn.gmail_last_tid or \
|
||||||
|
tid > self.conn.gmail_last_tid:
|
||||||
|
self.conn.gmail_last_tid = tid
|
||||||
|
self.gmail_messages_list.append({
|
||||||
|
'From': unread_senders,
|
||||||
|
'Subject': gmail_subject,
|
||||||
|
'Snippet': gmail_snippet,
|
||||||
|
'url': gmessage.getAttr('url'),
|
||||||
|
'participation': gmessage.getAttr('participation'),
|
||||||
|
'messages': gmessage.getAttr('messages'),
|
||||||
|
'date': gmessage.getAttr('date')})
|
||||||
|
self.conn.gmail_last_time = int(mb.getAttr('result-time'))
|
||||||
|
|
||||||
|
self.jid = gajim.get_jid_from_account(self.name)
|
||||||
|
log.debug(('You have %s new gmail e-mails on %s.') % (self.newmsgs,
|
||||||
|
self.jid))
|
||||||
|
return True
|
|
@ -68,6 +68,8 @@ connections = {} # 'account name': 'account (connection.Connection) instance'
|
||||||
ipython_window = None
|
ipython_window = None
|
||||||
|
|
||||||
ged = None # Global Events Dispatcher
|
ged = None # Global Events Dispatcher
|
||||||
|
nec = None # Network Events Controller
|
||||||
|
plugin_manager = None # Plugins Manager
|
||||||
|
|
||||||
log = logging.getLogger('gajim')
|
log = logging.getLogger('gajim')
|
||||||
|
|
||||||
|
@ -88,6 +90,9 @@ TMP = gajimpaths['TMP']
|
||||||
DATA_DIR = gajimpaths['DATA']
|
DATA_DIR = gajimpaths['DATA']
|
||||||
ICONS_DIR = gajimpaths['ICONS']
|
ICONS_DIR = gajimpaths['ICONS']
|
||||||
HOME_DIR = gajimpaths['HOME']
|
HOME_DIR = gajimpaths['HOME']
|
||||||
|
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
|
||||||
|
gajimpaths['PLUGINS_USER']]
|
||||||
|
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
||||||
|
|
|
@ -30,6 +30,9 @@ log = logging.getLogger('gajim.common.ged')
|
||||||
PRECORE = 30
|
PRECORE = 30
|
||||||
CORE = 40
|
CORE = 40
|
||||||
POSTCORE = 50
|
POSTCORE = 50
|
||||||
|
GUI1 = 60
|
||||||
|
GUI2 = 70
|
||||||
|
POSTGUI = 80
|
||||||
|
|
||||||
class GlobalEventsDispatcher(object):
|
class GlobalEventsDispatcher(object):
|
||||||
|
|
||||||
|
@ -61,4 +64,5 @@ class GlobalEventsDispatcher(object):
|
||||||
log.debug('%s\nArgs: %s'%(event_name, str(args)))
|
log.debug('%s\nArgs: %s'%(event_name, str(args)))
|
||||||
if event_name in self.handlers:
|
if event_name in self.handlers:
|
||||||
for priority, handler in self.handlers[event_name]:
|
for priority, handler in self.handlers[event_name]:
|
||||||
handler(*args, **kwargs)
|
if handler(*args, **kwargs):
|
||||||
|
return
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Network Events Controller.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 10th August 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
#from plugins.helpers import log
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
class NetworkEventsController(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.incoming_events_generators = {}
|
||||||
|
'''
|
||||||
|
Keys: names of events
|
||||||
|
Values: list of class objects that are subclasses
|
||||||
|
of `NetworkIncomingEvent`
|
||||||
|
'''
|
||||||
|
|
||||||
|
def register_incoming_event(self, event_class):
|
||||||
|
for base_event_name in event_class.base_network_events:
|
||||||
|
event_list = self.incoming_events_generators.setdefault(base_event_name, [])
|
||||||
|
if not event_class in event_list:
|
||||||
|
event_list.append(event_class)
|
||||||
|
|
||||||
|
def unregister_incoming_event(self, event_class):
|
||||||
|
for base_event_name in event_class.base_network_events:
|
||||||
|
if base_event_name in self.incoming_events_generators:
|
||||||
|
self.incoming_events_generators[base_event_name].remove(event_class)
|
||||||
|
|
||||||
|
def register_outgoing_event(self, event_class):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unregister_outgoing_event(self, event_class):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def push_incoming_event(self, event_object):
|
||||||
|
if event_object.generate():
|
||||||
|
if self._generate_events_based_on_incoming_event(event_object):
|
||||||
|
gajim.ged.raise_event(event_object.name, event_object)
|
||||||
|
|
||||||
|
def push_outgoing_event(self, event_object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _generate_events_based_on_incoming_event(self, event_object):
|
||||||
|
'''
|
||||||
|
:return: True if even_object should be dispatched through Global
|
||||||
|
Events Dispatcher, False otherwise. This can be used to replace
|
||||||
|
base events with those that more data computed (easier to use
|
||||||
|
by handlers).
|
||||||
|
:note: replacing mechanism is not implemented currently, but will be
|
||||||
|
based on attribute in new network events object.
|
||||||
|
'''
|
||||||
|
base_event_name = event_object.name
|
||||||
|
if base_event_name in self.incoming_events_generators:
|
||||||
|
for new_event_class in self.incoming_events_generators[base_event_name]:
|
||||||
|
new_event_object = new_event_class(None, base_event=event_object)
|
||||||
|
if new_event_object.generate():
|
||||||
|
if self._generate_events_based_on_incoming_event(new_event_object):
|
||||||
|
gajim.ged.raise_event(new_event_object.name, new_event_object)
|
||||||
|
return True
|
||||||
|
|
||||||
|
class NetworkEvent(object):
|
||||||
|
name = ''
|
||||||
|
|
||||||
|
def __init__(self, new_name, **kwargs):
|
||||||
|
if new_name:
|
||||||
|
self.name = new_name
|
||||||
|
|
||||||
|
self._set_kwargs_as_attributes(**kwargs)
|
||||||
|
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
'''
|
||||||
|
Generates new event (sets it's attributes) based on event object.
|
||||||
|
|
||||||
|
Base event object name is one of those in `base_network_events`.
|
||||||
|
|
||||||
|
Reference to base event object is stored in `self.base_event` attribute.
|
||||||
|
|
||||||
|
Note that this is a reference, so modifications to that event object
|
||||||
|
are possible before dispatching to Global Events Dispatcher.
|
||||||
|
|
||||||
|
:return: True if generated event should be dispatched, False otherwise.
|
||||||
|
'''
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _set_kwargs_as_attributes(self, **kwargs):
|
||||||
|
for k, v in kwargs.iteritems():
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<NetworkEvent object> Attributes: %s'%(pformat(self.__dict__))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<NetworkEvent object> Attributes: %s'%(pformat(self.__dict__))
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkIncomingEvent(NetworkEvent):
|
||||||
|
base_network_events = []
|
||||||
|
'''
|
||||||
|
Names of base network events that new event is going to be generated on.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkOutgoingEvent(NetworkEvent):
|
||||||
|
pass
|
|
@ -0,0 +1,548 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
##
|
||||||
|
## Copyright (C) 2005-2006 Yann Leboulanger <asterix@lagaule.org>
|
||||||
|
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||||
|
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||||
|
##
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
# gajim-remote help will show you the D-BUS API of Gajim
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import locale
|
||||||
|
import signal
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
|
||||||
|
|
||||||
|
from common import exceptions
|
||||||
|
from common import i18n
|
||||||
|
|
||||||
|
try:
|
||||||
|
PREFERRED_ENCODING = locale.getpreferredencoding()
|
||||||
|
except:
|
||||||
|
PREFERRED_ENCODING = 'UTF-8'
|
||||||
|
|
||||||
|
def send_error(error_message):
|
||||||
|
'''Writes error message to stderr and exits'''
|
||||||
|
print >> sys.stderr, error_message.encode(PREFERRED_ENCODING)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
import osx.dbus
|
||||||
|
osx.dbus.load(False)
|
||||||
|
import dbus
|
||||||
|
import dbus.service
|
||||||
|
import dbus.glib
|
||||||
|
except:
|
||||||
|
print str(exceptions.DbusNotSupported())
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject'
|
||||||
|
INTERFACE = 'org.gajim.dbusplugin.RemoteInterface'
|
||||||
|
SERVICE = 'org.gajim.dbusplugin'
|
||||||
|
BASENAME = 'gajim-remote-plugin'
|
||||||
|
|
||||||
|
|
||||||
|
class GajimRemote:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.argv_len = len(sys.argv)
|
||||||
|
# define commands dict. Prototype :
|
||||||
|
# {
|
||||||
|
# 'command': [comment, [list of arguments] ]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# each argument is defined as a tuple:
|
||||||
|
# (argument name, help on argument, is mandatory)
|
||||||
|
#
|
||||||
|
self.commands = {
|
||||||
|
'help': [
|
||||||
|
_('Shows a help on specific command'),
|
||||||
|
[
|
||||||
|
#User gets help for the command, specified by this parameter
|
||||||
|
(_('command'),
|
||||||
|
_('show help on command'), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'toggle_roster_appearance': [
|
||||||
|
_('Shows or hides the roster window'),
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
'show_next_pending_event': [
|
||||||
|
_('Pops up a window with the next pending event'),
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
'list_contacts': [
|
||||||
|
_('Prints a list of all contacts in the roster. Each contact '
|
||||||
|
'appears on a separate line'),
|
||||||
|
[
|
||||||
|
(_('account'), _('show only contacts of the given account'),
|
||||||
|
False)
|
||||||
|
]
|
||||||
|
|
||||||
|
],
|
||||||
|
'list_accounts': [
|
||||||
|
_('Prints a list of registered accounts'),
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
'change_status': [
|
||||||
|
_('Changes the status of account or accounts'),
|
||||||
|
[
|
||||||
|
#offline, online, chat, away, xa, dnd, invisible should not be translated
|
||||||
|
(_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True),
|
||||||
|
(_('message'), _('status message'), False),
|
||||||
|
(_('account'), _('change status of account "account". '
|
||||||
|
'If not specified, try to change status of all accounts that have '
|
||||||
|
'"sync with global status" option set'), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'open_chat': [
|
||||||
|
_('Shows the chat dialog so that you can send messages to a contact'),
|
||||||
|
[
|
||||||
|
('jid', _('JID of the contact that you want to chat with'),
|
||||||
|
True),
|
||||||
|
(_('account'), _('if specified, contact is taken from the '
|
||||||
|
'contact list of this account'), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'send_chat_message': [
|
||||||
|
_('Sends new chat message to a contact in the roster. Both OpenPGP key '
|
||||||
|
'and account are optional. If you want to set only \'account\', '
|
||||||
|
'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
|
||||||
|
[
|
||||||
|
('jid', _('JID of the contact that will receive the message'), True),
|
||||||
|
(_('message'), _('message contents'), True),
|
||||||
|
(_('pgp key'), _('if specified, the message will be encrypted '
|
||||||
|
'using this public key'), False),
|
||||||
|
(_('account'), _('if specified, the message will be sent '
|
||||||
|
'using this account'), False),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'send_single_message': [
|
||||||
|
_('Sends new single message to a contact in the roster. Both OpenPGP key '
|
||||||
|
'and account are optional. If you want to set only \'account\', '
|
||||||
|
'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
|
||||||
|
[
|
||||||
|
('jid', _('JID of the contact that will receive the message'), True),
|
||||||
|
(_('subject'), _('message subject'), True),
|
||||||
|
(_('message'), _('message contents'), True),
|
||||||
|
(_('pgp key'), _('if specified, the message will be encrypted '
|
||||||
|
'using this public key'), False),
|
||||||
|
(_('account'), _('if specified, the message will be sent '
|
||||||
|
'using this account'), False),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'send_groupchat_message': [
|
||||||
|
_('Sends new message to a groupchat you\'ve joined.'),
|
||||||
|
[
|
||||||
|
('room_jid', _('JID of the room that will receive the message'), True),
|
||||||
|
(_('message'), _('message contents'), True),
|
||||||
|
(_('account'), _('if specified, the message will be sent '
|
||||||
|
'using this account'), False),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'contact_info': [
|
||||||
|
_('Gets detailed info on a contact'),
|
||||||
|
[
|
||||||
|
('jid', _('JID of the contact'), True)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'account_info': [
|
||||||
|
_('Gets detailed info on a account'),
|
||||||
|
[
|
||||||
|
('account', _('Name of the account'), True)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'send_file': [
|
||||||
|
_('Sends file to a contact'),
|
||||||
|
[
|
||||||
|
(_('file'), _('File path'), True),
|
||||||
|
('jid', _('JID of the contact'), True),
|
||||||
|
(_('account'), _('if specified, file will be sent using this '
|
||||||
|
'account'), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'prefs_list': [
|
||||||
|
_('Lists all preferences and their values'),
|
||||||
|
[ ]
|
||||||
|
],
|
||||||
|
'prefs_put': [
|
||||||
|
_('Sets value of \'key\' to \'value\'.'),
|
||||||
|
[
|
||||||
|
(_('key=value'), _('\'key\' is the name of the preference, '
|
||||||
|
'\'value\' is the value to set it to'), True)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'prefs_del': [
|
||||||
|
_('Deletes a preference item'),
|
||||||
|
[
|
||||||
|
(_('key'), _('name of the preference to be deleted'), True)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'prefs_store': [
|
||||||
|
_('Writes the current state of Gajim preferences to the .config '
|
||||||
|
'file'),
|
||||||
|
[ ]
|
||||||
|
],
|
||||||
|
'remove_contact': [
|
||||||
|
_('Removes contact from roster'),
|
||||||
|
[
|
||||||
|
('jid', _('JID of the contact'), True),
|
||||||
|
(_('account'), _('if specified, contact is taken from the '
|
||||||
|
'contact list of this account'), False)
|
||||||
|
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'add_contact': [
|
||||||
|
_('Adds contact to roster'),
|
||||||
|
[
|
||||||
|
(_('jid'), _('JID of the contact'), True),
|
||||||
|
(_('account'), _('Adds new contact to this account'), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
'get_status': [
|
||||||
|
_('Returns current status (the global one unless account is specified)'),
|
||||||
|
[
|
||||||
|
(_('account'), _(''), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
'get_status_message': [
|
||||||
|
_('Returns current status message(the global one unless account is specified)'),
|
||||||
|
[
|
||||||
|
(_('account'), _(''), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
'get_unread_msgs_number': [
|
||||||
|
_('Returns number of unread messages'),
|
||||||
|
[ ]
|
||||||
|
],
|
||||||
|
'start_chat': [
|
||||||
|
_('Opens \'Start Chat\' dialog'),
|
||||||
|
[
|
||||||
|
(_('account'), _('Starts chat, using this account'), True)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'send_xml': [
|
||||||
|
_('Sends custom XML'),
|
||||||
|
[
|
||||||
|
('xml', _('XML to send'), True),
|
||||||
|
('account', _('Account in which the xml will be sent; '
|
||||||
|
'if not specified, xml will be sent to all accounts'),
|
||||||
|
False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'handle_uri': [
|
||||||
|
_('Handle a xmpp:/ uri'),
|
||||||
|
[
|
||||||
|
(_('uri'), _(''), True),
|
||||||
|
(_('account'), _(''), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'join_room': [
|
||||||
|
_('Join a MUC room'),
|
||||||
|
[
|
||||||
|
(_('room'), _(''), True),
|
||||||
|
(_('nick'), _(''), False),
|
||||||
|
(_('password'), _(''), False),
|
||||||
|
(_('account'), _(''), False)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'check_gajim_running': [
|
||||||
|
_('Check if Gajim is running'),
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
'toggle_ipython': [
|
||||||
|
_('Shows or hides the ipython window'),
|
||||||
|
[]
|
||||||
|
],
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
path = os.getcwd()
|
||||||
|
if '.svn' in os.listdir(path) or '_svn' in os.listdir(path):
|
||||||
|
# command only for svn
|
||||||
|
self.commands['toggle_ipython'] = [
|
||||||
|
_('Shows or hides the ipython window'),
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
self.sbus = None
|
||||||
|
if self.argv_len < 2 or sys.argv[1] not in self.commands.keys():
|
||||||
|
# no args or bad args
|
||||||
|
send_error(self.compose_help())
|
||||||
|
self.command = sys.argv[1]
|
||||||
|
if self.command == 'help':
|
||||||
|
if self.argv_len == 3:
|
||||||
|
print self.help_on_command(sys.argv[2]).encode(PREFERRED_ENCODING)
|
||||||
|
else:
|
||||||
|
print self.compose_help().encode(PREFERRED_ENCODING)
|
||||||
|
sys.exit(0)
|
||||||
|
if self.command == 'handle_uri':
|
||||||
|
self.handle_uri()
|
||||||
|
if self.command == 'check_gajim_running':
|
||||||
|
print self.check_gajim_running()
|
||||||
|
sys.exit(0)
|
||||||
|
self.init_connection()
|
||||||
|
self.check_arguments()
|
||||||
|
|
||||||
|
if self.command == 'contact_info':
|
||||||
|
if self.argv_len < 3:
|
||||||
|
send_error(_('Missing argument "contact_jid"'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.call_remote_method()
|
||||||
|
except exceptions.ServiceNotAvailable:
|
||||||
|
# At this point an error message has already been displayed
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
self.print_result(res)
|
||||||
|
|
||||||
|
def print_result(self, res):
|
||||||
|
''' Print retrieved result to the output '''
|
||||||
|
if res is not None:
|
||||||
|
if self.command in ('open_chat', 'send_chat_message', 'send_single_message', 'start_chat'):
|
||||||
|
if self.command in ('send_message', 'send_single_message'):
|
||||||
|
self.argv_len -= 2
|
||||||
|
|
||||||
|
if res is False:
|
||||||
|
if self.argv_len < 4:
|
||||||
|
send_error(_('\'%s\' is not in your roster.\n'
|
||||||
|
'Please specify account for sending the message.') % sys.argv[2])
|
||||||
|
else:
|
||||||
|
send_error(_('You have no active account'))
|
||||||
|
elif self.command == 'list_accounts':
|
||||||
|
if isinstance(res, list):
|
||||||
|
for account in res:
|
||||||
|
if isinstance(account, unicode):
|
||||||
|
print account.encode(PREFERRED_ENCODING)
|
||||||
|
else:
|
||||||
|
print account
|
||||||
|
elif self.command == 'account_info':
|
||||||
|
if res:
|
||||||
|
print self.print_info(0, res, True)
|
||||||
|
elif self.command == 'list_contacts':
|
||||||
|
for account_dict in res:
|
||||||
|
print self.print_info(0, account_dict, True)
|
||||||
|
elif self.command == 'prefs_list':
|
||||||
|
pref_keys = res.keys()
|
||||||
|
pref_keys.sort()
|
||||||
|
for pref_key in pref_keys:
|
||||||
|
result = '%s = %s' % (pref_key, res[pref_key])
|
||||||
|
if isinstance(result, unicode):
|
||||||
|
print result.encode(PREFERRED_ENCODING)
|
||||||
|
else:
|
||||||
|
print result
|
||||||
|
elif self.command == 'contact_info':
|
||||||
|
print self.print_info(0, res, True)
|
||||||
|
elif res:
|
||||||
|
print unicode(res).encode(PREFERRED_ENCODING)
|
||||||
|
|
||||||
|
def check_gajim_running(self):
|
||||||
|
if not self.sbus:
|
||||||
|
try:
|
||||||
|
self.sbus = dbus.SessionBus()
|
||||||
|
except:
|
||||||
|
raise exceptions.SessionBusNotPresent
|
||||||
|
|
||||||
|
test = False
|
||||||
|
if hasattr(self.sbus, 'name_has_owner'):
|
||||||
|
if self.sbus.name_has_owner(SERVICE):
|
||||||
|
test = True
|
||||||
|
elif dbus.dbus_bindings.bus_name_has_owner(self.sbus.get_connection(),
|
||||||
|
SERVICE):
|
||||||
|
test = True
|
||||||
|
return test
|
||||||
|
|
||||||
|
def init_connection(self):
|
||||||
|
''' create the onnection to the session dbus,
|
||||||
|
or exit if it is not possible '''
|
||||||
|
try:
|
||||||
|
self.sbus = dbus.SessionBus()
|
||||||
|
except:
|
||||||
|
raise exceptions.SessionBusNotPresent
|
||||||
|
|
||||||
|
from pprint import pprint
|
||||||
|
pprint(list(self.sbus.list_names()))
|
||||||
|
if not self.check_gajim_running():
|
||||||
|
send_error(_('It seems Gajim is not running. So you can\'t use gajim-remote.'))
|
||||||
|
obj = self.sbus.get_object(SERVICE, OBJ_PATH)
|
||||||
|
interface = dbus.Interface(obj, INTERFACE)
|
||||||
|
|
||||||
|
# get the function asked
|
||||||
|
self.method = interface.__getattr__(self.command)
|
||||||
|
|
||||||
|
def make_arguments_row(self, args):
|
||||||
|
''' return arguments list. Mandatory arguments are enclosed with:
|
||||||
|
'<', '>', optional arguments - with '[', ']' '''
|
||||||
|
str = ''
|
||||||
|
for argument in args:
|
||||||
|
str += ' '
|
||||||
|
if argument[2]:
|
||||||
|
str += '<'
|
||||||
|
else:
|
||||||
|
str += '['
|
||||||
|
str += argument[0]
|
||||||
|
if argument[2]:
|
||||||
|
str += '>'
|
||||||
|
else:
|
||||||
|
str += ']'
|
||||||
|
return str
|
||||||
|
|
||||||
|
def help_on_command(self, command):
|
||||||
|
''' return help message for a given command '''
|
||||||
|
if command in self.commands:
|
||||||
|
command_props = self.commands[command]
|
||||||
|
arguments_str = self.make_arguments_row(command_props[1])
|
||||||
|
str = _('Usage: %s %s %s \n\t %s') % (BASENAME, command,
|
||||||
|
arguments_str, command_props[0])
|
||||||
|
if len(command_props[1]) > 0:
|
||||||
|
str += '\n\n' + _('Arguments:') + '\n'
|
||||||
|
for argument in command_props[1]:
|
||||||
|
str += ' ' + argument[0] + ' - ' + argument[1] + '\n'
|
||||||
|
return str
|
||||||
|
send_error(_('%s not found') % command)
|
||||||
|
|
||||||
|
def compose_help(self):
|
||||||
|
''' print usage, and list available commands '''
|
||||||
|
str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
|
||||||
|
commands = self.commands.keys()
|
||||||
|
commands.sort()
|
||||||
|
for command in commands:
|
||||||
|
str += ' ' + command
|
||||||
|
for argument in self.commands[command][1]:
|
||||||
|
str += ' '
|
||||||
|
if argument[2]:
|
||||||
|
str += '<'
|
||||||
|
else:
|
||||||
|
str += '['
|
||||||
|
str += argument[0]
|
||||||
|
if argument[2]:
|
||||||
|
str += '>'
|
||||||
|
else:
|
||||||
|
str += ']'
|
||||||
|
str += '\n'
|
||||||
|
return str
|
||||||
|
|
||||||
|
def print_info(self, level, prop_dict, encode_return = False):
|
||||||
|
''' return formated string from data structure '''
|
||||||
|
if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
|
||||||
|
return ''
|
||||||
|
ret_str = ''
|
||||||
|
if isinstance(prop_dict, (list, tuple)):
|
||||||
|
ret_str = ''
|
||||||
|
spacing = ' ' * level * 4
|
||||||
|
for val in prop_dict:
|
||||||
|
if val is None:
|
||||||
|
ret_str +='\t'
|
||||||
|
elif isinstance(val, int):
|
||||||
|
ret_str +='\t' + str(val)
|
||||||
|
elif isinstance(val, (str, unicode)):
|
||||||
|
ret_str +='\t' + val
|
||||||
|
elif isinstance(val, (list, tuple)):
|
||||||
|
res = ''
|
||||||
|
for items in val:
|
||||||
|
res += self.print_info(level+1, items)
|
||||||
|
if res != '':
|
||||||
|
ret_str += '\t' + res
|
||||||
|
elif isinstance(val, dict):
|
||||||
|
ret_str += self.print_info(level+1, val)
|
||||||
|
ret_str = '%s(%s)\n' % (spacing, ret_str[1:])
|
||||||
|
elif isinstance(prop_dict, dict):
|
||||||
|
for key in prop_dict.keys():
|
||||||
|
val = prop_dict[key]
|
||||||
|
spacing = ' ' * level * 4
|
||||||
|
if isinstance(val, (unicode, int, str)):
|
||||||
|
if val is not None:
|
||||||
|
val = val.strip()
|
||||||
|
ret_str += '%s%-10s: %s\n' % (spacing, key, val)
|
||||||
|
elif isinstance(val, (list, tuple)):
|
||||||
|
res = ''
|
||||||
|
for items in val:
|
||||||
|
res += self.print_info(level+1, items)
|
||||||
|
if res != '':
|
||||||
|
ret_str += '%s%s: \n%s' % (spacing, key, res)
|
||||||
|
elif isinstance(val, dict):
|
||||||
|
res = self.print_info(level+1, val)
|
||||||
|
if res != '':
|
||||||
|
ret_str += '%s%s: \n%s' % (spacing, key, res)
|
||||||
|
if (encode_return):
|
||||||
|
try:
|
||||||
|
ret_str = ret_str.encode(PREFERRED_ENCODING)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return ret_str
|
||||||
|
|
||||||
|
def check_arguments(self):
|
||||||
|
''' Make check if all necessary arguments are given '''
|
||||||
|
argv_len = self.argv_len - 2
|
||||||
|
args = self.commands[self.command][1]
|
||||||
|
if len(args) < argv_len:
|
||||||
|
send_error(_('Too many arguments. \n'
|
||||||
|
'Type "%s help %s" for more info') % (BASENAME, self.command))
|
||||||
|
if len(args) > argv_len:
|
||||||
|
if args[argv_len][2]:
|
||||||
|
send_error(_('Argument "%s" is not specified. \n'
|
||||||
|
'Type "%s help %s" for more info') %
|
||||||
|
(args[argv_len][0], BASENAME, self.command))
|
||||||
|
self.arguments = []
|
||||||
|
i = 0
|
||||||
|
for arg in sys.argv[2:]:
|
||||||
|
i += 1
|
||||||
|
if i < len(args):
|
||||||
|
self.arguments.append(arg)
|
||||||
|
else:
|
||||||
|
# it's latest argument with spaces
|
||||||
|
self.arguments.append(' '.join(sys.argv[i+1:]))
|
||||||
|
break
|
||||||
|
# add empty string for missing args
|
||||||
|
self.arguments += ['']*(len(args)-i)
|
||||||
|
|
||||||
|
def handle_uri(self):
|
||||||
|
if not sys.argv[2:][0].startswith('xmpp:'):
|
||||||
|
send_error(_('Wrong uri'))
|
||||||
|
sys.argv[2] = sys.argv[2][5:]
|
||||||
|
uri = sys.argv[2:][0]
|
||||||
|
if not '?' in uri:
|
||||||
|
self.command = sys.argv[1] = 'open_chat'
|
||||||
|
return
|
||||||
|
(jid, action) = uri.split('?', 1)
|
||||||
|
sys.argv[2] = jid
|
||||||
|
if action == 'join':
|
||||||
|
self.command = sys.argv[1] = 'join_room'
|
||||||
|
# Move account parameter from position 3 to 5
|
||||||
|
sys.argv.append('')
|
||||||
|
sys.argv.append(sys.argv[3])
|
||||||
|
sys.argv[3] = ''
|
||||||
|
return
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def call_remote_method(self):
|
||||||
|
''' calls self.method with arguments from sys.argv[2:] '''
|
||||||
|
args = [i.decode(PREFERRED_ENCODING) for i in self.arguments]
|
||||||
|
args = [dbus.String(i) for i in args]
|
||||||
|
try:
|
||||||
|
res = self.method(*args)
|
||||||
|
return res
|
||||||
|
except Exception:
|
||||||
|
raise exceptions.ServiceNotAvailable
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
GajimRemote()
|
|
@ -58,7 +58,7 @@ except Exception:
|
||||||
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
|
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
|
||||||
INTERFACE = 'org.gajim.dbus.RemoteInterface'
|
INTERFACE = 'org.gajim.dbus.RemoteInterface'
|
||||||
SERVICE = 'org.gajim.dbus'
|
SERVICE = 'org.gajim.dbus'
|
||||||
BASENAME = 'gajim-remote'
|
BASENAME = 'gajim-remote-plugin'
|
||||||
|
|
||||||
class GajimRemote:
|
class GajimRemote:
|
||||||
|
|
||||||
|
|
|
@ -1669,6 +1669,10 @@ class GroupchatControl(ChatControlBase):
|
||||||
del win._controls[self.account][self.contact.jid]
|
del win._controls[self.account][self.contact.jid]
|
||||||
|
|
||||||
def shutdown(self, status='offline'):
|
def shutdown(self, status='offline'):
|
||||||
|
# PluginSystem: calling shutdown of super class (ChatControlBase)
|
||||||
|
# to let it remove it's GUI extension points
|
||||||
|
super(GroupchatControl, self).shutdown()
|
||||||
|
|
||||||
# Preventing autorejoin from being activated
|
# Preventing autorejoin from being activated
|
||||||
self.autorejoin = False
|
self.autorejoin = False
|
||||||
|
|
||||||
|
|
|
@ -148,23 +148,24 @@ class Interface:
|
||||||
self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
|
self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
|
||||||
account, room_jid, title, prompt)
|
account, room_jid, title, prompt)
|
||||||
|
|
||||||
def handle_event_http_auth(self, account, data):
|
def handle_event_http_auth(self, obj):
|
||||||
#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
|
#('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
|
||||||
def response(account, iq_obj, answer):
|
def response(account, answer):
|
||||||
gajim.connections[account].build_http_auth_answer(iq_obj, answer)
|
obj.conn.build_http_auth_answer(obj.iq_obj, answer)
|
||||||
|
|
||||||
def on_yes(is_checked, account, iq_obj):
|
def on_yes(is_checked, obj):
|
||||||
response(account, iq_obj, 'yes')
|
response(obj, 'yes')
|
||||||
|
|
||||||
|
account = obj.conn.name
|
||||||
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
|
||||||
if data[4]:
|
if obj.msg:
|
||||||
sec_msg = data[4] + '\n' + sec_msg
|
sec_msg = obj.msg + '\n' + sec_msg
|
||||||
dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
|
dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
|
||||||
'%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
|
'%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url,
|
||||||
'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
|
'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
|
||||||
on_response_no=(response, account, data[3], 'no'))
|
on_response_no=(response, obj, '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))
|
||||||
|
@ -825,57 +826,24 @@ class Interface:
|
||||||
if self.remote_ctrl:
|
if self.remote_ctrl:
|
||||||
self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
|
self.remote_ctrl.raise_signal('VcardInfo', (account, vcard))
|
||||||
|
|
||||||
def handle_event_last_status_time(self, account, array):
|
def handle_event_last_status_time(self, obj):
|
||||||
# ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
|
# ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
|
||||||
tim = array[2]
|
if obj.seconds < 0:
|
||||||
if tim < 0:
|
|
||||||
# Ann error occured
|
# Ann error occured
|
||||||
return
|
return
|
||||||
win = None
|
account = obj.conn.name
|
||||||
if array[0] in self.instances[account]['infos']:
|
c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
|
||||||
win = self.instances[account]['infos'][array[0]]
|
|
||||||
elif array[0] + '/' + array[1] in self.instances[account]['infos']:
|
|
||||||
win = self.instances[account]['infos'][array[0] + '/' + array[1]]
|
|
||||||
c = gajim.contacts.get_contact(account, array[0], array[1])
|
|
||||||
if c: # c can be none if it's a gc contact
|
if c: # c can be none if it's a gc contact
|
||||||
if array[3]:
|
if obj.status:
|
||||||
c.status = array[3]
|
c.status = obj.status
|
||||||
self.roster.draw_contact(c.jid, account) # draw offline status
|
self.roster.draw_contact(c.jid, account) # draw offline status
|
||||||
last_time = time.localtime(time.time() - tim)
|
last_time = time.localtime(time.time() - obj.seconds)
|
||||||
if c.show == 'offline':
|
if c.show == 'offline':
|
||||||
c.last_status_time = last_time
|
c.last_status_time = last_time
|
||||||
else:
|
else:
|
||||||
c.last_activity_time = last_time
|
c.last_activity_time = last_time
|
||||||
if win:
|
if self.roster.tooltip.id and self.roster.tooltip.win:
|
||||||
win.set_last_status_time()
|
self.roster.tooltip.update_last_time(last_time)
|
||||||
if self.roster.tooltip.id and self.roster.tooltip.win:
|
|
||||||
self.roster.tooltip.update_last_time(last_time)
|
|
||||||
if self.remote_ctrl:
|
|
||||||
self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
|
|
||||||
|
|
||||||
def handle_event_os_info(self, account, array):
|
|
||||||
#'OS_INFO' (account, (jid, resource, client_info, os_info))
|
|
||||||
win = None
|
|
||||||
if array[0] in self.instances[account]['infos']:
|
|
||||||
win = self.instances[account]['infos'][array[0]]
|
|
||||||
elif array[0] + '/' + array[1] in self.instances[account]['infos']:
|
|
||||||
win = self.instances[account]['infos'][array[0] + '/' + array[1]]
|
|
||||||
if win:
|
|
||||||
win.set_os_info(array[1], array[2], array[3])
|
|
||||||
if self.remote_ctrl:
|
|
||||||
self.remote_ctrl.raise_signal('OsInfo', (account, array))
|
|
||||||
|
|
||||||
def handle_event_entity_time(self, account, array):
|
|
||||||
#'ENTITY_TIME' (account, (jid, resource, time_info))
|
|
||||||
win = None
|
|
||||||
if array[0] in self.instances[account]['infos']:
|
|
||||||
win = self.instances[account]['infos'][array[0]]
|
|
||||||
elif array[0] + '/' + array[1] in self.instances[account]['infos']:
|
|
||||||
win = self.instances[account]['infos'][array[0] + '/' + array[1]]
|
|
||||||
if win:
|
|
||||||
win.set_entity_time(array[1], array[2])
|
|
||||||
if self.remote_ctrl:
|
|
||||||
self.remote_ctrl.raise_signal('EntityTime', (account, array))
|
|
||||||
|
|
||||||
def handle_event_gc_notify(self, account, array):
|
def handle_event_gc_notify(self, account, array):
|
||||||
#'GC_NOTIFY' (account, (room_jid, show, status, nick,
|
#'GC_NOTIFY' (account, (room_jid, show, status, nick,
|
||||||
|
@ -1321,45 +1289,42 @@ class Interface:
|
||||||
notify.popup(event_type, jid, account, 'file-send-error', path,
|
notify.popup(event_type, jid, account, 'file-send-error', path,
|
||||||
event_type, file_props['name'])
|
event_type, file_props['name'])
|
||||||
|
|
||||||
def handle_event_gmail_notify(self, account, array):
|
def handle_event_gmail_notify(self, obj):
|
||||||
jid = array[0]
|
jid = obj.jid
|
||||||
gmail_new_messages = int(array[1])
|
gmail_new_messages = int(obj.newmsgs)
|
||||||
gmail_messages_list = array[2]
|
gmail_messages_list = obj.gmail_messages_list
|
||||||
if gajim.config.get('notify_on_new_gmail_email'):
|
if not gajim.config.get('notify_on_new_gmail_email'):
|
||||||
path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
|
return
|
||||||
title = _('New mail on %(gmail_mail_address)s') % \
|
path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
|
||||||
{'gmail_mail_address': jid}
|
title = _('New mail on %(gmail_mail_address)s') % \
|
||||||
text = i18n.ngettext('You have %d new mail conversation',
|
{'gmail_mail_address': jid}
|
||||||
'You have %d new mail conversations', gmail_new_messages,
|
text = i18n.ngettext('You have %d new mail conversation',
|
||||||
gmail_new_messages, gmail_new_messages)
|
'You have %d new mail conversations', gmail_new_messages,
|
||||||
|
gmail_new_messages, gmail_new_messages)
|
||||||
|
|
||||||
if gajim.config.get('notify_on_new_gmail_email_extra'):
|
if gajim.config.get('notify_on_new_gmail_email_extra'):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for gmessage in gmail_messages_list:
|
for gmessage in gmail_messages_list:
|
||||||
# FIXME: emulate Gtalk client popups. find out what they
|
# FIXME: emulate Gtalk client popups. find out what they
|
||||||
# parse and how they decide what to show each message has a
|
# parse and how they decide what to show each message has a
|
||||||
# 'From', 'Subject' and 'Snippet' field
|
# 'From', 'Subject' and 'Snippet' field
|
||||||
if cnt >= 5:
|
if cnt >= 5:
|
||||||
break
|
break
|
||||||
senders = ',\n '.join(reversed(gmessage['From']))
|
senders = ',\n '.join(reversed(gmessage['From']))
|
||||||
text += _('\n\nFrom: %(from_address)s\nSubject: '
|
text += _('\n\nFrom: %(from_address)s\nSubject: '
|
||||||
'%(subject)s\n%(snippet)s') % \
|
'%(subject)s\n%(snippet)s') % {'from_address': senders,
|
||||||
{'from_address': senders,
|
'subject': gmessage['Subject'],
|
||||||
'subject': gmessage['Subject'],
|
'snippet': gmessage['Snippet']}
|
||||||
'snippet': gmessage['Snippet']}
|
cnt += 1
|
||||||
cnt += 1
|
|
||||||
|
|
||||||
command = gajim.config.get('notify_on_new_gmail_email_command')
|
command = gajim.config.get('notify_on_new_gmail_email_command')
|
||||||
if command:
|
if command:
|
||||||
Popen(command, shell=True)
|
Popen(command, shell=True)
|
||||||
|
|
||||||
if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
|
if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
|
||||||
helpers.play_sound('gmail_received')
|
helpers.play_sound('gmail_received')
|
||||||
notify.popup(_('New E-mail'), jid, account, 'gmail',
|
notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
|
||||||
path_to_image=path, title=title, text=text)
|
path_to_image=path, title=title, text=text)
|
||||||
|
|
||||||
if self.remote_ctrl:
|
|
||||||
self.remote_ctrl.raise_signal('NewGmail', (account, array))
|
|
||||||
|
|
||||||
def handle_event_file_request_error(self, account, array):
|
def handle_event_file_request_error(self, account, array):
|
||||||
# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
|
# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
|
||||||
|
@ -2122,9 +2087,6 @@ class Interface:
|
||||||
'ACC_OK': [self.handle_event_acc_ok],
|
'ACC_OK': [self.handle_event_acc_ok],
|
||||||
'MYVCARD': [self.handle_event_myvcard],
|
'MYVCARD': [self.handle_event_myvcard],
|
||||||
'VCARD': [self.handle_event_vcard],
|
'VCARD': [self.handle_event_vcard],
|
||||||
'LAST_STATUS_TIME': [self.handle_event_last_status_time],
|
|
||||||
'OS_INFO': [self.handle_event_os_info],
|
|
||||||
'ENTITY_TIME': [self.handle_event_entity_time],
|
|
||||||
'GC_NOTIFY': [self.handle_event_gc_notify],
|
'GC_NOTIFY': [self.handle_event_gc_notify],
|
||||||
'GC_MSG': [self.handle_event_gc_msg],
|
'GC_MSG': [self.handle_event_gc_msg],
|
||||||
'GC_SUBJECT': [self.handle_event_gc_subject],
|
'GC_SUBJECT': [self.handle_event_gc_subject],
|
||||||
|
@ -2140,12 +2102,10 @@ class Interface:
|
||||||
'CON_TYPE': [self.handle_event_con_type],
|
'CON_TYPE': [self.handle_event_con_type],
|
||||||
'CONNECTION_LOST': [self.handle_event_connection_lost],
|
'CONNECTION_LOST': [self.handle_event_connection_lost],
|
||||||
'FILE_REQUEST': [self.handle_event_file_request],
|
'FILE_REQUEST': [self.handle_event_file_request],
|
||||||
'GMAIL_NOTIFY': [self.handle_event_gmail_notify],
|
|
||||||
'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
|
'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
|
||||||
'FILE_SEND_ERROR': [self.handle_event_file_send_error],
|
'FILE_SEND_ERROR': [self.handle_event_file_send_error],
|
||||||
'STANZA_ARRIVED': [self.handle_event_stanza_arrived],
|
'STANZA_ARRIVED': [self.handle_event_stanza_arrived],
|
||||||
'STANZA_SENT': [self.handle_event_stanza_sent],
|
'STANZA_SENT': [self.handle_event_stanza_sent],
|
||||||
'HTTP_AUTH': [self.handle_event_http_auth],
|
|
||||||
'VCARD_PUBLISHED': [self.handle_event_vcard_published],
|
'VCARD_PUBLISHED': [self.handle_event_vcard_published],
|
||||||
'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published],
|
'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published],
|
||||||
'ASK_NEW_NICK': [self.handle_event_ask_new_nick],
|
'ASK_NEW_NICK': [self.handle_event_ask_new_nick],
|
||||||
|
@ -2186,7 +2146,10 @@ class Interface:
|
||||||
'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
|
'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
|
||||||
'JINGLE_ERROR': [self.handle_event_jingle_error],
|
'JINGLE_ERROR': [self.handle_event_jingle_error],
|
||||||
'PEP_RECEIVED': [self.handle_event_pep_received],
|
'PEP_RECEIVED': [self.handle_event_pep_received],
|
||||||
'CAPS_RECEIVED': [self.handle_event_caps_received]
|
'CAPS_RECEIVED': [self.handle_event_caps_received],
|
||||||
|
'gmail-notify': [self.handle_event_gmail_notify],
|
||||||
|
'http-auth-received': [self.handle_event_http_auth],
|
||||||
|
'last-result-received': [self.handle_event_last_status_time],
|
||||||
}
|
}
|
||||||
|
|
||||||
def register_core_handlers(self):
|
def register_core_handlers(self):
|
||||||
|
@ -2197,7 +2160,7 @@ class Interface:
|
||||||
"""
|
"""
|
||||||
for event_name, event_handlers in self.handlers.iteritems():
|
for event_name, event_handlers in self.handlers.iteritems():
|
||||||
for event_handler in event_handlers:
|
for event_handler in event_handlers:
|
||||||
gajim.ged.register_event_handler(event_name, ged.CORE,
|
gajim.ged.register_event_handler(event_name, ged.GUI1,
|
||||||
event_handler)
|
event_handler)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -3246,6 +3209,10 @@ class Interface:
|
||||||
self.show_systray()
|
self.show_systray()
|
||||||
|
|
||||||
self.roster = roster_window.RosterWindow()
|
self.roster = roster_window.RosterWindow()
|
||||||
|
# Creating plugin manager
|
||||||
|
import plugins
|
||||||
|
gajim.plugin_manager = plugins.PluginManager()
|
||||||
|
|
||||||
self.roster._before_fill()
|
self.roster._before_fill()
|
||||||
for account in gajim.connections:
|
for account in gajim.connections:
|
||||||
gajim.connections[account].load_roster_from_db()
|
gajim.connections[account].load_roster_from_db()
|
||||||
|
@ -3276,7 +3243,6 @@ class Interface:
|
||||||
pass
|
pass
|
||||||
gobject.timeout_add_seconds(5, remote_init)
|
gobject.timeout_add_seconds(5, remote_init)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
gajim.interface = self
|
gajim.interface = self
|
||||||
gajim.thread_interface = ThreadInterface
|
gajim.thread_interface = ThreadInterface
|
||||||
|
@ -3398,6 +3364,9 @@ class Interface:
|
||||||
|
|
||||||
# Creating Global Events Dispatcher
|
# Creating Global Events Dispatcher
|
||||||
gajim.ged = ged.GlobalEventsDispatcher()
|
gajim.ged = ged.GlobalEventsDispatcher()
|
||||||
|
# Creating Network Events Controller
|
||||||
|
from common import nec
|
||||||
|
gajim.nec = nec.NetworkEventsController()
|
||||||
self.create_core_handlers_list()
|
self.create_core_handlers_list()
|
||||||
self.register_core_handlers()
|
self.register_core_handlers()
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ TYPE_PM = 'pm'
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
||||||
class MessageControl:
|
class MessageControl(object):
|
||||||
"""
|
"""
|
||||||
An abstract base widget that can embed in the gtk.Notebook of a
|
An abstract base widget that can embed in the gtk.Notebook of a
|
||||||
MessageWindow
|
MessageWindow
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Main file of plugins package.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 05/30/2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
from pluginmanager import PluginManager
|
||||||
|
from plugin import GajimPlugin
|
||||||
|
|
||||||
|
__all__ = ['PluginManager', 'GajimPlugin']
|
|
@ -0,0 +1,220 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
GUI classes related to plug-in management.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 6th June 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
__all__ = ['PluginsWindow']
|
||||||
|
|
||||||
|
import pango
|
||||||
|
import gtk, gobject
|
||||||
|
|
||||||
|
import gtkgui_helpers
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
|
||||||
|
class PluginsWindow(object):
|
||||||
|
'''Class for Plugins window'''
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def __init__(self):
|
||||||
|
'''Initialize Plugins window'''
|
||||||
|
self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui')
|
||||||
|
self.window = self.xml.get_object('plugins_window')
|
||||||
|
self.window.set_transient_for(gajim.interface.roster.window)
|
||||||
|
|
||||||
|
widgets_to_extract = ('plugins_notebook',
|
||||||
|
'plugin_name_label',
|
||||||
|
'plugin_version_label',
|
||||||
|
'plugin_authors_label',
|
||||||
|
'plugin_homepage_linkbutton',
|
||||||
|
'plugin_description_textview',
|
||||||
|
'uninstall_plugin_button',
|
||||||
|
'configure_plugin_button',
|
||||||
|
'installed_plugins_treeview')
|
||||||
|
|
||||||
|
for widget_name in widgets_to_extract:
|
||||||
|
setattr(self, widget_name, self.xml.get_object(widget_name))
|
||||||
|
|
||||||
|
attr_list = pango.AttrList()
|
||||||
|
attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
|
||||||
|
self.plugin_name_label.set_attributes(attr_list)
|
||||||
|
|
||||||
|
self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT,
|
||||||
|
gobject.TYPE_STRING,
|
||||||
|
gobject.TYPE_BOOLEAN)
|
||||||
|
self.installed_plugins_treeview.set_model(self.installed_plugins_model)
|
||||||
|
|
||||||
|
renderer = gtk.CellRendererText()
|
||||||
|
col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1)
|
||||||
|
self.installed_plugins_treeview.append_column(col)
|
||||||
|
|
||||||
|
renderer = gtk.CellRendererToggle()
|
||||||
|
renderer.set_property('activatable', True)
|
||||||
|
renderer.connect('toggled', self.installed_plugins_toggled_cb)
|
||||||
|
col = gtk.TreeViewColumn(_('Active'), renderer, active=2)
|
||||||
|
self.installed_plugins_treeview.append_column(col)
|
||||||
|
|
||||||
|
# connect signal for selection change
|
||||||
|
selection = self.installed_plugins_treeview.get_selection()
|
||||||
|
selection.connect('changed',
|
||||||
|
self.installed_plugins_treeview_selection_changed)
|
||||||
|
selection.set_mode(gtk.SELECTION_SINGLE)
|
||||||
|
|
||||||
|
self._clear_installed_plugin_info()
|
||||||
|
|
||||||
|
self.fill_installed_plugins_model()
|
||||||
|
|
||||||
|
self.xml.connect_signals(self)
|
||||||
|
|
||||||
|
self.plugins_notebook.set_current_page(0)
|
||||||
|
|
||||||
|
self.window.show_all()
|
||||||
|
gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def installed_plugins_treeview_selection_changed(self, treeview_selection):
|
||||||
|
model, iter = treeview_selection.get_selected()
|
||||||
|
if iter:
|
||||||
|
plugin = model.get_value(iter, 0)
|
||||||
|
plugin_name = model.get_value(iter, 1)
|
||||||
|
is_active = model.get_value(iter, 2)
|
||||||
|
|
||||||
|
self._display_installed_plugin_info(plugin)
|
||||||
|
else:
|
||||||
|
self._clear_installed_plugin_info()
|
||||||
|
|
||||||
|
def _display_installed_plugin_info(self, plugin):
|
||||||
|
self.plugin_name_label.set_text(plugin.name)
|
||||||
|
self.plugin_version_label.set_text(plugin.version)
|
||||||
|
self.plugin_authors_label.set_text(", ".join(plugin.authors))
|
||||||
|
self.plugin_homepage_linkbutton.set_uri(plugin.homepage)
|
||||||
|
self.plugin_homepage_linkbutton.set_label(plugin.homepage)
|
||||||
|
self.plugin_homepage_linkbutton.set_property('sensitive', True)
|
||||||
|
|
||||||
|
desc_textbuffer = self.plugin_description_textview.get_buffer()
|
||||||
|
desc_textbuffer.set_text(plugin.description)
|
||||||
|
self.plugin_description_textview.set_property('sensitive', True)
|
||||||
|
self.uninstall_plugin_button.set_property('sensitive', True)
|
||||||
|
if plugin.config_dialog is None:
|
||||||
|
self.configure_plugin_button.set_property('sensitive', False)
|
||||||
|
else:
|
||||||
|
self.configure_plugin_button.set_property('sensitive', True)
|
||||||
|
|
||||||
|
def _clear_installed_plugin_info(self):
|
||||||
|
self.plugin_name_label.set_text('')
|
||||||
|
self.plugin_version_label.set_text('')
|
||||||
|
self.plugin_authors_label.set_text('')
|
||||||
|
self.plugin_homepage_linkbutton.set_uri('')
|
||||||
|
self.plugin_homepage_linkbutton.set_label('')
|
||||||
|
self.plugin_homepage_linkbutton.set_property('sensitive', False)
|
||||||
|
|
||||||
|
desc_textbuffer = self.plugin_description_textview.get_buffer()
|
||||||
|
desc_textbuffer.set_text('')
|
||||||
|
self.plugin_description_textview.set_property('sensitive', False)
|
||||||
|
self.uninstall_plugin_button.set_property('sensitive', False)
|
||||||
|
self.configure_plugin_button.set_property('sensitive', False)
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def fill_installed_plugins_model(self):
|
||||||
|
pm = gajim.plugin_manager
|
||||||
|
self.installed_plugins_model.clear()
|
||||||
|
self.installed_plugins_model.set_sort_column_id(1, gtk.SORT_ASCENDING)
|
||||||
|
|
||||||
|
for plugin in pm.plugins:
|
||||||
|
self.installed_plugins_model.append([plugin,
|
||||||
|
plugin.name,
|
||||||
|
plugin.active])
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def installed_plugins_toggled_cb(self, cell, path):
|
||||||
|
is_active = self.installed_plugins_model[path][2]
|
||||||
|
plugin = self.installed_plugins_model[path][0]
|
||||||
|
|
||||||
|
if is_active:
|
||||||
|
gajim.plugin_manager.deactivate_plugin(plugin)
|
||||||
|
else:
|
||||||
|
gajim.plugin_manager.activate_plugin(plugin)
|
||||||
|
|
||||||
|
self.installed_plugins_model[path][2] = not is_active
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def on_plugins_window_destroy(self, widget):
|
||||||
|
'''Close window'''
|
||||||
|
del gajim.interface.instances['plugins']
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def on_close_button_clicked(self, widget):
|
||||||
|
self.window.destroy()
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def on_configure_plugin_button_clicked(self, widget):
|
||||||
|
#log.debug('widget: %s'%(widget))
|
||||||
|
selection = self.installed_plugins_treeview.get_selection()
|
||||||
|
model, iter = selection.get_selected()
|
||||||
|
if iter:
|
||||||
|
plugin = model.get_value(iter, 0)
|
||||||
|
plugin_name = model.get_value(iter, 1)
|
||||||
|
is_active = model.get_value(iter, 2)
|
||||||
|
|
||||||
|
|
||||||
|
result = plugin.config_dialog.run(self.window)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# No plugin selected. this should never be reached. As configure
|
||||||
|
# plugin button should only be clickable when plugin is selected.
|
||||||
|
# XXX: maybe throw exception here?
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('PluginsWindow')
|
||||||
|
def on_uninstall_plugin_button_clicked(self, widget):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GajimPluginConfigDialog(gtk.Dialog):
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfigDialog')
|
||||||
|
def __init__(self, plugin, **kwargs):
|
||||||
|
gtk.Dialog.__init__(self, '%s %s'%(plugin.name, _('Configuration')), **kwargs)
|
||||||
|
self.plugin = plugin
|
||||||
|
self.add_button('gtk-close', gtk.RESPONSE_CLOSE)
|
||||||
|
|
||||||
|
self.child.set_spacing(3)
|
||||||
|
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfigDialog')
|
||||||
|
def run(self, parent=None):
|
||||||
|
self.set_transient_for(parent)
|
||||||
|
self.on_run()
|
||||||
|
self.show_all()
|
||||||
|
result = super(GajimPluginConfigDialog, self).run()
|
||||||
|
self.hide()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_run(self):
|
||||||
|
pass
|
|
@ -0,0 +1,140 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Helper code related to plug-ins management system.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 30th May 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
__all__ = ['log', 'log_calls', 'Singleton']
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger('gajim.plugin_system')
|
||||||
|
'''
|
||||||
|
Logger for code related to plug-in system.
|
||||||
|
|
||||||
|
:type: logging.Logger
|
||||||
|
'''
|
||||||
|
|
||||||
|
consoleloghandler = logging.StreamHandler()
|
||||||
|
#consoleloghandler.setLevel(1)
|
||||||
|
consoleloghandler.setFormatter(
|
||||||
|
logging.Formatter('%(levelname)s: %(message)s'))
|
||||||
|
#logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s'))
|
||||||
|
#log.setLevel(logging.DEBUG)
|
||||||
|
log.addHandler(consoleloghandler)
|
||||||
|
log.propagate = False
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
class log_calls(object):
|
||||||
|
'''
|
||||||
|
Decorator class for functions to easily log when they are entered and left.
|
||||||
|
'''
|
||||||
|
|
||||||
|
filter_out_classes = ['GajimPluginConfig', 'PluginManager',
|
||||||
|
'GajimPluginConfigDialog', 'PluginsWindow']
|
||||||
|
'''
|
||||||
|
List of classes from which no logs should be emited when methods are called,
|
||||||
|
eventhough `log_calls` decorator is used.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, classname='', log=log):
|
||||||
|
'''
|
||||||
|
:Keywords:
|
||||||
|
classname : str
|
||||||
|
Name of class to prefix function name (if function is a method).
|
||||||
|
log : logging.Logger
|
||||||
|
Logger to use when outputing debug information on when function has
|
||||||
|
been entered and when left. By default: `plugins.helpers.log`
|
||||||
|
is used.
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.full_func_name = ''
|
||||||
|
'''
|
||||||
|
Full name of function, with class name (as prefix) if given
|
||||||
|
to decorator.
|
||||||
|
|
||||||
|
Otherwise, it's only function name retrieved from function object
|
||||||
|
for which decorator was called.
|
||||||
|
|
||||||
|
:type: str
|
||||||
|
'''
|
||||||
|
self.log_this_class = True
|
||||||
|
'''
|
||||||
|
Determines whether wrapper of given function should log calls of this
|
||||||
|
function or not.
|
||||||
|
|
||||||
|
:type: bool
|
||||||
|
'''
|
||||||
|
|
||||||
|
if classname:
|
||||||
|
self.full_func_name = classname+'.'
|
||||||
|
|
||||||
|
if classname in self.filter_out_classes:
|
||||||
|
self.log_this_class = False
|
||||||
|
|
||||||
|
def __call__(self, f):
|
||||||
|
'''
|
||||||
|
:param f: function to be wrapped with logging statements
|
||||||
|
|
||||||
|
:return: given function wrapped by *log.debug* statements
|
||||||
|
:rtype: function
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.full_func_name += f.func_name
|
||||||
|
if self.log_this_class:
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
log.debug('%(funcname)s() <entered>'%{
|
||||||
|
'funcname': self.full_func_name})
|
||||||
|
result = f(*args, **kwargs)
|
||||||
|
log.debug('%(funcname)s() <left>'%{
|
||||||
|
'funcname': self.full_func_name})
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
result = f(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class Singleton(type):
|
||||||
|
'''
|
||||||
|
Singleton metaclass.
|
||||||
|
'''
|
||||||
|
def __init__(cls, name, bases, dic):
|
||||||
|
super(Singleton, cls).__init__(name, bases, dic)
|
||||||
|
cls.instance=None
|
||||||
|
|
||||||
|
def __call__(cls,*args,**kw):
|
||||||
|
if cls.instance is None:
|
||||||
|
cls.instance=super(Singleton, cls).__call__(*args,**kw)
|
||||||
|
#log.debug('%(classname)s - new instance created'%{
|
||||||
|
#'classname' : cls.__name__})
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
#log.debug('%(classname)s - returning already existing instance'%{
|
||||||
|
#'classname' : cls.__name__})
|
||||||
|
|
||||||
|
return cls.instance
|
|
@ -0,0 +1,234 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Base class for implementing plugin.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 1st June 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from common import gajim
|
||||||
|
|
||||||
|
from plugins.helpers import log_calls, log
|
||||||
|
from plugins.gui import GajimPluginConfigDialog
|
||||||
|
|
||||||
|
|
||||||
|
class GajimPlugin(object):
|
||||||
|
'''
|
||||||
|
Base class for implementing Gajim plugins.
|
||||||
|
'''
|
||||||
|
name = u''
|
||||||
|
'''
|
||||||
|
Name of plugin.
|
||||||
|
|
||||||
|
Will be shown in plugins management GUI.
|
||||||
|
|
||||||
|
:type: unicode
|
||||||
|
'''
|
||||||
|
short_name = u''
|
||||||
|
'''
|
||||||
|
Short name of plugin.
|
||||||
|
|
||||||
|
Used for quick indentification of plugin.
|
||||||
|
|
||||||
|
:type: unicode
|
||||||
|
|
||||||
|
:todo: decide whether we really need this one, because class name (with
|
||||||
|
module name) can act as such short name
|
||||||
|
'''
|
||||||
|
version = u''
|
||||||
|
'''
|
||||||
|
Version of plugin.
|
||||||
|
|
||||||
|
:type: unicode
|
||||||
|
|
||||||
|
:todo: decide how to compare version between each other (which one
|
||||||
|
is higher). Also rethink: do we really need to compare versions
|
||||||
|
of plugins between each other? This would be only useful if we detect
|
||||||
|
same plugin class but with different version and we want only the newest
|
||||||
|
one to be active - is such policy good?
|
||||||
|
'''
|
||||||
|
description = u''
|
||||||
|
'''
|
||||||
|
Plugin description.
|
||||||
|
|
||||||
|
:type: unicode
|
||||||
|
|
||||||
|
:todo: should be allow rich text here (like HTML or reStructuredText)?
|
||||||
|
'''
|
||||||
|
authors = []
|
||||||
|
'''
|
||||||
|
Plugin authors.
|
||||||
|
|
||||||
|
:type: [] of unicode
|
||||||
|
|
||||||
|
:todo: should we decide on any particular format of author strings?
|
||||||
|
Especially: should we force format of giving author's e-mail?
|
||||||
|
'''
|
||||||
|
homepage = u''
|
||||||
|
'''
|
||||||
|
URL to plug-in's homepage.
|
||||||
|
|
||||||
|
:type: unicode
|
||||||
|
|
||||||
|
:todo: should we check whether provided string is valid URI? (Maybe
|
||||||
|
using 'property')
|
||||||
|
'''
|
||||||
|
gui_extension_points = {}
|
||||||
|
'''
|
||||||
|
Extension points that plugin wants to connect with and handlers to be used.
|
||||||
|
|
||||||
|
Keys of this string should be strings with name of GUI extension point
|
||||||
|
to handles. Values should be 2-element tuples with references to handling
|
||||||
|
functions. First function will be used to connect plugin with extpoint,
|
||||||
|
the second one to successfuly disconnect from it. Connecting takes places
|
||||||
|
when plugin is activated and extpoint already exists, or when plugin is
|
||||||
|
already activated but extpoint is being created (eg. chat window opens).
|
||||||
|
Disconnecting takes place when plugin is deactivated and extpoint exists
|
||||||
|
or when extpoint is destroyed and plugin is activate (eg. chat window
|
||||||
|
closed).
|
||||||
|
'''
|
||||||
|
config_default_values = {}
|
||||||
|
'''
|
||||||
|
Default values for keys that should be stored in plug-in config.
|
||||||
|
|
||||||
|
This dict is used when when someone calls for config option but it has not
|
||||||
|
been set yet.
|
||||||
|
|
||||||
|
Values are tuples: (default_value, option_description). The first one can
|
||||||
|
be anything (this is the advantage of using shelve/pickle instead of
|
||||||
|
custom-made config I/O handling); the second one should be unicode (gettext
|
||||||
|
can be used if need and/or translation is planned).
|
||||||
|
|
||||||
|
:type: {} of 2-element tuples
|
||||||
|
'''
|
||||||
|
events_handlers = {}
|
||||||
|
'''
|
||||||
|
Dictionary with events handlers.
|
||||||
|
|
||||||
|
Keys are event names. Values should be 2-element tuples with handler
|
||||||
|
priority as first element and reference to handler function as second
|
||||||
|
element. Priority is integer. See `ged` module for predefined priorities
|
||||||
|
like `ged.PRECORE`, `ged.CORE` or `ged.POSTCORE`.
|
||||||
|
|
||||||
|
:type: {} with 2-element tuples
|
||||||
|
'''
|
||||||
|
events = []
|
||||||
|
'''
|
||||||
|
New network event classes to be registered in Network Events Controller.
|
||||||
|
|
||||||
|
:type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent`
|
||||||
|
subclasses.
|
||||||
|
'''
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def __init__(self):
|
||||||
|
self.config = GajimPluginConfig(self)
|
||||||
|
'''
|
||||||
|
Plug-in configuration dictionary.
|
||||||
|
|
||||||
|
Automatically saved and loaded and plug-in (un)load.
|
||||||
|
|
||||||
|
:type: `plugins.plugin.GajimPluginConfig`
|
||||||
|
'''
|
||||||
|
self.load_config()
|
||||||
|
self.config_dialog = GajimPluginConfigDialog(self)
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def save_config(self):
|
||||||
|
self.config.save()
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def load_config(self):
|
||||||
|
self.config.load()
|
||||||
|
|
||||||
|
def __eq__(self, plugin):
|
||||||
|
if self.short_name == plugin.short_name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ne__(self, plugin):
|
||||||
|
if self.short_name != plugin.short_name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def local_file_path(self, file_name):
|
||||||
|
return os.path.join(self.__path__, file_name)
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def init(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def activate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@log_calls('GajimPlugin')
|
||||||
|
def deactivate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
import shelve
|
||||||
|
import UserDict
|
||||||
|
|
||||||
|
class GajimPluginConfig(UserDict.DictMixin):
|
||||||
|
@log_calls('GajimPluginConfig')
|
||||||
|
def __init__(self, plugin):
|
||||||
|
self.plugin = plugin
|
||||||
|
self.FILE_PATH = os.path.join(gajim.PLUGINS_CONFIG_DIR, self.plugin.short_name)
|
||||||
|
#log.debug('FILE_PATH = %s'%(self.FILE_PATH))
|
||||||
|
self.data = None
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfig')
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if not key in self.data:
|
||||||
|
self.data[key] = self.plugin.config_default_values[key][0]
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
return self.data[key]
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfig')
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.data[key] = value
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.data.keys()
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfig')
|
||||||
|
def save(self):
|
||||||
|
self.data.sync()
|
||||||
|
#log.debug(str(self.data))
|
||||||
|
|
||||||
|
@log_calls('GajimPluginConfig')
|
||||||
|
def load(self):
|
||||||
|
self.data = shelve.open(self.FILE_PATH)
|
||||||
|
|
||||||
|
class GajimPluginException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class GajimPluginInitError(GajimPluginException):
|
||||||
|
pass
|
|
@ -0,0 +1,456 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Plug-in management related classes.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 30th May 2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
__all__ = ['PluginManager']
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import fnmatch
|
||||||
|
|
||||||
|
from common import gajim
|
||||||
|
from common import nec
|
||||||
|
|
||||||
|
from plugins.helpers import log, log_calls, Singleton
|
||||||
|
from plugins.plugin import GajimPlugin
|
||||||
|
|
||||||
|
class PluginManager(object):
|
||||||
|
'''
|
||||||
|
Main plug-in management class.
|
||||||
|
|
||||||
|
Currently:
|
||||||
|
- scans for plugins
|
||||||
|
- activates them
|
||||||
|
- handles GUI extension points, when called by GUI objects after plugin
|
||||||
|
is activated (by dispatching info about call to handlers in plugins)
|
||||||
|
|
||||||
|
:todo: add more info about how GUI extension points work
|
||||||
|
:todo: add list of available GUI extension points
|
||||||
|
:todo: implement mechanism to dynamically load plugins where GUI extension
|
||||||
|
points have been already called (i.e. when plugin is activated
|
||||||
|
after GUI object creation). [DONE?]
|
||||||
|
:todo: implement mechanism to dynamically deactive plugins (call plugin's
|
||||||
|
deactivation handler) [DONE?]
|
||||||
|
:todo: when plug-in is deactivated all GUI extension points are removed
|
||||||
|
from `PluginManager.gui_extension_points_handlers`. But when
|
||||||
|
object that invoked GUI extension point is abandoned by Gajim, eg.
|
||||||
|
closed ChatControl object, the reference to called GUI extension
|
||||||
|
points is still in `PluginManager.gui_extension_points`. These
|
||||||
|
should be removed, so that object can be destroyed by Python.
|
||||||
|
Possible solution: add call to clean up method in classes
|
||||||
|
'destructors' (classes that register GUI extension points)
|
||||||
|
'''
|
||||||
|
|
||||||
|
__metaclass__ = Singleton
|
||||||
|
|
||||||
|
#@log_calls('PluginManager')
|
||||||
|
def __init__(self):
|
||||||
|
self.plugins = []
|
||||||
|
'''
|
||||||
|
Detected plugin classes.
|
||||||
|
|
||||||
|
Each class object in list is `GajimPlugin` subclass.
|
||||||
|
|
||||||
|
:type: [] of class objects
|
||||||
|
'''
|
||||||
|
self.active_plugins = []
|
||||||
|
'''
|
||||||
|
Instance objects of active plugins.
|
||||||
|
|
||||||
|
These are object instances of classes held `plugins`, but only those
|
||||||
|
that were activated.
|
||||||
|
|
||||||
|
:type: [] of `GajimPlugin` based objects
|
||||||
|
'''
|
||||||
|
self.gui_extension_points = {}
|
||||||
|
'''
|
||||||
|
Registered GUI extension points.
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.gui_extension_points_handlers = {}
|
||||||
|
'''
|
||||||
|
Registered handlers of GUI extension points.
|
||||||
|
'''
|
||||||
|
|
||||||
|
for path in gajim.PLUGINS_DIRS:
|
||||||
|
self.add_plugins(PluginManager.scan_dir_for_plugins(path))
|
||||||
|
|
||||||
|
#log.debug('plugins: %s'%(self.plugins))
|
||||||
|
|
||||||
|
self._activate_all_plugins_from_global_config()
|
||||||
|
|
||||||
|
#log.debug('active: %s'%(self.active_plugins))
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _plugin_has_entry_in_global_config(self, plugin):
|
||||||
|
if gajim.config.get_per('plugins', plugin.short_name) is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _create_plugin_entry_in_global_config(self, plugin):
|
||||||
|
gajim.config.add_per('plugins', plugin.short_name)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def add_plugin(self, plugin_class):
|
||||||
|
'''
|
||||||
|
:todo: what about adding plug-ins that are already added? Module reload
|
||||||
|
and adding class from reloaded module or ignoring adding plug-in?
|
||||||
|
'''
|
||||||
|
plugin = plugin_class()
|
||||||
|
|
||||||
|
if plugin not in self.plugins:
|
||||||
|
if not self._plugin_has_entry_in_global_config(plugin):
|
||||||
|
self._create_plugin_entry_in_global_config(plugin)
|
||||||
|
|
||||||
|
self.plugins.append(plugin)
|
||||||
|
plugin.active = False
|
||||||
|
else:
|
||||||
|
log.info('Not loading plugin %s v%s from module %s (identified by short name: %s). Plugin already loaded.'%(
|
||||||
|
plugin.name, plugin.version, plugin.__module__, plugin.short_name))
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def add_plugins(self, plugin_classes):
|
||||||
|
for plugin_class in plugin_classes:
|
||||||
|
self.add_plugin(plugin_class)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def gui_extension_point(self, gui_extpoint_name, *args):
|
||||||
|
'''
|
||||||
|
Invokes all handlers (from plugins) for particular GUI extension point
|
||||||
|
and adds it to collection for further processing (eg. by plugins not active
|
||||||
|
yet).
|
||||||
|
|
||||||
|
:param gui_extpoint_name: name of GUI extension point.
|
||||||
|
:type gui_extpoint_name: unicode
|
||||||
|
:param args: parameters to be passed to extension point handlers
|
||||||
|
(typically and object that invokes `gui_extension_point`; however,
|
||||||
|
this can be practically anything)
|
||||||
|
:type args: tuple
|
||||||
|
|
||||||
|
:todo: GUI extension points must be documented well - names with
|
||||||
|
parameters that will be passed to handlers (in plugins). Such
|
||||||
|
documentation must be obeyed both in core and in plugins. This
|
||||||
|
is a loosely coupled approach and is pretty natural in Python.
|
||||||
|
|
||||||
|
:bug: what if only some handlers are successfully connected? we should
|
||||||
|
revert all those connections that where successfully made. Maybe
|
||||||
|
call 'self._deactivate_plugin()' or sth similar.
|
||||||
|
Looking closer - we only rewrite tuples here. Real check should be
|
||||||
|
made in method that invokes gui_extpoints handlers.
|
||||||
|
'''
|
||||||
|
|
||||||
|
self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args)
|
||||||
|
self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def remove_gui_extension_point(self, gui_extpoint_name, *args):
|
||||||
|
'''
|
||||||
|
Removes GUI extension point from collection held by `PluginManager`.
|
||||||
|
|
||||||
|
From this point this particular extension point won't be visible
|
||||||
|
to plugins (eg. it won't invoke any handlers when plugin is activated).
|
||||||
|
|
||||||
|
GUI extension point is removed completely (there is no way to recover it
|
||||||
|
from inside `PluginManager`).
|
||||||
|
|
||||||
|
Removal is needed when instance object that given extension point was
|
||||||
|
connect with is destroyed (eg. ChatControl is closed or context menu
|
||||||
|
is hidden).
|
||||||
|
|
||||||
|
Each `PluginManager.gui_extension_point` call should have a call of
|
||||||
|
`PluginManager.remove_gui_extension_point` related to it.
|
||||||
|
|
||||||
|
:note: in current implementation different arguments mean different
|
||||||
|
extension points. The same arguments and the same name mean
|
||||||
|
the same extension point.
|
||||||
|
:todo: instead of using argument to identify which extpoint should be
|
||||||
|
removed, maybe add additional 'id' argument - this would work similar
|
||||||
|
hash in Python objects. 'id' would be calculated based on arguments
|
||||||
|
passed or on anything else (even could be constant). This would give
|
||||||
|
core developers (that add new extpoints) more freedom, but is this
|
||||||
|
necessary?
|
||||||
|
|
||||||
|
:param gui_extpoint_name: name of GUI extension point.
|
||||||
|
:type gui_extpoint_name: unicode
|
||||||
|
:param args: arguments that `PluginManager.gui_extension_point` was
|
||||||
|
called with for this extension point. This is used (along with
|
||||||
|
extension point name) to identify element to be removed.
|
||||||
|
:type args: tuple
|
||||||
|
'''
|
||||||
|
|
||||||
|
if gui_extpoint_name in self.gui_extension_points:
|
||||||
|
#log.debug('Removing GUI extpoint\n name: %s\n args: %s'%(gui_extpoint_name, args))
|
||||||
|
self.gui_extension_points[gui_extpoint_name].remove(args)
|
||||||
|
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args):
|
||||||
|
'''
|
||||||
|
Adds GUI extension point call to list of calls.
|
||||||
|
|
||||||
|
This is done only if such call hasn't been added already
|
||||||
|
(same extension point name and same arguments).
|
||||||
|
|
||||||
|
:note: This is assumption that GUI extension points are different only
|
||||||
|
if they have different name or different arguments.
|
||||||
|
|
||||||
|
:param gui_extpoint_name: GUI extension point name used to identify it
|
||||||
|
by plugins.
|
||||||
|
:type gui_extpoint_name: str
|
||||||
|
|
||||||
|
:param args: parameters to be passed to extension point handlers
|
||||||
|
(typically and object that invokes `gui_extension_point`; however,
|
||||||
|
this can be practically anything)
|
||||||
|
:type args: tuple
|
||||||
|
|
||||||
|
'''
|
||||||
|
if ((gui_extpoint_name not in self.gui_extension_points)
|
||||||
|
or (args not in self.gui_extension_points[gui_extpoint_name])):
|
||||||
|
self.gui_extension_points.setdefault(gui_extpoint_name, []).append(args)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, *args):
|
||||||
|
if gui_extpoint_name in self.gui_extension_points_handlers:
|
||||||
|
for handlers in self.gui_extension_points_handlers[gui_extpoint_name]:
|
||||||
|
handlers[0](*args)
|
||||||
|
|
||||||
|
def _register_events_handlers_in_ged(self, plugin):
|
||||||
|
for event_name, handler in plugin.events_handlers.iteritems():
|
||||||
|
priority = handler[0]
|
||||||
|
handler_function = handler[1]
|
||||||
|
gajim.ged.register_event_handler(event_name,
|
||||||
|
priority,
|
||||||
|
handler_function)
|
||||||
|
|
||||||
|
def _remove_events_handler_from_ged(self, plugin):
|
||||||
|
for event_name, handler in plugin.events_handlers.iteritems():
|
||||||
|
priority = handler[0]
|
||||||
|
handler_function = handler[1]
|
||||||
|
gajim.ged.remove_event_handler(event_name,
|
||||||
|
priority,
|
||||||
|
handler_function)
|
||||||
|
|
||||||
|
def _register_network_events_in_nec(self, plugin):
|
||||||
|
for event_class in plugin.events:
|
||||||
|
setattr(event_class, 'plugin', plugin)
|
||||||
|
if issubclass(event_class, nec.NetworkIncomingEvent):
|
||||||
|
gajim.nec.register_incoming_event(event_class)
|
||||||
|
elif issubclass(event_class, nec.NetworkOutgoingEvent):
|
||||||
|
gajim.nec.register_outgoing_event(event_class)
|
||||||
|
|
||||||
|
def _remove_network_events_from_nec(self, plugin):
|
||||||
|
for event_class in plugin.events:
|
||||||
|
if issubclass(event_class, nec.NetworkIncomingEvent):
|
||||||
|
gajim.nec.unregister_incoming_event(event_class)
|
||||||
|
elif issubclass(event_class, nec.NetworkOutgoingEvent):
|
||||||
|
gajim.nec.unregister_outgoing_event(event_class)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def activate_plugin(self, plugin):
|
||||||
|
'''
|
||||||
|
:param plugin: plugin to be activated
|
||||||
|
:type plugin: class object of `GajimPlugin` subclass
|
||||||
|
|
||||||
|
:todo: success checks should be implemented using exceptions. Such
|
||||||
|
control should also be implemented in deactivation. Exceptions
|
||||||
|
should be shown to user inside popup dialog, so the reason
|
||||||
|
for not activating plugin is known.
|
||||||
|
'''
|
||||||
|
success = False
|
||||||
|
if not plugin.active:
|
||||||
|
|
||||||
|
self._add_gui_extension_points_handlers_from_plugin(plugin)
|
||||||
|
self._handle_all_gui_extension_points_with_plugin(plugin)
|
||||||
|
self._register_events_handlers_in_ged(plugin)
|
||||||
|
self._register_network_events_in_nec(plugin)
|
||||||
|
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.active_plugins.append(plugin)
|
||||||
|
plugin.activate()
|
||||||
|
self._set_plugin_active_in_global_config(plugin)
|
||||||
|
plugin.active = True
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def deactivate_plugin(self, plugin):
|
||||||
|
# remove GUI extension points handlers (provided by plug-in) from
|
||||||
|
# handlers list
|
||||||
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
||||||
|
plugin.gui_extension_points.iteritems():
|
||||||
|
self.gui_extension_points_handlers[gui_extpoint_name].remove(gui_extpoint_handlers)
|
||||||
|
|
||||||
|
# detaching plug-in from handler GUI extension points (calling
|
||||||
|
# cleaning up method that must be provided by plug-in developer
|
||||||
|
# for each handled GUI extension point)
|
||||||
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
||||||
|
plugin.gui_extension_points.iteritems():
|
||||||
|
if gui_extpoint_name in self.gui_extension_points:
|
||||||
|
for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]:
|
||||||
|
handler = gui_extpoint_handlers[1]
|
||||||
|
if handler:
|
||||||
|
handler(*gui_extension_point_args)
|
||||||
|
|
||||||
|
self._remove_events_handler_from_ged(plugin)
|
||||||
|
self._remove_network_events_from_nec(plugin)
|
||||||
|
|
||||||
|
# removing plug-in from active plug-ins list
|
||||||
|
plugin.deactivate()
|
||||||
|
self.active_plugins.remove(plugin)
|
||||||
|
self._set_plugin_active_in_global_config(plugin, False)
|
||||||
|
plugin.active = False
|
||||||
|
|
||||||
|
def _deactivate_all_plugins(self):
|
||||||
|
for plugin_object in self.active_plugins:
|
||||||
|
self.deactivate_plugin(plugin_object)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _add_gui_extension_points_handlers_from_plugin(self, plugin):
|
||||||
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
||||||
|
plugin.gui_extension_points.iteritems():
|
||||||
|
self.gui_extension_points_handlers.setdefault(gui_extpoint_name, []).append(
|
||||||
|
gui_extpoint_handlers)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _handle_all_gui_extension_points_with_plugin(self, plugin):
|
||||||
|
for gui_extpoint_name, gui_extpoint_handlers in \
|
||||||
|
plugin.gui_extension_points.iteritems():
|
||||||
|
if gui_extpoint_name in self.gui_extension_points:
|
||||||
|
for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]:
|
||||||
|
handler = gui_extpoint_handlers[0]
|
||||||
|
if handler:
|
||||||
|
handler(*gui_extension_point_args)
|
||||||
|
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def _activate_all_plugins(self):
|
||||||
|
'''
|
||||||
|
Activates all plugins in `plugins`.
|
||||||
|
|
||||||
|
Activated plugins are appended to `active_plugins` list.
|
||||||
|
'''
|
||||||
|
#self.active_plugins = []
|
||||||
|
for plugin in self.plugins:
|
||||||
|
self.activate_plugin(plugin)
|
||||||
|
|
||||||
|
def _activate_all_plugins_from_global_config(self):
|
||||||
|
for plugin in self.plugins:
|
||||||
|
if self._plugin_is_active_in_global_config(plugin):
|
||||||
|
self.activate_plugin(plugin)
|
||||||
|
|
||||||
|
def _plugin_is_active_in_global_config(self, plugin):
|
||||||
|
return gajim.config.get_per('plugins', plugin.short_name, 'active')
|
||||||
|
|
||||||
|
def _set_plugin_active_in_global_config(self, plugin, active=True):
|
||||||
|
gajim.config.set_per('plugins', plugin.short_name, 'active', active)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@log_calls('PluginManager')
|
||||||
|
def scan_dir_for_plugins(path):
|
||||||
|
'''
|
||||||
|
Scans given directory for plugin classes.
|
||||||
|
|
||||||
|
:param path: directory to scan for plugins
|
||||||
|
:type path: unicode
|
||||||
|
|
||||||
|
:return: list of found plugin classes (subclasses of `GajimPlugin`
|
||||||
|
:rtype: [] of class objects
|
||||||
|
|
||||||
|
:note: currently it only searches for plugin classes in '\*.py' files
|
||||||
|
present in given direcotory `path` (no recursion here)
|
||||||
|
|
||||||
|
:todo: add scanning packages
|
||||||
|
:todo: add scanning zipped modules
|
||||||
|
'''
|
||||||
|
plugins_found = []
|
||||||
|
if os.path.isdir(path):
|
||||||
|
dir_list = os.listdir(path)
|
||||||
|
#log.debug(dir_list)
|
||||||
|
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
#log.debug(sys.path)
|
||||||
|
|
||||||
|
for elem_name in dir_list:
|
||||||
|
#log.debug('- "%s"'%(elem_name))
|
||||||
|
file_path = os.path.join(path, elem_name)
|
||||||
|
#log.debug(' "%s"'%(file_path))
|
||||||
|
|
||||||
|
module = None
|
||||||
|
|
||||||
|
if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'):
|
||||||
|
module_name = os.path.splitext(elem_name)[0]
|
||||||
|
#log.debug('Possible module detected.')
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
#log.debug('Module imported.')
|
||||||
|
except ValueError, value_error:
|
||||||
|
pass
|
||||||
|
#log.debug('Module not imported successfully. ValueError: %s'%(value_error))
|
||||||
|
except ImportError, import_error:
|
||||||
|
pass
|
||||||
|
#log.debug('Module not imported successfully. ImportError: %s'%(import_error))
|
||||||
|
|
||||||
|
elif os.path.isdir(file_path):
|
||||||
|
module_name = elem_name
|
||||||
|
file_path += os.path.sep
|
||||||
|
#log.debug('Possible package detected.')
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
#log.debug('Package imported.')
|
||||||
|
except ValueError, value_error:
|
||||||
|
pass
|
||||||
|
#log.debug('Package not imported successfully. ValueError: %s'%(value_error))
|
||||||
|
except ImportError, import_error:
|
||||||
|
pass
|
||||||
|
#log.debug('Package not imported successfully. ImportError: %s'%(import_error))
|
||||||
|
|
||||||
|
|
||||||
|
if module:
|
||||||
|
log.debug('Attributes processing started')
|
||||||
|
for module_attr_name in [attr_name for attr_name in dir(module)
|
||||||
|
if not (attr_name.startswith('__') or
|
||||||
|
attr_name.endswith('__'))]:
|
||||||
|
module_attr = getattr(module, module_attr_name)
|
||||||
|
log.debug('%s : %s'%(module_attr_name, module_attr))
|
||||||
|
|
||||||
|
try:
|
||||||
|
if issubclass(module_attr, GajimPlugin) and \
|
||||||
|
not module_attr is GajimPlugin:
|
||||||
|
log.debug('is subclass of GajimPlugin')
|
||||||
|
#log.debug('file_path: %s\nabspath: %s\ndirname: %s'%(file_path, os.path.abspath(file_path), os.path.dirname(os.path.abspath(file_path))))
|
||||||
|
#log.debug('file_path: %s\ndirname: %s\nabspath: %s'%(file_path, os.path.dirname(file_path), os.path.abspath(os.path.dirname(file_path))))
|
||||||
|
module_attr.__path__ = os.path.abspath(os.path.dirname(file_path))
|
||||||
|
plugins_found.append(module_attr)
|
||||||
|
except TypeError, type_error:
|
||||||
|
pass
|
||||||
|
#log.debug('module_attr: %s, error : %s'%(
|
||||||
|
#module_name+'.'+module_attr_name,
|
||||||
|
#type_error))
|
||||||
|
|
||||||
|
#log.debug(module)
|
||||||
|
|
||||||
|
return plugins_found
|
|
@ -0,0 +1,410 @@
|
||||||
|
"""
|
||||||
|
pycallgraph
|
||||||
|
|
||||||
|
U{http://pycallgraph.slowchop.com/}
|
||||||
|
|
||||||
|
Copyright Gerald Kaszuba 2007
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = '0.4.1'
|
||||||
|
__author__ = 'Gerald Kaszuba'
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from distutils import sysconfig
|
||||||
|
|
||||||
|
# Initialise module variables.
|
||||||
|
# TODO Move these into settings
|
||||||
|
trace_filter = None
|
||||||
|
time_filter = None
|
||||||
|
|
||||||
|
|
||||||
|
def colourize_node(calls, total_time):
|
||||||
|
value = float(total_time * 2 + calls) / 3
|
||||||
|
return '%f %f %f' % (value / 2 + .5, value, 0.9)
|
||||||
|
|
||||||
|
|
||||||
|
def colourize_edge(calls, total_time):
|
||||||
|
value = float(total_time * 2 + calls) / 3
|
||||||
|
return '%f %f %f' % (value / 2 + .5, value, 0.7)
|
||||||
|
|
||||||
|
|
||||||
|
def reset_settings():
|
||||||
|
global settings
|
||||||
|
global graph_attributes
|
||||||
|
global __version__
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
'node_attributes': {
|
||||||
|
'label': r'%(func)s\ncalls: %(hits)i\ntotal time: %(total_time)f',
|
||||||
|
'color': '%(col)s',
|
||||||
|
},
|
||||||
|
'node_colour': colourize_node,
|
||||||
|
'edge_colour': colourize_edge,
|
||||||
|
'dont_exclude_anything': False,
|
||||||
|
'include_stdlib': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: Move this into settings
|
||||||
|
graph_attributes = {
|
||||||
|
'graph': {
|
||||||
|
'fontname': 'Verdana',
|
||||||
|
'fontsize': 7,
|
||||||
|
'fontcolor': '0 0 0.5',
|
||||||
|
'label': r'Generated by Python Call Graph v%s\n' \
|
||||||
|
r'http://pycallgraph.slowchop.com' % __version__,
|
||||||
|
},
|
||||||
|
'node': {
|
||||||
|
'fontname': 'Verdana',
|
||||||
|
'fontsize': 7,
|
||||||
|
'color': '.5 0 .9',
|
||||||
|
'style': 'filled',
|
||||||
|
'shape': 'rect',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def reset_trace():
|
||||||
|
"""Resets all collected statistics. This is run automatically by
|
||||||
|
start_trace(reset=True) and when the module is loaded.
|
||||||
|
"""
|
||||||
|
global call_dict
|
||||||
|
global call_stack
|
||||||
|
global func_count
|
||||||
|
global func_count_max
|
||||||
|
global func_time
|
||||||
|
global func_time_max
|
||||||
|
global call_stack_timer
|
||||||
|
|
||||||
|
call_dict = {}
|
||||||
|
|
||||||
|
# current call stack
|
||||||
|
call_stack = ['__main__']
|
||||||
|
|
||||||
|
# counters for each function
|
||||||
|
func_count = {}
|
||||||
|
func_count_max = 0
|
||||||
|
|
||||||
|
# accumative time per function
|
||||||
|
func_time = {}
|
||||||
|
func_time_max = 0
|
||||||
|
|
||||||
|
# keeps track of the start time of each call on the stack
|
||||||
|
call_stack_timer = []
|
||||||
|
|
||||||
|
|
||||||
|
class PyCallGraphException(Exception):
|
||||||
|
"""Exception used for pycallgraph"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GlobbingFilter(object):
|
||||||
|
"""Filter module names using a set of globs.
|
||||||
|
|
||||||
|
Objects are matched against the exclude list first, then the include list.
|
||||||
|
Anything that passes through without matching either, is excluded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, include=None, exclude=None, max_depth=None,
|
||||||
|
min_depth=None):
|
||||||
|
if include is None and exclude is None:
|
||||||
|
include = ['*']
|
||||||
|
exclude = []
|
||||||
|
elif include is None:
|
||||||
|
include = ['*']
|
||||||
|
elif exclude is None:
|
||||||
|
exclude = []
|
||||||
|
self.include = include
|
||||||
|
self.exclude = exclude
|
||||||
|
self.max_depth = max_depth or 9999
|
||||||
|
self.min_depth = min_depth or 0
|
||||||
|
|
||||||
|
def __call__(self, stack, module_name=None, class_name=None,
|
||||||
|
func_name=None, full_name=None):
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
if len(stack) > self.max_depth:
|
||||||
|
return False
|
||||||
|
if len(stack) < self.min_depth:
|
||||||
|
return False
|
||||||
|
for pattern in self.exclude:
|
||||||
|
if fnmatch(full_name, pattern):
|
||||||
|
return False
|
||||||
|
for pattern in self.include:
|
||||||
|
if fnmatch(full_name, pattern):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_module_stdlib(file_name):
|
||||||
|
"""Returns True if the file_name is in the lib directory."""
|
||||||
|
# TODO: Move these calls away from this function so it doesn't have to run
|
||||||
|
# every time.
|
||||||
|
lib_path = sysconfig.get_python_lib()
|
||||||
|
path = os.path.split(lib_path)
|
||||||
|
if path[1] == 'site-packages':
|
||||||
|
lib_path = path[0]
|
||||||
|
return file_name.lower().startswith(lib_path.lower())
|
||||||
|
|
||||||
|
|
||||||
|
def start_trace(reset=True, filter_func=None, time_filter_func=None):
|
||||||
|
"""Begins a trace. Setting reset to True will reset all previously recorded
|
||||||
|
trace data. filter_func needs to point to a callable function that accepts
|
||||||
|
the parameters (call_stack, module_name, class_name, func_name, full_name).
|
||||||
|
Every call will be passed into this function and it is up to the function
|
||||||
|
to decide if it should be included or not. Returning False means the call
|
||||||
|
will be filtered out and not included in the call graph.
|
||||||
|
"""
|
||||||
|
global trace_filter
|
||||||
|
global time_filter
|
||||||
|
if reset:
|
||||||
|
reset_trace()
|
||||||
|
|
||||||
|
if filter_func:
|
||||||
|
trace_filter = filter_func
|
||||||
|
else:
|
||||||
|
trace_filter = GlobbingFilter(exclude=['pycallgraph.*'])
|
||||||
|
|
||||||
|
if time_filter_func:
|
||||||
|
time_filter = time_filter_func
|
||||||
|
else:
|
||||||
|
time_filter = GlobbingFilter()
|
||||||
|
|
||||||
|
sys.settrace(tracer)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_trace():
|
||||||
|
"""Stops the currently running trace, if any."""
|
||||||
|
sys.settrace(None)
|
||||||
|
|
||||||
|
|
||||||
|
def tracer(frame, event, arg):
|
||||||
|
"""This is an internal function that is called every time a call is made
|
||||||
|
during a trace. It keeps track of relationships between calls.
|
||||||
|
"""
|
||||||
|
global func_count_max
|
||||||
|
global func_count
|
||||||
|
global trace_filter
|
||||||
|
global time_filter
|
||||||
|
global call_stack
|
||||||
|
global func_time
|
||||||
|
global func_time_max
|
||||||
|
|
||||||
|
if event == 'call':
|
||||||
|
keep = True
|
||||||
|
code = frame.f_code
|
||||||
|
|
||||||
|
# Stores all the parts of a human readable name of the current call.
|
||||||
|
full_name_list = []
|
||||||
|
|
||||||
|
# Work out the module name
|
||||||
|
module = inspect.getmodule(code)
|
||||||
|
if module:
|
||||||
|
module_name = module.__name__
|
||||||
|
module_path = module.__file__
|
||||||
|
if not settings['include_stdlib'] \
|
||||||
|
and is_module_stdlib(module_path):
|
||||||
|
keep = False
|
||||||
|
if module_name == '__main__':
|
||||||
|
module_name = ''
|
||||||
|
else:
|
||||||
|
module_name = ''
|
||||||
|
if module_name:
|
||||||
|
full_name_list.append(module_name)
|
||||||
|
|
||||||
|
# Work out the class name.
|
||||||
|
try:
|
||||||
|
class_name = frame.f_locals['self'].__class__.__name__
|
||||||
|
full_name_list.append(class_name)
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
class_name = ''
|
||||||
|
|
||||||
|
# Work out the current function or method
|
||||||
|
func_name = code.co_name
|
||||||
|
if func_name == '?':
|
||||||
|
func_name = '__main__'
|
||||||
|
full_name_list.append(func_name)
|
||||||
|
|
||||||
|
# Create a readable representation of the current call
|
||||||
|
full_name = '.'.join(full_name_list)
|
||||||
|
|
||||||
|
# Load the trace filter, if any. 'keep' determines if we should ignore
|
||||||
|
# this call
|
||||||
|
if keep and trace_filter:
|
||||||
|
keep = trace_filter(call_stack, module_name, class_name,
|
||||||
|
func_name, full_name)
|
||||||
|
|
||||||
|
# Store the call information
|
||||||
|
if keep:
|
||||||
|
|
||||||
|
fr = call_stack[-1]
|
||||||
|
if fr not in call_dict:
|
||||||
|
call_dict[fr] = {}
|
||||||
|
if full_name not in call_dict[fr]:
|
||||||
|
call_dict[fr][full_name] = 0
|
||||||
|
call_dict[fr][full_name] += 1
|
||||||
|
|
||||||
|
if full_name not in func_count:
|
||||||
|
func_count[full_name] = 0
|
||||||
|
func_count[full_name] += 1
|
||||||
|
if func_count[full_name] > func_count_max:
|
||||||
|
func_count_max = func_count[full_name]
|
||||||
|
|
||||||
|
call_stack.append(full_name)
|
||||||
|
call_stack_timer.append(time.time())
|
||||||
|
|
||||||
|
else:
|
||||||
|
call_stack.append('')
|
||||||
|
call_stack_timer.append(None)
|
||||||
|
|
||||||
|
if event == 'return':
|
||||||
|
if call_stack:
|
||||||
|
full_name = call_stack.pop(-1)
|
||||||
|
t = call_stack_timer.pop(-1)
|
||||||
|
if t and time_filter(stack=call_stack, full_name=full_name):
|
||||||
|
if full_name not in func_time:
|
||||||
|
func_time[full_name] = 0
|
||||||
|
call_time = (time.time() - t)
|
||||||
|
func_time[full_name] += call_time
|
||||||
|
if func_time[full_name] > func_time_max:
|
||||||
|
func_time_max = func_time[full_name]
|
||||||
|
|
||||||
|
return tracer
|
||||||
|
|
||||||
|
|
||||||
|
def get_dot(stop=True):
|
||||||
|
"""Returns a string containing a DOT file. Setting stop to True will cause
|
||||||
|
the trace to stop.
|
||||||
|
"""
|
||||||
|
global func_time_max
|
||||||
|
|
||||||
|
def frac_calculation(func, count):
|
||||||
|
global func_count_max
|
||||||
|
global func_time
|
||||||
|
global func_time_max
|
||||||
|
calls_frac = float(count) / func_count_max
|
||||||
|
try:
|
||||||
|
total_time = func_time[func]
|
||||||
|
except KeyError:
|
||||||
|
total_time = 0
|
||||||
|
if func_time_max:
|
||||||
|
total_time_frac = float(total_time) / func_time_max
|
||||||
|
else:
|
||||||
|
total_time_frac = 0
|
||||||
|
return calls_frac, total_time_frac, total_time
|
||||||
|
|
||||||
|
if stop:
|
||||||
|
stop_trace()
|
||||||
|
ret = ['digraph G {', ]
|
||||||
|
for comp, comp_attr in graph_attributes.items():
|
||||||
|
ret.append('%s [' % comp)
|
||||||
|
for attr, val in comp_attr.items():
|
||||||
|
ret.append('%(attr)s = "%(val)s",' % locals())
|
||||||
|
ret.append('];')
|
||||||
|
for func, hits in func_count.items():
|
||||||
|
calls_frac, total_time_frac, total_time = frac_calculation(func, hits)
|
||||||
|
col = settings['node_colour'](calls_frac, total_time_frac)
|
||||||
|
attribs = ['%s="%s"' % a for a in settings['node_attributes'].items()]
|
||||||
|
node_str = '"%s" [%s];' % (func, ','.join(attribs))
|
||||||
|
ret.append(node_str % locals())
|
||||||
|
for fr_key, fr_val in call_dict.items():
|
||||||
|
if fr_key == '':
|
||||||
|
continue
|
||||||
|
for to_key, to_val in fr_val.items():
|
||||||
|
calls_frac, total_time_frac, totla_time = \
|
||||||
|
frac_calculation(to_key, to_val)
|
||||||
|
col = settings['edge_colour'](calls_frac, total_time_frac)
|
||||||
|
edge = '[ color = "%s" ]' % col
|
||||||
|
ret.append('"%s"->"%s" %s' % (fr_key, to_key, edge))
|
||||||
|
ret.append('}')
|
||||||
|
ret = '\n'.join(ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def save_dot(filename):
|
||||||
|
"""Generates a DOT file and writes it into filename."""
|
||||||
|
open(filename, 'w').write(get_dot())
|
||||||
|
|
||||||
|
|
||||||
|
def make_graph(filename, format=None, tool=None, stop=None):
|
||||||
|
"""This has been changed to make_dot_graph."""
|
||||||
|
raise PyCallGraphException( \
|
||||||
|
'make_graph is depricated. Please use make_dot_graph')
|
||||||
|
|
||||||
|
|
||||||
|
def make_dot_graph(filename, format='png', tool='dot', stop=True):
|
||||||
|
"""Creates a graph using a Graphviz tool that supports the dot language. It
|
||||||
|
will output into a file specified by filename with the format specified.
|
||||||
|
Setting stop to True will stop the current trace.
|
||||||
|
"""
|
||||||
|
if stop:
|
||||||
|
stop_trace()
|
||||||
|
|
||||||
|
# create a temporary file to be used for the dot data
|
||||||
|
fd, tempname = tempfile.mkstemp()
|
||||||
|
f = os.fdopen(fd, 'w')
|
||||||
|
f.write(get_dot())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# normalize filename
|
||||||
|
regex_user_expand = re.compile('\A~')
|
||||||
|
if regex_user_expand.match(filename):
|
||||||
|
filename = os.path.expanduser(filename)
|
||||||
|
else:
|
||||||
|
filename = os.path.expandvars(filename) # expand, just in case
|
||||||
|
|
||||||
|
cmd = '%(tool)s -T%(format)s -o%(filename)s %(tempname)s' % locals()
|
||||||
|
try:
|
||||||
|
ret = os.system(cmd)
|
||||||
|
if ret:
|
||||||
|
raise PyCallGraphException( \
|
||||||
|
'The command "%(cmd)s" failed with error ' \
|
||||||
|
'code %(ret)i.' % locals())
|
||||||
|
finally:
|
||||||
|
os.unlink(tempname)
|
||||||
|
|
||||||
|
|
||||||
|
def simple_memoize(callable_object):
|
||||||
|
"""Simple memoization for functions without keyword arguments.
|
||||||
|
|
||||||
|
This is useful for mapping code objects to module in this context.
|
||||||
|
inspect.getmodule() requires a number of system calls, which may slow down
|
||||||
|
the tracing considerably. Caching the mapping from code objects (there is
|
||||||
|
*one* code object for each function, regardless of how many simultaneous
|
||||||
|
activations records there are).
|
||||||
|
|
||||||
|
In this context we can ignore keyword arguments, but a generic memoizer
|
||||||
|
ought to take care of that as well.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cache = dict()
|
||||||
|
def wrapper(*rest):
|
||||||
|
if rest not in cache:
|
||||||
|
cache[rest] = callable_object(*rest)
|
||||||
|
return cache[rest]
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
settings = {}
|
||||||
|
graph_attributes = {}
|
||||||
|
reset_settings()
|
||||||
|
reset_trace()
|
||||||
|
inspect.getmodule = simple_memoize(inspect.getmodule)
|
|
@ -0,0 +1,310 @@
|
||||||
|
# lint Python modules using external checkers.
|
||||||
|
#
|
||||||
|
# This is the main checker controling the other ones and the reports
|
||||||
|
# generation. It is itself both a raw checker and an astng checker in order
|
||||||
|
# to:
|
||||||
|
# * handle message activation / deactivation at the module level
|
||||||
|
# * handle some basic but necessary stats'data (number of classes, methods...)
|
||||||
|
#
|
||||||
|
[MASTER]
|
||||||
|
|
||||||
|
# Specify a configuration file.
|
||||||
|
#rcfile=
|
||||||
|
|
||||||
|
# Python code to execute, usually for sys.path manipulation such as
|
||||||
|
# pygtk.require().
|
||||||
|
#init-hook=
|
||||||
|
|
||||||
|
# Profiled execution.
|
||||||
|
profile=no
|
||||||
|
|
||||||
|
# Add <file or directory> to the black list. It should be a base name, not a
|
||||||
|
# path. You may set this option multiple times.
|
||||||
|
ignore=CVS
|
||||||
|
|
||||||
|
# Pickle collected data for later comparisons.
|
||||||
|
persistent=yes
|
||||||
|
|
||||||
|
# Set the cache size for astng objects.
|
||||||
|
cache-size=500
|
||||||
|
|
||||||
|
# List of plugins (as comma separated values of python modules names) to load,
|
||||||
|
# usually to register additional checkers.
|
||||||
|
load-plugins=
|
||||||
|
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
|
||||||
|
# Enable only checker(s) with the given id(s). This option conflicts with the
|
||||||
|
# disable-checker option
|
||||||
|
#enable-checker=
|
||||||
|
|
||||||
|
# Enable all checker(s) except those with the given id(s). This option
|
||||||
|
# conflicts with the enable-checker option
|
||||||
|
#disable-checker=
|
||||||
|
|
||||||
|
# Enable all messages in the listed categories.
|
||||||
|
#enable-msg-cat=
|
||||||
|
|
||||||
|
# Disable all messages in the listed categories.
|
||||||
|
#disable-msg-cat=
|
||||||
|
|
||||||
|
# Enable the message(s) with the given id(s).
|
||||||
|
enable-msg=R0801
|
||||||
|
|
||||||
|
# Disable the message(s) with the given id(s).
|
||||||
|
disable-msg=W0312
|
||||||
|
# disable-msg=
|
||||||
|
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
|
||||||
|
# set the output format. Available formats are text, parseable, colorized, msvs
|
||||||
|
# (visual studio) and html
|
||||||
|
output-format=text
|
||||||
|
|
||||||
|
# Include message's id in output
|
||||||
|
include-ids=yes
|
||||||
|
|
||||||
|
# Put messages in a separate file for each module / package specified on the
|
||||||
|
# command line instead of printing them on stdout. Reports (if any) will be
|
||||||
|
# written in a file name "pylint_global.[txt|html]".
|
||||||
|
files-output=no
|
||||||
|
|
||||||
|
# Tells wether to display a full report or only the messages
|
||||||
|
reports=yes
|
||||||
|
|
||||||
|
# Python expression which should return a note less than 10 (10 is the highest
|
||||||
|
# note).You have access to the variables errors warning, statement which
|
||||||
|
# respectivly contain the number of errors / warnings messages and the total
|
||||||
|
# number of statements analyzed. This is used by the global evaluation report
|
||||||
|
# (R0004).
|
||||||
|
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||||
|
|
||||||
|
# Add a comment according to your evaluation note. This is used by the global
|
||||||
|
# evaluation report (R0004).
|
||||||
|
comment=no
|
||||||
|
|
||||||
|
# Enable the report(s) with the given id(s).
|
||||||
|
#enable-report=
|
||||||
|
|
||||||
|
# Disable the report(s) with the given id(s).
|
||||||
|
#disable-report=
|
||||||
|
|
||||||
|
|
||||||
|
# try to find bugs in the code using type inference
|
||||||
|
#
|
||||||
|
[TYPECHECK]
|
||||||
|
|
||||||
|
# Tells wether missing members accessed in mixin class should be ignored. A
|
||||||
|
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||||
|
ignore-mixin-members=yes
|
||||||
|
|
||||||
|
# List of classes names for which member attributes should not be checked
|
||||||
|
# (useful for classes with attributes dynamicaly set).
|
||||||
|
ignored-classes=SQLObject
|
||||||
|
|
||||||
|
# When zope mode is activated, consider the acquired-members option to ignore
|
||||||
|
# access to some undefined attributes.
|
||||||
|
zope=no
|
||||||
|
|
||||||
|
# List of members which are usually get through zope's acquisition mecanism and
|
||||||
|
# so shouldn't trigger E0201 when accessed (need zope=yes to be considered).
|
||||||
|
acquired-members=REQUEST,acl_users,aq_parent
|
||||||
|
|
||||||
|
|
||||||
|
# checks for
|
||||||
|
# * unused variables / imports
|
||||||
|
# * undefined variables
|
||||||
|
# * redefinition of variable from builtins or from an outer scope
|
||||||
|
# * use of variable before assigment
|
||||||
|
#
|
||||||
|
[VARIABLES]
|
||||||
|
|
||||||
|
# Tells wether we should check for unused import in __init__ files.
|
||||||
|
init-import=no
|
||||||
|
|
||||||
|
# A regular expression matching names used for dummy variables (i.e. not used).
|
||||||
|
dummy-variables-rgx=_|dummy
|
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
|
# you should avoid to define new builtins when possible.
|
||||||
|
additional-builtins=
|
||||||
|
|
||||||
|
|
||||||
|
# checks for :
|
||||||
|
# * doc strings
|
||||||
|
# * modules / classes / functions / methods / arguments / variables name
|
||||||
|
# * number of arguments, local variables, branchs, returns and statements in
|
||||||
|
# functions, methods
|
||||||
|
# * required module attributes
|
||||||
|
# * dangerous default values as arguments
|
||||||
|
# * redefinition of function / method / class
|
||||||
|
# * uses of the global statement
|
||||||
|
#
|
||||||
|
[BASIC]
|
||||||
|
|
||||||
|
# Required attributes for module, separated by a comma
|
||||||
|
required-attributes=
|
||||||
|
|
||||||
|
# Regular expression which should only match functions or classes name which do
|
||||||
|
# not require a docstring
|
||||||
|
no-docstring-rgx=__.*__
|
||||||
|
|
||||||
|
# Regular expression which should only match correct module names
|
||||||
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct module level names
|
||||||
|
const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct class names
|
||||||
|
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct function names
|
||||||
|
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct method names
|
||||||
|
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct instance attribute names
|
||||||
|
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct argument names
|
||||||
|
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct variable names
|
||||||
|
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||||
|
|
||||||
|
# Regular expression which should only match correct list comprehension /
|
||||||
|
# generator expression variable names
|
||||||
|
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
|
||||||
|
# Good variable names which should always be accepted, separated by a comma
|
||||||
|
good-names=i,j,k,ex,Run,_
|
||||||
|
|
||||||
|
# Bad variable names which should always be refused, separated by a comma
|
||||||
|
bad-names=foo,bar,baz,toto,tutu,tata
|
||||||
|
|
||||||
|
# List of builtins function names that should not be used, separated by a comma
|
||||||
|
bad-functions=map,filter,apply,input
|
||||||
|
|
||||||
|
|
||||||
|
# checks for sign of poor/misdesign:
|
||||||
|
# * number of methods, attributes, local variables...
|
||||||
|
# * size, complexity of functions, methods
|
||||||
|
#
|
||||||
|
[DESIGN]
|
||||||
|
|
||||||
|
# Maximum number of arguments for function / method
|
||||||
|
max-args=5
|
||||||
|
|
||||||
|
# Maximum number of locals for function / method body
|
||||||
|
max-locals=15
|
||||||
|
|
||||||
|
# Maximum number of return / yield for function / method body
|
||||||
|
max-returns=6
|
||||||
|
|
||||||
|
# Maximum number of branch for function / method body
|
||||||
|
max-branchs=12
|
||||||
|
|
||||||
|
# Maximum number of statements in function / method body
|
||||||
|
max-statements=50
|
||||||
|
|
||||||
|
# Maximum number of parents for a class (see R0901).
|
||||||
|
max-parents=7
|
||||||
|
|
||||||
|
# Maximum number of attributes for a class (see R0902).
|
||||||
|
max-attributes=7
|
||||||
|
|
||||||
|
# Minimum number of public methods for a class (see R0903).
|
||||||
|
min-public-methods=2
|
||||||
|
|
||||||
|
# Maximum number of public methods for a class (see R0904).
|
||||||
|
max-public-methods=20
|
||||||
|
|
||||||
|
|
||||||
|
# checks for
|
||||||
|
# * external modules dependencies
|
||||||
|
# * relative / wildcard imports
|
||||||
|
# * cyclic imports
|
||||||
|
# * uses of deprecated modules
|
||||||
|
#
|
||||||
|
[IMPORTS]
|
||||||
|
|
||||||
|
# Deprecated modules which should not be used, separated by a comma
|
||||||
|
deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
|
||||||
|
|
||||||
|
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||||
|
# given file (report R0402 must not be disabled)
|
||||||
|
import-graph=
|
||||||
|
|
||||||
|
# Create a graph of external dependencies in the given file (report R0402 must
|
||||||
|
# not be disabled)
|
||||||
|
ext-import-graph=
|
||||||
|
|
||||||
|
# Create a graph of internal dependencies in the given file (report R0402 must
|
||||||
|
# not be disabled)
|
||||||
|
int-import-graph=
|
||||||
|
|
||||||
|
|
||||||
|
# checks for :
|
||||||
|
# * methods without self as first argument
|
||||||
|
# * overridden methods signature
|
||||||
|
# * access only to existant members via self
|
||||||
|
# * attributes not defined in the __init__ method
|
||||||
|
# * supported interfaces implementation
|
||||||
|
# * unreachable code
|
||||||
|
#
|
||||||
|
[CLASSES]
|
||||||
|
|
||||||
|
# List of interface methods to ignore, separated by a comma. This is used for
|
||||||
|
# instance to not check methods defines in Zope's Interface base class.
|
||||||
|
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||||
|
|
||||||
|
# List of method names used to declare (i.e. assign) instance attributes.
|
||||||
|
defining-attr-methods=__init__,__new__,setUp
|
||||||
|
|
||||||
|
|
||||||
|
# checks for similarities and duplicated code. This computation may be
|
||||||
|
# memory / CPU intensive, so you should disable it if you experiments some
|
||||||
|
# problems.
|
||||||
|
#
|
||||||
|
[SIMILARITIES]
|
||||||
|
|
||||||
|
# Minimum lines number of a similarity.
|
||||||
|
min-similarity-lines=4
|
||||||
|
|
||||||
|
# Ignore comments when computing similarities.
|
||||||
|
ignore-comments=yes
|
||||||
|
|
||||||
|
# Ignore docstrings when computing similarities.
|
||||||
|
ignore-docstrings=yes
|
||||||
|
|
||||||
|
|
||||||
|
# checks for:
|
||||||
|
# * warning notes in the code like FIXME, XXX
|
||||||
|
# * PEP 263: source code with non ascii character but no encoding declaration
|
||||||
|
#
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
|
||||||
|
# List of note tags to take in consideration, separated by a comma.
|
||||||
|
notes=FIXME,XXX,TODO
|
||||||
|
|
||||||
|
|
||||||
|
# checks for :
|
||||||
|
# * unauthorized constructions
|
||||||
|
# * strict indentation
|
||||||
|
# * line length
|
||||||
|
# * use of <> instead of !=
|
||||||
|
#
|
||||||
|
[FORMAT]
|
||||||
|
|
||||||
|
# Maximum number of characters on a single line.
|
||||||
|
max-line-length=80
|
||||||
|
|
||||||
|
# Maximum number of lines in a module
|
||||||
|
max-module-lines=1000
|
||||||
|
|
||||||
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||||
|
# tab).
|
||||||
|
indent-string=' '
|
|
@ -36,6 +36,7 @@ from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from time import time
|
from time import time
|
||||||
from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
|
from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
|
||||||
|
from common import ged
|
||||||
|
|
||||||
from common import dbus_support
|
from common import dbus_support
|
||||||
if dbus_support.supported:
|
if dbus_support.supported:
|
||||||
|
@ -103,6 +104,31 @@ class Remote:
|
||||||
|
|
||||||
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
|
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||||
self.signal_object = SignalObject(bus_name)
|
self.signal_object = SignalObject(bus_name)
|
||||||
|
|
||||||
|
gajim.ged.register_event_handler('last-result-received', ged.POSTGUI,
|
||||||
|
self.on_last_status_time)
|
||||||
|
gajim.ged.register_event_handler('version-result-received', ged.POSTGUI,
|
||||||
|
self.on_os_info)
|
||||||
|
gajim.ged.register_event_handler('time-result-received', ged.POSTGUI,
|
||||||
|
self.on_time)
|
||||||
|
gajim.ged.register_event_handler('gmail-nofify', ged.POSTGUI,
|
||||||
|
self.on_gmail_notify)
|
||||||
|
|
||||||
|
def on_last_status_time(self, obj):
|
||||||
|
self.raise_signal('LastStatusTime', (obj.conn.name, [
|
||||||
|
obj.jid, obj.resource, obj.seconds, obj.status]))
|
||||||
|
|
||||||
|
def on_os_info(self, obj):
|
||||||
|
self.raise_signal('OsInfo', (obj.conn.name, [obj.jid, obj.resource,
|
||||||
|
obj.client_info, obj.os_info]))
|
||||||
|
|
||||||
|
def on_time(self, obj):
|
||||||
|
self.raise_signal('EntityTime', (obj.conn.name, [obj.jid, obj.resource,
|
||||||
|
obj.time_info]))
|
||||||
|
|
||||||
|
def on_gmail_notify(self, obj):
|
||||||
|
self.raise_signal('NewGmail', (obj.conn.name, [obj.jid, obj.newmsgs,
|
||||||
|
obj.gmail_messages_list]))
|
||||||
|
|
||||||
def raise_signal(self, signal, arg):
|
def raise_signal(self, signal, arg):
|
||||||
if self.signal_object:
|
if self.signal_object:
|
||||||
|
|
|
@ -53,6 +53,8 @@ import tooltips
|
||||||
import message_control
|
import message_control
|
||||||
import adhoc_commands
|
import adhoc_commands
|
||||||
import features_window
|
import features_window
|
||||||
|
import plugins
|
||||||
|
import plugins.gui
|
||||||
|
|
||||||
from common import gajim
|
from common import gajim
|
||||||
from common import helpers
|
from common import helpers
|
||||||
|
@ -3509,6 +3511,12 @@ class RosterWindow:
|
||||||
gajim.interface.instances['preferences'] = config.PreferencesWindow(
|
gajim.interface.instances['preferences'] = config.PreferencesWindow(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_plugins_menuitem_activate(self, widget):
|
||||||
|
if gajim.interface.instances.has_key('plugins'):
|
||||||
|
gajim.interface.instances['plugins'].window.present()
|
||||||
|
else:
|
||||||
|
gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
|
||||||
|
|
||||||
def on_publish_tune_toggled(self, widget, account):
|
def on_publish_tune_toggled(self, widget, account):
|
||||||
active = widget.get_active()
|
active = widget.get_active()
|
||||||
gajim.config.set_per('accounts', account, 'publish_tune', active)
|
gajim.config.set_per('accounts', account, 'publish_tune', active)
|
||||||
|
|
|
@ -272,6 +272,11 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
|
||||||
[full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject,
|
[full_jid_with_resource, msgtxt, tim, encrypted, msg_type, subject,
|
||||||
chatstate, msg_id, composing_xep, user_nick, xhtml, form_node]))
|
chatstate, msg_id, composing_xep, user_nick, xhtml, form_node]))
|
||||||
|
|
||||||
|
gajim.ged.raise_event('NewMessage',
|
||||||
|
(self.conn.name, [full_jid_with_resource, msgtxt, tim,
|
||||||
|
encrypted, msg_type, subject, chatstate, msg_id,
|
||||||
|
composing_xep, user_nick, xhtml, form_node]))
|
||||||
|
|
||||||
def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
|
def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
|
||||||
subject=None, resource='', msg_id=None, user_nick='',
|
subject=None, resource='', msg_id=None, user_nick='',
|
||||||
advanced_notif_num=None, xhtml=None, form_node=None, displaymarking=None):
|
advanced_notif_num=None, xhtml=None, form_node=None, displaymarking=None):
|
||||||
|
|
30
src/vcard.py
30
src/vcard.py
|
@ -42,6 +42,7 @@ import gtkgui_helpers
|
||||||
|
|
||||||
from common import helpers
|
from common import helpers
|
||||||
from common import gajim
|
from common import gajim
|
||||||
|
from common import ged
|
||||||
from common.i18n import Q_
|
from common.i18n import Q_
|
||||||
|
|
||||||
def get_avatar_pixbuf_encoded_mime(photo):
|
def get_avatar_pixbuf_encoded_mime(photo):
|
||||||
|
@ -125,6 +126,13 @@ class VcardWindow:
|
||||||
self.update_progressbar_timeout_id = gobject.timeout_add(100,
|
self.update_progressbar_timeout_id = gobject.timeout_add(100,
|
||||||
self.update_progressbar)
|
self.update_progressbar)
|
||||||
|
|
||||||
|
gajim.ged.register_event_handler('version-result-received', ged.GUI1,
|
||||||
|
self.set_os_info)
|
||||||
|
gajim.ged.register_event_handler('last-result-received', ged.GUI2,
|
||||||
|
self.set_last_status_time)
|
||||||
|
gajim.ged.register_event_handler('time-result-received', ged.GUI1,
|
||||||
|
self.set_entity_time)
|
||||||
|
|
||||||
self.fill_jabber_page()
|
self.fill_jabber_page()
|
||||||
annotations = gajim.connections[self.account].annotations
|
annotations = gajim.connections[self.account].annotations
|
||||||
if self.contact.jid in annotations:
|
if self.contact.jid in annotations:
|
||||||
|
@ -150,6 +158,12 @@ class VcardWindow:
|
||||||
if annotation != connection.annotations.get(self.contact.jid, ''):
|
if annotation != connection.annotations.get(self.contact.jid, ''):
|
||||||
connection.annotations[self.contact.jid] = annotation
|
connection.annotations[self.contact.jid] = annotation
|
||||||
connection.store_annotations()
|
connection.store_annotations()
|
||||||
|
gajim.ged.remove_event_handler('version-result-received', ged.GUI1,
|
||||||
|
self.set_os_info)
|
||||||
|
gajim.ged.remove_event_handler('last-result-received', ged.GUI2,
|
||||||
|
self.set_last_status_time)
|
||||||
|
gajim.ged.remove_event_handler('time-result-received', ged.GUI1,
|
||||||
|
self.set_entity_time)
|
||||||
|
|
||||||
def on_vcard_information_window_key_press_event(self, widget, event):
|
def on_vcard_information_window_key_press_event(self, widget, event):
|
||||||
if event.keyval == gtk.keysyms.Escape:
|
if event.keyval == gtk.keysyms.Escape:
|
||||||
|
@ -226,10 +240,10 @@ class VcardWindow:
|
||||||
self.progressbar.hide()
|
self.progressbar.hide()
|
||||||
self.update_progressbar_timeout_id = None
|
self.update_progressbar_timeout_id = None
|
||||||
|
|
||||||
def set_last_status_time(self):
|
def set_last_status_time(self, obj):
|
||||||
self.fill_status_label()
|
self.fill_status_label()
|
||||||
|
|
||||||
def set_os_info(self, resource, client_info, os_info):
|
def set_os_info(self, obj):
|
||||||
if self.xml.get_object('information_notebook').get_n_pages() < 5:
|
if self.xml.get_object('information_notebook').get_n_pages() < 5:
|
||||||
return
|
return
|
||||||
i = 0
|
i = 0
|
||||||
|
@ -237,9 +251,9 @@ class VcardWindow:
|
||||||
os = ''
|
os = ''
|
||||||
while i in self.os_info:
|
while i in self.os_info:
|
||||||
if not self.os_info[i]['resource'] or \
|
if not self.os_info[i]['resource'] or \
|
||||||
self.os_info[i]['resource'] == resource:
|
self.os_info[i]['resource'] == obj.resource:
|
||||||
self.os_info[i]['client'] = client_info
|
self.os_info[i]['client'] = obj.client_info
|
||||||
self.os_info[i]['os'] = os_info
|
self.os_info[i]['os'] = obj.os_info
|
||||||
if i > 0:
|
if i > 0:
|
||||||
client += '\n'
|
client += '\n'
|
||||||
os += '\n'
|
os += '\n'
|
||||||
|
@ -256,15 +270,15 @@ class VcardWindow:
|
||||||
self.os_info_arrived = True
|
self.os_info_arrived = True
|
||||||
self.test_remove_progressbar()
|
self.test_remove_progressbar()
|
||||||
|
|
||||||
def set_entity_time(self, resource, time_info):
|
def set_entity_time(self, obj):
|
||||||
if self.xml.get_object('information_notebook').get_n_pages() < 5:
|
if self.xml.get_object('information_notebook').get_n_pages() < 5:
|
||||||
return
|
return
|
||||||
i = 0
|
i = 0
|
||||||
time_s = ''
|
time_s = ''
|
||||||
while i in self.time_info:
|
while i in self.time_info:
|
||||||
if not self.time_info[i]['resource'] or \
|
if not self.time_info[i]['resource'] or \
|
||||||
self.time_info[i]['resource'] == resource:
|
self.time_info[i]['resource'] == obj.resource:
|
||||||
self.time_info[i]['time'] = time_info
|
self.time_info[i]['time'] = obj.time_info
|
||||||
if i > 0:
|
if i > 0:
|
||||||
time_s += '\n'
|
time_s += '\n'
|
||||||
time_s += self.time_info[i]['time']
|
time_s += self.time_info[i]['time']
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## This file is part of Gajim.
|
||||||
|
##
|
||||||
|
## Gajim is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published
|
||||||
|
## by the Free Software Foundation; version 3 only.
|
||||||
|
##
|
||||||
|
## Gajim is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License
|
||||||
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
##
|
||||||
|
|
||||||
|
'''
|
||||||
|
Testing PluginManager class.
|
||||||
|
|
||||||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:since: 05/30/2008
|
||||||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||||||
|
:license: GPL
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
gajim_root = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||||
|
sys.path.append(gajim_root + '/src')
|
||||||
|
|
||||||
|
# a temporary version of ~/.gajim for testing
|
||||||
|
configdir = gajim_root + '/test/tmp'
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
# define _ for i18n
|
||||||
|
import __builtin__
|
||||||
|
__builtin__._ = lambda x: x
|
||||||
|
|
||||||
|
# wipe config directory
|
||||||
|
import os
|
||||||
|
if os.path.isdir(configdir):
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(configdir)
|
||||||
|
|
||||||
|
os.mkdir(configdir)
|
||||||
|
|
||||||
|
import common.configpaths
|
||||||
|
common.configpaths.gajimpaths.init(configdir)
|
||||||
|
common.configpaths.gajimpaths.init_profile()
|
||||||
|
|
||||||
|
# for some reason common.gajim needs to be imported before xmpppy?
|
||||||
|
from common import gajim
|
||||||
|
from common import xmpp
|
||||||
|
|
||||||
|
gajim.DATA_DIR = gajim_root + '/data'
|
||||||
|
|
||||||
|
from common.stanza_session import StanzaSession
|
||||||
|
|
||||||
|
# name to use for the test account
|
||||||
|
account_name = 'test'
|
||||||
|
|
||||||
|
from plugins import PluginManager
|
||||||
|
|
||||||
|
class PluginManagerTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.pluginmanager = PluginManager()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_01_Singleton(self):
|
||||||
|
""" 1. Checking whether PluginManger class is singleton. """
|
||||||
|
self.pluginmanager.test_arg = 1
|
||||||
|
secondPluginManager = PluginManager()
|
||||||
|
|
||||||
|
self.failUnlessEqual(id(secondPluginManager), id(self.pluginmanager),
|
||||||
|
'Different IDs in references to PluginManager objects (not a singleton)')
|
||||||
|
self.failUnlessEqual(secondPluginManager.test_arg, 1,
|
||||||
|
'References point to different PluginManager objects (not a singleton')
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(PluginManagerTestCase)
|
||||||
|
return suite
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
runner = unittest.TextTestRunner()
|
||||||
|
test_suite = suite()
|
||||||
|
runner.run(test_suite)
|
Loading…
Reference in New Issue