merge plugin system to trunk
This commit is contained in:
commit
05a1af2c55
49 changed files with 6536 additions and 258 deletions
|
@ -19,7 +19,7 @@
|
|||
<object class="GtkHBox" id="hbox3024">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="gc_banner_status_image">
|
||||
<object class="GtkImage" id="banner_status_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
</object>
|
||||
|
|
644
data/gui/plugins_window.ui
Normal file
644
data/gui/plugins_window.ui
Normal file
|
@ -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>
|
||||
<object class="GtkImageMenuItem" id="join_gc_menuitem">
|
||||
<property name="label" translatable="yes">Join _Group Chat...</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="image">image3</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="accel_group">accelgroup1</property>
|
||||
|
@ -69,8 +69,8 @@
|
|||
<child>
|
||||
<object class="GtkImageMenuItem" id="add_new_contact_menuitem">
|
||||
<property name="label" translatable="yes">Add _Contact...</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="image">image4</property>
|
||||
<property name="use_stock">False</property>
|
||||
<property name="accel_group">accelgroup1</property>
|
||||
|
@ -159,6 +159,16 @@
|
|||
<signal name="activate" handler="on_preferences_menuitem_activate"/>
|
||||
</object>
|
||||
</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>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -437,4 +447,9 @@
|
|||
<property name="stock">gtk-properties</property>
|
||||
<property name="icon-size">1</property>
|
||||
</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>
|
||||
|
|
102
plugins/acronyms_expander.py
Normal file
102
plugins/acronyms_expander.py
Normal file
|
@ -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'])
|
2
plugins/banner_tweaks/__init__.py
Normal file
2
plugins/banner_tweaks/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
from plugin import BannerTweaksPlugin
|
75
plugins/banner_tweaks/config_dialog.ui
Normal file
75
plugins/banner_tweaks/config_dialog.ui
Normal file
|
@ -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>
|
205
plugins/banner_tweaks/plugin.py
Normal file
205
plugins/banner_tweaks/plugin.py
Normal file
|
@ -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()
|
1
plugins/dbus_plugin/__init__.py
Normal file
1
plugins/dbus_plugin/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from plugin import DBusPlugin
|
738
plugins/dbus_plugin/plugin.py
Normal file
738
plugins/dbus_plugin/plugin.py
Normal file
|
@ -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
|
1
plugins/events_dump/__init__.py
Normal file
1
plugins/events_dump/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from plugin import EventsDumpPlugin
|
129
plugins/events_dump/plugin.py
Normal file
129
plugins/events_dump/plugin.py
Normal file
|
@ -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
|
1
plugins/google_translation/__init__.py
Normal file
1
plugins/google_translation/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from plugin import GoogleTranslationPlugin
|
118
plugins/google_translation/plugin.py
Normal file
118
plugins/google_translation/plugin.py
Normal file
|
@ -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.
|
2
plugins/length_notifier/__init__.py
Normal file
2
plugins/length_notifier/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
from length_notifier import LengthNotifierPlugin
|
152
plugins/length_notifier/config_dialog.ui
Normal file
152
plugins/length_notifier/config_dialog.ui
Normal file
|
@ -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>
|
161
plugins/length_notifier/length_notifier.py
Normal file
161
plugins/length_notifier/length_notifier.py
Normal file
|
@ -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
|
1
plugins/new_events_example/__init__.py
Normal file
1
plugins/new_events_example/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from plugin import NewEventsExamplePlugin
|
147
plugins/new_events_example/plugin.py
Normal file
147
plugins/new_events_example/plugin.py
Normal file
|
@ -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
|
4
plugins/roster_buttons/__init__.py
Normal file
4
plugins/roster_buttons/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
__all__ = ['RosterButtonsPlugin']
|
||||
|
||||
from plugin import RosterButtonsPlugin
|
86
plugins/roster_buttons/plugin.py
Normal file
86
plugins/roster_buttons/plugin.py
Normal file
|
@ -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
|
70
plugins/roster_buttons/roster_buttons.ui
Normal file
70
plugins/roster_buttons/roster_buttons.ui
Normal file
|
@ -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>
|
772
plugins/snarl_notifications/PySnarl.py
Executable file
772
plugins/snarl_notifications/PySnarl.py
Executable file
|
@ -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)
|
1
plugins/snarl_notifications/__init__.py
Normal file
1
plugins/snarl_notifications/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from plugin import SnarlNotificationsPlugin
|
90
plugins/snarl_notifications/plugin.py
Normal file
90
plugins/snarl_notifications/plugin.py
Normal file
|
@ -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._update_banner_state_image()
|
||||
gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
|
||||
self)
|
||||
|
||||
def draw_banner_text(self):
|
||||
"""
|
||||
|
@ -409,6 +411,10 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
self.command_hits = []
|
||||
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):
|
||||
# now set the one the user selected
|
||||
per_type = 'contacts'
|
||||
|
@ -444,6 +450,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
|
|||
i += 1
|
||||
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):
|
||||
"""
|
||||
Override the default context menu and we prepend an option to switch
|
||||
|
@ -1585,6 +1597,10 @@ class ChatControl(ChatControlBase):
|
|||
else:
|
||||
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):
|
||||
if jingle_type not in ('audio', 'video'):
|
||||
return
|
||||
|
@ -2455,7 +2471,13 @@ class ChatControl(ChatControlBase):
|
|||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
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.contact.chatstate = None
|
||||
self.contact.our_chatstate = None
|
||||
|
|
|
@ -268,6 +268,8 @@ def check_and_possibly_create_paths():
|
|||
MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
|
||||
MY_CACHE = configpaths.gajimpaths['MY_CACHE']
|
||||
|
||||
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
|
||||
|
||||
if not os.path.exists(MY_DATA):
|
||||
create_path(MY_DATA)
|
||||
elif os.path.isfile(MY_DATA):
|
||||
|
@ -333,6 +335,13 @@ def check_and_possibly_create_paths():
|
|||
print _('Gajim will now 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):
|
||||
head, tail = os.path.split(directory)
|
||||
if not os.path.exists(head):
|
||||
|
|
|
@ -455,6 +455,9 @@ class Config:
|
|||
'roster': [opt_str, '', _("'yes', 'no' or ''")],
|
||||
'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 = {
|
||||
|
|
|
@ -140,7 +140,8 @@ class ConfigPaths:
|
|||
|
||||
d = {'MY_DATA': '', 'LOG_DB': u'logs.db', 'MY_CACERTS': u'cacerts.pem',
|
||||
'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:
|
||||
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('ICONS', None, os.path.join(basedir, windowsify(u'icons')))
|
||||
self.add('HOME', None, fse(os.path.expanduser('~')))
|
||||
self.add('PLUGINS_BASE', None, os.path.join(basedir,
|
||||
windowsify(u'plugins')))
|
||||
try:
|
||||
self.add('TMP', None, fse(tempfile.gettempdir()))
|
||||
except IOError, e:
|
||||
|
@ -172,14 +175,17 @@ class ConfigPaths:
|
|||
conffile = windowsify(u'config')
|
||||
pidfile = windowsify(u'gajim')
|
||||
secretsfile = windowsify(u'secrets')
|
||||
pluginsconfdir = windowsify(u'pluginsconfig')
|
||||
|
||||
if len(profile) > 0:
|
||||
conffile += u'.' + profile
|
||||
pidfile += u'.' + profile
|
||||
secretsfile += u'.' + profile
|
||||
pluginsconfdir += u'.' + profile
|
||||
pidfile += u'.pid'
|
||||
self.add('CONFIG_FILE', TYPE_CONFIG, conffile)
|
||||
self.add('PID_FILE', TYPE_CACHE, pidfile)
|
||||
self.add('SECRETS_FILE', TYPE_DATA, secretsfile)
|
||||
self.add('PLUGINS_CONFIG_DIR', TYPE_CONFIG, pluginsconfdir)
|
||||
|
||||
gajimpaths = ConfigPaths()
|
||||
|
|
|
@ -41,6 +41,7 @@ from calendar import timegm
|
|||
import datetime
|
||||
|
||||
import common.xmpp
|
||||
import common.caps_cache as capscache
|
||||
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
|
@ -50,7 +51,10 @@ from common.pubsub import ConnectionPubSub
|
|||
from common.pep import ConnectionPEP
|
||||
from common.protocol.caps import ConnectionCaps
|
||||
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:
|
||||
from common.jingle import ConnectionJingle
|
||||
else:
|
||||
|
@ -552,6 +556,9 @@ class ConnectionVcard:
|
|||
def _IqCB(self, con, iq_obj):
|
||||
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
|
||||
found_tim = None
|
||||
for tim in self.awaiting_timeouts:
|
||||
|
@ -808,33 +815,16 @@ class ConnectionHandlersBase:
|
|||
|
||||
def _ErrorCB(self, con, iq_obj):
|
||||
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())
|
||||
if id_ in self.last_ids:
|
||||
self.dispatch('LAST_STATUS_TIME', (jid_stripped, resource, -1, ''))
|
||||
self.last_ids.remove(id_)
|
||||
return
|
||||
gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
return True
|
||||
|
||||
def _LastResultCB(self, con, iq_obj):
|
||||
log.debug('LastResultCB')
|
||||
qp = iq_obj.getTag('query')
|
||||
seconds = qp.getAttr('seconds')
|
||||
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))
|
||||
gajim.nec.push_incoming_event(LastResultReceivedEvent(None, conn=self,
|
||||
iq_obj=iq_obj))
|
||||
|
||||
def get_sessions(self, jid):
|
||||
"""
|
||||
|
@ -1004,6 +994,9 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
self.gmail_last_tid = 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):
|
||||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
|
@ -1018,33 +1011,33 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
common.xmpp.protocol.ERR_NOT_AUTHORIZED)
|
||||
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):
|
||||
log.debug('HttpAuthCB')
|
||||
opt = gajim.config.get_per('accounts', self.name, 'http_auth')
|
||||
if opt in ('yes', 'no'):
|
||||
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))
|
||||
gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None, conn=self,
|
||||
iq_obj=iq_obj))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _ErrorCB(self, con, iq_obj):
|
||||
log.debug('ErrorCB')
|
||||
ConnectionHandlersBase._ErrorCB(self, con, iq_obj)
|
||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(jid_from)
|
||||
if ConnectionHandlersBase._ErrorCB(self, con, iq_obj):
|
||||
return
|
||||
id_ = unicode(iq_obj.getID())
|
||||
if id_ in self.version_ids:
|
||||
self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
|
||||
self.version_ids.remove(id_)
|
||||
gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
return
|
||||
if id_ in self.entity_time_ids:
|
||||
self.dispatch('ENTITY_TIME', (jid_stripped, resource, ''))
|
||||
self.entity_time_ids.remove(id_)
|
||||
gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
return
|
||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||
errmsg = iq_obj.getErrorMsg()
|
||||
errcode = iq_obj.getErrorCode()
|
||||
self.dispatch('ERROR_ANSWER', (id_, jid_from, errmsg, errcode))
|
||||
|
@ -1210,25 +1203,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
|
||||
def _VersionResultCB(self, con, iq_obj):
|
||||
log.debug('VersionResultCB')
|
||||
client_info = ''
|
||||
os_info = ''
|
||||
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))
|
||||
gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
|
||||
def _TimeCB(self, con, iq_obj):
|
||||
log.debug('TimeCB')
|
||||
|
@ -1260,50 +1236,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
|
||||
def _TimeRevisedResultCB(self, con, iq_obj):
|
||||
log.debug('TimeRevisedResultCB')
|
||||
time_info = ''
|
||||
qp = 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())
|
||||
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))
|
||||
gajim.nec.push_incoming_event(TimeResultReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
|
||||
def _gMailNewMailCB(self, con, gm):
|
||||
"""
|
||||
|
@ -1329,54 +1263,15 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
self.connection.send(iq)
|
||||
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
|
||||
in gmail account
|
||||
"""
|
||||
if not gm.getTag('mailbox'):
|
||||
return
|
||||
self.gmail_url = gm.getTag('mailbox').getAttr('url')
|
||||
if gm.getTag('mailbox').getNamespace() == common.xmpp.NS_GMAILNOTIFY:
|
||||
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
|
||||
log.debug('gMailQueryCB')
|
||||
gajim.nec.push_incoming_event(GMailQueryReceivedEvent(None,
|
||||
conn=self, iq_obj=iq_obj))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _rosterItemExchangeCB(self, con, msg):
|
||||
"""
|
||||
|
@ -1427,6 +1322,10 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
Called when we receive a message
|
||||
"""
|
||||
log.debug('MessageCB')
|
||||
|
||||
gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
|
||||
conn=con, xmpp_msg=msg, account=self.name))
|
||||
|
||||
mtype = msg.getType()
|
||||
|
||||
# check if the message is a roster item exchange (XEP-0144)
|
||||
|
@ -1782,6 +1681,8 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
"""
|
||||
Called when we receive a presence
|
||||
"""
|
||||
gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
|
||||
conn=con, xmpp_pres=prs))
|
||||
ptype = prs.getType()
|
||||
if ptype == 'available':
|
||||
ptype = None
|
||||
|
@ -2449,3 +2350,213 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
|
|||
con.RegisterHandler('presence', 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')
|
||||
|
||||
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
|
||||
|
||||
ged = None # Global Events Dispatcher
|
||||
nec = None # Network Events Controller
|
||||
plugin_manager = None # Plugins Manager
|
||||
|
||||
log = logging.getLogger('gajim')
|
||||
|
||||
|
@ -88,6 +90,9 @@ TMP = gajimpaths['TMP']
|
|||
DATA_DIR = gajimpaths['DATA']
|
||||
ICONS_DIR = gajimpaths['ICONS']
|
||||
HOME_DIR = gajimpaths['HOME']
|
||||
PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'],
|
||||
gajimpaths['PLUGINS_USER']]
|
||||
PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR']
|
||||
|
||||
try:
|
||||
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
||||
|
|
|
@ -30,6 +30,9 @@ log = logging.getLogger('gajim.common.ged')
|
|||
PRECORE = 30
|
||||
CORE = 40
|
||||
POSTCORE = 50
|
||||
GUI1 = 60
|
||||
GUI2 = 70
|
||||
POSTGUI = 80
|
||||
|
||||
class GlobalEventsDispatcher(object):
|
||||
|
||||
|
@ -61,4 +64,5 @@ class GlobalEventsDispatcher(object):
|
|||
log.debug('%s\nArgs: %s'%(event_name, str(args)))
|
||||
if event_name in self.handlers:
|
||||
for priority, handler in self.handlers[event_name]:
|
||||
handler(*args, **kwargs)
|
||||
if handler(*args, **kwargs):
|
||||
return
|
||||
|
|
134
src/common/nec.py
Normal file
134
src/common/nec.py
Normal file
|
@ -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
|
548
src/gajim-remote-plugin.py
Executable file
548
src/gajim-remote-plugin.py
Executable file
|
@ -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'
|
||||
INTERFACE = 'org.gajim.dbus.RemoteInterface'
|
||||
SERVICE = 'org.gajim.dbus'
|
||||
BASENAME = 'gajim-remote'
|
||||
BASENAME = 'gajim-remote-plugin'
|
||||
|
||||
class GajimRemote:
|
||||
|
||||
|
|
|
@ -1669,6 +1669,10 @@ class GroupchatControl(ChatControlBase):
|
|||
del win._controls[self.account][self.contact.jid]
|
||||
|
||||
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
|
||||
self.autorejoin = False
|
||||
|
||||
|
|
|
@ -148,23 +148,24 @@ class Interface:
|
|||
self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
|
||||
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))
|
||||
def response(account, iq_obj, answer):
|
||||
gajim.connections[account].build_http_auth_answer(iq_obj, answer)
|
||||
def response(account, answer):
|
||||
obj.conn.build_http_auth_answer(obj.iq_obj, answer)
|
||||
|
||||
def on_yes(is_checked, account, iq_obj):
|
||||
response(account, iq_obj, 'yes')
|
||||
def on_yes(is_checked, obj):
|
||||
response(obj, 'yes')
|
||||
|
||||
account = obj.conn.name
|
||||
sec_msg = _('Do you accept this request?')
|
||||
if gajim.get_number_of_connected_accounts() > 1:
|
||||
sec_msg = _('Do you accept this request on account %s?') % account
|
||||
if data[4]:
|
||||
sec_msg = data[4] + '\n' + sec_msg
|
||||
if obj.msg:
|
||||
sec_msg = obj.msg + '\n' + sec_msg
|
||||
dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
|
||||
'%(url)s (id: %(id)s)') % {'method': data[0], 'url': data[1],
|
||||
'id': data[2]}, sec_msg, on_response_yes=(on_yes, account, data[3]),
|
||||
on_response_no=(response, account, data[3], 'no'))
|
||||
'%(url)s (id: %(id)s)') % {'method': obj.method, 'url': obj.url,
|
||||
'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
|
||||
on_response_no=(response, obj, 'no'))
|
||||
|
||||
def handle_event_error_answer(self, account, array):
|
||||
#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
|
||||
|
@ -825,57 +826,24 @@ class Interface:
|
|||
if self.remote_ctrl:
|
||||
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))
|
||||
tim = array[2]
|
||||
if tim < 0:
|
||||
if obj.seconds < 0:
|
||||
# Ann error occured
|
||||
return
|
||||
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]]
|
||||
c = gajim.contacts.get_contact(account, array[0], array[1])
|
||||
account = obj.conn.name
|
||||
c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
|
||||
if c: # c can be none if it's a gc contact
|
||||
if array[3]:
|
||||
c.status = array[3]
|
||||
if obj.status:
|
||||
c.status = obj.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':
|
||||
c.last_status_time = last_time
|
||||
else:
|
||||
c.last_activity_time = last_time
|
||||
if win:
|
||||
win.set_last_status_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))
|
||||
if self.roster.tooltip.id and self.roster.tooltip.win:
|
||||
self.roster.tooltip.update_last_time(last_time)
|
||||
|
||||
def handle_event_gc_notify(self, account, array):
|
||||
#'GC_NOTIFY' (account, (room_jid, show, status, nick,
|
||||
|
@ -1321,45 +1289,42 @@ class Interface:
|
|||
notify.popup(event_type, jid, account, 'file-send-error', path,
|
||||
event_type, file_props['name'])
|
||||
|
||||
def handle_event_gmail_notify(self, account, array):
|
||||
jid = array[0]
|
||||
gmail_new_messages = int(array[1])
|
||||
gmail_messages_list = array[2]
|
||||
if gajim.config.get('notify_on_new_gmail_email'):
|
||||
path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
|
||||
title = _('New mail on %(gmail_mail_address)s') % \
|
||||
{'gmail_mail_address': jid}
|
||||
text = i18n.ngettext('You have %d new mail conversation',
|
||||
'You have %d new mail conversations', gmail_new_messages,
|
||||
gmail_new_messages, gmail_new_messages)
|
||||
def handle_event_gmail_notify(self, obj):
|
||||
jid = obj.jid
|
||||
gmail_new_messages = int(obj.newmsgs)
|
||||
gmail_messages_list = obj.gmail_messages_list
|
||||
if not gajim.config.get('notify_on_new_gmail_email'):
|
||||
return
|
||||
path = gtkgui_helpers.get_icon_path('gajim-new_email_recv', 48)
|
||||
title = _('New mail on %(gmail_mail_address)s') % \
|
||||
{'gmail_mail_address': jid}
|
||||
text = i18n.ngettext('You have %d new mail conversation',
|
||||
'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'):
|
||||
cnt = 0
|
||||
for gmessage in gmail_messages_list:
|
||||
# FIXME: emulate Gtalk client popups. find out what they
|
||||
# parse and how they decide what to show each message has a
|
||||
# 'From', 'Subject' and 'Snippet' field
|
||||
if cnt >= 5:
|
||||
break
|
||||
senders = ',\n '.join(reversed(gmessage['From']))
|
||||
text += _('\n\nFrom: %(from_address)s\nSubject: '
|
||||
'%(subject)s\n%(snippet)s') % \
|
||||
{'from_address': senders,
|
||||
'subject': gmessage['Subject'],
|
||||
'snippet': gmessage['Snippet']}
|
||||
cnt += 1
|
||||
if gajim.config.get('notify_on_new_gmail_email_extra'):
|
||||
cnt = 0
|
||||
for gmessage in gmail_messages_list:
|
||||
# FIXME: emulate Gtalk client popups. find out what they
|
||||
# parse and how they decide what to show each message has a
|
||||
# 'From', 'Subject' and 'Snippet' field
|
||||
if cnt >= 5:
|
||||
break
|
||||
senders = ',\n '.join(reversed(gmessage['From']))
|
||||
text += _('\n\nFrom: %(from_address)s\nSubject: '
|
||||
'%(subject)s\n%(snippet)s') % {'from_address': senders,
|
||||
'subject': gmessage['Subject'],
|
||||
'snippet': gmessage['Snippet']}
|
||||
cnt += 1
|
||||
|
||||
command = gajim.config.get('notify_on_new_gmail_email_command')
|
||||
if command:
|
||||
Popen(command, shell=True)
|
||||
command = gajim.config.get('notify_on_new_gmail_email_command')
|
||||
if command:
|
||||
Popen(command, shell=True)
|
||||
|
||||
if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
|
||||
helpers.play_sound('gmail_received')
|
||||
notify.popup(_('New E-mail'), jid, account, 'gmail',
|
||||
path_to_image=path, title=title, text=text)
|
||||
|
||||
if self.remote_ctrl:
|
||||
self.remote_ctrl.raise_signal('NewGmail', (account, array))
|
||||
if gajim.config.get_per('soundevents', 'gmail_received', 'enabled'):
|
||||
helpers.play_sound('gmail_received')
|
||||
notify.popup(_('New E-mail'), jid, obj.conn.name, 'gmail',
|
||||
path_to_image=path, title=title, text=text)
|
||||
|
||||
def handle_event_file_request_error(self, account, array):
|
||||
# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
|
||||
|
@ -2122,9 +2087,6 @@ class Interface:
|
|||
'ACC_OK': [self.handle_event_acc_ok],
|
||||
'MYVCARD': [self.handle_event_myvcard],
|
||||
'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_MSG': [self.handle_event_gc_msg],
|
||||
'GC_SUBJECT': [self.handle_event_gc_subject],
|
||||
|
@ -2140,12 +2102,10 @@ class Interface:
|
|||
'CON_TYPE': [self.handle_event_con_type],
|
||||
'CONNECTION_LOST': [self.handle_event_connection_lost],
|
||||
'FILE_REQUEST': [self.handle_event_file_request],
|
||||
'GMAIL_NOTIFY': [self.handle_event_gmail_notify],
|
||||
'FILE_REQUEST_ERROR': [self.handle_event_file_request_error],
|
||||
'FILE_SEND_ERROR': [self.handle_event_file_send_error],
|
||||
'STANZA_ARRIVED': [self.handle_event_stanza_arrived],
|
||||
'STANZA_SENT': [self.handle_event_stanza_sent],
|
||||
'HTTP_AUTH': [self.handle_event_http_auth],
|
||||
'VCARD_PUBLISHED': [self.handle_event_vcard_published],
|
||||
'VCARD_NOT_PUBLISHED': [self.handle_event_vcard_not_published],
|
||||
'ASK_NEW_NICK': [self.handle_event_ask_new_nick],
|
||||
|
@ -2186,7 +2146,10 @@ class Interface:
|
|||
'JINGLE_DISCONNECTED': [self.handle_event_jingle_disconnected],
|
||||
'JINGLE_ERROR': [self.handle_event_jingle_error],
|
||||
'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):
|
||||
|
@ -2197,7 +2160,7 @@ class Interface:
|
|||
"""
|
||||
for event_name, event_handlers in self.handlers.iteritems():
|
||||
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)
|
||||
|
||||
################################################################################
|
||||
|
@ -3246,6 +3209,10 @@ class Interface:
|
|||
self.show_systray()
|
||||
|
||||
self.roster = roster_window.RosterWindow()
|
||||
# Creating plugin manager
|
||||
import plugins
|
||||
gajim.plugin_manager = plugins.PluginManager()
|
||||
|
||||
self.roster._before_fill()
|
||||
for account in gajim.connections:
|
||||
gajim.connections[account].load_roster_from_db()
|
||||
|
@ -3276,7 +3243,6 @@ class Interface:
|
|||
pass
|
||||
gobject.timeout_add_seconds(5, remote_init)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
gajim.interface = self
|
||||
gajim.thread_interface = ThreadInterface
|
||||
|
@ -3398,6 +3364,9 @@ class Interface:
|
|||
|
||||
# Creating Global Events Dispatcher
|
||||
gajim.ged = ged.GlobalEventsDispatcher()
|
||||
# Creating Network Events Controller
|
||||
from common import nec
|
||||
gajim.nec = nec.NetworkEventsController()
|
||||
self.create_core_handlers_list()
|
||||
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
|
||||
MessageWindow
|
||||
|
|
30
src/plugins/__init__.py
Normal file
30
src/plugins/__init__.py
Normal file
|
@ -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']
|
220
src/plugins/gui.py
Normal file
220
src/plugins/gui.py
Normal file
|
@ -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
|
140
src/plugins/helpers.py
Normal file
140
src/plugins/helpers.py
Normal file
|
@ -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
|
234
src/plugins/plugin.py
Normal file
234
src/plugins/plugin.py
Normal file
|
@ -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
|
456
src/plugins/pluginmanager.py
Normal file
456
src/plugins/pluginmanager.py
Normal file
|
@ -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
|
410
src/pycallgraph.py
Normal file
410
src/pycallgraph.py
Normal file
|
@ -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)
|
310
src/pylint.rc
Normal file
310
src/pylint.rc
Normal file
|
@ -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 time import time
|
||||
from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
|
||||
from common import ged
|
||||
|
||||
from common import dbus_support
|
||||
if dbus_support.supported:
|
||||
|
@ -103,6 +104,31 @@ class Remote:
|
|||
|
||||
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||
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):
|
||||
if self.signal_object:
|
||||
|
|
|
@ -53,6 +53,8 @@ import tooltips
|
|||
import message_control
|
||||
import adhoc_commands
|
||||
import features_window
|
||||
import plugins
|
||||
import plugins.gui
|
||||
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
|
@ -3509,6 +3511,12 @@ class RosterWindow:
|
|||
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):
|
||||
active = widget.get_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,
|
||||
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='',
|
||||
subject=None, resource='', msg_id=None, user_nick='',
|
||||
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 gajim
|
||||
from common import ged
|
||||
from common.i18n import Q_
|
||||
|
||||
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)
|
||||
|
||||
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()
|
||||
annotations = gajim.connections[self.account].annotations
|
||||
if self.contact.jid in annotations:
|
||||
|
@ -150,6 +158,12 @@ class VcardWindow:
|
|||
if annotation != connection.annotations.get(self.contact.jid, ''):
|
||||
connection.annotations[self.contact.jid] = annotation
|
||||
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):
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
|
@ -226,10 +240,10 @@ class VcardWindow:
|
|||
self.progressbar.hide()
|
||||
self.update_progressbar_timeout_id = None
|
||||
|
||||
def set_last_status_time(self):
|
||||
def set_last_status_time(self, obj):
|
||||
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:
|
||||
return
|
||||
i = 0
|
||||
|
@ -237,9 +251,9 @@ class VcardWindow:
|
|||
os = ''
|
||||
while i in self.os_info:
|
||||
if not self.os_info[i]['resource'] or \
|
||||
self.os_info[i]['resource'] == resource:
|
||||
self.os_info[i]['client'] = client_info
|
||||
self.os_info[i]['os'] = os_info
|
||||
self.os_info[i]['resource'] == obj.resource:
|
||||
self.os_info[i]['client'] = obj.client_info
|
||||
self.os_info[i]['os'] = obj.os_info
|
||||
if i > 0:
|
||||
client += '\n'
|
||||
os += '\n'
|
||||
|
@ -256,15 +270,15 @@ class VcardWindow:
|
|||
self.os_info_arrived = True
|
||||
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:
|
||||
return
|
||||
i = 0
|
||||
time_s = ''
|
||||
while i in self.time_info:
|
||||
if not self.time_info[i]['resource'] or \
|
||||
self.time_info[i]['resource'] == resource:
|
||||
self.time_info[i]['time'] = time_info
|
||||
self.time_info[i]['resource'] == obj.resource:
|
||||
self.time_info[i]['time'] = obj.time_info
|
||||
if i > 0:
|
||||
time_s += '\n'
|
||||
time_s += self.time_info[i]['time']
|
||||
|
|
93
test/test_pluginmanager.py
Normal file
93
test/test_pluginmanager.py
Normal file
|
@ -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…
Add table
Reference in a new issue