Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndré Apitzsch <git@apitzsch.eu>2017-06-21 00:09:12 +0300
committerAndré Apitzsch <git@apitzsch.eu>2017-07-02 18:25:47 +0300
commit125ce523e44f11620de2cb09c55bce95d03f0993 (patch)
tree59fcf5f02b403d48c61da8707324cc7e19a4dd7f /src
parent68a57e7c917244be40d569700f09ab67f11db45a (diff)
Rename src directory
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am18
-rw-r--r--src/adhoc_commands.py651
-rw-r--r--src/advanced_configuration_window.py342
-rw-r--r--src/app_actions.py222
-rw-r--r--src/atom_window.py152
-rw-r--r--src/cell_renderer_image.py159
-rw-r--r--src/chat_control.py1881
-rw-r--r--src/chat_control_base.py1369
-rw-r--r--src/command_system/__init__.py20
-rw-r--r--src/command_system/dispatcher.py118
-rw-r--r--src/command_system/errors.py53
-rw-r--r--src/command_system/framework.py340
-rw-r--r--src/command_system/implementation/__init__.py20
-rw-r--r--src/command_system/implementation/custom.py122
-rw-r--r--src/command_system/implementation/execute.py120
-rw-r--r--src/command_system/implementation/hosts.py42
-rw-r--r--src/command_system/implementation/middleware.py189
-rw-r--r--src/command_system/implementation/standard.py409
-rw-r--r--src/command_system/mapping.py341
-rw-r--r--src/command_system/tools.py33
-rw-r--r--src/common/__init__.py0
-rw-r--r--src/common/account.py35
-rw-r--r--src/common/atom.py176
-rw-r--r--src/common/caps_cache.py440
-rw-r--r--src/common/check_X509.py183
-rw-r--r--src/common/check_paths.py364
-rw-r--r--src/common/commands.py495
-rw-r--r--src/common/config.py826
-rw-r--r--src/common/configpaths.py197
-rw-r--r--src/common/connection.py2982
-rw-r--r--src/common/connection_handlers.py2297
-rw-r--r--src/common/connection_handlers_events.py2834
-rw-r--r--src/common/contacts.py868
-rw-r--r--src/common/crypto.py151
-rw-r--r--src/common/dataforms.py754
-rw-r--r--src/common/dbus_support.py189
-rw-r--r--src/common/defs.py44
-rw-r--r--src/common/dh.py233
-rw-r--r--src/common/events.py454
-rw-r--r--src/common/exceptions.py149
-rw-r--r--src/common/file_props.py166
-rwxr-xr-xsrc/common/fuzzyclock.py70
-rw-r--r--src/common/gajim.py486
-rw-r--r--src/common/ged.py102
-rw-r--r--src/common/gpg.py150
-rw-r--r--src/common/helpers.py1531
-rw-r--r--src/common/i18n.py105
-rw-r--r--src/common/idle.py99
-rw-r--r--src/common/jingle.py233
-rw-r--r--src/common/jingle_content.py240
-rw-r--r--src/common/jingle_ft.py413
-rw-r--r--src/common/jingle_ftstates.py229
-rw-r--r--src/common/jingle_rtp.py477
-rw-r--r--src/common/jingle_session.py833
-rw-r--r--src/common/jingle_transport.py449
-rw-r--r--src/common/jingle_xtls.py299
-rw-r--r--src/common/location_listener.py152
-rw-r--r--src/common/logger.py1193
-rw-r--r--src/common/logging_helpers.py196
-rw-r--r--src/common/message_archiving.py459
-rw-r--r--src/common/multimedia_helpers.py111
-rw-r--r--src/common/nec.py171
-rw-r--r--src/common/optparser.py1001
-rw-r--r--src/common/passwords.py175
-rw-r--r--src/common/pep.py473
-rw-r--r--src/common/protocol/__init__.py3
-rw-r--r--src/common/protocol/bytestream.py1023
-rw-r--r--src/common/protocol/caps.py118
-rw-r--r--src/common/proxy65_manager.py487
-rw-r--r--src/common/pubsub.py226
-rw-r--r--src/common/resolver.py157
-rw-r--r--src/common/rst_xhtml_generator.py169
-rw-r--r--src/common/sleepy.py145
-rw-r--r--src/common/socks5.py1474
-rw-r--r--src/common/stanza_session.py1213
-rw-r--r--src/common/zeroconf/__init__.py0
-rw-r--r--src/common/zeroconf/client_zeroconf.py864
-rw-r--r--src/common/zeroconf/connection_handlers_zeroconf.py114
-rw-r--r--src/common/zeroconf/connection_zeroconf.py384
-rw-r--r--src/common/zeroconf/roster_zeroconf.py159
-rw-r--r--src/common/zeroconf/zeroconf.py63
-rw-r--r--src/common/zeroconf/zeroconf_avahi.py482
-rw-r--r--src/common/zeroconf/zeroconf_bonjour.py335
-rw-r--r--src/config.py4346
-rw-r--r--src/conversation_textview.py1422
-rw-r--r--src/dataforms_widget.py687
-rw-r--r--src/dialogs.py5590
-rw-r--r--src/disco.py2223
-rw-r--r--src/features_window.py238
-rw-r--r--src/filetransfers_window.py1089
-rw-r--r--src/gajim-remote.py609
-rw-r--r--src/gajim.py398
-rw-r--r--src/gajim_themes_window.py409
-rw-r--r--src/groupchat_control.py2947
-rw-r--r--src/groups.py73
-rw-r--r--src/gtkexcepthook.py85
-rw-r--r--src/gtkgui_helpers.py1188
-rw-r--r--src/gtkspell.py84
-rw-r--r--src/gui_interface.py3177
-rw-r--r--src/gui_menu_builder.py770
-rw-r--r--src/history_manager.py674
-rw-r--r--src/history_window.py670
-rw-r--r--src/htmltextview.py1285
-rw-r--r--src/ipython_view.py629
-rw-r--r--src/logind_listener.py114
-rw-r--r--src/message_control.py257
-rw-r--r--src/message_textview.py310
-rw-r--r--src/message_window.py1312
-rw-r--r--src/music_track_listener.py303
-rw-r--r--src/negotiation.py86
-rw-r--r--src/network_watcher.py101
-rw-r--r--src/notify.py479
-rw-r--r--src/plugins/__init__.py30
-rw-r--r--src/plugins/gajimplugin.py293
-rw-r--r--src/plugins/gui.py353
-rw-r--r--src/plugins/helpers.py137
-rw-r--r--src/plugins/pluginmanager.py671
-rw-r--r--src/plugins/plugins_i18n.py40
-rw-r--r--src/profile_window.py420
-rw-r--r--src/pylint.rc310
-rw-r--r--src/remote_control.py948
-rw-r--r--src/roster_window.py6091
-rw-r--r--src/search_window.py247
-rw-r--r--src/secrets.py127
-rw-r--r--src/session.py554
-rw-r--r--src/shortcuts_window.py50
-rw-r--r--src/statusicon.py484
-rw-r--r--src/tooltips.py924
-rw-r--r--src/upower_listener.py47
-rw-r--r--src/vcard.py646
130 files changed, 0 insertions, 81428 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
deleted file mode 100644
index 6bf92b1bd..000000000
--- a/src/Makefile.am
+++ /dev/null
@@ -1,18 +0,0 @@
-INCLUDES = \
- $(PYTHON_INCLUDES)
-export MACOSX_DEPLOYMENT_TARGET=10.4
-
-gajimsrcdir = $(gajim_srcdir)
-nobase_dist_gajimsrc_PYTHON = \
- $(srcdir)/*.py \
- $(srcdir)/common/*.py \
- $(srcdir)/common/protocol/*.py \
- $(srcdir)/common/zeroconf/*.py \
- $(srcdir)/command_system/*.py \
- $(srcdir)/command_system/implementation/*.py \
- $(srcdir)/plugins/*.py
-
-dist-hook:
- rm -f $(distdir)/ipython_view.py
-
-MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py
deleted file mode 100644
index 1e427ddd0..000000000
--- a/src/adhoc_commands.py
+++ /dev/null
@@ -1,651 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/adhoc_commands.py
-##
-## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-# FIXME: think if we need caching command list. it may be wrong if there will
-# be entities that often change the list, it may be slow to fetch it every time
-
-from gi.repository import GLib
-from gi.repository import Gtk
-
-import nbxmpp
-from common import gajim
-from common import dataforms
-
-import gtkgui_helpers
-import dialogs
-import dataforms_widget
-
-class CommandWindow:
- """
- Class for a window for single ad-hoc commands session
-
- Note, that there might be more than one for one account/jid pair in one
- moment.
-
- TODO: Maybe put this window into MessageWindow? consider this when it will
- be possible to manage more than one window of one.
- TODO: Account/jid pair in MessageWindowMgr.
- TODO: GTK 2.10 has a special wizard-widget, consider using it...
- """
-
- def __init__(self, account, jid, commandnode=None):
- """
- Create new window
- """
-
- # an account object
- self.account = gajim.connections[account]
- self.jid = jid
- self.commandnode = commandnode
- self.data_form_widget = None
-
- # retrieving widgets from xml
- self.xml = gtkgui_helpers.get_gtk_builder('adhoc_commands_window.ui')
- self.window = self.xml.get_object('adhoc_commands_window')
- self.window.connect('delete-event',
- self.on_adhoc_commands_window_delete_event)
- for name in ('restart_button', 'back_button', 'forward_button',
- 'execute_button', 'finish_button', 'close_button', 'stages_notebook',
- 'retrieving_commands_stage_vbox', 'command_list_stage_vbox',
- 'command_list_vbox', 'sending_form_stage_vbox',
- 'sending_form_progressbar', 'notes_label', 'no_commands_stage_vbox',
- 'error_stage_vbox', 'error_description_label'):
- setattr(self, name, self.xml.get_object(name))
-
- self.initiate()
-
- def initiate(self):
-
- self.pulse_id = None # to satisfy self.setup_pulsing()
- self.commandlist = None # a list of (commandname, commanddescription)
-
- # command's data
- self.sessionid = None
- self.dataform = None
- self.allow_stage3_close = False
-
- # creating data forms widget
- if self.data_form_widget:
- self.sending_form_stage_vbox.remove(self.data_form_widget)
- self.data_form_widget.destroy()
- self.data_form_widget = dataforms_widget.DataFormWidget()
- self.data_form_widget.show()
- self.sending_form_stage_vbox.pack_start(self.data_form_widget, True, True, 0)
-
- if self.commandnode:
- # Execute command
- self.stage3()
- else:
- # setting initial stage
- self.stage1()
-
- # displaying the window
- self.window.set_title(_('Ad-hoc Commands - Gajim'))
- self.xml.connect_signals(self)
- self.window.show_all()
-
- self.restart_button.set_sensitive(False)
-
- # These functions are set up by appropriate stageX methods.
- def stage_finish(self, *anything):
- pass
-
- def stage_back_button_clicked(self, *anything):
- assert False
-
- def stage_forward_button_clicked(self, *anything):
- assert False
-
- def stage_execute_button_clicked(self, *anything):
- assert False
-
- def stage_close_button_clicked(self, *anything):
- assert False
-
- def stage_restart_button_clicked(self, *anything):
- assert False
-
- def stage_adhoc_commands_window_delete_event(self, *anything):
- assert False
-
- def do_nothing(self, *anything):
- return False
-
- # Widget callbacks...
- def on_back_button_clicked(self, *anything):
- return self.stage_back_button_clicked(*anything)
-
- def on_forward_button_clicked(self, *anything):
- return self.stage_forward_button_clicked(*anything)
-
- def on_execute_button_clicked(self, *anything):
- return self.stage_execute_button_clicked(*anything)
-
- def on_finish_button_clicked(self, *anything):
- return self.stage_finish_button_clicked(*anything)
-
- def on_close_button_clicked(self, *anything):
- return self.stage_close_button_clicked(*anything)
-
- def on_restart_button_clicked(self, *anything):
- return self.stage_restart_button_clicked(*anything)
-
- def on_adhoc_commands_window_destroy(self, *anything):
- # TODO: do all actions that are needed to remove this object from memory
- self.remove_pulsing()
-
- def on_adhoc_commands_window_delete_event(self, *anything):
- return self.stage_adhoc_commands_window_delete_event(self.window)
-
- def __del__(self):
- print('Object has been deleted.')
-
-# stage 1: waiting for command list
- def stage1(self):
- """
- Prepare the first stage. Request command list, set appropriate state of
- widgets
- """
- # close old stage...
- self.stage_finish()
-
- # show the stage
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.retrieving_commands_stage_vbox))
-
- # set widgets' state
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
- self.finish_button.set_sensitive(False)
-
- # request command list
- self.request_command_list()
- self.setup_pulsing(
- self.xml.get_object('retrieving_commands_progressbar'))
-
- # setup the callbacks
- self.stage_finish = self.stage1_finish
- self.stage_close_button_clicked = self.stage1_close_button_clicked
- self.stage_restart_button_clicked = self.stage1_restart_button_clicked
- self.stage_adhoc_commands_window_delete_event = \
- self.stage1_adhoc_commands_window_delete_event
-
- def stage1_finish(self):
- self.remove_pulsing()
-
- def stage1_close_button_clicked(self, widget):
- # cancelling in this stage is not critical, so we don't
- # show any popups to user
- self.stage_finish()
- self.window.destroy()
-
- def stage1_restart_button_clicked(self, widget):
- self.stage_finish()
- self.restart()
-
- def stage1_adhoc_commands_window_delete_event(self, widget):
- self.stage1_finish()
- return True
-
-# stage 2: choosing the command to execute
- def stage2(self):
- """
- Populate the command list vbox with radiobuttons
-
- FIXME: If there is more commands, maybe some kind of list, set widgets
- state
- """
- # close old stage
- self.stage_finish()
-
- assert len(self.commandlist)>0
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(self.command_list_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(True)
- self.execute_button.set_sensitive(False)
- self.finish_button.set_sensitive(False)
-
- # build the commands list radiobuttons
- first_radio = None
- for (commandnode, commandname) in self.commandlist:
- radio = Gtk.RadioButton.new_with_label_from_widget(first_radio, commandname)
- radio.connect("toggled", self.on_command_radiobutton_toggled,
- commandnode)
- if not first_radio:
- first_radio = radio
- self.commandnode = commandnode
- self.command_list_vbox.pack_start(radio, False, True, 0)
- self.command_list_vbox.show_all()
-
- self.stage_finish = self.stage2_finish
- self.stage_close_button_clicked = self.stage2_close_button_clicked
- self.stage_restart_button_clicked = self.stage2_restart_button_clicked
- self.stage_forward_button_clicked = self.stage2_forward_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
-
- def stage2_finish(self):
- """
- Remove widgets we created. Not needed when the window is destroyed
- """
- def remove_widget(widget, param):
- self.command_list_vbox.remove(widget)
- self.command_list_vbox.foreach(remove_widget, None)
-
- def stage2_close_button_clicked(self, widget):
- self.stage_finish()
- self.window.destroy()
-
- def stage2_restart_button_clicked(self, widget):
- self.stage_finish()
- self.restart()
-
- def stage2_forward_button_clicked(self, widget):
- self.stage3()
-
- def on_command_radiobutton_toggled(self, widget, commandnode):
- self.commandnode = commandnode
-
- def on_check_commands_1_button_clicked(self, widget):
- self.stage1()
-
-# stage 3: command invocation
- def stage3(self):
- # close old stage
- self.stage_finish()
-
- self.form_status = None
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(
- self.sending_form_stage_vbox))
-
- self.restart_button.set_sensitive(True)
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
- self.finish_button.set_sensitive(False)
-
- self.stage3_submit_form()
-
- self.stage_finish = self.stage3_finish
- self.stage_back_button_clicked = self.stage3_back_button_clicked
- self.stage_forward_button_clicked = self.stage3_forward_button_clicked
- self.stage_execute_button_clicked = self.stage3_execute_button_clicked
- self.stage_finish_button_clicked = self.stage3_finish_button_clicked
- self.stage_close_button_clicked = self.stage3_close_button_clicked
- self.stage_restart_button_clicked = self.stage3_restart_button_clicked
- self.stage_adhoc_commands_window_delete_event = \
- self.stage3_close_button_clicked
-
- def stage3_finish(self):
- pass
-
- def stage3_can_close(self, cb):
- if self.form_status == 'completed':
- cb()
- return
-
- def on_yes(button):
- self.send_cancel()
- dialog.destroy()
- cb()
-
- dialog = dialogs.HigDialog(self.window, Gtk.MessageType.WARNING,
- Gtk.ButtonsType.YES_NO, _('Cancel confirmation'),
- _('You are in process of executing command. Do you really want to '
- 'cancel it?'), on_response_yes=on_yes)
- dialog.popup()
-
- def stage3_close_button_clicked(self, widget):
- """
- We are in the middle of executing command. Ask user if he really want to
- cancel the process, then cancel it
- """
- # this works also as a handler for window_delete_event, so we have to
- # return appropriate values
- if self.allow_stage3_close:
- return False
-
- def on_ok():
- self.allow_stage3_close = True
- self.window.destroy()
-
- self.stage3_can_close(on_ok)
-
- return True # Block event, don't close window
-
- def stage3_restart_button_clicked(self, widget):
- def on_ok():
- self.restart()
-
- self.stage3_can_close(on_ok)
-
- def stage3_back_button_clicked(self, widget):
- self.stage3_submit_form('prev')
-
- def stage3_forward_button_clicked(self, widget):
- self.stage3_submit_form('next')
-
- def stage3_execute_button_clicked(self, widget):
- self.stage3_submit_form('execute')
-
- def stage3_finish_button_clicked(self, widget):
- self.stage3_submit_form('complete')
-
- def stage3_submit_form(self, action='execute'):
- self.data_form_widget.set_sensitive(False)
-
- if self.data_form_widget.get_data_form():
- df = self.data_form_widget.get_data_form()
- if not df.is_valid():
- dialogs.ErrorDialog(
- _('Invalid Form'),
- _('The form is not filled correctly.'),
- transient_for=self.window)
- self.data_form_widget.set_sensitive(True)
- return
- self.data_form_widget.data_form.type_ = 'submit'
- else:
- self.data_form_widget.hide()
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
- self.finish_button.set_sensitive(False)
-
- self.sending_form_progressbar.show()
- self.setup_pulsing(self.sending_form_progressbar)
- self.send_command(action)
-
- def stage3_next_form(self, command):
- if not isinstance(command, nbxmpp.Node):
- self.stage5(error=_('Service sent malformed data'), senderror=True)
- return
-
- self.remove_pulsing()
- self.sending_form_progressbar.hide()
-
- if not self.sessionid:
- self.sessionid = command.getAttr('sessionid')
- elif self.sessionid != command.getAttr('sessionid'):
- self.stage5(error=_('Service changed the session identifier.'),
- senderror=True)
- return
-
- self.form_status = command.getAttr('status')
-
- self.commandnode = command.getAttr('node')
- if command.getTag('x'):
- self.dataform = dataforms.ExtendForm(node=command.getTag('x'))
-
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.selectable = True
- self.data_form_widget.data_form = self.dataform
- except dataforms.Error:
- self.stage5(error=_('Service sent malformed data'),
- senderror=True)
- return
- self.data_form_widget.show()
- if self.data_form_widget.title:
- self.window.set_title(_('%s - Ad-hoc Commands - Gajim') % \
- self.data_form_widget.title)
- else:
- self.data_form_widget.hide()
-
- actions = command.getTag('actions')
- if actions:
- # actions, actions, actions...
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(actions.getTag('prev') is not None)
- self.forward_button.set_sensitive(
- actions.getTag('next') is not None)
- self.execute_button.set_sensitive(True)
- self.finish_button.set_sensitive(actions.getTag('complete') is not \
- None)
- else:
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(True)
- self.finish_button.set_sensitive(False)
-
- if self.form_status == 'completed':
- self.close_button.set_sensitive(True)
- self.back_button.hide()
- self.forward_button.hide()
- self.execute_button.hide()
- self.finish_button.hide()
- self.close_button.show()
- self.stage_adhoc_commands_window_delete_event = \
- self.stage3_close_button_clicked
-
- note = command.getTag('note')
- if note:
- self.notes_label.set_text(note.getData())
- self.notes_label.set_no_show_all(False)
- self.notes_label.show()
- else:
- self.notes_label.set_no_show_all(True)
- self.notes_label.hide()
-
- def restart(self):
- self.commandnode = None
- self.initiate()
-
-# stage 4: no commands are exposed
- def stage4(self):
- """
- Display the message. Wait for user to close the window
- """
- # close old stage
- self.stage_finish()
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(self.no_commands_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.set_sensitive(False)
- self.forward_button.set_sensitive(False)
- self.execute_button.set_sensitive(False)
- self.finish_button.set_sensitive(False)
-
- self.stage_finish = self.do_nothing
- self.stage_close_button_clicked = self.stage4_close_button_clicked
- self.stage_restart_button_clicked = self.stage4_restart_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
-
- def stage4_close_button_clicked(self, widget):
- self.window.destroy()
-
- def stage4_restart_button_clicked(self, widget):
- self.restart()
-
- def on_check_commands_2_button_clicked(self, widget):
- self.stage1()
-
-# stage 5: an error has occured
- def stage5(self, error=None, errorid=None, senderror=False):
- """
- Display the error message. Wait for user to close the window
- """
- # FIXME: sending error to responder
- # close old stage
- self.stage_finish()
-
- assert errorid or error
-
- if errorid:
- # we've got error code, display appropriate message
- try:
- errorname = nbxmpp.NS_STANZAS + ' ' + str(errorid)
- errordesc = nbxmpp.ERRORS[errorname][2]
- error = errordesc
- del errorname, errordesc
- except KeyError: # when stanza doesn't have error description
- error = _('Service returned an error.')
- elif error:
- # we've got error message
- pass
- else:
- # we don't know what's that, bailing out
- assert False
-
- self.stages_notebook.set_current_page(
- self.stages_notebook.page_num(self.error_stage_vbox))
-
- self.close_button.set_sensitive(True)
- self.back_button.hide()
- self.forward_button.hide()
- self.execute_button.hide()
- self.finish_button.hide()
-
- self.error_description_label.set_text(error)
-
- self.stage_finish = self.do_nothing
- self.stage_close_button_clicked = self.stage5_close_button_clicked
- self.stage_restart_button_clicked = self.stage5_restart_button_clicked
- self.stage_adhoc_commands_window_delete_event = self.do_nothing
-
- def stage5_close_button_clicked(self, widget):
- self.window.destroy()
-
- def stage5_restart_button_clicked(self, widget):
- self.restart()
-
-# helpers to handle pulsing in progressbar
- def setup_pulsing(self, progressbar):
- """
- Set the progressbar to pulse. Makes a custom function to repeatedly call
- progressbar.pulse() method
- """
- assert not self.pulse_id
- assert isinstance(progressbar, Gtk.ProgressBar)
-
- def callback():
- progressbar.pulse()
- return True # important to keep callback be called back!
-
- # 12 times per second (80 miliseconds)
- self.pulse_id = GLib.timeout_add(80, callback)
-
- def remove_pulsing(self):
- """
- Stop pulsing, useful when especially when removing widget
- """
- if self.pulse_id:
- GLib.source_remove(self.pulse_id)
- self.pulse_id = None
-
-# handling xml stanzas
- def request_command_list(self):
- """
- Request the command list. Change stage on delivery
- """
- query = nbxmpp.Iq(typ='get', to=nbxmpp.JID(self.jid),
- queryNS=nbxmpp.NS_DISCO_ITEMS)
- query.setQuerynode(nbxmpp.NS_COMMANDS)
-
- def callback(response):
- '''Called on response to query.'''
- # FIXME: move to connection_handlers.py
- # is error => error stage
- error = response.getError()
- if error:
- # extracting error description
- self.stage5(errorid=error)
- return
-
- # no commands => no commands stage
- # commands => command selection stage
- query = response.getTag('query')
- if query and query.getAttr('node') == nbxmpp.NS_COMMANDS:
- items = query.getTags('item')
- else:
- items = []
- if len(items)==0:
- self.commandlist = []
- self.stage4()
- else:
- self.commandlist = [(t.getAttr('node'), t.getAttr('name')) \
- for t in items]
- self.stage2()
-
- self.account.connection.SendAndCallForResponse(query, callback)
-
- def send_command(self, action='execute'):
- """
- Send the command with data form. Wait for reply
- """
- # create the stanza
- assert action in ('execute', 'prev', 'next', 'complete')
-
- stanza = nbxmpp.Iq(typ='set', to=self.jid)
- cmdnode = stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
- attrs={'node':self.commandnode, 'action':action})
-
- if self.sessionid:
- cmdnode.setAttr('sessionid', self.sessionid)
-
- if self.data_form_widget.data_form:
- cmdnode.addChild(node=self.data_form_widget.data_form.get_purged())
-
- def callback(response):
- # FIXME: move to connection_handlers.py
- err = response.getError()
- if err:
- self.stage5(errorid = err)
- else:
- self.stage3_next_form(response.getTag('command'))
-
- self.account.connection.SendAndCallForResponse(stanza, callback)
-
- def send_cancel(self):
- """
- Send the command with action='cancel'
- """
- assert self.commandnode
- if self.sessionid and self.account.connection:
- # we already have sessionid, so the service sent at least one reply.
- stanza = nbxmpp.Iq(typ='set', to=self.jid)
- stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS, attrs={
- 'node':self.commandnode,
- 'sessionid':self.sessionid,
- 'action':'cancel'
- })
-
- self.account.connection.send(stanza)
- else:
- # we did not received any reply from service;
- # FIXME: we should wait and then send cancel; for now we do nothing
- pass
diff --git a/src/advanced_configuration_window.py b/src/advanced_configuration_window.py
deleted file mode 100644
index a3e06b7af..000000000
--- a/src/advanced_configuration_window.py
+++ /dev/null
@@ -1,342 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/advanced.py
-##
-## Copyright (C) 2005 Travis Shirk <travis AT pobox.com>
-## Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from enum import IntEnum, unique
-
-from gi.repository import Gtk
-import gtkgui_helpers
-from gi.repository import GLib
-from gi.repository import Pango
-
-from common import gajim
-
-@unique
-class Column(IntEnum):
- PREFERENCE_NAME = 0
- VALUE = 1
- TYPE = 2
-
-def rate_limit(rate):
- """
- Call func at most *rate* times per second
- """
- def decorator(func):
- timeout = [None]
- def f(*args, **kwargs):
- if timeout[0] is not None:
- GLib.source_remove(timeout[0])
- timeout[0] = None
- def timeout_func():
- func(*args, **kwargs)
- timeout[0] = None
- timeout[0] = GLib.timeout_add(int(1000.0 / rate), timeout_func)
- return f
- return decorator
-
-def tree_model_iter_children(model, treeiter):
- it = model.iter_children(treeiter)
- while it:
- yield it
- it = model.iter_next(it)
-
-def tree_model_pre_order(model, treeiter):
- yield treeiter
- for childiter in tree_model_iter_children(model, treeiter):
- for it in tree_model_pre_order(model, childiter):
- yield it
-
-
-class AdvancedConfigurationWindow(object):
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('advanced_configuration_window.ui')
- self.window = self.xml.get_object('advanced_configuration_window')
- self.window.set_transient_for(
- gajim.interface.instances['preferences'].window)
- self.entry = self.xml.get_object('advanced_entry')
- self.desc_label = self.xml.get_object('advanced_desc_label')
- self.restart_box = self.xml.get_object('restart_box')
- self.reset_button = self.xml.get_object('reset_button')
-
- # Format:
- # key = option name (root/subopt/opt separated by \n then)
- # value = array(oldval, newval)
- self.changed_opts = {}
-
- # For i18n
- self.right_true_dict = {True: _('Activated'), False: _('Deactivated')}
- self.types = {
- 'boolean': _('Boolean'),
- 'integer': _('Integer'),
- 'string': _('Text'),
- 'color': _('Color')}
-
- treeview = self.xml.get_object('advanced_treeview')
- self.treeview = treeview
- self.model = Gtk.TreeStore(str, str, str)
- self.fill_model()
- self.model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
- self.modelfilter = self.model.filter_new()
- self.modelfilter.set_visible_func(self.visible_func)
-
- renderer_text = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Preference Name'), renderer_text, text = 0)
- treeview.insert_column(col, -1)
- col.set_resizable(True)
-
- renderer_text = Gtk.CellRendererText()
- renderer_text.connect('edited', self.on_config_edited)
- renderer_text.set_property('ellipsize', Pango.EllipsizeMode.END)
- col = Gtk.TreeViewColumn(_('Value'),renderer_text, text = 1)
- treeview.insert_column(col, -1)
- col.set_cell_data_func(renderer_text, self.cb_value_column_data)
-
- col.props.resizable = True
- col.props.expand = True
- col.props.sizing = Gtk.TreeViewColumnSizing.FIXED
-
- renderer_text = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Type'), renderer_text, text = 2)
- treeview.insert_column(col, -1)
- col.props.sizing = Gtk.TreeViewColumnSizing.FIXED
-
- treeview.set_model(self.modelfilter)
-
- # connect signal for selection change
- treeview.get_selection().connect('changed',
- self.on_advanced_treeview_selection_changed)
-
- self.xml.connect_signals(self)
- self.restart_box.set_no_show_all(True)
- self.window.show_all()
- gajim.interface.instances['advanced_config'] = self
-
- def cb_value_column_data(self, col, cell, model, iter_, data):
- """
- Check if it's boolen or holds password stuff and if yes make the
- cellrenderertext not editable, else - it's editable
- """
- optname = model[iter_][Column.PREFERENCE_NAME]
- opttype = model[iter_][Column.TYPE]
- if opttype == self.types['boolean'] or optname == 'password':
- cell.set_property('editable', False)
- else:
- cell.set_property('editable', True)
-
- @staticmethod
- def get_option_path(model, iter_):
- # It looks like path made from reversed array
- # path[0] is the true one optname
- # path[1] is the key name
- # path[2] is the root of tree
- # last two is optional
- path = [model[iter_][0]]
- parent = model.iter_parent(iter_)
- while parent:
- path.append(model[parent][0])
- parent = model.iter_parent(parent)
- return path
-
- def on_advanced_treeview_selection_changed(self, treeselection):
- model, iter_ = treeselection.get_selected()
- # Check for GtkTreeIter
- if iter_:
- opt_path = self.get_option_path(model, iter_)
- # Get text from first column in this row
- desc = None
- if len(opt_path) == 3:
- desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
- opt_path[0])
- elif len(opt_path) == 1:
- desc = gajim.config.get_desc(opt_path[0])
- if desc:
- self.desc_label.set_text(desc)
- else:
- #we talk about option description in advanced configuration editor
- self.desc_label.set_text(_('(None)'))
- if len(opt_path) == 3 or (len(opt_path) == 1 and not \
- model.iter_has_child(iter_)):
- self.reset_button.set_sensitive(True)
- else:
- self.reset_button.set_sensitive(False)
- else:
- self.reset_button.set_sensitive(False)
-
- def remember_option(self, option, oldval, newval):
- if option in self.changed_opts:
- self.changed_opts[option] = (self.changed_opts[option][0], newval)
- else:
- self.changed_opts[option] = (oldval, newval)
-
- def on_advanced_treeview_row_activated(self, treeview, path, column):
- modelpath = self.modelfilter.convert_path_to_child_path(path)
- modelrow = self.model[modelpath]
- option = modelrow[0]
- if modelrow[2] == self.types['boolean']:
- for key in self.right_true_dict.keys():
- if self.right_true_dict[key] == modelrow[1]:
- modelrow[1] = str(key)
- newval = {'False': True, 'True': False}[modelrow[1]]
- if len(modelpath.get_indices()) > 1:
- optnamerow = self.model[modelpath.get_indices()[0]]
- optname = optnamerow[0]
- modelpath.up()
- keyrow = self.model[modelpath]
- key = keyrow[0]
- self.remember_option(option + '\n' + key + '\n' + optname,
- modelrow[1], newval)
- gajim.config.set_per(optname, key, option, newval)
- else:
- self.remember_option(option, modelrow[1], newval)
- gajim.config.set(option, newval)
- modelrow[1] = self.right_true_dict[newval]
- self.check_for_restart()
-
- def check_for_restart(self):
- self.restart_box.hide()
- for opt in self.changed_opts:
- opt_path = opt.split('\n')
- if len(opt_path)==3:
- restart = gajim.config.get_restart_per(opt_path[2], opt_path[1],
- opt_path[0])
- else:
- restart = gajim.config.get_restart(opt_path[0])
- if restart:
- if self.changed_opts[opt][0] != self.changed_opts[opt][1]:
- self.restart_box.set_no_show_all(False)
- self.restart_box.show_all()
- break
-
- def on_config_edited(self, cell, path, text):
- # convert modelfilter path to model path
- path=Gtk.TreePath.new_from_string(path)
- modelpath = self.modelfilter.convert_path_to_child_path(path)
- modelrow = self.model[modelpath]
- option = modelrow[0]
- if modelpath.get_depth() > 2:
- modelpath.up() # Get parent
- keyrow = self.model[modelpath]
- key = keyrow[0]
- modelpath.up() # Get parent
- optnamerow = self.model[modelpath]
- optname = optnamerow[0]
- self.remember_option(option + '\n' + key + '\n' + optname, modelrow[1],
- text)
- gajim.config.set_per(optname, key, option, text)
- else:
- self.remember_option(option, modelrow[1], text)
- gajim.config.set(option, text)
- modelrow[1] = text
- self.check_for_restart()
-
- @staticmethod
- def on_advanced_configuration_window_destroy(widget):
- del gajim.interface.instances['advanced_config']
-
- def on_reset_button_clicked(self, widget):
- model, iter_ = self.treeview.get_selection().get_selected()
- # Check for GtkTreeIter
- if iter_:
- path = model.get_path(iter_)
- opt_path = self.get_option_path(model, iter_)
- if len(opt_path) == 1:
- default = gajim.config.get_default(opt_path[0])
- elif len(opt_path) == 3:
- default = gajim.config.get_default_per(opt_path[2], opt_path[0])
-
- if model[iter_][Column.TYPE] == self.types['boolean']:
- if self.right_true_dict[default] == model[iter_][Column.VALUE]:
- return
- modelpath = self.modelfilter.convert_path_to_child_path(path)
- modelrow = self.model[modelpath]
- option = modelrow[0]
- if len(modelpath) > 1:
- optnamerow = self.model[modelpath[0]]
- optname = optnamerow[0]
- keyrow = self.model[modelpath[:2]]
- key = keyrow[0]
- self.remember_option(option + '\n' + key + '\n' + optname,
- modelrow[Column.VALUE], default)
- gajim.config.set_per(optname, key, option, default)
- else:
- self.remember_option(option, modelrow[Column.VALUE], default)
- gajim.config.set(option, default)
- modelrow[Column.VALUE] = self.right_true_dict[default]
- self.check_for_restart()
- else:
- if str(default) == model[iter_][Column.VALUE]:
- return
- self.on_config_edited(None, path.to_string(), str(default))
-
- def on_advanced_close_button_clicked(self, widget):
- self.window.destroy()
-
- def fill_model(self, node=None, parent=None):
- for item, option in gajim.config.get_children(node):
- name = item[-1]
- if option is None: # Node
- newparent = self.model.append(parent, [name, '', ''])
- self.fill_model(item, newparent)
- else: # Leaf
- if len(item) == 1:
- type_ = self.types[gajim.config.get_type(name)]
- elif len(item) == 3:
- type_ = self.types[gajim.config.get_type_per(item[0],
- item[2])]
- if name == 'password':
- value = _('Hidden')
- else:
- if type_ == self.types['boolean']:
- value = self.right_true_dict[option]
- else:
- try:
- value = str(option)
- except:
- value = option
- self.model.append(parent, [name, value, type_])
-
- def visible_func(self, model, treeiter, data):
- search_string = self.entry.get_text().lower()
- for it in tree_model_pre_order(model, treeiter):
- if model[it][Column.TYPE] != '':
- opt_path = self.get_option_path(model, it)
- if len(opt_path) == 3:
- desc = gajim.config.get_desc_per(opt_path[2], opt_path[1],
- opt_path[0])
- elif len(opt_path) == 1:
- desc = gajim.config.get_desc(opt_path[0])
- if search_string in model[it][Column.PREFERENCE_NAME] or (desc and \
- search_string in desc.lower()):
- return True
- return False
-
- @rate_limit(3)
- def on_advanced_entry_changed(self, widget):
- self.modelfilter.refilter()
- if not widget.get_text():
- # Maybe the expanded rows should be remembered here ...
- self.treeview.collapse_all()
- else:
- # ... and be restored correctly here
- self.treeview.expand_all()
diff --git a/src/app_actions.py b/src/app_actions.py
deleted file mode 100644
index 9095b99f6..000000000
--- a/src/app_actions.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/app_actions.py
-##
-## Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.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/>.
-##
-
-from common import helpers
-from common import gajim
-from common.exceptions import GajimGeneralException
-from gi.repository import Gtk
-import sys
-import os
-import config
-import dialogs
-import features_window
-import shortcuts_window
-import plugins.gui
-import history_window
-import disco
-
-
-class AppActions():
- ''' Action Callbacks '''
- def __init__(self, app: Gtk.Application):
- self.application = app
-
- # Application Menu Actions
-
- def on_preferences(self, action, param):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].window.present()
- else:
- gajim.interface.instances['preferences'] = \
- config.PreferencesWindow()
-
- def on_plugins(self, action, param):
- if 'plugins' in gajim.interface.instances:
- gajim.interface.instances['plugins'].window.present()
- else:
- gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow()
-
- def on_accounts(self, action, param):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = config.AccountsWindow()
-
- def on_history_manager(self, action, param):
- config_path = '-c %s' % gajim.gajimpaths.data_root
- posix = os.name != 'nt'
- if os.path.exists('history_manager.exe'): # Windows
- helpers.exec_command('history_manager.exe %s' % config_path,
- posix=posix)
- else: # Linux or running from Git
- helpers.exec_command(
- '%s history_manager.py %s' % (sys.executable, config_path),
- posix=posix)
-
- def on_manage_bookmarks(self, action, param):
- config.ManageBookmarksWindow()
-
- def on_quit(self, action, param):
- gajim.interface.roster.on_quit_request()
-
- # Accounts Actions
-
- def on_profile(self, action, param):
- gajim.interface.edit_own_details(param.get_string())
-
- def on_activate_bookmark(self, action, param):
- dict_ = param.unpack()
- account, jid, nick, password = \
- dict_['account'], dict_['jid'], None, None
- if 'nick' in dict_:
- nick = dict_['nick']
- if 'password' in dict_:
- password = dict_['password']
- gajim.interface.join_gc_room(account, jid, nick, password)
-
- def on_send_server_message(self, action, param):
- account = param.get_string()
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/online'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_service_disco(self, action, param):
- account = param.get_string()
- server_jid = gajim.config.get_per('accounts', account, 'hostname')
- if server_jid in gajim.interface.instances[account]['disco']:
- gajim.interface.instances[account]['disco'][server_jid].\
- window.present()
- else:
- try:
- # Object will add itself to the window dict
- disco.ServiceDiscoveryWindow(account, address_entry=True)
- except GajimGeneralException:
- pass
-
- def on_join_gc(self, action, param):
- account = param.get_string()
- invisible_show = gajim.SHOW_LIST.index('invisible')
- if gajim.connections[account].connected == invisible_show:
- dialogs.ErrorDialog(_(
- 'You cannot join a group chat while you are invisible'))
- return
- if 'join_gc' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['join_gc'].window.present()
- else:
- try:
- gajim.interface.instances[account]['join_gc'] = \
- dialogs.JoinGroupchatWindow(account)
- except GajimGeneralException:
- pass
-
- def on_add_contact(self, action, param):
- dialogs.AddNewContactWindow(param.get_string())
-
- def on_new_chat(self, action, param):
- dialogs.NewChatDialog(param.get_string())
-
- def on_single_message(self, action, param):
- dialogs.SingleMessageWindow(param.get_string(), action='send')
-
- # Advanced Actions
-
- def on_archiving_preferences(self, action, param):
- account = param.get_string()
- if 'archiving_preferences' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['archiving_preferences'].window.\
- present()
- else:
- if gajim.connections[account].archiving_313_supported:
- gajim.interface.instances[account]['archiving_preferences'] = \
- dialogs.Archiving313PreferencesWindow(account)
- else:
- gajim.interface.instances[account]['archiving_preferences'] = \
- dialogs.ArchivingPreferencesWindow(account)
-
- def on_privacy_lists(self, action, param):
- account = param.get_string()
- if 'privacy_lists' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['privacy_lists'].window.present()
- else:
- gajim.interface.instances[account]['privacy_lists'] = \
- dialogs.PrivacyListsWindow(account)
-
- def on_xml_console(self, action, param):
- account = param.get_string()
- if 'xml_console' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['xml_console'].window.present()
- else:
- gajim.interface.instances[account]['xml_console'] = \
- dialogs.XMLConsoleWindow(account)
-
- # Admin Actions
-
- def on_set_motd(self, action, param):
- account = param.get_string()
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_update_motd(self, action, param):
- account = param.get_string()
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd/update'
- dialogs.SingleMessageWindow(account, server, 'send')
-
- def on_delete_motd(self, action, param):
- account = param.get_string()
- server = gajim.config.get_per('accounts', account, 'hostname')
- server += '/announce/motd/delete'
- gajim.connections[account].send_motd(server)
-
- # Help Actions
-
- def on_contents(self, action, param):
- helpers.launch_browser_mailer(
- 'url', 'https://dev.gajim.org/gajim/gajim/wikis')
-
- def on_faq(self, action, param):
- helpers.launch_browser_mailer(
- 'url', 'https://dev.gajim.org/gajim/gajim/wikis/help/gajimfaq')
-
- def on_keyboard_shortcuts(self, action, param):
- shortcuts_window.show(self.application.get_active_window())
-
- def on_features(self, action, param):
- features_window.FeaturesWindow()
-
- def on_about(self, action, param):
- dialogs.AboutDialog()
-
- # View Actions
-
- def on_file_transfers(self, action, param):
- if gajim.interface.instances['file_transfers']. \
- window.get_property('visible'):
- gajim.interface.instances['file_transfers'].window.present()
- else:
- gajim.interface.instances['file_transfers'].window.show_all()
-
- def on_history(self, action, param):
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- else:
- gajim.interface.instances['logs'] = history_window.\
- HistoryWindow()
diff --git a/src/atom_window.py b/src/atom_window.py
deleted file mode 100644
index 88a0b5030..000000000
--- a/src/atom_window.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/atom_window.py
-##
-## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-from gi.repository import Gdk
-from gi.repository import GLib
-
-import gtkgui_helpers
-from common import helpers
-from common import i18n
-
-class AtomWindow:
- window = None
- entries = []
-
- @classmethod
- def newAtomEntry(cls, entry):
- """
- Queue new entry, open window if there's no one opened
- """
- cls.entries.append(entry)
-
- if cls.window is None:
- cls.window = AtomWindow()
- else:
- cls.window.updateCounter()
-
- @classmethod
- def windowClosed(cls):
- cls.window = None
-
- def __init__(self):
- """
- Create new window... only if we have anything to show
- """
- assert len(self.__class__.entries)
-
- self.entry = None # the entry actually displayed
-
- self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui')
- self.window = self.xml.get_object('atom_entry_window')
- for name in ('new_entry_label', 'feed_title_label',
- 'feed_title_eventbox', 'feed_tagline_label', 'entry_title_label',
- 'entry_title_eventbox', 'last_modified_label', 'close_button',
- 'next_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.displayNextEntry()
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- self.entry_title_eventbox.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
- self.feed_title_eventbox.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
-
- def displayNextEntry(self):
- """
- Get next entry from the queue and display it in the window
- """
- assert len(self.__class__.entries)>0
-
- newentry = self.__class__.entries.pop(0)
-
- # fill the fields
- if newentry.feed_link is not None:
- self.feed_title_label.set_markup(
- '<span foreground="blue" underline="single">%s</span>' % \
- GLib.markup_escape_text(newentry.feed_title))
- else:
- self.feed_title_label.set_markup(GLib.markup_escape_text(
- newentry.feed_title))
-
- self.feed_tagline_label.set_markup(
- '<small>%s</small>' % GLib.markup_escape_text(
- newentry.feed_tagline))
-
- if newentry.title:
- if newentry.uri is not None:
- self.entry_title_label.set_markup(
- '<span foreground="blue" underline="single">%s</span>' % \
- GLib.markup_escape_text(newentry.title))
- else:
- self.entry_title_label.set_markup(GLib.markup_escape_text(
- newentry.title))
- else:
- self.entry_title_label.set_markup('')
-
- self.last_modified_label.set_text(newentry.updated)
-
- # update the counters
- self.updateCounter()
-
- self.entry = newentry
-
- def updateCounter(self):
- """
- Display number of events on the top of window, sometimes it needs to be
- changed
- """
- count = len(self.__class__.entries)
- if count:
- self.new_entry_label.set_text(i18n.ngettext(
- 'You have received new entries (and %d not displayed):',
- 'You have received new entries (and %d not displayed):', count,
- count, count))
- self.next_button.set_sensitive(True)
- else:
- self.new_entry_label.set_text(_('You have received new entry:'))
- self.next_button.set_sensitive(False)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
- self.windowClosed()
-
- def on_next_button_clicked(self, widget):
- self.displayNextEntry()
-
- def on_entry_title_eventbox_button_press_event(self, widget, event):
- #FIXME: make it using special gtk2.10 widget
- if event.button == 1: # left click
- uri = self.entry.uri
- if uri is not None:
- helpers.launch_browser_mailer('url', uri)
- return True
-
- def on_feed_title_eventbox_button_press_event(self, widget, event):
- #FIXME: make it using special gtk2.10 widget
- if event.button == 1: # left click
- uri = self.entry.feed_uri
- if uri is not None:
- helpers.launch_browser_mailer('url', uri)
- return True
diff --git a/src/cell_renderer_image.py b/src/cell_renderer_image.py
deleted file mode 100644
index 94bc6cb0e..000000000
--- a/src/cell_renderer_image.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/cell_renderer_image.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Travis Shirk <travis AT pobox.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/>.
-##
-
-from gi.repository import GLib
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GObject
-
-class CellRendererImage(Gtk.CellRendererPixbuf):
-
- __gproperties__ = {
- 'image': (GObject.TYPE_OBJECT, 'Image',
- 'Image', GObject.ParamFlags.READWRITE),
- }
-
- def __init__(self, col_index, tv_index):
- super(CellRendererImage, self).__init__()
- self.image = None
- self.col_index = col_index
- self.tv_index = tv_index
- self.iters = {}
-
- def do_set_property(self, pspec, value):
- setattr(self, pspec.name, value)
-
- def do_get_property(self, pspec):
- return getattr(self, pspec.name)
-
- def do_activate(event, widget, path, bg_area, cell_area, flags):
- """Renderers cannot be activated; always return True."""
- return True
-
- def do_editing_started(event, widget, path, fb_area, cell_area, flags):
- """Renderers cannot be edited; always return None."""
- return None
-
- def func(self, model, path, iter_, image_tree):
- image, tree = image_tree
- if model.get_value(iter_, self.tv_index) != image:
- return
- self.redraw = 1
- col = tree.get_column(self.col_index)
- cell_area = tree.get_cell_area(path, col)
-
- tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width,
- cell_area.height)
-
- def animation_timeout(self, tree, image):
- if image.get_storage_type() != Gtk.ImageType.ANIMATION:
- return
- self.redraw = 0
- iter_ = self.iters[image]
- timeval = GLib.TimeVal()
- timeval.tv_sec = GLib.get_monotonic_time() / 1000000
- iter_.advance(timeval)
- model = tree.get_model()
- if model:
- model.foreach(self.func, (image, tree))
- if self.redraw:
- GLib.timeout_add(iter_.get_delay_time(),
- self.animation_timeout, tree, image)
- elif image in self.iters:
- del self.iters[image]
-
- def do_render(self, ctx, widget, background_area, cell_area, flags):
- if not self.image:
- return
-
- if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
- if self.image not in self.iters:
- if not isinstance(widget, Gtk.TreeView):
- return
- animation = self.image.get_animation()
- timeval = GLib.TimeVal()
- timeval.tv_sec = GLib.get_monotonic_time() / 1000000
- iter_ = animation.get_iter(timeval)
- self.iters[self.image] = iter_
- GLib.timeout_add(iter_.get_delay_time(), self.animation_timeout,
- widget, self.image)
-
- pix = self.iters[self.image].get_pixbuf()
- elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
- pix = self.image.get_pixbuf()
- else:
- return
-
- calc_width = self.get_property('xpad') * 2 + pix.get_width()
- calc_height = self.get_property('ypad') * 2 + pix.get_height()
-
- x_pos = cell_area.x + self.get_property('xalign') * \
- (cell_area.width - calc_width - self.get_property('xpad'))
- y_pos = cell_area.y + self.get_property('yalign') * \
- (cell_area.height - calc_height - self.get_property('ypad'))
- Gdk.cairo_set_source_pixbuf(ctx, pix, x_pos, y_pos)
- ctx.paint()
-
- def do_get_preferred_height(self, widget):
- """
- Return the height we need for this cell.
-
- Each cell is drawn individually and is only as wide as it needs
- to be, we let the TreeViewColumn take care of making them all
- line up.
- """
- if not self.image:
- return 0, 0
- if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
- animation = self.image.get_animation()
- timeval = GLib.TimeVal()
- timeval.tv_sec = GLib.get_monotonic_time() / 1000000
- pix = animation.get_iter(timeval).get_pixbuf()
- elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
- pix = self.image.get_pixbuf()
- else:
- return 0, 0, 0, 0
- calc_height = self.get_property('ypad') * 2 + pix.get_height()
- return calc_height, calc_height
-
- def do_get_preferred_width(self, widget):
- """
- Return the width we need for this cell.
-
- Each cell is drawn individually and is only as wide as it needs
- to be, we let the TreeViewColumn take care of making them all
- line up.
- """
- if not self.image:
- return 0, 0
- if self.image.get_storage_type() == Gtk.ImageType.ANIMATION:
- animation = self.image.get_animation()
- timeval = GLib.TimeVal()
- timeval.tv_sec = GLib.get_monotonic_time() / 1000000
- pix = animation.get_iter(timeval).get_pixbuf()
- elif self.image.get_storage_type() == Gtk.ImageType.PIXBUF:
- pix = self.image.get_pixbuf()
- else:
- return 0, 0, 0, 0
- calc_width = self.get_property('xpad') * 2 + pix.get_width()
- return calc_width, calc_width
diff --git a/src/chat_control.py b/src/chat_control.py
deleted file mode 100644
index b85719f65..000000000
--- a/src/chat_control.py
+++ /dev/null
@@ -1,1881 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/chat_control.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import time
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import Pango
-from gi.repository import GLib
-import gtkgui_helpers
-import gui_menu_builder
-import message_control
-import dialogs
-
-from common import logger
-from common import gajim
-from common import helpers
-from common import exceptions
-from common import ged
-from common import i18n
-from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
-from common.contacts import GC_Contact
-from common.logger import KindConstant
-from nbxmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
-from nbxmpp.protocol import NS_ESESSION
-from nbxmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO
-from nbxmpp.protocol import NS_JINGLE_ICE_UDP, NS_JINGLE_FILE_TRANSFER_5
-from nbxmpp.protocol import NS_CHATSTATES
-from common.connection_handlers_events import MessageOutgoingEvent
-from common.exceptions import GajimGeneralException
-
-from command_system.implementation.hosts import ChatCommands
-
-try:
- import gtkspell
- HAS_GTK_SPELL = True
-except (ImportError, ValueError):
- HAS_GTK_SPELL = False
-
-from chat_control_base import ChatControlBase
-
-################################################################################
-class ChatControl(ChatControlBase):
- """
- A control for standard 1-1 chat
- """
- (
- JINGLE_STATE_NULL,
- JINGLE_STATE_CONNECTING,
- JINGLE_STATE_CONNECTION_RECEIVED,
- JINGLE_STATE_CONNECTED,
- JINGLE_STATE_ERROR
- ) = range(5)
-
- TYPE_ID = message_control.TYPE_CHAT
- old_msg_kind = None # last kind of the printed message
-
- # Set a command host to bound to. Every command given through a chat will be
- # processed with this command host.
- COMMAND_HOST = ChatCommands
-
- def __init__(self, parent_win, contact, acct, session, resource=None):
- ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
- 'chat_control', contact, acct, resource)
-
- self.last_recv_message_id = None
- self.last_recv_message_marks = None
- self.last_message_timestamp = None
-
- # for muc use:
- # widget = self.xml.get_object('muc_window_actions_button')
- self.actions_button = self.xml.get_object('message_window_actions_button')
- id_ = self.actions_button.connect('clicked',
- self.on_actions_button_clicked)
- self.handlers[id_] = self.actions_button
-
- self._formattings_button = self.xml.get_object('formattings_button')
-
- self._add_to_roster_button = self.xml.get_object(
- 'add_to_roster_button')
- id_ = self._add_to_roster_button.connect('clicked',
- self._on_add_to_roster_menuitem_activate)
- self.handlers[id_] = self._add_to_roster_button
-
- self._audio_button = self.xml.get_object('audio_togglebutton')
- id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
- self.handlers[id_] = self._audio_button
- # add a special img
- gtkgui_helpers.add_image_to_button(self._audio_button,
- 'gajim-mic_inactive')
-
- self._video_button = self.xml.get_object('video_togglebutton')
- id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
- self.handlers[id_] = self._video_button
- # add a special img
- gtkgui_helpers.add_image_to_button(self._video_button,
- 'gajim-cam_inactive')
-
- self._send_file_button = self.xml.get_object('send_file_button')
- # add a special img for send file button
- pixbuf = gtkgui_helpers.get_icon_pixmap('document-send', quiet=True)
- img = Gtk.Image.new_from_pixbuf(pixbuf)
- self._send_file_button.set_image(img)
- id_ = self._send_file_button.connect('clicked',
- self._on_send_file_menuitem_activate)
- self.handlers[id_] = self._send_file_button
-
- self._convert_to_gc_button = self.xml.get_object(
- 'convert_to_gc_button')
- id_ = self._convert_to_gc_button.connect('clicked',
- self._on_convert_to_gc_menuitem_activate)
- self.handlers[id_] = self._convert_to_gc_button
-
- self._contact_information_button = self.xml.get_object(
- 'contact_information_button')
- id_ = self._contact_information_button.connect('clicked',
- self._on_contact_information_menuitem_activate)
- self.handlers[id_] = self._contact_information_button
-
- compact_view = gajim.config.get('compact_view')
- self.chat_buttons_set_visible(compact_view)
- self.widget_set_visible(self.xml.get_object('banner_eventbox'),
- gajim.config.get('hide_chat_banner'))
-
- self.authentication_button = self.xml.get_object(
- 'authentication_button')
- id_ = self.authentication_button.connect('clicked',
- self._on_authentication_button_clicked)
- self.handlers[id_] = self.authentication_button
-
- # Add lock image to show chat encryption
- self.lock_image = self.xml.get_object('lock_image')
-
- # Convert to GC icon
- img = self.xml.get_object('convert_to_gc_button_image')
- img.set_from_pixbuf(gtkgui_helpers.load_icon(
- 'muc_active').get_pixbuf())
-
- self._audio_banner_image = self.xml.get_object('audio_banner_image')
- self._video_banner_image = self.xml.get_object('video_banner_image')
- self.audio_sid = None
- self.audio_state = self.JINGLE_STATE_NULL
- self.audio_available = False
- self.video_sid = None
- self.video_state = self.JINGLE_STATE_NULL
- self.video_available = False
-
- self.update_toolbar()
-
- self._pep_images = {}
- self._pep_images['mood'] = self.xml.get_object('mood_image')
- self._pep_images['activity'] = self.xml.get_object('activity_image')
- self._pep_images['tune'] = self.xml.get_object('tune_image')
- self._pep_images['location'] = self.xml.get_object('location_image')
- self.update_all_pep_types()
-
- # keep timeout id and window obj for possible big avatar
- # it is on enter-notify and leave-notify so no need to be
- # per jid
- self.show_bigger_avatar_timeout_id = None
- self.bigger_avatar_window = None
- self.show_avatar()
-
- # Hook up signals
- message_tv_buffer = self.msg_textview.get_buffer()
- id_ = message_tv_buffer.connect('changed',
- self._on_message_tv_buffer_changed)
- self.handlers[id_] = message_tv_buffer
-
- widget = self.xml.get_object('avatar_eventbox')
- widget.set_property('height-request', gajim.config.get(
- 'chat_avatar_height'))
- id_ = widget.connect('enter-notify-event',
- self.on_avatar_eventbox_enter_notify_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('leave-notify-event',
- self.on_avatar_eventbox_leave_notify_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('button-press-event',
- self.on_avatar_eventbox_button_press_event)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('location_eventbox')
- id_ = widget.connect('button-release-event',
- self.on_location_eventbox_button_release_event)
- self.handlers[id_] = widget
- id_ = widget.connect('enter-notify-event',
- self.on_location_eventbox_enter_notify_event)
- self.handlers[id_] = widget
- id_ = widget.connect('leave-notify-event',
- self.on_location_eventbox_leave_notify_event)
- self.handlers[id_] = widget
-
- for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'):
- widget = self.xml.get_object(key + '_button')
- id_ = widget.connect('pressed', self.on_num_button_pressed, key)
- self.handlers[id_] = widget
- id_ = widget.connect('released', self.on_num_button_released)
- self.handlers[id_] = widget
-
- self.dtmf_window = self.xml.get_object('dtmf_window')
- self.dtmf_window.get_child().set_direction(Gtk.TextDirection.LTR)
- id_ = self.dtmf_window.connect('focus-out-event',
- self.on_dtmf_window_focus_out_event)
- self.handlers[id_] = self.dtmf_window
-
- widget = self.xml.get_object('dtmf_button')
- id_ = widget.connect('clicked', self.on_dtmf_button_clicked)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('mic_hscale')
- id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('sound_hscale')
- id_ = widget.connect('value_changed', self.on_sound_hscale_value_changed)
- self.handlers[id_] = widget
-
- self.info_bar = Gtk.InfoBar()
- content_area = self.info_bar.get_content_area()
- self.info_bar_label = Gtk.Label()
- self.info_bar_label.set_use_markup(True)
- self.info_bar_label.set_halign(Gtk.Align.START)
- self.info_bar_label.set_valign(Gtk.Align.START)
- content_area.add(self.info_bar_label)
- self.info_bar.set_no_show_all(True)
- widget = self.xml.get_object('vbox2')
- widget.pack_start(self.info_bar, False, True, 5)
- widget.reorder_child(self.info_bar, 1)
-
- # List of waiting infobar messages
- self.info_bar_queue = []
-
- self.subscribe_events()
-
- if not session:
- # Don't use previous session if we want to a specific resource
- # and it's not the same
- if not resource:
- resource = contact.resource
- session = gajim.connections[self.account].find_controlless_session(
- self.contact.jid, resource)
-
- self.setup_seclabel(self.xml.get_object('label_selector'))
- if session:
- session.control = self
- self.session = session
-
- if session.enable_encryption:
- self.print_esession_details()
-
- # Enable encryption if needed
- self.no_autonegotiation = False
-
- self.update_ui()
- self.set_lock_image()
-
- self.encryption_menu = self.xml.get_object('encryption_menu')
- self.encryption_menu.set_menu_model(
- gui_menu_builder.get_encryption_menu(self.contact, self.type_id))
- self.set_encryption_menu_icon()
- # restore previous conversation
- self.restore_conversation()
- self.msg_textview.grab_focus()
-
- gajim.ged.register_event_handler('pep-received', ged.GUI1,
- self._nec_pep_received)
- gajim.ged.register_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- gajim.ged.register_event_handler('failed-decrypt', ged.GUI1,
- self._nec_failed_decrypt)
- gajim.ged.register_event_handler('chatstate-received', ged.GUI1,
- self._nec_chatstate_received)
- gajim.ged.register_event_handler('caps-received', ged.GUI1,
- self._nec_caps_received)
-
- # PluginSystem: adding GUI extension point for this ChatControl
- # instance object
- gajim.plugin_manager.gui_extension_point('chat_control', self)
-
- def subscribe_events(self):
- """
- Register listeners to the events class
- """
- gajim.events.event_added_subscribe(self.on_event_added)
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- def unsubscribe_events(self):
- """
- Unregister listeners to the events class
- """
- gajim.events.event_added_unsubscribe(self.on_event_added)
- gajim.events.event_removed_unsubscribe(self.on_event_removed)
-
- def _update_toolbar(self):
- if (gajim.connections[self.account].connected > 1 and not \
- self.TYPE_ID == 'pm') or (self.contact.show != 'offline' and \
- self.TYPE_ID == 'pm'):
- emoticons_button = self.xml.get_object('emoticons_button')
- emoticons_button.set_sensitive(True)
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(True)
- # Formatting
- # TODO: find out what encryption allows for xhtml and which not
- if self.contact.supports(NS_XHTML_IM):
- self._formattings_button.set_sensitive(True)
- self._formattings_button.set_tooltip_text(_(
- 'Show a list of formattings'))
- else:
- self._formattings_button.set_sensitive(False)
- if self.contact.supports(NS_XHTML_IM):
- self._formattings_button.set_tooltip_text(_('Formatting is not '
- 'available so long as GPG is active'))
- else:
- self._formattings_button.set_tooltip_text(_('This contact does '
- 'not support HTML'))
-
- # Add to roster
- if not isinstance(self.contact, GC_Contact) \
- and _('Not in Roster') in self.contact.groups and \
- gajim.connections[self.account].roster_supported:
- self._add_to_roster_button.show()
- else:
- self._add_to_roster_button.hide()
-
- # Jingle detection
- if self.contact.supports(NS_JINGLE_ICE_UDP) and \
- gajim.HAVE_FARSTREAM and self.contact.resource:
- self.audio_available = self.contact.supports(NS_JINGLE_RTP_AUDIO)
- self.video_available = self.contact.supports(NS_JINGLE_RTP_VIDEO)
- else:
- if self.video_available or self.audio_available:
- self.stop_jingle()
- self.video_available = False
- self.audio_available = False
-
- # Audio buttons
- self._audio_button.set_sensitive(self.audio_available)
-
- # Video buttons
- self._video_button.set_sensitive(self.video_available)
-
- # change tooltip text for audio and video buttons if farstream is
- # not installed
- audio_tooltip_text = _('Toggle audio session') + '\n'
- video_tooltip_text = _('Toggle video session') + '\n'
- if not gajim.HAVE_FARSTREAM:
- ext_text = _('Feature not available, see Help->Features')
- self._audio_button.set_tooltip_text(audio_tooltip_text + ext_text)
- self._video_button.set_tooltip_text(video_tooltip_text + ext_text)
- elif not self.audio_available :
- ext_text =_('Feature not supported by remote client')
- self._audio_button.set_tooltip_text(audio_tooltip_text + ext_text)
- self._video_button.set_tooltip_text(video_tooltip_text + ext_text)
- else:
- self._audio_button.set_tooltip_text(audio_tooltip_text[:-1])
- self._video_button.set_tooltip_text(video_tooltip_text[:-1])
-
- # Send file
- if ((self.contact.supports(NS_FILE) or \
- self.contact.supports(NS_JINGLE_FILE_TRANSFER_5)) and \
- (self.type_id == 'chat' or self.gc_contact.resource)) and \
- self.contact.show != 'offline':
- self._send_file_button.set_sensitive(True)
- self._send_file_button.set_tooltip_text(_('Send files'))
- else:
- self._send_file_button.set_sensitive(False)
- if not (self.contact.supports(NS_FILE) or self.contact.supports(
- NS_JINGLE_FILE_TRANSFER_5)):
- self._send_file_button.set_tooltip_text(_(
- "This contact does not support file transfer."))
- else:
- self._send_file_button.set_tooltip_text(
- _("You need to know the real JID of the contact to send "
- "them a file."))
-
- # Convert to GC
- if gajim.config.get_per('accounts', self.account, 'is_zeroconf'):
- self._convert_to_gc_button.set_no_show_all(True)
- self._convert_to_gc_button.hide()
- else:
- if self.contact.supports(NS_MUC):
- self._convert_to_gc_button.set_sensitive(True)
- else:
- self._convert_to_gc_button.set_sensitive(False)
-
- # Information
- if gajim.account_is_disconnected(self.account):
- self._contact_information_button.set_sensitive(False)
- else:
- self._contact_information_button.set_sensitive(True)
-
-
- def update_all_pep_types(self):
- for pep_type in self._pep_images:
- self.update_pep(pep_type)
-
- def update_pep(self, pep_type):
- if isinstance(self.contact, GC_Contact):
- return
- if pep_type not in self._pep_images:
- return
- pep = self.contact.pep
- img = self._pep_images[pep_type]
- if pep_type in pep:
- img.set_from_pixbuf(gtkgui_helpers.get_pep_as_pixbuf(pep[pep_type]))
- img.set_tooltip_markup(pep[pep_type].asMarkupText())
- img.show()
- else:
- img.hide()
-
- def _nec_pep_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.jid != self.contact.jid:
- return
-
- if obj.pep_type == 'nickname':
- self.update_ui()
- self.parent_win.redraw_tab(self)
- self.parent_win.show_title()
- else:
- self.update_pep(obj.pep_type)
-
- def _update_jingle(self, jingle_type):
- if jingle_type not in ('audio', 'video'):
- return
- banner_image = getattr(self, '_' + jingle_type + '_banner_image')
- state = getattr(self, jingle_type + '_state')
- if state == self.JINGLE_STATE_NULL:
- banner_image.hide()
- else:
- banner_image.show()
- if state == self.JINGLE_STATE_CONNECTING:
- banner_image.set_from_stock(
- Gtk.STOCK_CONVERT, 1)
- elif state == self.JINGLE_STATE_CONNECTION_RECEIVED:
- banner_image.set_from_stock(
- Gtk.STOCK_NETWORK, 1)
- elif state == self.JINGLE_STATE_CONNECTED:
- banner_image.set_from_stock(
- Gtk.STOCK_CONNECT, 1)
- elif state == self.JINGLE_STATE_ERROR:
- banner_image.set_from_stock(
- Gtk.STOCK_DIALOG_WARNING, 1)
- self.update_toolbar()
-
- def update_audio(self):
- self._update_jingle('audio')
- hbox = self.xml.get_object('audio_buttons_hbox')
- if self.audio_state == self.JINGLE_STATE_CONNECTED:
- # Set volume from config
- input_vol = gajim.config.get('audio_input_volume')
- output_vol = gajim.config.get('audio_output_volume')
- input_vol = max(min(input_vol, 100), 0)
- output_vol = max(min(output_vol, 100), 0)
- self.xml.get_object('mic_hscale').set_value(input_vol)
- self.xml.get_object('sound_hscale').set_value(output_vol)
- # Show vbox
- hbox.set_no_show_all(False)
- hbox.show_all()
- elif not self.audio_sid:
- hbox.set_no_show_all(True)
- hbox.hide()
-
- def update_video(self):
- self._update_jingle('video')
-
- def change_resource(self, resource):
- old_full_jid = self.get_full_jid()
- self.resource = resource
- new_full_jid = self.get_full_jid()
- # update gajim.last_message_time
- if old_full_jid in gajim.last_message_time[self.account]:
- gajim.last_message_time[self.account][new_full_jid] = \
- gajim.last_message_time[self.account][old_full_jid]
- # update events
- gajim.events.change_jid(self.account, old_full_jid, new_full_jid)
- # update MessageWindow._controls
- self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
-
- def stop_jingle(self, sid=None, reason=None):
- if self.audio_sid and sid in (self.audio_sid, None):
- self.close_jingle_content('audio')
- if self.video_sid and sid in (self.video_sid, None):
- self.close_jingle_content('video')
-
- def _set_jingle_state(self, jingle_type, state, sid=None, reason=None):
- if jingle_type not in ('audio', 'video'):
- return
- if state in ('connecting', 'connected', 'stop', 'error') and reason:
- str = _('%(type)s state : %(state)s, reason: %(reason)s') % {
- 'type': jingle_type.capitalize(), 'state': state, 'reason': reason}
- self.print_conversation(str, 'info')
-
- states = {'connecting': self.JINGLE_STATE_CONNECTING,
- 'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED,
- 'connected': self.JINGLE_STATE_CONNECTED,
- 'stop': self.JINGLE_STATE_NULL,
- 'error': self.JINGLE_STATE_ERROR}
-
- jingle_state = states[state]
- if getattr(self, jingle_type + '_state') == jingle_state or state == 'error':
- return
-
- if state == 'stop' and getattr(self, jingle_type + '_sid') not in (None, sid):
- return
-
- setattr(self, jingle_type + '_state', jingle_state)
-
- if jingle_state == self.JINGLE_STATE_NULL:
- setattr(self, jingle_type + '_sid', None)
- if state in ('connection_received', 'connecting'):
- setattr(self, jingle_type + '_sid', sid)
-
- getattr(self, '_' + jingle_type + '_button').set_active(jingle_state != self.JINGLE_STATE_NULL)
-
- getattr(self, 'update_' + jingle_type)()
-
- def set_audio_state(self, state, sid=None, reason=None):
- self._set_jingle_state('audio', state, sid=sid, reason=reason)
-
- def set_video_state(self, state, sid=None, reason=None):
- self._set_jingle_state('video', state, sid=sid, reason=reason)
-
- def _get_audio_content(self):
- session = gajim.connections[self.account].get_jingle_session(
- self.contact.get_full_jid(), self.audio_sid)
- return session.get_content('audio')
-
- def on_num_button_pressed(self, widget, num):
- self._get_audio_content()._start_dtmf(num)
-
- def on_num_button_released(self, released):
- self._get_audio_content()._stop_dtmf()
-
- def on_dtmf_button_clicked(self, widget):
- self.dtmf_window.show_all()
-
- def on_dtmf_window_focus_out_event(self, widget, event):
- self.dtmf_window.hide()
-
- def on_mic_hscale_value_changed(self, widget, value):
- self._get_audio_content().set_mic_volume(value / 100)
- # Save volume to config
- gajim.config.set('audio_input_volume', value)
-
- def on_sound_hscale_value_changed(self, widget, value):
- self._get_audio_content().set_out_volume(value / 100)
- # Save volume to config
- gajim.config.set('audio_output_volume', value)
-
- def on_avatar_eventbox_enter_notify_event(self, widget, event):
- """
- Enter the eventbox area so we under conditions add a timeout to show a
- bigger avatar after 0.5 sec
- """
- jid = self.contact.jid
- avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if avatar_pixbuf in ('ask', None):
- return
- avatar_w = avatar_pixbuf.get_width()
- avatar_h = avatar_pixbuf.get_height()
-
- scaled_buf = self.xml.get_object('avatar_image').get_pixbuf()
- scaled_buf_w = scaled_buf.get_width()
- scaled_buf_h = scaled_buf.get_height()
-
- # do we have something bigger to show?
- if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h:
- # wait for 0.5 sec in case we leave earlier
- if self.show_bigger_avatar_timeout_id is not None:
- GLib.source_remove(self.show_bigger_avatar_timeout_id)
- self.show_bigger_avatar_timeout_id = GLib.timeout_add(500,
- self.show_bigger_avatar, widget)
-
- def on_avatar_eventbox_leave_notify_event(self, widget, event):
- """
- Left the eventbox area that holds the avatar img
- """
- # did we add a timeout? if yes remove it
- if self.show_bigger_avatar_timeout_id is not None:
- GLib.source_remove(self.show_bigger_avatar_timeout_id)
- self.show_bigger_avatar_timeout_id = None
-
- def on_avatar_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = Gtk.Menu()
- menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
- id_ = menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.contact.get_shown_name())
- self.handlers[id_] = menuitem
- menu.append(menuitem)
- menu.show_all()
- menu.connect('selection-done', lambda w: w.destroy())
- # show the menu
- menu.show_all()
- menu.attach_to_widget(widget, None)
- menu.popup(None, None, None, None, event.button, event.time)
- return True
-
- def on_location_eventbox_button_release_event(self, widget, event):
- if 'location' in self.contact.pep:
- location = self.contact.pep['location']._pep_specific_data
- if ('lat' in location) and ('lon' in location):
- uri = 'http://www.openstreetmap.org/?' + \
- 'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % {'lat': location['lat'],
- 'lon': location['lon']}
- helpers.launch_browser_mailer('url', uri)
-
- def on_location_eventbox_leave_notify_event(self, widget, event):
- """
- Just moved the mouse so show the cursor
- """
- cursor = gtkgui_helpers.get_cursor('LEFT_PTR')
- self.parent_win.window.get_window().set_cursor(cursor)
-
- def on_location_eventbox_enter_notify_event(self, widget, event):
- cursor = gtkgui_helpers.get_cursor('HAND2')
- self.parent_win.window.get_window().set_cursor(cursor)
-
- def update_ui(self):
- # The name banner is drawn here
- ChatControlBase.update_ui(self)
- self.update_toolbar()
-
- def _update_banner_state_image(self):
- contact = gajim.contacts.get_contact_with_highest_priority(self.account,
- self.contact.jid)
- if not contact or self.resource:
- # For transient contacts
- contact = self.contact
- show = contact.show
- jid = contact.jid
-
- # Set banner image
- img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
- size='32', icon_name=show)
- img_16 = gajim.interface.roster.get_appropriate_state_images(jid,
- icon_name=show)
- if show in img_32 and img_32[show].get_pixbuf():
- # we have 32x32! use it!
- banner_image = img_32[show]
- use_size_32 = True
- else:
- banner_image = img_16[show]
- use_size_32 = False
-
- banner_status_img = self.xml.get_object('banner_status_image')
- if banner_image.get_storage_type() == Gtk.ImageType.ANIMATION:
- banner_status_img.set_from_animation(banner_image.get_animation())
- else:
- pix = banner_image.get_pixbuf()
- if pix is not None:
- if use_size_32:
- banner_status_img.set_from_pixbuf(pix)
- else: # we need to scale 16x16 to 32x32
- scaled_pix = pix.scale_simple(32, 32,
- GdkPixbuf.InterpType.BILINEAR)
- banner_status_img.set_from_pixbuf(scaled_pix)
-
- def draw_banner_text(self):
- """
- Draw the text in the fat line at the top of the window that houses the
- name, jid
- """
- contact = self.contact
- jid = contact.jid
-
- banner_name_label = self.xml.get_object('banner_name_label')
-
- name = contact.get_shown_name()
- if self.resource:
- name += '/' + self.resource
- if self.TYPE_ID == message_control.TYPE_PM:
- name = i18n.direction_mark + _(
- '%(nickname)s from group chat %(room_name)s') % \
- {'nickname': name, 'room_name': self.room_name}
- name = i18n.direction_mark + GLib.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 == self.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() == \
- self.contact.get_shown_name():
- acct_info = i18n.direction_mark + ' (%s)' % \
- GLib.markup_escape_text(self.account)
- break
-
- status = contact.status
- if status is not None:
- banner_name_label.set_ellipsize(Pango.EllipsizeMode.END)
- self.banner_status_label.set_ellipsize(Pango.EllipsizeMode.END)
- status_reduced = helpers.reduce_chars_newlines(status, max_lines=1)
- else:
- status_reduced = ''
- status_escaped = GLib.markup_escape_text(status_reduced)
-
- font_attrs, font_attrs_small = self.get_font_attrs()
- 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 st == 'all' or cs == 'composing':
- chatstate = helpers.get_uf_chatstate(cs)
- else:
- chatstate = ''
-
- label_text = '<span %s>%s</span><span %s>%s %s</span>' \
- % (font_attrs, name, font_attrs_small, acct_info, chatstate)
- if acct_info:
- acct_info = i18n.direction_mark + ' ' + acct_info
- label_tooltip = '%s%s %s' % (name, 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)
- if acct_info:
- acct_info = i18n.direction_mark + ' ' + acct_info
- label_tooltip = '%s%s' % (name, acct_info)
-
- if status_escaped:
- status_text = self.urlfinder.sub(self.make_href, status_escaped)
- status_text = '<span %s>%s</span>' % (font_attrs_small, status_text)
- self.banner_status_label.set_tooltip_text(status)
- self.banner_status_label.set_no_show_all(False)
- self.banner_status_label.show()
- else:
- status_text = ''
- self.banner_status_label.hide()
- self.banner_status_label.set_no_show_all(True)
-
- self.banner_status_label.set_markup(status_text)
- # setup the label that holds name and jid
- banner_name_label.set_markup(label_text)
- banner_name_label.set_tooltip_text(label_tooltip)
-
- def close_jingle_content(self, jingle_type):
- sid = getattr(self, jingle_type + '_sid')
- if not sid:
- return
- setattr(self, jingle_type + '_sid', None)
- setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL)
- session = gajim.connections[self.account].get_jingle_session(
- self.contact.get_full_jid(), sid)
- if session:
- content = session.get_content(jingle_type)
- if content:
- session.remove_content(content.creator, content.name)
- getattr(self, '_' + jingle_type + '_button').set_active(False)
- getattr(self, 'update_' + jingle_type)()
-
- def on_jingle_button_toggled(self, widget, jingle_type):
- img_name = 'gajim-%s_%s' % ({'audio': 'mic', 'video': 'cam'}[jingle_type],
- {True: 'active', False: 'inactive'}[widget.get_active()])
- path_to_img = gtkgui_helpers.get_icon_path(img_name)
-
- if widget.get_active():
- if getattr(self, jingle_type + '_state') == \
- self.JINGLE_STATE_NULL:
- if jingle_type == 'video':
- video_hbox = self.xml.get_object('video_hbox')
- video_hbox.set_no_show_all(False)
- if gajim.config.get('video_see_self'):
- fixed = self.xml.get_object('outgoing_fixed')
- fixed.set_no_show_all(False)
- video_hbox.show_all()
- out_da = self.xml.get_object('outgoing_drawingarea')
- out_da.realize()
- if os.name == 'nt':
- out_xid = out_da.get_window().handle
- else:
- out_xid = out_da.get_window().get_xid()
- else:
- out_xid = None
- video_hbox.show_all()
- in_da = self.xml.get_object('incoming_drawingarea')
- in_da.realize()
- in_xid = in_da.get_window().get_xid()
- sid = gajim.connections[self.account].start_video(
- self.contact.get_full_jid(), in_xid, out_xid)
- else:
- sid = getattr(gajim.connections[self.account],
- 'start_' + jingle_type)(self.contact.get_full_jid())
- getattr(self, 'set_' + jingle_type + '_state')('connecting', sid)
- else:
- video_hbox = self.xml.get_object('video_hbox')
- video_hbox.set_no_show_all(True)
- video_hbox.hide()
- fixed = self.xml.get_object('outgoing_fixed')
- fixed.set_no_show_all(True)
- self.close_jingle_content(jingle_type)
-
- img = getattr(self, '_' + jingle_type + '_button').get_property('image')
- img.set_from_file(path_to_img)
-
- def on_audio_button_toggled(self, widget):
- self.on_jingle_button_toggled(widget, 'audio')
-
- def on_video_button_toggled(self, widget):
- self.on_jingle_button_toggled(widget, 'video')
-
- def set_lock_image(self):
- loggable = self.session and self.session.is_loggable()
-
- encryption_state = {'visible': self.encryption is not None,
- 'enc_type': self.encryption,
- 'authenticated': False}
-
- if self.encryption:
- gajim.plugin_manager.extension_point(
- 'encryption_state' + self.encryption, self, encryption_state)
-
- self._show_lock_image(**encryption_state)
-
- def _show_lock_image(self, visible, enc_type='',
- authenticated=False):
- """
- Set lock icon visibility and create tooltip
- """
- if authenticated:
- authenticated_string = _('and authenticated')
- img_path = gtkgui_helpers.get_icon_path('security-high')
- else:
- authenticated_string = _('and NOT authenticated')
- img_path = gtkgui_helpers.get_icon_path('security-low')
- self.lock_image.set_from_file(img_path)
-
- tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string}
-
- self.authentication_button.set_tooltip_text(tooltip)
- self.widget_set_visible(self.authentication_button, not visible)
- self.lock_image.set_sensitive(visible)
-
- def _on_authentication_button_clicked(self, widget):
- if self.encryption:
- gajim.plugin_manager.extension_point(
- 'encryption_dialog' + self.encryption, self)
-
- def send_message(self, message, keyID='', chatstate=None, xhtml=None,
- process_commands=True, attention=False):
- """
- Send a message to contact
- """
-
- if self.encryption:
- self.sendmessage = True
- gajim.plugin_manager.extension_point(
- 'send_message' + self.encryption, self)
- if not self.sendmessage:
- return
-
- message = helpers.remove_invalid_xml_chars(message)
- if message in ('', None, '\n'):
- return None
-
- contact = self.contact
- keyID = contact.keyID
-
- chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
- 'disabled'
-
- chatstate_to_send = None
- if contact is not None:
- if contact.supports(NS_CHATSTATES):
- # send active chatstate on every message (as XEP says)
- chatstate_to_send = 'active'
- contact.our_chatstate = 'active'
-
- GLib.source_remove(self.possible_paused_timeout_id)
- GLib.source_remove(self.possible_inactive_timeout_id)
- self._schedule_activity_timers()
-
- def _on_sent(obj, msg_stanza, message, encrypted, xhtml, label):
- id_ = msg_stanza.getID()
- xep0184_id = None
- if self.contact.jid != gajim.get_jid_from_account(self.account):
- if gajim.config.get_per('accounts', self.account, 'request_receipt'):
- xep0184_id = id_
- if label:
- displaymarking = label.getTag('displaymarking')
- else:
- displaymarking = None
- if self.correcting:
- self.correcting = False
- gtkgui_helpers.remove_css_class(
- self.msg_textview, 'msgcorrectingcolor')
-
- self.print_conversation(message, self.contact.jid,
- encrypted=encrypted, xep0184_id=xep0184_id, xhtml=xhtml,
- displaymarking=displaymarking, msg_stanza_id=id_,
- correct_id=obj.correct_id,
- additional_data=obj.additional_data)
-
- ChatControlBase.send_message(self, message, keyID, type_='chat',
- chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
- callback_args=[message, self.encryption, xhtml, self.get_seclabel()],
- process_commands=process_commands,
- attention=attention)
-
- def on_cancel_session_negotiation(self):
- msg = _('Session negotiation cancelled')
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- def print_archiving_session_details(self):
- """
- Print esession settings to textview
- """
- archiving = bool(self.session) and isinstance(self.session,
- ArchivingStanzaSession) and self.session.archiving
- if archiving:
- msg = _('This session WILL be archived on server')
- else:
- msg = _('This session WILL NOT be archived on server')
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- def print_esession_details(self):
- """
- Print esession settings to textview
- """
- e2e_is_active = bool(self.session) and self.session.enable_encryption
- if e2e_is_active:
- msg = _('This session is encrypted')
-
- if self.session.is_loggable():
- msg += _(' and WILL be logged')
- else:
- msg += _(' and WILL NOT be logged')
-
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- if not self.session.verified_identity:
- ChatControlBase.print_conversation_line(self, _("Remote contact's identity not verified. Click the shield button for more details."), 'status', '', None)
- else:
- msg = _('E2E encryption disabled')
- ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
-
- self._show_lock_image(e2e_is_active, 'E2E',
- self.session and self.session.verified_identity)
-
- def print_session_details(self, old_session=None):
- if isinstance(self.session, EncryptedStanzaSession) or \
- (old_session and isinstance(old_session, EncryptedStanzaSession)):
- self.print_esession_details()
- elif isinstance(self.session, ArchivingStanzaSession):
- self.print_archiving_session_details()
-
- def get_our_nick(self):
- return gajim.nicks[self.account]
-
- def print_conversation(self, text, frm='', tim=None, encrypted=None,
- subject=None, xhtml=None, simple=False, xep0184_id=None,
- displaymarking=None, msg_log_id=None, correct_id=None,
- msg_stanza_id=None, additional_data=None):
- """
- Print a line in the conversation
-
- If frm is set to status: it's a status message.
- if frm is set to error: it's an error message. The difference between
- status and error is mainly that with error, msg count as a new message
- (in systray and in control).
- If frm is set to info: it's a information message.
- If frm is set to print_queue: it is incomming from queue.
- If frm is set to another value: it's an outgoing message.
- If frm is not set: it's an incomming message.
- """
- contact = self.contact
-
- if additional_data is None:
- additional_data = {}
-
- if frm == 'status':
- if not gajim.config.get('print_status_in_chats'):
- return
- kind = 'status'
- name = ''
- elif frm == 'error':
- kind = 'error'
- name = ''
- elif frm == 'info':
- kind = 'info'
- name = ''
- else:
- if not frm:
- kind = 'incoming'
- name = contact.get_shown_name()
- elif frm == 'print_queue': # incoming message, but do not update time
- kind = 'incoming_queue'
- name = contact.get_shown_name()
- else:
- kind = 'outgoing'
- name = self.get_our_nick()
- if not xhtml and not encrypted and \
- gajim.config.get('rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- xhtml = create_xhtml(text)
- if xhtml:
- xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
- ChatControlBase.print_conversation_line(self, text, kind, name, tim,
- subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
- simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking,
- msg_log_id=msg_log_id, msg_stanza_id=msg_stanza_id,
- correct_id=correct_id, additional_data=additional_data,
- encrypted=encrypted)
- if text.startswith('/me ') or text.startswith('/me\n'):
- self.old_msg_kind = None
- else:
- self.old_msg_kind = kind
-
- def get_tab_label(self):
- unread = ''
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
- num_unread = len(gajim.events.get_events(self.account, jid,
- ['printed_' + self.type_id, self.type_id]))
- if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
- unread = '*'
- elif num_unread > 1:
- unread = '[' + str(num_unread) + ']'
-
- name = self.contact.get_shown_name()
- if self.resource:
- name += '/' + self.resource
- label_str = GLib.markup_escape_text(name)
- if num_unread: # if unread, text in the label becomes bold
- label_str = '<b>' + unread + label_str + '</b>'
- return label_str
-
- def get_tab_image(self, count_unread=True):
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
-
- if gajim.config.get('show_avatar_in_tabs'):
- avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if avatar_pixbuf not in ('ask', None):
- avatar_pixbuf = gtkgui_helpers.get_scaled_pixbuf_by_size(
- avatar_pixbuf, 16, 16)
- return avatar_pixbuf
-
- if count_unread:
- num_unread = len(gajim.events.get_events(self.account, jid,
- ['printed_' + self.type_id, self.type_id]))
- else:
- num_unread = 0
- # Set tab image (always 16x16); unread messages show the 'event' image
- tab_img = None
-
- if num_unread and gajim.config.get('show_unread_tab_icon'):
- img_16 = gajim.interface.roster.get_appropriate_state_images(
- self.contact.jid, icon_name='event')
- tab_img = img_16['event']
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, self.contact.jid)
- if not contact or self.resource:
- # For transient contacts
- contact = self.contact
- img_16 = gajim.interface.roster.get_appropriate_state_images(
- self.contact.jid, icon_name=contact.show)
- tab_img = img_16[contact.show]
-
- return tab_img
-
- def prepare_context_menu(self, hide_buttonbar_items=False):
- """
- Set compact view menuitem active state sets active and sensitivity state
- for history_menuitem (False for tranasports) and file_transfer_menuitem
- and hide()/show() for add_to_roster_menuitem
- """
- if gajim.jid_is_transport(self.contact.jid):
- menu = gui_menu_builder.get_transport_menu(self.contact,
- self.account)
- else:
- menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
- use_multiple_contacts=False, show_start_chat=False,
- show_encryption=True, control=self,
- show_buttonbar_items=not hide_buttonbar_items)
- return menu
-
- def send_chatstate(self, state, contact=None):
- """
- Send OUR chatstate as STANDLONE chat state message (eg. no body)
- to contact only if new chatstate is different from the previous one
- if jid is not specified, send to active tab
- """
- # JEP 85 does not allow resending the same chatstate
- # this function checks for that and just returns so it's safe to call it
- # with same state.
-
- # This functions also checks for violation in state transitions
- # and raises RuntimeException with appropriate message
- # more on that http://xmpp.org/extensions/xep-0085.html#statechart
-
- # do not send if we have chat state notifications disabled
- # that means we won't reply to the <active/> from other peer
- # so we do not broadcast jep85 capabalities
- chatstate_setting = gajim.config.get('outgoing_chat_state_notifications')
- if chatstate_setting == 'disabled':
- return
-
- # Dont leak presence to contacts
- # which are not allowed to see our status
- if contact and contact.sub in ('to', 'none'):
- return
-
- if self.contact.jid == gajim.get_jid_from_account(self.account):
- return
-
- elif chatstate_setting == 'composing_only' and state != 'active' and\
- state != 'composing':
- return
-
- if contact is None:
- contact = self.parent_win.get_active_contact()
- if contact is None:
- # contact was from pm in MUC, and left the room so contact is None
- # so we cannot send chatstate anymore
- return
-
- # Don't send chatstates to offline contacts
- if contact.show == 'offline':
- return
-
- if not contact.supports(NS_CHATSTATES):
- return
- if contact.our_chatstate == False:
- return
-
- # if the new state we wanna send (state) equals
- # the current state (contact.our_chatstate) then return
- if contact.our_chatstate == state:
- return
-
- # if wel're inactive prevent composing (XEP violation)
- if contact.our_chatstate == 'inactive' and state == 'composing':
- # go active before
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, chatstate='active',
- control=self))
- contact.our_chatstate = 'active'
- self.reset_kbd_mouse_timeout_vars()
-
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, chatstate=state,
- msg_id=contact.msg_log_id, control=self))
-
- contact.our_chatstate = state
- if state == 'active':
- self.reset_kbd_mouse_timeout_vars()
-
- def shutdown(self):
- # PluginSystem: removing GUI extension points connected with ChatControl
- # instance object
- gajim.plugin_manager.remove_gui_extension_point('chat_control', self)
-
- gajim.ged.remove_event_handler('pep-received', ged.GUI1,
- self._nec_pep_received)
- gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- gajim.ged.remove_event_handler('failed-decrypt', ged.GUI1,
- self._nec_failed_decrypt)
- gajim.ged.remove_event_handler('chatstate-received', ged.GUI1,
- self._nec_chatstate_received)
- gajim.ged.remove_event_handler('caps-received', ged.GUI1,
- self._nec_caps_received)
-
- self.unsubscribe_events()
-
- # Send 'gone' chatstate
- self.send_chatstate('gone', self.contact)
- self.contact.chatstate = None
- self.contact.our_chatstate = None
-
- for jingle_type in ('audio', 'video'):
- self.close_jingle_content(jingle_type)
-
- # disconnect self from session
- if self.session:
- self.session.control = None
-
- # Remove bigger avatar window
- if self.bigger_avatar_window:
- self.bigger_avatar_window.destroy()
- # Clean events
- gajim.events.remove_events(self.account, self.get_full_jid(),
- types=['printed_' + self.type_id, self.type_id])
- # Remove contact instance if contact has been removed
- key = (self.contact.jid, self.account)
- roster = gajim.interface.roster
- if key in roster.contacts_to_be_removed.keys() and \
- not roster.contact_has_pending_roster_events(self.contact,
- self.account):
- backend = roster.contacts_to_be_removed[key]['backend']
- del roster.contacts_to_be_removed[key]
- roster.remove_contact(self.contact.jid, self.account, force=True,
- backend=backend)
- # remove all register handlers on widgets, created by self.xml
- # to prevent circular references among objects
- for i in list(self.handlers.keys()):
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- self.conv_textview.del_handlers()
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- spell_obj = gtkspell.get_from_text_view(self.msg_textview)
- if spell_obj:
- spell_obj.detach()
- self.msg_textview.destroy()
- # PluginSystem: calling shutdown of super class (ChatControlBase) to let
- # it remove it's GUI extension points
- super(ChatControl, self).shutdown()
-
- def minimizable(self):
- return False
-
- def safe_shutdown(self):
- return False
-
- def allow_shutdown(self, method, on_yes, on_no, on_minimize):
- if time.time() - gajim.last_message_time[self.account]\
- [self.get_full_jid()] < 2:
- # 2 seconds
-
- def on_ok():
- on_yes(self)
-
- def on_cancel():
- on_no(self)
-
- dialogs.ConfirmationDialog(
- #%s is being replaced in the code with JID
- _('You just received a new message from "%s"') % \
- self.contact.jid,
- _('If you close this tab and you have history disabled, '\
- 'this message will be lost.'), on_response_ok=on_ok,
- on_response_cancel=on_cancel,
- transient_for=self.parent_win.window)
- return
- on_yes(self)
-
- def _nec_chatstate_received(self, obj):
- """
- Handle incoming chatstate that jid SENT TO us
- """
- self.draw_banner_text()
- # update chatstate in tab for this chat
- self.parent_win.redraw_tab(self, self.contact.chatstate)
-
- def _nec_caps_received(self, obj):
- if obj.conn.name != self.account:
- return
- if self.TYPE_ID == 'chat' and obj.jid != self.contact.jid:
- return
- if self.TYPE_ID == 'pm' and obj.fjid != self.contact.jid:
- return
- self.update_ui()
-
- def _nec_ping_reply(self, obj):
- if obj.control:
- if obj.control != self:
- return
- else:
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Pong! (%s s.)') % obj.seconds, 'status')
-
- def set_control_active(self, state):
- ChatControlBase.set_control_active(self, state)
- # Hide bigger avatar window
- if self.bigger_avatar_window:
- self.bigger_avatar_window.destroy()
- self.bigger_avatar_window = None
- # Re-show the small avatar
- self.show_avatar()
-
- def show_avatar(self):
- if not gajim.config.get('show_avatar_in_chat'):
- return
-
- jid_with_resource = self.contact.get_full_jid()
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid_with_resource)
- if pixbuf == 'ask':
- # we don't have the vcard
- if self.TYPE_ID == message_control.TYPE_PM:
- if self.gc_contact.jid:
- # We know the real jid of this contact
- real_jid = self.gc_contact.jid
- if self.gc_contact.resource:
- real_jid += '/' + self.gc_contact.resource
- else:
- real_jid = jid_with_resource
- gajim.connections[self.account].request_vcard(real_jid,
- jid_with_resource)
- else:
- gajim.connections[self.account].request_vcard(jid_with_resource)
- return
- elif pixbuf:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat')
- else:
- scaled_pixbuf = None
-
- image = self.xml.get_object('avatar_image')
- image.set_from_pixbuf(scaled_pixbuf)
- image.show_all()
-
- def _nec_vcard_received(self, obj):
- if obj.conn.name != self.account:
- return
- j = gajim.get_jid_without_resource(self.contact.jid)
- if obj.jid != j:
- return
- self.show_avatar()
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- if not selection.get_data():
- return
- if self.TYPE_ID == message_control.TYPE_PM:
- c = self.gc_contact
- else:
- c = self.contact
- if target_type == self.TARGET_TYPE_URI_LIST:
- if not c.resource: # If no resource is known, we can't send a file
- return
- uri = selection.get_data().strip()
- uri_splitted = uri.split() # we may have more than one file dropped
- for uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- if os.path.isfile(path): # is it file?
- ft = gajim.interface.instances['file_transfers']
- ft.send_file(self.account, c, path)
- return
-
- # chat2muc
- treeview = gajim.interface.roster.tree
- model = treeview.get_model()
- data = selection.get_data()
- path = treeview.get_selection().get_selected_rows()[1][0]
- iter_ = model.get_iter(path)
- type_ = model[iter_][2]
- if type_ != 'contact': # source is not a contact
- return
- dropped_jid = data
-
- dropped_transport = gajim.get_transport_name_from_jid(dropped_jid)
- c_transport = gajim.get_transport_name_from_jid(c.jid)
- if dropped_transport or c_transport:
- return # transport contacts cannot be invited
-
- dialogs.TransformChatToMUC(self.account, [c.jid], [dropped_jid])
-
- def _on_message_tv_buffer_changed(self, textbuffer):
- super()._on_message_tv_buffer_changed(textbuffer)
- if textbuffer.get_char_count() and self.encryption:
- gajim.plugin_manager.extension_point(
- 'typing' + self.encryption, self)
- if (not self.session or not self.session.status) and \
- gajim.connections[self.account].archiving_136_supported:
- self.begin_archiving_negotiation()
-
- def restore_conversation(self):
- jid = self.contact.jid
- # don't restore lines if it's a transport
- if gajim.jid_is_transport(jid):
- return
-
- # How many lines to restore and when to time them out
- restore_how_many = gajim.config.get('restore_lines')
- if restore_how_many <= 0:
- return
- timeout = gajim.config.get('restore_timeout') # in minutes
-
- # number of messages that are in queue and are already logged, we want
- # to avoid duplication
- pending_how_many = len(gajim.events.get_events(self.account, jid,
- ['chat', 'pm']))
- if self.resource:
- pending_how_many += len(gajim.events.get_events(self.account,
- self.contact.get_full_jid(), ['chat', 'pm']))
-
- rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
- pending_how_many, timeout, self.account)
-
- local_old_kind = None
- self.conv_textview.just_cleared = True
- for row in rows: # row[0] time, row[1] has kind, row[2] the message, row[3] subject, row[4] additional_data
- msg = row[2]
- additional_data = row[4]
- if not msg: # message is empty, we don't print it
- continue
- if row[1] in (KindConstant.CHAT_MSG_SENT,
- KindConstant.SINGLE_MSG_SENT):
- kind = 'outgoing'
- name = self.get_our_nick()
- elif row[1] in (KindConstant.SINGLE_MSG_RECV,
- KindConstant.CHAT_MSG_RECV):
- kind = 'incoming'
- name = self.contact.get_shown_name()
- elif row[1] == KindConstant.ERROR:
- kind = 'status'
- name = self.contact.get_shown_name()
-
- tim = float(row[0])
-
- if gajim.config.get('restored_messages_small'):
- small_attr = ['small']
- else:
- small_attr = []
- xhtml = None
- if msg.startswith('<body '):
- xhtml = msg
- if row[3]:
- msg = _('Subject: %(subject)s\n%(message)s') % \
- {'subject': row[3], 'message': msg}
- ChatControlBase.print_conversation_line(self, msg, kind, name,
- tim, small_attr, small_attr + ['restored_message'],
- small_attr + ['restored_message'], False,
- old_kind=local_old_kind, xhtml=xhtml, additional_data=additional_data)
- if row[2].startswith('/me ') or row[2].startswith('/me\n'):
- local_old_kind = None
- else:
- local_old_kind = kind
- if len(rows):
- self.conv_textview.print_empty_line()
-
- def read_queue(self):
- """
- Read queue and print messages containted in it
- """
- jid = self.contact.jid
- jid_with_resource = jid
- if self.resource:
- jid_with_resource += '/' + self.resource
- events = gajim.events.get_events(self.account, jid_with_resource)
-
- # list of message ids which should be marked as read
- message_ids = []
- for event in events:
- if event.type_ != self.type_id:
- continue
- if event.kind == 'error':
- kind = 'info'
- else:
- kind = 'print_queue'
- if event.sent_forwarded:
- kind = 'out'
- self.print_conversation(event.message, kind, tim=event.time,
- encrypted=event.encrypted, subject=event.subject,
- xhtml=event.xhtml, displaymarking=event.displaymarking,
- correct_id=event.correct_id)
- if isinstance(event.msg_log_id, int):
- message_ids.append(event.msg_log_id)
-
- if event.session and not self.session:
- self.set_session(event.session)
- if message_ids:
- gajim.logger.set_read_messages(message_ids)
- gajim.events.remove_events(self.account, jid_with_resource,
- types=[self.type_id])
-
- typ = 'chat' # Is it a normal chat or a pm ?
-
- # reset to status image in gc if it is a pm
- # Is it a pm ?
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- self.account)
- if control and control.type_id == message_control.TYPE_GC:
- control.update_ui()
- control.parent_win.show_title()
- typ = 'pm'
-
- self.redraw_after_event_removed(jid)
- if (self.contact.show in ('offline', 'error')):
- show_offline = gajim.config.get('showoffline')
- show_transports = gajim.config.get('show_transports_group')
- if (not show_transports and gajim.jid_is_transport(jid)) or \
- (not show_offline and typ == 'chat' and \
- len(gajim.contacts.get_contacts(self.account, jid)) < 2):
- gajim.interface.roster.remove_to_be_removed(self.contact.jid,
- self.account)
- elif typ == 'pm':
- control.remove_contact(nick)
-
- def show_bigger_avatar(self, small_avatar):
- """
- Resize the avatar, if needed, so it has at max half the screen size and
- shows it
- """
- #if not small_avatar.window:
- ### Tab has been closed since we hovered the avatar
- #return
- avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(
- self.contact.jid)
- if avatar_pixbuf in ('ask', None):
- return
- # Hide the small avatar
- # this code hides the small avatar when we show a bigger one in case
- # the avatar has a transparency hole in the middle
- # so when we show the big one we avoid seeing the small one behind.
- # It's why I set it transparent.
- image = self.xml.get_object('avatar_image')
- pixbuf = image.get_pixbuf()
- pixbuf.fill(0xffffff00) # RGBA
- image.set_from_pixbuf(pixbuf)
- #image.queue_draw()
-
- screen_w = Gdk.Screen.width()
- screen_h = Gdk.Screen.height()
- avatar_w = avatar_pixbuf.get_width()
- avatar_h = avatar_pixbuf.get_height()
- half_scr_w = screen_w / 2
- half_scr_h = screen_h / 2
- if avatar_w > half_scr_w:
- avatar_w = half_scr_w
- if avatar_h > half_scr_h:
- avatar_h = half_scr_h
- # we should make the cursor visible
- # gtk+ doesn't make use of the motion notify on gtkwindow by default
- # so this line adds that
-
- alloc = small_avatar.get_allocation()
- # make the bigger avatar window show up centered
- small_avatar_x, small_avatar_y = alloc.x, alloc.y
- translated_coordinates = small_avatar.translate_coordinates(
- gajim.interface.roster.window, 0, 0)
- if translated_coordinates:
- small_avatar_x, small_avatar_y = translated_coordinates
- roster_x, roster_y = self.parent_win.window.get_window().get_origin()[1:]
- center_x = roster_x + small_avatar_x + (alloc.width / 2)
- center_y = roster_y + small_avatar_y + (alloc.height / 2)
- pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2)
-
- dialogs.BigAvatarWindow(avatar_pixbuf, pos_x, pos_y, avatar_w,
- avatar_h, self.show_avatar)
-
- self.show_bigger_avatar_timeout_id = None
-
- def _on_send_file_menuitem_activate(self, widget):
- self._on_send_file()
-
- def _on_add_to_roster_menuitem_activate(self, widget):
- dialogs.AddNewContactWindow(self.account, self.contact.jid)
-
- def _on_contact_information_menuitem_activate(self, widget):
- gajim.interface.roster.on_info(widget, self.contact, self.account)
-
- def _on_convert_to_gc_menuitem_activate(self, widget):
- """
- User wants to invite some friends to chat
- """
- dialogs.TransformChatToMUC(self.account, [self.contact.jid])
-
- def activate_esessions(self):
- if not (self.session and self.session.enable_encryption):
- self.begin_e2e_negotiation()
-
- def terminate_esessions(self):
- if not (self.session and self.session.enable_encryption):
- return
- # e2e was enabled, disable it
- jid = str(self.session.jid)
- thread_id = self.session.thread_id
-
- self.session.terminate_e2e()
-
- gajim.connections[self.account].delete_session(jid, thread_id)
-
- # presumably the user had a good reason to shut it off, so
- # disable autonegotiation too
- self.no_autonegotiation = True
-
- def begin_negotiation(self):
- self.no_autonegotiation = True
-
- if not self.session:
- fjid = self.contact.get_full_jid()
- new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
- self.set_session(new_sess)
-
- def begin_e2e_negotiation(self):
- self.begin_negotiation()
- self.session.resource = self.contact.resource
- self.session.negotiate_e2e(False)
-
- def begin_archiving_negotiation(self):
- self.begin_negotiation()
- self.session.negotiate_archiving()
-
- def _nec_failed_decrypt(self, obj):
- if obj.session != self.session:
- return
-
- details = _('Unable to decrypt message from %s\nIt may have been '
- 'tampered with.') % obj.fjid
- self.print_conversation_line(details, 'status', '', obj.timestamp)
-
- # terminate the session
- thread_id = self.session.thread_id
- self.session.terminate_e2e()
- obj.conn.delete_session(obj.fjid, thread_id)
-
- # restart the session
- self.begin_e2e_negotiation()
-
- # Stop emission so it doesn't go to gui_interface
- return True
-
- def got_connected(self):
- ChatControlBase.got_connected(self)
- # Refreshing contact
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, self.contact.jid)
- if isinstance(contact, GC_Contact):
- contact = contact.as_contact()
- if contact:
- self.contact = contact
- self.draw_banner()
- emoticons_button = self.xml.get_object('emoticons_button')
- emoticons_button.set_sensitive(True)
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(True)
-
- def got_disconnected(self):
- # Emoticons button
- emoticons_button = self.xml.get_object('emoticons_button')
- emoticons_button.set_sensitive(False)
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(False)
- # Add to roster
- self._add_to_roster_button.hide()
- # Audio button
- self._audio_button.set_sensitive(False)
- # Video button
- self._video_button.set_sensitive(False)
- # Send file button
- self._send_file_button.set_tooltip_text('')
- self._send_file_button.set_sensitive(False)
- # Convert to GC button
- self._convert_to_gc_button.set_sensitive(False)
-
- ChatControlBase.got_disconnected(self)
-
- def update_status_display(self, name, uf_show, status):
- """
- Print the contact's status and update the status/GPG image
- """
- self.update_ui()
- self.parent_win.redraw_tab(self)
-
- self.print_conversation(_('%(name)s is now %(status)s') % {'name': name,
- 'status': uf_show}, 'status')
-
- if status:
- self.print_conversation(' (', 'status', simple=True)
- self.print_conversation('%s' % (status), 'status', simple=True)
- self.print_conversation(')', 'status', simple=True)
-
- def _info_bar_show_message(self):
- if self.info_bar.get_visible():
- # A message is already shown
- return
- if not self.info_bar_queue:
- return
- markup, buttons, args, type_ = self.info_bar_queue[0]
- self.info_bar_label.set_markup(markup)
-
- # Remove old buttons
- area = self.info_bar.get_action_area()
- for b in area.get_children():
- area.remove(b)
-
- # Add new buttons
- for button in buttons:
- self.info_bar.add_action_widget(button, 0)
-
- self.info_bar.set_message_type(type_)
- self.info_bar.set_no_show_all(False)
- self.info_bar.show_all()
-
- def _add_info_bar_message(self, markup, buttons, args,
- type_=Gtk.MessageType.INFO):
- self.info_bar_queue.append((markup, buttons, args, type_))
- self._info_bar_show_message()
-
- def _get_file_props_event(self, file_props, type_):
- evs = gajim.events.get_events(self.account, self.contact.jid, [type_])
- for ev in evs:
- if ev.file_props == file_props:
- return ev
- return None
-
- def _on_accept_file_request(self, widget, file_props):
- gajim.interface.instances['file_transfers'].on_file_request_accepted(
- self.account, self.contact, file_props)
- ev = self._get_file_props_event(file_props, 'file-request')
- if ev:
- gajim.events.remove_events(self.account, self.contact.jid, event=ev)
-
- def _on_cancel_file_request(self, widget, file_props):
- gajim.connections[self.account].send_file_rejection(file_props)
- ev = self._get_file_props_event(file_props, 'file-request')
- if ev:
- gajim.events.remove_events(self.account, self.contact.jid, event=ev)
-
- def _got_file_request(self, file_props):
- """
- Show an InfoBar on top of control
- """
- markup = '<b>%s:</b> %s' % (_('File transfer'), file_props.name)
- if file_props.desc:
- markup += ' (%s)' % file_props.desc
- markup += '\n%s: %s' % (_('Size'), helpers.convert_bytes(
- file_props.size))
- b1 = Gtk.Button(_('_Accept'))
- b1.connect('clicked', self._on_accept_file_request, file_props)
- b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
- b2.connect('clicked', self._on_cancel_file_request, file_props)
- self._add_info_bar_message(markup, [b1, b2], file_props,
- Gtk.MessageType.QUESTION)
-
- def _on_open_ft_folder(self, widget, file_props):
- path = os.path.split(file_props.file_name)[0]
- if os.path.exists(path) and os.path.isdir(path):
- helpers.launch_file_manager(path)
- ev = self._get_file_props_event(file_props, 'file-completed')
- if ev:
- gajim.events.remove_events(self.account, self.contact.jid, event=ev)
-
- def _on_ok(self, widget, file_props, type_):
- ev = self._get_file_props_event(file_props, type_)
- if ev:
- gajim.events.remove_events(self.account, self.contact.jid, event=ev)
-
- def _got_file_completed(self, file_props):
- markup = '<b>%s:</b> %s' % (_('File transfer completed'),
- file_props.name)
- if file_props.desc:
- markup += ' (%s)' % file_props.desc
- b1 = Gtk.Button.new_with_mnemonic(_('Open _Containing Folder'))
- b1.connect('clicked', self._on_open_ft_folder, file_props)
- b2 = Gtk.Button(stock=Gtk.STOCK_OK)
- b2.connect('clicked', self._on_ok, file_props, 'file-completed')
- self._add_info_bar_message(markup, [b1, b2], file_props)
-
- def _got_file_error(self, file_props, type_, pri_txt, sec_txt):
- markup = '<b>%s:</b> %s' % (pri_txt, sec_txt)
- b = Gtk.Button(stock=Gtk.STOCK_OK)
- b.connect('clicked', self._on_ok, file_props, type_)
- self._add_info_bar_message(markup, [b], file_props, Gtk.MessageType.ERROR)
-
- def _on_accept_gc_invitation(self, widget, event):
- try:
- if event.is_continued:
- gajim.interface.join_gc_room(self.account, event.room_jid,
- gajim.nicks[self.account], event.password,
- is_continued=True)
- else:
- dialogs.JoinGroupchatWindow(self.account, event.room_jid)
- except GajimGeneralException:
- pass
- gajim.events.remove_events(self.account, self.contact.jid, event=event)
-
- def _on_cancel_gc_invitation(self, widget, event):
- gajim.events.remove_events(self.account, self.contact.jid, event=event)
-
- def _get_gc_invitation(self, event):
- markup = '<b>%s:</b> %s' % (_('Groupchat Invitation'), event.room_jid)
- if event.comment:
- markup += ' (%s)' % event.comment
- b1 = Gtk.Button(_('_Join'))
- b1.connect('clicked', self._on_accept_gc_invitation, event)
- b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
- b2.connect('clicked', self._on_cancel_gc_invitation, event)
- self._add_info_bar_message(markup, [b1, b2], (event.room_jid,
- event.comment), Gtk.MessageType.QUESTION)
-
- def on_event_added(self, event):
- if event.account != self.account:
- return
- if event.jid != self.contact.jid:
- return
- if event.type_ == 'file-request':
- self._got_file_request(event.file_props)
- elif event.type_ == 'file-completed':
- self._got_file_completed(event.file_props)
- elif event.type_ in ('file-error', 'file-stopped'):
- msg_err = ''
- if event.file_props.error == -1:
- msg_err = _('Remote contact stopped transfer')
- elif event.file_props.error == -6:
- msg_err = _('Error opening file')
- self._got_file_error(event.file_props, event.type_,
- _('File transfer stopped'), msg_err)
- elif event.type_ in ('file-request-error', 'file-send-error'):
- self._got_file_error(event.file_props, event.type_,
- _('File transfer cancelled'),
- _('Connection with peer cannot be established.'))
- elif event.type_ == 'gc-invitation':
- self._get_gc_invitation(event)
-
- def on_event_removed(self, event_list):
- """
- Called when one or more events are removed from the event list
- """
- for ev in event_list:
- if ev.account != self.account:
- continue
- if ev.jid != self.contact.jid:
- continue
- if ev.type_ not in ('file-request', 'file-completed', 'file-error',
- 'file-stopped', 'file-request-error', 'file-send-error',
- 'gc-invitation'):
- continue
- i = 0
- removed = False
- for ib_msg in self.info_bar_queue:
- if ev.type_ == 'gc-invitation':
- if ev.room_jid == ib_msg[2][0]:
- self.info_bar_queue.remove(ib_msg)
- removed = True
- else: # file-*
- if ib_msg[2] == ev.file_props:
- self.info_bar_queue.remove(ib_msg)
- removed = True
- if removed:
- if i == 0:
- # We are removing the one currently displayed
- self.info_bar.set_no_show_all(True)
- self.info_bar.hide()
- # show next one?
- GLib.idle_add(self._info_bar_show_message)
- break
- i += 1
diff --git a/src/chat_control_base.py b/src/chat_control_base.py
deleted file mode 100644
index 1550ac551..000000000
--- a/src/chat_control_base.py
+++ /dev/null
@@ -1,1369 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/chat_control_base.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import time
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import Pango
-from gi.repository import GObject
-from gi.repository import GLib
-from gi.repository import Gio
-import gtkgui_helpers
-from gtkgui_helpers import Color
-import message_control
-import dialogs
-import history_window
-import notify
-import re
-
-from common import events
-from common import gajim
-from common import helpers
-from common import ged
-from message_control import MessageControl
-from conversation_textview import ConversationTextview
-from message_textview import MessageTextView
-from common.contacts import GC_Contact
-from common.connection_handlers_events import MessageOutgoingEvent
-
-from command_system.implementation.middleware import ChatCommandProcessor
-from command_system.implementation.middleware import CommandTools
-
-# The members of these modules are not referenced directly anywhere in this
-# module, but still they need to be kept around. Importing them automatically
-# registers the contained CommandContainers with the command system, thereby
-# populating the list of available commands.
-import command_system.implementation.standard
-import command_system.implementation.execute
-
-try:
- import gtkspell
- HAS_GTK_SPELL = True
-except (ImportError, ValueError):
- HAS_GTK_SPELL = False
-
-################################################################################
-class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
- """
- A base class containing a banner, ConversationTextview, MessageTextView
- """
-
- keymap = Gdk.Keymap.get_default()
- try:
- keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
- except TypeError:
- keycode_c = 54
- try:
- keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
- except TypeError:
- keycode_ins = 118
- except IndexError:
- # There is no KEY_Insert (MacOS)
- keycode_ins = None
-
- def make_href(self, match):
- url_color = gajim.config.get('urlmsgcolor')
- url = match.group()
- if not '://' in url:
- url = 'http://' + url
- return '<a href="%s"><span color="%s">%s</span></a>' % (url,
- url_color, match.group())
-
- def get_font_attrs(self):
- """
- Get pango font attributes for banner from theme settings
- """
- theme = gajim.config.get('roster_theme')
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
- bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
-
- if bannerfont:
- font = Pango.FontDescription(bannerfont)
- else:
- font = Pango.FontDescription('Normal')
- if bannerfontattrs:
- # B attribute is set by default
- if 'B' in bannerfontattrs:
- font.set_weight(Pango.Weight.HEAVY)
- if 'I' in bannerfontattrs:
- font.set_style(Pango.Style.ITALIC)
-
- font_attrs = 'font_desc="%s"' % font.to_string()
-
- # in case there is no font specified we use x-large font size
- if font.get_size() == 0:
- font_attrs = '%s size="x-large"' % font_attrs
- font.set_weight(Pango.Weight.NORMAL)
- font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
- return (font_attrs, font_attrs_small)
-
- def get_nb_unread(self):
- jid = self.contact.jid
- if self.resource:
- jid += '/' + self.resource
- type_ = self.type_id
- return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
- type_]))
-
- def draw_banner(self):
- """
- Draw the fat line at the top of the window that houses the icon, jid, etc
-
- Derived types MAY implement this.
- """
- self.draw_banner_text()
- self._update_banner_state_image()
- gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
- self)
-
- def update_toolbar(self):
- """
- update state of buttons in toolbar
- """
- self._update_toolbar()
- gajim.plugin_manager.gui_extension_point(
- 'chat_control_base_update_toolbar', self)
-
- def draw_banner_text(self):
- """
- Derived types SHOULD implement this
- """
- pass
-
- def update_ui(self):
- """
- Derived types SHOULD implement this
- """
- self.draw_banner()
-
- def repaint_themed_widgets(self):
- """
- Derived types MAY implement this
- """
- self.draw_banner()
-
- def _update_banner_state_image(self):
- """
- Derived types MAY implement this
- """
- pass
-
- def _update_toolbar(self):
- """
- Derived types MAY implement this
- """
- pass
-
- def _nec_our_status(self, obj):
- if self.account != obj.conn.name:
- return
- if obj.show == 'offline' or (obj.show == 'invisible' and \
- obj.conn.is_zeroconf):
- self.got_disconnected()
- else:
- # Other code rejoins all GCs, so we don't do it here
- if not self.type_id == message_control.TYPE_GC:
- self.got_connected()
- if self.parent_win:
- self.parent_win.redraw_tab(self)
-
- def _nec_ping_sent(self, obj):
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Ping?'), 'status')
-
- def _nec_ping_error(self, obj):
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Error.'), 'status')
-
- def status_url_clicked(self, widget, url):
- helpers.launch_browser_mailer('url', url)
-
- def setup_seclabel(self, combo):
- self.seclabel_combo = combo
- self.seclabel_combo.hide()
- self.seclabel_combo.set_no_show_all(True)
- lb = Gtk.ListStore(str)
- self.seclabel_combo.set_model(lb)
- cell = Gtk.CellRendererText()
- cell.set_property('xpad', 5) # padding for status text
- self.seclabel_combo.pack_start(cell, True)
- # text to show is in in first column of liststore
- self.seclabel_combo.add_attribute(cell, 'text', 0)
- if gajim.connections[self.account].seclabel_supported:
- gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
-
- def on_seclabels_ready(self):
- lb = self.seclabel_combo.get_model()
- lb.clear()
- i = 0
- sel = 0
- catalogue = gajim.connections[self.account].seclabel_catalogues[
- self.contact.jid]
- for label in catalogue[2]:
- lb.append([label])
- if label == catalogue[3]:
- sel = i
- i += 1
- self.seclabel_combo.set_active(sel)
- self.seclabel_combo.set_no_show_all(False)
- self.seclabel_combo.show_all()
-
- def __init__(self, type_id, parent_win, widget_name, contact, acct,
- resource=None):
- # Undo needs this variable to know if space has been pressed.
- # Initialize it to True so empty textview is saved in undo list
- self.space_pressed = True
-
- if resource is None:
- # We very likely got a contact with a random resource.
- # This is bad, we need the highest for caps etc.
- c = gajim.contacts.get_contact_with_highest_priority(acct,
- contact.jid)
- if c and not isinstance(c, GC_Contact):
- contact = c
-
- MessageControl.__init__(self, type_id, parent_win, widget_name,
- contact, acct, resource=resource)
-
- widget = self.xml.get_object('history_button')
- # set document-open-recent icon for history button
- if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
- img = self.xml.get_object('history_image')
- img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
-
- id_ = widget.connect('clicked', self._on_history_menuitem_activate)
- self.handlers[id_] = widget
-
- # when/if we do XHTML we will put formatting buttons back
- widget = self.xml.get_object('emoticons_button')
- widget.set_sensitive(False)
- id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
- self.handlers[id_] = widget
-
- # Create banner and connect signals
- widget = self.xml.get_object('banner_eventbox')
- id_ = widget.connect('button-press-event',
- self._on_banner_eventbox_button_press_event)
- self.handlers[id_] = widget
-
- self.urlfinder = re.compile(
- r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
-
- self.banner_status_label = self.xml.get_object('banner_label')
- id_ = self.banner_status_label.connect('populate_popup',
- self.on_banner_label_populate_popup)
- self.handlers[id_] = self.banner_status_label
-
- # Init DND
- self.TARGET_TYPE_URI_LIST = 80
- self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
- self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
- Gtk.TargetFlags.SAME_APP, 0)]
- id_ = self.widget.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.widget
- self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
- Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
- self.dnd_list, Gdk.DragAction.COPY)
-
- # Create textviews and connect signals
- self.conv_textview = ConversationTextview(self.account)
- id_ = self.conv_textview.connect('quote', self.on_quote)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('key_press_event',
- self._conv_textview_key_press_event)
- self.handlers[id_] = self.conv_textview.tv
- # FIXME: DND on non editable TextView, find a better way
- self.drag_entered = False
- id_ = self.conv_textview.tv.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
- self.handlers[id_] = self.conv_textview.tv
- id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
- self.handlers[id_] = self.conv_textview.tv
- self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
- Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
- self.dnd_list, Gdk.DragAction.COPY)
-
- self.conv_scrolledwindow = self.xml.get_object(
- 'conversation_scrolledwindow')
- self.conv_scrolledwindow.add(self.conv_textview.tv)
- widget = self.conv_scrolledwindow.get_vadjustment()
- id_ = widget.connect('value-changed',
- self.on_conversation_vadjustment_value_changed)
- self.handlers[id_] = widget
- id_ = widget.connect('changed',
- self.on_conversation_vadjustment_changed)
- self.handlers[id_] = widget
- self.was_at_the_end = True
- self.correcting = False
- self.last_sent_msg = None
- self.last_received_txt = {} # one per name
- self.last_received_id = {} # one per name
-
- # add MessageTextView to UI and connect signals
- self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
- self.msg_textview = MessageTextView()
- self.msg_scrolledwindow.add(self.msg_textview)
- id_ = self.msg_textview.connect('key_press_event',
- self._on_message_textview_key_press_event)
- self.handlers[id_] = self.msg_textview
- id_ = self.msg_textview.connect('configure-event',
- self.on_configure_event)
- self.handlers[id_] = self.msg_textview
- id_ = self.msg_textview.connect('populate_popup',
- self.on_msg_textview_populate_popup)
- self.handlers[id_] = self.msg_textview
- # Setup DND
- id_ = self.msg_textview.connect('drag_data_received',
- self._on_drag_data_received)
- self.handlers[id_] = self.msg_textview
- self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
- Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
-
- # Hook up send button
- widget = self.xml.get_object('send_button')
- id_ = widget.connect('clicked', self._on_send_button_clicked)
- widget.set_sensitive(False)
- self.handlers[id_] = widget
-
- # the following vars are used to keep history of user's messages
- self.sent_history = []
- self.sent_history_pos = 0
- self.received_history = []
- self.received_history_pos = 0
- self.orig_msg = None
-
- # Emoticons menu
- # set image no matter if user wants at this time emoticons or not
- # (so toggle works ok)
- img = self.xml.get_object('emoticons_button_image')
- img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
- 'smile.png'))
- self.toggle_emoticons()
-
- # Attach speller
- if gajim.config.get('use_speller') and HAS_GTK_SPELL:
- self.set_speller()
- self.conv_textview.tv.show()
-
- # For XEP-0172
- self.user_nick = None
-
- self.smooth = True
-
- self.command_hits = []
- self.last_key_tabs = False
-
- # chatstate timers and state
- self.reset_kbd_mouse_timeout_vars()
- self._schedule_activity_timers()
- message_tv_buffer = self.msg_textview.get_buffer()
- id_ = message_tv_buffer.connect('changed',
- self._on_message_tv_buffer_changed)
- self.handlers[id_] = message_tv_buffer
- if parent_win is not None:
- id_ = parent_win.window.connect('motion-notify-event',
- self._on_window_motion_notify)
- self.handlers[id_] = parent_win.window
-
- self.encryption = self.get_encryption_state()
- if self.parent_win:
- self.add_window_actions()
-
- # 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)
-
- gajim.ged.register_event_handler('our-show', ged.GUI1,
- self._nec_our_status)
- gajim.ged.register_event_handler('ping-sent', ged.GUI1,
- self._nec_ping_sent)
- gajim.ged.register_event_handler('ping-reply', ged.GUI1,
- self._nec_ping_reply)
- gajim.ged.register_event_handler('ping-error', ged.GUI1,
- self._nec_ping_error)
-
- # This is bascially a very nasty hack to surpass the inability
- # to properly use the super, because of the old code.
- CommandTools.__init__(self)
-
- def add_window_actions(self):
- action = Gio.SimpleAction.new_stateful(
- "%s-encryptiongroup" % self.contact.jid,
- GLib.VariantType.new("s"),
- GLib.Variant("s", self.encryption or 'disabled'))
- action.connect("change-state", self.change_encryption)
- self.parent_win.window.add_action(action)
-
- def change_encryption(self, action, param):
- encryption = param.get_string()
- if encryption == 'disabled':
- encryption = None
-
- if self.encryption == encryption:
- return
-
- if encryption:
- plugin = gajim.plugin_manager.encryption_plugins[encryption]
- if not plugin.activate_encryption(self):
- return
- else:
- if not self.widget_name == 'groupchat_control':
- self.terminate_esessions()
- action.set_state(param)
- self.set_encryption_state(encryption)
- self.set_encryption_menu_icon()
- self.set_lock_image()
-
- def set_encryption_state(self, encryption):
- config_key = '%s-%s' % (self.account, self.contact.jid)
- self.encryption = encryption
- gajim.config.set_per('encryption', config_key,
- 'encryption', self.encryption or '')
-
- def get_encryption_state(self):
- config_key = '%s-%s' % (self.account, self.contact.jid)
- state = gajim.config.get_per('encryption', config_key, 'encryption')
- return state or None
-
- def set_encryption_menu_icon(self):
- for child in self.encryption_menu.get_children():
- if isinstance(child, Gtk.Image):
- image = child
- break
-
- if not self.encryption:
- icon = gtkgui_helpers.get_icon_pixmap(
- 'channel-insecure-symbolic', color=[Color.BLACK])
- else:
- icon = gtkgui_helpers.get_icon_pixmap('channel-secure-symbolic')
- image.set_from_pixbuf(icon)
-
- def set_speller(self):
- # now set the one the user selected
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- lang = gajim.config.get_per(per_type, self.contact.jid,
- 'speller_language')
- if not lang:
- # use the default one
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- if lang:
- try:
- self.spell = gtkspell.Spell(self.msg_textview, lang)
- self.msg_textview.lang = lang
- self.spell.connect('language_changed', self.on_language_changed)
- except (GObject.GError, RuntimeError, TypeError, OSError):
- dialogs.AspellDictError(lang)
-
- def on_language_changed(self, spell, lang):
- per_type = 'contacts'
- if self.type_id == message_control.TYPE_GC:
- per_type = 'rooms'
- if not gajim.config.get_per(per_type, self.contact.jid):
- gajim.config.add_per(per_type, self.contact.jid)
- gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
- lang)
- self.msg_textview.lang = lang
-
- def on_banner_label_populate_popup(self, label, menu):
- """
- Override the default context menu and add our own menutiems
- """
- item = Gtk.SeparatorMenuItem.new()
- menu.prepend(item)
-
- menu2 = self.prepare_context_menu()
- i = 0
- for item in menu2:
- menu2.remove(item)
- menu.prepend(item)
- menu.reorder_child(item, i)
- i += 1
- menu.show_all()
-
- def shutdown(self):
- super(ChatControlBase, self).shutdown()
- # Disconnect timer callbacks
- GLib.source_remove(self.possible_paused_timeout_id)
- GLib.source_remove(self.possible_inactive_timeout_id)
- # PluginSystem: removing GUI extension points connected with ChatControlBase
- # instance object
- gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
- self)
- gajim.plugin_manager.remove_gui_extension_point(
- 'chat_control_base_draw_banner', self)
- gajim.ged.remove_event_handler('our-show', ged.GUI1,
- self._nec_our_status)
-
- def on_msg_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend an option to switch
- languages
- """
- item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
- menu.prepend(item)
- id_ = item.connect('activate', self.msg_textview.undo)
- self.handlers[id_] = item
-
- item = Gtk.SeparatorMenuItem.new()
- menu.prepend(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
- menu.prepend(item)
- id_ = item.connect('activate', self.msg_textview.clear)
- self.handlers[id_] = item
-
- menu.show_all()
-
- def on_quote(self, widget, text):
- text = '>' + text.replace('\n', '\n>') + '\n'
- message_buffer = self.msg_textview.get_buffer()
- message_buffer.insert_at_cursor(text)
-
- # moved from ChatControl
- def _on_banner_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- self.parent_win.popup_menu(event)
-
- def _on_send_button_clicked(self, widget):
- """
- When send button is pressed: send the current message
- """
- message_buffer = self.msg_textview.get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter, False)
- xhtml = self.msg_textview.get_xhtml()
-
- # send the message
- self.send_message(message, xhtml=xhtml)
-
- def _conv_textview_key_press_event(self, widget, event):
- # translate any layout to latin_layout
- valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
- keycode = entries[0].keycode
- if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
- self.keycode_c, self.keycode_ins)) or (
- event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
- event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
- return False
- self.parent_win.notebook.event(event)
- return True
-
- def show_emoticons_menu(self):
- if not gajim.config.get('emoticons_theme'):
- return
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
-
- def _on_message_textview_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_space:
- self.space_pressed = True
-
- elif (self.space_pressed or self.msg_textview.undo_pressed) and \
- event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
- not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
- # If the space key has been pressed and now it hasnt,
- # we save the buffer into the undo list. But be carefull we're not
- # pressiong Control again (as in ctrl+z)
- _buffer = widget.get_buffer()
- start_iter, end_iter = _buffer.get_bounds()
- self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
- self.space_pressed = False
-
- # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
- if self.widget_name == 'groupchat_control':
- if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
- self.last_key_tabs = False
- if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
- # CTRL + SHIFT + TAB
- if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
- event.keyval == Gdk.KEY_ISO_Left_Tab:
- self.parent_win.move_to_next_unread_tab(False)
- return True
- # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
- elif event.keyval == Gdk.KEY_Page_Down or \
- event.keyval == Gdk.KEY_Page_Up:
- self.conv_textview.tv.event(event)
- return True
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
- if event.keyval == Gdk.KEY_Tab: # CTRL + TAB
- self.parent_win.move_to_next_unread_tab(True)
- return True
-
- message_buffer = self.msg_textview.get_buffer()
- event_state = event.get_state()
- if event.keyval == Gdk.KEY_Tab:
- start, end = message_buffer.get_bounds()
- position = message_buffer.get_insert()
- end = message_buffer.get_iter_at_mark(position)
- text = message_buffer.get_text(start, end, False)
- splitted = text.split()
- if (text.startswith(self.COMMAND_PREFIX) and not
- text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
- text = splitted[0]
- bare = text.lstrip(self.COMMAND_PREFIX)
- if len(text) == 1:
- self.command_hits = []
- for command in self.list_commands():
- for name in command.names:
- self.command_hits.append(name)
- else:
- if (self.last_key_tabs and self.command_hits and
- self.command_hits[0].startswith(bare)):
- self.command_hits.append(self.command_hits.pop(0))
- else:
- self.command_hits = []
- for command in self.list_commands():
- for name in command.names:
- if name.startswith(bare):
- self.command_hits.append(name)
-
- if self.command_hits:
- message_buffer.delete(start, end)
- message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
- self.command_hits[0] + ' ')
- self.last_key_tabs = True
- return True
- if self.widget_name != 'groupchat_control':
- self.last_key_tabs = False
- if event.keyval == Gdk.KEY_Up:
- if event_state & Gdk.ModifierType.CONTROL_MASK:
- if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
- self.scroll_messages('up', message_buffer, 'received')
- else: # Ctrl+UP
- self.scroll_messages('up', message_buffer, 'sent')
- return True
- elif event.keyval == Gdk.KEY_Down:
- if event_state & Gdk.ModifierType.CONTROL_MASK:
- if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
- self.scroll_messages('down', message_buffer, 'received')
- else: # Ctrl+Down
- self.scroll_messages('down', message_buffer, 'sent')
- return True
- elif event.keyval == Gdk.KEY_Return or \
- event.keyval == Gdk.KEY_KP_Enter: # ENTER
- message_textview = widget
- message_buffer = message_textview.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
- message = message_buffer.get_text(start_iter, end_iter, False)
- xhtml = self.msg_textview.get_xhtml()
-
- if event_state & Gdk.ModifierType.SHIFT_MASK:
- send_message = False
- else:
- is_ctrl_enter = bool(event_state & Gdk.ModifierType.CONTROL_MASK)
- send_message = is_ctrl_enter == gajim.config.get('send_on_ctrl_enter')
-
- if send_message and gajim.connections[self.account].connected < 2:
- # we are not connected
- dialogs.ErrorDialog(_('A connection is not available'),
- _('Your message can not be sent until you are connected.'))
- elif send_message:
- self.send_message(message, xhtml=xhtml)
- else:
- message_buffer.insert_at_cursor('\n')
-
- return True
- elif event.keyval == Gdk.KEY_z: # CTRL+z
- if event_state & Gdk.ModifierType.CONTROL_MASK:
- self.msg_textview.undo()
- return True
-
- return False
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- """
- Derived types SHOULD implement this
- """
- pass
-
- def _on_drag_leave(self, widget, context, time):
- # FIXME: DND on non editable TextView, find a better way
- self.drag_entered = False
- self.conv_textview.tv.set_editable(False)
-
- def _on_drag_motion(self, widget, context, x, y, time):
- # FIXME: DND on non editable TextView, find a better way
- if not self.drag_entered:
- # We drag new data over the TextView, make it editable to catch dnd
- self.drag_entered_conv = True
- self.conv_textview.tv.set_editable(True)
-
- def get_seclabel(self):
- label = None
- if self.seclabel_combo is not None:
- idx = self.seclabel_combo.get_active()
- if idx != -1:
- cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
- lname = cat[2][idx]
- label = cat[1][lname]
- return label
-
- def send_message(self, message, keyID='', type_='chat', chatstate=None,
- msg_id=None, resource=None, xhtml=None, callback=None, callback_args=None,
- process_commands=True, attention=False):
- """
- Send the given message to the active tab. Doesn't return None if error
- """
- if not message or message == '\n':
- return None
-
- if callback_args is None:
- callback_args = []
-
- if process_commands and self.process_as_command(message):
- return
-
- # refresh timers
- self.reset_kbd_mouse_timeout_vars()
-
- notifications = gajim.config.get('outgoing_chat_state_notifications')
- if (self.contact.jid == gajim.get_jid_from_account(self.account) or
- notifications == 'disabled'):
- chatstate = None
-
- label = self.get_seclabel()
-
- def _cb(obj, msg, cb, *cb_args):
- self.last_sent_msg = obj.msg_id
- if cb:
- cb(obj, msg, *cb_args)
-
- if self.correcting and self.last_sent_msg:
- correct_id = self.last_sent_msg
- else:
- correct_id = None
-
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, message=message,
- keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
- resource=resource, user_nick=self.user_nick, xhtml=xhtml,
- label=label, callback=_cb, callback_args=[callback] + callback_args,
- control=self, attention=attention, correct_id=correct_id,
- automatic_message=False, encryption=self.encryption))
-
- # Record the history of sent messages
- self.save_message(message, 'sent')
-
- # Be sure to send user nickname only once according to JEP-0172
- self.user_nick = None
-
- # Clear msg input
- message_buffer = self.msg_textview.get_buffer()
- message_buffer.set_text('') # clear message buffer (and tv of course)
-
- def check_for_possible_paused_chatstate(self, arg):
- """
- Did we move mouse of that window or write something in message textview
- in the last 5 seconds? If yes - we go active for mouse, composing for
- kbd. If not - we go paused if we were previously composing
- """
- contact = self.contact
- jid = contact.jid
- current_state = contact.our_chatstate
- if current_state is False: # jid doesn't support chatstates
- return False # stop looping
-
- message_buffer = self.msg_textview.get_buffer()
- if (self.kbd_activity_in_last_5_secs and
- message_buffer.get_char_count()):
- # Only composing if the keyboard activity was in text entry
- self.send_chatstate('composing', self.contact)
- elif (self.mouse_over_in_last_5_secs and
- current_state == 'inactive' and
- jid == self.parent_win.get_active_jid()):
- self.send_chatstate('active', self.contact)
- else:
- if current_state == 'composing':
- self.send_chatstate('paused', self.contact) # pause composing
-
- # assume no activity and let the motion-notify or 'insert-text' make them
- # True refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds!
- self.reset_kbd_mouse_timeout_vars()
- return True # loop forever
-
- def check_for_possible_inactive_chatstate(self, arg):
- """
- Did we move mouse over that window or wrote something in message textview
- in the last 30 seconds? if yes - we go active. If no - we go inactive
- """
- contact = self.contact
-
- current_state = contact.our_chatstate
- if current_state is False: # jid doesn't support chatstates
- return False # stop looping
-
- if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs:
- return True # loop forever
-
- if not self.mouse_over_in_last_30_secs or \
- self.kbd_activity_in_last_30_secs:
- self.send_chatstate('inactive', contact)
-
- # assume no activity and let the motion-notify or 'insert-text' make them
- # True refresh 30 seconds too or else it's 30 - 5 = 25 seconds!
- self.reset_kbd_mouse_timeout_vars()
- return True # loop forever
-
- def _schedule_activity_timers(self):
- self.possible_paused_timeout_id = GLib.timeout_add_seconds(5,
- self.check_for_possible_paused_chatstate, None)
- self.possible_inactive_timeout_id = GLib.timeout_add_seconds(30,
- self.check_for_possible_inactive_chatstate, None)
-
- def reset_kbd_mouse_timeout_vars(self):
- self.kbd_activity_in_last_5_secs = False
- self.mouse_over_in_last_5_secs = False
- self.mouse_over_in_last_30_secs = False
- self.kbd_activity_in_last_30_secs = False
-
- def _on_window_motion_notify(self, widget, event):
- """
- It gets called no matter if it is the active window or not
- """
- if not self.parent_win:
- # when a groupchat is minimized there is no parent window
- return
- if self.parent_win.get_active_jid() == self.contact.jid:
- # if window is the active one, change vars assisting chatstate
- self.mouse_over_in_last_5_secs = True
- self.mouse_over_in_last_30_secs = True
-
- def _on_message_tv_buffer_changed(self, textbuffer):
- self.kbd_activity_in_last_5_secs = True
- self.kbd_activity_in_last_30_secs = True
- if textbuffer.get_char_count():
- self.send_chatstate('composing', self.contact)
- else:
- self.send_chatstate('active', self.contact)
-
- def save_message(self, message, msg_type):
- # save the message, so user can scroll though the list with key up/down
- if msg_type == 'sent':
- history = self.sent_history
- pos = self.sent_history_pos
- else:
- history = self.received_history
- pos = self.received_history_pos
- size = len(history)
- scroll = False if pos == size else True # are we scrolling?
- # we don't want size of the buffer to grow indefinately
- max_size = gajim.config.get('key_up_lines')
- for i in range(size - max_size + 1):
- if pos == 0:
- break
- history.pop(0)
- pos -= 1
- history.append(message)
- if not scroll or msg_type == 'sent':
- pos = len(history)
- if msg_type == 'sent':
- self.sent_history_pos = pos
- self.orig_msg = None
- else:
- self.received_history_pos = pos
-
- def print_conversation_line(self, text, kind, name, tim,
- other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
- count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
- xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
- msg_stanza_id=None, correct_id=None, additional_data=None,
- encrypted=None):
- """
- Print 'chat' type messages
- correct_id = (message_id, correct_id)
- """
- jid = self.contact.jid
- full_jid = self.get_full_jid()
- textview = self.conv_textview
- end = False
- if self.was_at_the_end or kind == 'outgoing':
- end = True
-
- if other_tags_for_name is None:
- other_tags_for_name = []
- if other_tags_for_time is None:
- other_tags_for_time = []
- if other_tags_for_text is None:
- other_tags_for_text = []
- if additional_data is None:
- additional_data = {}
-
- textview.print_conversation_line(text, jid, kind, name, tim,
- other_tags_for_name, other_tags_for_time, other_tags_for_text,
- subject, old_kind, xhtml, simple=simple, graphics=graphics,
- displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
- correct_id=correct_id, additional_data=additional_data,
- encrypted=encrypted)
-
- if xep0184_id is not None:
- textview.add_xep0184_mark(xep0184_id)
-
- if not count_as_new:
- return
- if kind in ('incoming', 'incoming_queue', 'outgoing'):
- self.last_received_txt[name] = text
- if correct_id:
- self.last_received_id[name] = correct_id[0]
- if kind == 'incoming':
- if not self.type_id == message_control.TYPE_GC or \
- gajim.config.get('notify_on_all_muc_messages') or \
- 'marked' in other_tags_for_text:
- # it's a normal message, or a muc message with want to be
- # notified about if quitting just after
- # other_tags_for_text == ['marked'] --> highlighted gc message
- gajim.last_message_time[self.account][full_jid] = time.time()
-
- if kind in ('incoming', 'incoming_queue'):
- # Record the history of received messages
- self.save_message(text, 'received')
-
- if kind in ('incoming', 'incoming_queue', 'error'):
- gc_message = False
- if self.type_id == message_control.TYPE_GC:
- gc_message = True
-
- if ((self.parent_win and (not self.parent_win.get_active_control() or \
- self != self.parent_win.get_active_control() or \
- not self.parent_win.is_active() or not end)) or \
- (gc_message and \
- jid in gajim.interface.minimized_controls[self.account])) and \
- kind in ('incoming', 'incoming_queue', 'error'):
- # we want to have save this message in events list
- # other_tags_for_text == ['marked'] --> highlighted gc message
- if gc_message:
- if 'marked' in other_tags_for_text:
- event_type = events.PrintedMarkedGcMsgEvent
- else:
- event_type = events.PrintedGcMsgEvent
- event = 'gc_message_received'
- else:
- if self.type_id == message_control.TYPE_CHAT:
- event_type = events.PrintedChatEvent
- else:
- event_type = events.PrintedPmEvent
- event = 'message_received'
- show_in_roster = notify.get_show_in_roster(event,
- self.account, self.contact, self.session)
- show_in_systray = notify.get_show_in_systray(event,
- self.account, self.contact, event_type.type_)
-
- event = event_type(text, subject, self, msg_log_id,
- show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- gajim.events.add_event(self.account, full_jid, event)
- # We need to redraw contact if we show in roster
- if show_in_roster:
- gajim.interface.roster.draw_contact(self.contact.jid,
- self.account)
-
- if not self.parent_win:
- return
-
- if (not self.parent_win.get_active_control() or \
- self != self.parent_win.get_active_control() or \
- not self.parent_win.is_active() or not end) and \
- kind in ('incoming', 'incoming_queue', 'error'):
- self.parent_win.redraw_tab(self)
- if not self.parent_win.is_active():
- self.parent_win.show_title(True, self) # Enabled Urgent hint
- else:
- self.parent_win.show_title(False, self) # Disabled Urgent hint
-
- def toggle_emoticons(self):
- """
- Hide show emoticons_button and make sure emoticons_menu is always there
- when needed
- """
- emoticons_button = self.xml.get_object('emoticons_button')
- if gajim.config.get('emoticons_theme'):
- emoticons_button.show()
- emoticons_button.set_no_show_all(False)
- else:
- emoticons_button.hide()
- emoticons_button.set_no_show_all(True)
-
- def append_emoticon(self, str_):
- buffer_ = self.msg_textview.get_buffer()
- if buffer_.get_char_count():
- buffer_.insert_at_cursor(' %s ' % str_)
- else: # we are the beginning of buffer
- buffer_.insert_at_cursor('%s ' % str_)
- self.msg_textview.grab_focus()
-
- def on_emoticons_button_clicked(self, widget):
- """
- Popup emoticons menu
- """
- gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
- gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
-
- def on_color_menuitem_activate(self, widget):
- color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
- color_dialog.set_use_alpha(False)
- color_dialog.connect('response', self.msg_textview.color_set)
- color_dialog.show_all()
-
- def on_font_menuitem_activate(self, widget):
- font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
- start, finish = self.msg_textview.get_active_iters()
- font_dialog.connect('response', self.msg_textview.font_set, start, finish)
- font_dialog.show_all()
-
- def on_formatting_menuitem_activate(self, widget):
- tag = widget.get_name()
- self.msg_textview.set_tag(tag)
-
- def on_clear_formatting_menuitem_activate(self, widget):
- self.msg_textview.clear_tags()
-
- def on_actions_button_clicked(self, widget):
- """
- Popup action menu
- """
- menu = self.prepare_context_menu(hide_buttonbar_items=True)
- menu.show_all()
- menu.attach_to_widget(widget, None)
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
- self.parent_win)
-
- def update_tags(self):
- self.conv_textview.update_tags()
-
- def clear(self, tv):
- buffer_ = tv.get_buffer()
- start, end = buffer_.get_bounds()
- buffer_.delete(start, end)
-
- def _on_history_menuitem_activate(self, widget=None, jid=None):
- """
- When history menuitem is pressed: call history window
- """
- if not jid:
- jid = self.contact.jid
-
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- gajim.interface.instances['logs'].open_history(jid, self.account)
- else:
- gajim.interface.instances['logs'] = \
- history_window.HistoryWindow(jid, self.account)
-
- def _on_send_file(self, gc_contact=None):
- """
- gc_contact can be set when we are in a groupchat control
- """
- def _on_ok(c):
- gajim.interface.instances['file_transfers'].show_file_send_request(
- self.account, c)
- if self.TYPE_ID == message_control.TYPE_PM:
- gc_contact = self.gc_contact
- if gc_contact:
- # gc or pm
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- gc_contact.room_jid, self.account)
- self_contact = gajim.contacts.get_gc_contact(self.account,
- gc_control.room_jid, gc_control.nick)
- if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
- 'owner'] and self_contact.affiliation in ['admin', 'owner']:
- contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
- if not contact or contact.sub not in ('both', 'to'):
- prim_text = _('Really send file?')
- sec_text = _('If you send a file to %s, he/she will know your '
- 'real JID.') % gc_contact.name
- dialog = dialogs.NonModalConfirmationDialog(prim_text,
- sec_text, on_response_ok=(_on_ok, gc_contact))
- dialog.popup()
- return
- _on_ok(gc_contact)
- return
- _on_ok(self.contact)
-
- def on_minimize_menuitem_toggled(self, widget):
- """
- When a grouchat is minimized, unparent the tab, put it in roster etc
- """
- old_value = True
- non_minimized_gc = gajim.config.get_per('accounts', self.account,
- 'non_minimized_gc').split()
- if self.contact.jid in non_minimized_gc:
- old_value = False
- minimize = widget.get_active()
- if not minimize and not self.contact.jid in non_minimized_gc:
- non_minimized_gc.append(self.contact.jid)
- if minimize and self.contact.jid in non_minimized_gc:
- non_minimized_gc.remove(self.contact.jid)
- if old_value != minimize:
- gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
- ' '.join(non_minimized_gc))
-
- def set_control_active(self, state):
- if state:
- jid = self.contact.jid
- if self.was_at_the_end:
- # we are at the end
- type_ = ['printed_' + self.type_id]
- if self.type_id == message_control.TYPE_GC:
- type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
- if not gajim.events.remove_events(self.account, self.get_full_jid(),
- types=type_):
- # There were events to remove
- self.redraw_after_event_removed(jid)
- # send chatstate inactive to the one we're leaving
- # and active to the one we visit
- message_buffer = self.msg_textview.get_buffer()
- if message_buffer.get_char_count():
- self.send_chatstate('paused', self.contact)
- else:
- self.send_chatstate('active', self.contact)
- self.reset_kbd_mouse_timeout_vars()
- GLib.source_remove(self.possible_paused_timeout_id)
- GLib.source_remove(self.possible_inactive_timeout_id)
- self._schedule_activity_timers()
- else:
- self.send_chatstate('inactive', self.contact)
-
- def scroll_to_end_iter(self):
- self.conv_textview.scroll_to_end_iter()
- return False
-
- def on_configure_event(self, msg_textview, event):
- """
- When message_textview changes its size: if the new height will enlarge
- the window, enable the scrollbar automatic policy. Also enable scrollbar
- automatic policy for horizontal scrollbar if message we have in
- message_textview is too big
- """
- if msg_textview.get_window() is None:
- return
-
- min_height = self.conv_scrolledwindow.get_property('height-request')
- conversation_height = self.conv_textview.tv.get_window().get_size()[1]
- message_height = msg_textview.get_window().get_size()[1]
- message_width = msg_textview.get_window().get_size()[0]
- # new tab is not exposed yet
- if conversation_height < 2:
- return
-
- if conversation_height < min_height:
- min_height = conversation_height
-
- # we don't want to always resize in height the message_textview
- # so we have minimum on conversation_textview's scrolled window
- # but we also want to avoid window resizing so if we reach that
- # minimum for conversation_textview and maximum for message_textview
- # we set to automatic the scrollbar policy
- diff_y = message_height - event.height
- if diff_y != 0:
- if conversation_height + diff_y < min_height:
- if message_height + conversation_height - min_height > min_height:
- policy = self.msg_scrolledwindow.get_property(
- 'vscrollbar-policy')
- if policy != Gtk.PolicyType.AUTOMATIC:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- Gtk.PolicyType.AUTOMATIC)
- self.msg_scrolledwindow.set_property('height-request',
- message_height + conversation_height - min_height)
- else:
- self.msg_scrolledwindow.set_property('vscrollbar-policy',
- Gtk.PolicyType.NEVER)
- self.msg_scrolledwindow.set_property('height-request', -1)
-
- self.smooth = True # reinit the flag
- # enable scrollbar automatic policy for horizontal scrollbar
- # if message we have in message_textview is too big
- if event.width > message_width:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- Gtk.PolicyType.AUTOMATIC)
- else:
- self.msg_scrolledwindow.set_property('hscrollbar-policy',
- Gtk.PolicyType.NEVER)
-
- return True
-
- def on_conversation_vadjustment_changed(self, adjustment):
- # used to stay at the end of the textview when we shrink conversation
- # textview.
- if self.was_at_the_end:
- self.scroll_to_end_iter()
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- - adjustment.get_page_size()) < 18
-
- def on_conversation_vadjustment_value_changed(self, adjustment):
- self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- - adjustment.get_page_size()) < 18
- if self.resource:
- jid = self.contact.get_full_jid()
- else:
- jid = self.contact.jid
- types_list = []
- type_ = self.type_id
- if type_ == message_control.TYPE_GC:
- type_ = 'gc_msg'
- types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
- else: # Not a GC
- types_list = ['printed_' + type_, type_]
-
- if not len(gajim.events.get_events(self.account, jid, types_list)):
- return
- if not self.parent_win:
- return
- if self.conv_textview.at_the_end() and \
- self.parent_win.get_active_control() == self and \
- self.parent_win.window.is_active():
- # we are at the end
- if self.type_id == message_control.TYPE_GC:
- if not gajim.events.remove_events(self.account, jid,
- types=types_list):
- self.redraw_after_event_removed(jid)
- elif self.session and self.session.remove_events(types_list):
- # There were events to remove
- self.redraw_after_event_removed(jid)
-
- def redraw_after_event_removed(self, jid):
- """
- We just removed a 'printed_*' event, redraw contact in roster or
- gc_roster and titles in roster and msg_win
- """
- self.parent_win.redraw_tab(self)
- self.parent_win.show_title()
- # TODO : get the contact and check notify.get_show_in_roster()
- if self.type_id == message_control.TYPE_PM:
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
- room_jid, self.account)
- if room_jid in gajim.interface.minimized_controls[self.account]:
- groupchat_control = \
- gajim.interface.minimized_controls[self.account][room_jid]
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, room_jid)
- if contact:
- gajim.interface.roster.draw_contact(room_jid, self.account)
- if groupchat_control:
- groupchat_control.draw_contact(nick)
- if groupchat_control.parent_win:
- groupchat_control.parent_win.redraw_tab(groupchat_control)
- else:
- gajim.interface.roster.draw_contact(jid, self.account)
- gajim.interface.roster.show_title()
-
- def scroll_messages(self, direction, msg_buf, msg_type):
- if msg_type == 'sent':
- history = self.sent_history
- pos = self.sent_history_pos
- self.received_history_pos = len(self.received_history)
- else:
- history = self.received_history
- pos = self.received_history_pos
- self.sent_history_pos = len(self.sent_history)
- size = len(history)
- if self.orig_msg is None:
- # user was typing something and then went into history, so save
- # whatever is already typed
- start_iter = msg_buf.get_start_iter()
- end_iter = msg_buf.get_end_iter()
- self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
- if pos == size and size > 0 and direction == 'up' and \
- msg_type == 'sent' and not self.correcting and (not \
- history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
- self.correcting = True
- gtkgui_helpers.add_css_class(
- self.msg_textview, 'msgcorrectingcolor')
- message = history[pos - 1]
- msg_buf.set_text(message)
- return
- if self.correcting:
- # We were previously correcting
- gtkgui_helpers.remove_css_class(
- self.msg_textview, 'msgcorrectingcolor')
- self.correcting = False
- pos += -1 if direction == 'up' else +1
- if pos == -1:
- return
- if pos >= size:
- pos = size
- message = self.orig_msg
- self.orig_msg = None
- else:
- message = history[pos]
- if msg_type == 'sent':
- self.sent_history_pos = pos
- else:
- self.received_history_pos = pos
- if self.orig_msg is not None:
- message = '> %s\n' % message.replace('\n', '\n> ')
- msg_buf.set_text(message)
-
- def widget_set_visible(self, widget, state):
- """
- Show or hide a widget
- """
- # make the last message visible, when changing to "full view"
- if not state:
- GLib.idle_add(self.conv_textview.scroll_to_end_iter)
-
- widget.set_no_show_all(state)
- if state:
- widget.hide()
- else:
- widget.show_all()
-
- def chat_buttons_set_visible(self, state):
- """
- Toggle chat buttons
- """
- MessageControl.chat_buttons_set_visible(self, state)
- self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
-
- def got_connected(self):
- self.msg_textview.set_sensitive(True)
- self.msg_textview.set_editable(True)
- self.update_toolbar()
-
- def got_disconnected(self):
- self.msg_textview.set_sensitive(False)
- self.msg_textview.set_editable(False)
- self.conv_textview.tv.grab_focus()
-
- self.no_autonegotiation = False
- self.update_toolbar()
diff --git a/src/command_system/__init__.py b/src/command_system/__init__.py
deleted file mode 100644
index 2fb336264..000000000
--- a/src/command_system/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-The command system providing scalable, clean and convenient architecture
-in combination with declarative way of defining commands and a fair
-amount of automatization for routine processes.
-"""
diff --git a/src/command_system/dispatcher.py b/src/command_system/dispatcher.py
deleted file mode 100644
index 488dfa341..000000000
--- a/src/command_system/dispatcher.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-Backbone of the command system. Provides smart and controllable
-dispatching mechanism with an auto-discovery functionality. In addition
-to automatic discovery and dispatching, also features manual control
-over the process.
-"""
-
-from .tools import remove
-
-COMMANDS = {}
-CONTAINERS = {}
-
-def add_host(host):
- CONTAINERS[host] = []
-
-def remove_host(host):
- remove(CONTAINERS, host)
-
-def add_container(container):
- for host in container.HOSTS:
- CONTAINERS[host].append(container)
-
-def remove_container(container):
- for host in container.HOSTS:
- remove(CONTAINERS[host], container)
-
-def add_commands(container):
- commands = COMMANDS.setdefault(container, {})
- for command in traverse_commands(container):
- for name in command.names:
- commands[name] = command
-
-def remove_commands(container):
- remove(COMMANDS, container)
-
-def traverse_commands(container):
- for name in dir(container):
- attribute = getattr(container, name)
- if is_command(attribute):
- yield attribute
-
-def is_command(attribute):
- from .framework import Command
- return isinstance(attribute, Command)
-
-def is_root(namespace):
- metaclass = namespace.get("__metaclass__", None)
- if not metaclass:
- return False
- return issubclass(metaclass, Dispatchable)
-
-def get_command(host, name):
- for container in CONTAINERS[host]:
- command = COMMANDS[container].get(name)
- if command:
- return command
-
-def list_commands(host):
- for container in CONTAINERS[host]:
- commands = COMMANDS[container]
- for name, command in commands.items():
- yield name, command
-
-class Dispatchable(type):
-
- def __init__(self, name, bases, namespace):
- parents = super(Dispatchable, self)
- parents.__init__(name, bases, namespace)
- if not is_root(namespace):
- self.dispatch()
-
- def dispatch(self):
- if self.AUTOMATIC:
- self.enable()
-
-class Host(Dispatchable):
-
- def enable(self):
- add_host(self)
-
- def disable(self):
- remove_host(self)
-
-class Container(Dispatchable):
-
- def enable(self):
- add_container(self)
- add_commands(self)
-
- def disable(self):
- remove_commands(self)
- remove_container(self) \ No newline at end of file
diff --git a/src/command_system/errors.py b/src/command_system/errors.py
deleted file mode 100644
index d3b29e85e..000000000
--- a/src/command_system/errors.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-class BaseError(Exception):
- """
- Common base for errors which relate to a specific command.
- Encapsulates everything needed to identify a command, by either its
- object or name.
- """
-
- def __init__(self, message, command=None, name=None):
- self.message = message
-
- self.command = command
- self.name = name
-
- if command and not name:
- self.name = command.first_name
-
- super(BaseError, self).__init__()
-
- def __str__(self):
- return self.message
-
-class DefinitionError(BaseError):
- """
- Used to indicate errors occured on command definition.
- """
- pass
-
-class CommandError(BaseError):
- """
- Used to indicate errors occured during command execution.
- """
- pass
-
-class NoCommandError(BaseError):
- """
- Used to indicate an inability to find the specified command.
- """
- pass
diff --git a/src/command_system/framework.py b/src/command_system/framework.py
deleted file mode 100644
index 38d1436a6..000000000
--- a/src/command_system/framework.py
+++ /dev/null
@@ -1,340 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-Provides a tiny framework with simple, yet powerful and extensible
-architecture to implement commands in a straight and flexible,
-declarative way.
-"""
-
-from types import FunctionType
-from inspect import getargspec, getdoc
-
-from .dispatcher import Host, Container
-from .dispatcher import get_command, list_commands
-from .mapping import parse_arguments, adapt_arguments
-from .errors import DefinitionError, CommandError, NoCommandError
-
-class CommandHost(metaclass=Host):
- """
- Command host is a hub between numerous command processors and
- command containers. Aimed to participate in a dispatching process in
- order to provide clean and transparent architecture.
-
- The AUTOMATIC class variable, which must be defined by a command
- host, specifies whether the command host should be automatically
- dispatched and enabled by the dispatcher or not.
- """
- __metaclass__ = Host
-
-class CommandContainer(metaclass=Container):
- """
- Command container is an entity which holds defined commands,
- allowing them to be dispatched and proccessed correctly. Each
- command container may be bound to a one or more command hosts.
-
- The AUTOMATIC class variable, which must be defined by a command
- processor, specifies whether the command processor should be
- automatically dispatched and enabled by the dispatcher or not.
-
- Bounding is controlled by the HOSTS class variable, which must be
- defined by the command container. This variable should contain a
- sequence of hosts to bound to, as a tuple or list.
- """
- __metaclass__ = Container
-
-class CommandProcessor(object):
- """
- Command processor is an immediate command emitter. It does not
- participate in the dispatching process directly, but must define a
- host to bound to.
-
- Bounding is controlled by the COMMAND_HOST variable, which must be
- defined in the body of the command processor. This variable should
- be set to a specific command host.
- """
-
- # This defines a command prefix (or an initializer), which should
- # precede a text in order for it to be processed as a command.
- COMMAND_PREFIX = '/'
-
- def process_as_command(self, text):
- """
- Try to process text as a command. Returns True if it has been
- processed as a command and False otherwise.
- """
- prefix = text.startswith(self.COMMAND_PREFIX)
- length = len(text) > len(self.COMMAND_PREFIX)
- if not (prefix and length):
- return False
-
- body = text[len(self.COMMAND_PREFIX):]
- body = body.strip()
-
- parts = body.split(None, 1)
- name, arguments = parts if len(parts) > 1 else (parts[0], None)
-
- flag = self.looks_like_command(text, body, name, arguments)
- if flag is not None:
- return flag
-
- self.execute_command(name, arguments)
-
- return True
-
- def execute_command(self, name, arguments):
- command = self.get_command(name)
-
- args, opts = parse_arguments(arguments) if arguments else ([], [])
- args, kwargs = adapt_arguments(command, arguments, args, opts)
-
- if self.command_preprocessor(command, name, arguments, args, kwargs):
- return
- value = command(self, *args, **kwargs)
- self.command_postprocessor(command, name, arguments, args, kwargs, value)
-
- def command_preprocessor(self, command, name, arguments, args, kwargs):
- """
- Redefine this method in the subclass to execute custom code
- before command gets executed.
-
- If returns True then command execution will be interrupted and
- command will not be executed.
- """
- pass
-
- def command_postprocessor(self, command, name, arguments, args, kwargs, value):
- """
- Redefine this method in the subclass to execute custom code
- after command gets executed.
- """
- pass
-
- def looks_like_command(self, text, body, name, arguments):
- """
- This hook is being called before any processing, but after it
- was determined that text looks like a command.
-
- If returns value other then None - then further processing will
- be interrupted and that value will be used to return from
- process_as_command.
- """
- pass
-
- def get_command(self, name):
- command = get_command(self.COMMAND_HOST, name)
- if not command:
- raise NoCommandError("Command does not exist", name=name)
- return command
-
- def list_commands(self):
- commands = list_commands(self.COMMAND_HOST)
- commands = dict(commands)
- return sorted(list(commands.values()), key=lambda k: k.__repr__())
-
-class Command(object):
-
- def __init__(self, handler, *names, **properties):
- self.handler = handler
- self.names = names
-
- # Automatically set all the properties passed to a constructor
- # by the command decorator.
- for key, value in properties.items():
- setattr(self, key, value)
-
- def __call__(self, *args, **kwargs):
- try:
- return self.handler(*args, **kwargs)
-
- # This allows to use a shortcuted way of raising an exception
- # inside a handler. That is to raise a CommandError without
- # command or name attributes set. They will be set to a
- # corresponding values right here in case if they was not set by
- # the one who raised an exception.
- except CommandError as error:
- if not error.command and not error.name:
- raise CommandError(error.message, self)
- raise
-
- # This one is a little bit too wide, but as Python does not have
- # anything more constrained - there is no other choice. Take a
- # look here if command complains about invalid arguments while
- # they are ok.
- except TypeError:
- raise CommandError("Command received invalid arguments", self)
-
- def __repr__(self):
- return "<Command %s>" % ', '.join(self.names)
-
- def __cmp__(self, other):
- if self.first_name > other.first_name:
- return 1
- if self.first_name < other.first_name:
- return -1
- return 0
-
- @property
- def first_name(self):
- return self.names[0]
-
- @property
- def native_name(self):
- return self.handler.__name__
-
- def extract_documentation(self):
- """
- Extract handler's documentation which is a doc-string and
- transform it to a usable format.
- """
- return getdoc(self.handler)
-
- def extract_description(self):
- """
- Extract handler's description (which is a first line of the
- documentation). Try to keep them simple yet meaningful.
- """
- documentation = self.extract_documentation()
- return documentation.split('\n', 1)[0] if documentation else None
-
- def extract_specification(self):
- """
- Extract handler's arguments specification, as it was defined
- preserving their order.
- """
- names, var_args, var_kwargs, defaults = getargspec(self.handler)
-
- # Behavior of this code need to be checked. Might yield
- # incorrect results on some rare occasions.
- spec_args = names[:-len(defaults) if defaults else len(names)]
- spec_kwargs = list(zip(names[-len(defaults):], defaults)) if defaults else {}
-
- # Removing self from arguments specification. Command handler
- # should receive the processors as a first argument, which
- # should be self by the canonical means.
- if spec_args.pop(0) != 'self':
- raise DefinitionError("First argument must be self", self)
-
- return spec_args, spec_kwargs, var_args, var_kwargs
-
-def command(*names, **properties):
- """
- A decorator for defining commands in a declarative way. Provides
- facilities for setting command's names and properties.
-
- Names should contain a set of names (aliases) by which the command
- can be reached. If no names are given - the the native name (the one
- extracted from the command handler) will be used.
-
- If native=True is given (default) and names is non-empty - then the
- native name of the command will be prepended in addition to the
- given names.
-
- If usage=True is given (default) - then command help will be
- appended with autogenerated usage info, based of the command handler
- arguments introspection.
-
- If source=True is given - then the first argument of the command
- will receive the source arguments, as a raw, unprocessed string. The
- further mapping of arguments and options will not be affected.
-
- If raw=True is given - then command considered to be raw and should
- define positional arguments only. If it defines only one positional
- argument - this argument will receive all the raw and unprocessed
- arguments. If the command defines more then one positional argument
- - then all the arguments except the last one will be processed
- normally; the last argument will get what is left after the
- processing as raw and unprocessed string.
-
- If empty=True is given - this will allow to call a raw command
- without arguments.
-
- If extra=True is given - then all the extra arguments passed to a
- command will be collected into a sequence and given to the last
- positional argument.
-
- If overlap=True is given - then all the extra arguments will be
- mapped as if they were values for the keyword arguments.
-
- If expand=True is given (default) - then short, one-letter options
- will be expanded to a verbose ones, based of the comparison of the
- first letter. If more then one option with the same first letter is
- given - then only first one will be used in the expansion.
- """
- names = list(names)
-
- native = properties.get('native', True)
-
- usage = properties.get('usage', True)
- source = properties.get('source', False)
- raw = properties.get('raw', False)
- empty = properties.get('empty', False)
- extra = properties.get('extra', False)
- overlap = properties.get('overlap', False)
- expand = properties.get('expand', True)
-
- if empty and not raw:
- raise DefinitionError("Empty option can be used only with raw commands")
-
- if extra and overlap:
- raise DefinitionError("Extra and overlap options can not be used together")
-
- properties = {
- 'usage': usage,
- 'source': source,
- 'raw': raw,
- 'extra': extra,
- 'overlap': overlap,
- 'empty': empty,
- 'expand': expand
- }
-
- def decorator(handler):
- """
- Decorator which receives handler as a first argument and then
- wraps it in the command which then returns back.
- """
- command = Command(handler, *names, **properties)
-
- # Extract and inject a native name if either no other names are
- # specified or native property is enabled, while making
- # sure it is going to be the first one in the list.
- if not names or native:
- names.insert(0, command.native_name)
- command.names = tuple(names)
-
- return command
-
- # Workaround if we are getting called without parameters. Keep in
- # mind that in that case - first item in the names will be the
- # handler.
- if names and isinstance(names[0], FunctionType):
- return decorator(names.pop(0))
-
- return decorator
-
-def doc(text):
- """
- This decorator is used to bind a documentation (a help) to a
- command.
- """
- def decorator(target):
- if isinstance(target, Command):
- target.handler.__doc__ = text
- else:
- target.__doc__ = text
- return target
-
- return decorator
diff --git a/src/command_system/implementation/__init__.py b/src/command_system/implementation/__init__.py
deleted file mode 100644
index 4e179f5ea..000000000
--- a/src/command_system/implementation/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-The implementation and auxilary systems which implement the standard
-Gajim commands and also provide an infrastructure for adding custom
-commands.
-"""
diff --git a/src/command_system/implementation/custom.py b/src/command_system/implementation/custom.py
deleted file mode 100644
index 38cd2e2c3..000000000
--- a/src/command_system/implementation/custom.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-This module contains examples of how to create your own commands, by
-creating a new command container, bounded to a specific command host,
-and definding a set of commands inside of it.
-
-Keep in mind that this module is not being loaded from anywhere, so the
-code in here will not be executed and commands defined here will not be
-detected.
-"""
-
-from ..framework import CommandContainer, command, doc
-from .hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
-
-class CustomCommonCommands(CommandContainer):
- """
- The AUTOMATIC class variable, set to a positive value, instructs the
- command system to automatically discover the command container and
- enable it.
-
- This command container bounds to all three available in the default
- implementation command hosts. This means that commands defined in
- this container will be available to all: chat, private chat and a
- group chat.
- """
-
- AUTOMATIC = True
- HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
-
- @command
- def dance(self):
- """
- First line of the doc string is called a description and will be
- programmatically extracted and formatted.
-
- After that you can give more help, like explanation of the
- options. This one will be programatically extracted and
- formatted too.
-
- After all the documentation - there will be autogenerated (based
- on the method signature) usage information appended. You can
- turn it off, if you want.
- """
- return "I don't dance."
-
-class CustomChatCommands(CommandContainer):
- """
- This command container bounds only to the ChatCommands command host.
- Therefore commands defined inside of the container will be available
- only to a chat.
- """
-
- AUTOMATIC = True
- HOSTS = ChatCommands,
-
- @command("squal", "bawl")
- def sing(self):
- """
- This command has an additional aliases. It means the command will
- be available under three names: sing (the native name), squal
- (the first alias), bawl (the second alias).
-
- You can turn off the usage of the native name, if you want, and
- specify a name or a set of names, as aliases, under which a
- command will be available.
- """
- return "Buy yourself a stereo."
-
-class CustomPrivateChatCommands(CommandContainer):
- """
- This command container bounds only to the PrivateChatCommands
- command host. Therefore commands defined inside of the container
- will be available only to a private chat.
- """
-
- AUTOMATIC = True
- HOSTS = PrivateChatCommands,
-
- @command
- #Example string. Do not translate
- @doc(_("The same as using a doc-string, except it supports translation"))
- def make_coffee(self):
- return "I'm not a coffee machine!"
-
-class CustomGroupChatCommands(CommandContainer):
- """
- This command container bounds only to the GroupChatCommands command
- host. Therefore commands defined inside of the container will be
- available only to a group chat.
- """
-
- AUTOMATIC = True
- HOSTS = GroupChatCommands,
-
- @command
- def fetch(self):
- return "Buy yourself a dog."
diff --git a/src/command_system/implementation/execute.py b/src/command_system/implementation/execute.py
deleted file mode 100644
index 0e8718be4..000000000
--- a/src/command_system/implementation/execute.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-Provides facilities to safely execute expressions inside a shell process
-and capture the resulting output, in an asynchronous fashion, avoiding
-deadlocks. If the process execution time reaches the threshold - it is
-forced to terminate. Consists of a tiny framework and a couple of
-commands as a frontend.
-"""
-
-from subprocess import Popen, PIPE
-from os.path import expanduser
-
-from gi.repository import GLib
-
-from ..framework import CommandContainer, command, doc
-from .hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
-
-class Execute(CommandContainer):
- AUTOMATIC = True
- HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
-
- DIRECTORY = "~"
-
- POLL_INTERVAL = 100
- POLL_COUNT = 5
-
- @command("exec", raw=True)
- @doc(_("Execute expression inside a shell, show output"))
- def execute(self, expression):
- Execute.spawn(self, expression)
-
- @classmethod
- def spawn(cls, processor, expression):
- pipes = dict(stdout=PIPE, stderr=PIPE)
- directory = expanduser(cls.DIRECTORY)
- popen = Popen(expression, shell=True, cwd=directory, **pipes)
- cls.monitor(processor, popen)
-
- @classmethod
- def monitor(cls, processor, popen):
- poller = cls.poller(processor, popen)
- GLib.timeout_add(cls.POLL_INTERVAL, next, poller)
-
- @classmethod
- def poller(cls, processor, popen):
- for _ in range(cls.POLL_COUNT):
- yield cls.brush(processor, popen)
- cls.overdue(processor, popen)
- yield False
-
- @classmethod
- def brush(cls, processor, popen):
- if popen.poll() is not None:
- cls.terminated(processor, popen)
- return False
- return True
-
- @classmethod
- def terminated(cls, processor, popen):
- stdout, stderr = cls.fetch(popen)
- success = popen.returncode == 0
- if success and stdout:
- processor.echo(stdout)
- elif not success and stderr:
- processor.echo_error(stderr)
-
- @classmethod
- def overdue(cls, processor, popen):
- popen.terminate()
-
- @classmethod
- def fetch(cls, popen):
- data = popen.communicate()
- return map(cls.clean, data)
-
- @staticmethod
- def clean(text):
- strip = chr(10) + chr(32)
- return text.decode().strip(strip)
-
-class Show(Execute):
-
- @command("sh", raw=True)
- @doc(_("Execute expression inside a shell, send output"))
- def show(self, expression):
- Show.spawn(self, expression)
-
- @classmethod
- def terminated(cls, processor, popen):
- stdout, stderr = cls.fetch(popen)
- success = popen.returncode == 0
- if success and stdout:
- processor.send(stdout)
- elif not success and stderr:
- processor.echo_error(stderr)
diff --git a/src/command_system/implementation/hosts.py b/src/command_system/implementation/hosts.py
deleted file mode 100644
index 285372e43..000000000
--- a/src/command_system/implementation/hosts.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-The module defines a set of command hosts, which are bound to a
-different command processors, which are the source of commands.
-"""
-
-from ..framework import CommandHost
-
-class ChatCommands(CommandHost):
- """
- This command host is bound to the command processor which processes
- commands from a chat.
- """
- AUTOMATIC = True
-
-class PrivateChatCommands(CommandHost):
- """
- This command host is bound to the command processor which processes
- commands from a private chat.
- """
- AUTOMATIC = True
-
-class GroupChatCommands(CommandHost):
- """
- This command host is bound to the command processor which processes
- commands from a group chat.
- """
- AUTOMATIC = True
diff --git a/src/command_system/implementation/middleware.py b/src/command_system/implementation/middleware.py
deleted file mode 100644
index 1ad925771..000000000
--- a/src/command_system/implementation/middleware.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-Provides a glue to tie command system framework and the actual code
-where it would be dropped in. Defines a little bit of scaffolding to
-support interaction between the two and a few utility methods so you
-don't need to dig up the code itself to write basic commands.
-"""
-
-from traceback import print_exc
-
-from gi.repository import Pango
-from common import gajim
-
-from ..framework import CommandProcessor
-from ..errors import CommandError, NoCommandError
-
-class ChatCommandProcessor(CommandProcessor):
- """
- A basic scaffolding to provide convenient interaction between the
- command system and chat controls. It will be merged directly into
- the controls, by ChatCommandProcessor being among superclasses of
- the controls.
- """
-
- def process_as_command(self, text):
- self.command_succeeded = False
- parents = super(ChatCommandProcessor, self)
- flag = parents.process_as_command(text)
- if flag and self.command_succeeded:
- self.add_history(text)
- self.clear_input()
- return flag
-
- def execute_command(self, name, arguments):
- try:
- parents = super(ChatCommandProcessor, self)
- parents.execute_command(name, arguments)
- except NoCommandError as error:
- details = dict(name=error.name, message=error.message)
- message = "%(name)s: %(message)s\n" % details
- message += "Try using the //%(name)s or /say /%(name)s " % details
- message += "construct if you intended to send it as a text."
- self.echo_error(message)
- except CommandError as error:
- self.echo_error("%s: %s" % (error.name, error.message))
- except Exception:
- self.echo_error(_("Error during command execution!"))
- print_exc()
- else:
- self.command_succeeded = True
-
- def looks_like_command(self, text, body, name, arguments):
- # Command escape stuff goes here. If text was prepended by the
- # command prefix twice, like //not_a_command (if prefix is set
- # to /) then it will be escaped, that is sent just as a regular
- # message with one (only one) prefix removed, so message will be
- # /not_a_command.
- if body.startswith(self.COMMAND_PREFIX):
- self.send(body)
- return True
-
- def command_preprocessor(self, command, name, arguments, args, kwargs):
- # If command argument contain h or help option - forward it to
- # the /help command. Dont forget to pass self, as all commands
- # are unbound. And also don't forget to print output.
- if 'h' in kwargs or 'help' in kwargs:
- help = self.get_command('help')
- self.echo(help(self, name))
- return True
-
- def command_postprocessor(self, command, name, arguments, args, kwargs, value):
- # If command returns a string - print it to a user. A convenient
- # and sufficient in most simple cases shortcut to a using echo.
- if value and isinstance(value, str):
- self.echo(value)
-
-class CommandTools:
- """
- Contains a set of basic tools and shortcuts you can use in your
- commands to perform some simple operations. These will be merged
- directly into the controls, by CommandTools being among superclasses
- of the controls.
- """
-
- def __init__(self):
- self.install_tags()
-
- def install_tags(self):
- buffer = self.conv_textview.tv.get_buffer()
-
- name = "Monospace"
- font = Pango.FontDescription(name)
-
- command_ok_tag = buffer.create_tag("command_ok")
- command_ok_tag.set_property("font-desc", font)
- command_ok_tag.set_property("foreground", "#3465A4")
-
- command_error_tag = buffer.create_tag("command_error")
- command_error_tag.set_property("font-desc", font)
- command_error_tag.set_property("foreground", "#F57900")
-
- def shift_line(self):
- buffer = self.conv_textview.tv.get_buffer()
- iter = buffer.get_end_iter()
- if iter.ends_line() and not iter.is_start():
- buffer.insert_with_tags_by_name(iter, "\n", "eol")
-
- def append_with_tags(self, text, *tags):
- buffer = self.conv_textview.tv.get_buffer()
- iter = buffer.get_end_iter()
- buffer.insert_with_tags_by_name(iter, text, *tags)
-
- def echo(self, text, tag="command_ok"):
- """
- Print given text to the user, as a regular command output.
- """
- self.shift_line()
- self.append_with_tags(text, tag)
-
- def echo_error(self, text):
- """
- Print given text to the user, as an error command output.
- """
- self.echo(text, "command_error")
-
- def send(self, text):
- """
- Send a message to the contact.
- """
- self.send_message(text, process_commands=False)
-
- def set_input(self, text):
- """
- Set given text into the input.
- """
- buffer = self.msg_textview.get_buffer()
- buffer.set_text(text)
-
- def clear_input(self):
- """
- Clear input.
- """
- self.set_input(str())
-
- def add_history(self, text):
- """
- Add given text to the input history, so user can scroll through
- it using ctrl + up/down arrow keys.
- """
- self.save_message(text, 'sent')
-
- @property
- def connection(self):
- """
- Get the current connection object.
- """
- return gajim.connections[self.account]
-
- @property
- def full_jid(self):
- """
- Get a full JID of the contact.
- """
- return self.contact.get_full_jid()
diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py
deleted file mode 100644
index 4caba25d7..000000000
--- a/src/command_system/implementation/standard.py
+++ /dev/null
@@ -1,409 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-Provides an actual implementation for the standard commands.
-"""
-
-from time import localtime, strftime
-from datetime import date
-
-import dialogs
-from common import gajim
-from common import helpers
-from common.exceptions import GajimGeneralException
-from common.logger import KindConstant
-
-from ..errors import CommandError
-from ..framework import CommandContainer, command, doc
-from ..mapping import generate_usage
-
-from .hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
-
-class StandardCommonCommands(CommandContainer):
- """
- This command container contains standard commands which are common
- to all - chat, private chat, group chat.
- """
-
- AUTOMATIC = True
- HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
-
- @command
- @doc(_("Hide the chat buttons"))
- def compact(self):
- new_status = not self.hide_chat_buttons
- self.chat_buttons_set_visible(new_status)
-
- @command(overlap=True)
- @doc(_("Show help on a given command or a list of available commands if -a is given"))
- def help(self, command=None, all=False):
- if command:
- command = self.get_command(command)
-
- documentation = _(command.extract_documentation())
- usage = generate_usage(command)
-
- text = []
-
- if documentation:
- text.append(documentation)
- if command.usage:
- text.append(usage)
-
- return '\n\n'.join(text)
- elif all:
- for command in self.list_commands():
- names = ', '.join(command.names)
- description = command.extract_description()
-
- self.echo("%s - %s" % (names, description))
- else:
- help = self.get_command('help')
- self.echo(help(self, 'help'))
-
- @command(raw=True)
- @doc(_("Send a message to the contact"))
- def say(self, message):
- self.send(message)
-
- @command(raw=True)
- @doc(_("Send action (in the third person) to the current chat"))
- def me(self, action):
- self.send("/me %s" % action)
-
- @command('lastlog', overlap=True)
- @doc(_("Show logged messages which mention given text"))
- def grep(self, text, limit=None):
- results = gajim.logger.search_log(self.contact.jid, text, self.account)
-
- if not results:
- raise CommandError(_("%s: Nothing found") % text)
-
- if limit:
- try:
- results = results[len(results) - int(limit):]
- except ValueError:
- raise CommandError(_("Limit must be an integer"))
-
- for row in results:
- contact = row.contact_name
- if not contact:
- if row.kind == KindConstant.CHAT_MSG_SENT:
- contact = gajim.nicks[self.account]
- else:
- contact = self.contact.name
-
- time_obj = localtime(row.time)
- date_obj = date.fromtimestamp(row.time)
- date_ = strftime('%Y-%m-%d', time_obj)
- time_ = strftime('%H:%M:%S', time_obj)
-
- if date_obj == date.today():
- formatted = "[%s] %s: %s" % (time_, contact, row.message)
- else:
- formatted = "[%s, %s] %s: %s" % (date_, time_, contact, row.message)
-
- self.echo(formatted)
-
- @command(raw=True, empty=True)
- #Do not translate online, away, chat, xa, dnd
- @doc(_("""
- Set the current status
-
- Status can be given as one of the following values:
- online, away, chat, xa, dnd.
- """))
- def status(self, status, message):
- if status not in ('online', 'away', 'chat', 'xa', 'dnd'):
- raise CommandError("Invalid status given")
- for connection in gajim.connections.values():
- if not gajim.config.get_per('accounts', connection.name,
- 'sync_with_global_status'):
- continue
- if connection.connected < 2:
- continue
- connection.change_status(status, message)
-
- @command(raw=True, empty=True)
- @doc(_("Set the current status to away"))
- def away(self, message):
- if not message:
- message = _("Away")
-
- for connection in gajim.connections.values():
- if not gajim.config.get_per('accounts', connection.name,
- 'sync_with_global_status'):
- continue
- if connection.connected < 2:
- continue
- connection.change_status('away', message)
-
- @command('back', raw=True, empty=True)
- @doc(_("Set the current status to online"))
- def online(self, message):
- if not message:
- message = _("Available")
-
- for connection in gajim.connections.values():
- if not gajim.config.get_per('accounts', connection.name,
- 'sync_with_global_status'):
- continue
- if connection.connected < 2:
- continue
- connection.change_status('online', message)
-
-class StandardCommonChatCommands(CommandContainer):
- """
- This command container contans standard commands, which are common
- to a chat and a private chat only.
- """
-
- AUTOMATIC = True
- HOSTS = ChatCommands, PrivateChatCommands
-
- @command
- @doc(_("Clear the text window"))
- def clear(self):
- self.conv_textview.clear()
-
- @command
- @doc(_("Send a ping to the contact"))
- def ping(self):
- if self.account == gajim.ZEROCONF_ACC_NAME:
- raise CommandError(_('Command is not supported for zeroconf accounts'))
- gajim.connections[self.account].sendPing(self.contact)
-
- @command
- @doc(_("Send DTMF sequence through an open audio session"))
- def dtmf(self, sequence):
- if not self.audio_sid:
- raise CommandError(_("No open audio sessions with the contact"))
- for tone in sequence:
- if not (tone in ("*", "#") or tone.isdigit()):
- raise CommandError(_("%s is not a valid tone") % tone)
- gjs = self.connection.get_jingle_session
- session = gjs(self.full_jid, self.audio_sid)
- content = session.get_content("audio")
- content.batch_dtmf(sequence)
-
- @command
- @doc(_("Toggle audio session"))
- def audio(self):
- if not self.audio_available:
- raise CommandError(_("Audio sessions are not available"))
- # An audio session is toggled by inverting the state of the
- # appropriate button.
- state = self._audio_button.get_active()
- self._audio_button.set_active(not state)
-
- @command
- @doc(_("Toggle video session"))
- def video(self):
- if not self.video_available:
- raise CommandError(_("Video sessions are not available"))
- # A video session is toggled by inverting the state of the
- # appropriate button.
- state = self._video_button.get_active()
- self._video_button.set_active(not state)
-
- @command(raw=True)
- @doc(_("Send a message to the contact that will attract his (her) attention"))
- def attention(self, message):
- self.send_message(message, process_commands=False, attention=True)
-
-class StandardChatCommands(CommandContainer):
- """
- This command container contains standard commands which are unique
- to a chat.
- """
-
- AUTOMATIC = True
- HOSTS = ChatCommands,
-
-class StandardPrivateChatCommands(CommandContainer):
- """
- This command container contains standard commands which are unique
- to a private chat.
- """
-
- AUTOMATIC = True
- HOSTS = PrivateChatCommands,
-
-class StandardGroupChatCommands(CommandContainer):
- """
- This command container contains standard commands which are unique
- to a group chat.
- """
-
- AUTOMATIC = True
- HOSTS = GroupChatCommands,
-
- @command
- @doc(_("Clear the text window"))
- def clear(self):
- self.conv_textview.clear()
- self.gc_count_nicknames_colors = -1
- self.gc_custom_colors = {}
-
- @command(raw=True)
- @doc(_("Change your nickname in a group chat"))
- def nick(self, new_nick):
- try:
- new_nick = helpers.parse_resource(new_nick)
- except Exception:
- raise CommandError(_("Invalid nickname"))
- self.connection.join_gc(new_nick, self.room_jid, None, change_nick=True)
- self.new_nick = new_nick
-
- @command('query', raw=True)
- @doc(_("Open a private chat window with a specified occupant"))
- def chat(self, nick):
- nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
- if nick in nicks:
- self.on_send_pm(nick=nick)
- else:
- raise CommandError(_("Nickname not found"))
-
- @command('msg', raw=True)
- @doc(_("Open a private chat window with a specified occupant and send him a message"))
- def message(self, nick, a_message):
- nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
- if nick in nicks:
- self.on_send_pm(nick=nick, msg=a_message)
- else:
- raise CommandError(_("Nickname not found"))
-
- @command(raw=True, empty=True)
- @doc(_("Display or change a group chat topic"))
- def topic(self, new_topic):
- if new_topic:
- self.connection.send_gc_subject(self.room_jid, new_topic)
- else:
- return self.subject
-
- @command(raw=True, empty=True)
- @doc(_("Invite a user to a room for a reason"))
- def invite(self, jid, reason):
- self.connection.send_invite(self.room_jid, jid, reason)
- return _("Invited %s to %s") % (jid, self.room_jid)
-
- @command(raw=True, empty=True)
- @doc(_("Join a group chat given by a jid, optionally using given nickname"))
- def join(self, jid, nick):
- if not nick:
- nick = self.nick
-
- if '@' not in jid:
- jid = jid + '@' + gajim.get_server_from_jid(self.room_jid)
-
- try:
- gajim.interface.instances[self.account]['join_gc'].window.present()
- except KeyError:
- try:
- dialogs.JoinGroupchatWindow(account=self.account, room_jid=jid, nick=nick)
- except GajimGeneralException:
- pass
-
- @command('part', 'close', raw=True, empty=True)
- @doc(_("Leave the groupchat, optionally giving a reason, and close tab or window"))
- def leave(self, reason):
- self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND, reason)
-
- @command(raw=True, empty=True)
- @doc(_("""
- Ban user by a nick or a jid from a groupchat
-
- If given nickname is not found it will be treated as a jid.
- """))
- def ban(self, who, reason):
- if who in gajim.contacts.get_nick_list(self.account, self.room_jid):
- contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, who)
- who = contact.jid
- self.connection.gc_set_affiliation(self.room_jid, who, 'outcast', reason or str())
-
- @command(raw=True, empty=True)
- @doc(_("Kick user by a nick from a groupchat"))
- def kick(self, who, reason):
- if not who in gajim.contacts.get_nick_list(self.account, self.room_jid):
- raise CommandError(_("Nickname not found"))
- self.connection.gc_set_role(self.room_jid, who, 'none', reason or str())
-
- @command(raw=True)
- #Do not translate moderator, participant, visitor, none
- @doc(_("""Set occupant role in group chat.
- Role can be given as one of the following values:
- moderator, participant, visitor, none"""))
- def role(self, who, role):
- if role not in ('moderator', 'participant', 'visitor', 'none'):
- raise CommandError(_("Invalid role given"))
- if not who in gajim.contacts.get_nick_list(self.account, self.room_jid):
- raise CommandError(_("Nickname not found"))
- self.connection.gc_set_role(self.room_jid, who, role)
-
- @command(raw=True)
- #Do not translate owner, admin, member, outcast, none
- @doc(_("""Set occupant affiliation in group chat.
- Affiliation can be given as one of the following values:
- owner, admin, member, outcast, none"""))
- def affiliate(self, who, affiliation):
- if affiliation not in ('owner', 'admin', 'member', 'outcast', 'none'):
- raise CommandError(_("Invalid affiliation given"))
- if not who in gajim.contacts.get_nick_list(self.account, self.room_jid):
- raise CommandError(_("Nickname not found"))
- contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, who)
- self.connection.gc_set_affiliation(self.room_jid, contact.jid,
- affiliation)
-
- @command
- @doc(_("Display names of all group chat occupants"))
- def names(self, verbose=False):
- ggc = gajim.contacts.get_gc_contact
- gnl = gajim.contacts.get_nick_list
-
- get_contact = lambda nick: ggc(self.account, self.room_jid, nick)
- get_role = lambda nick: get_contact(nick).role
- nicks = gnl(self.account, self.room_jid)
-
- nicks = sorted(nicks)
- nicks = sorted(nicks, key=get_role)
-
- if not verbose:
- return ", ".join(nicks)
-
- for nick in nicks:
- contact = get_contact(nick)
- role = helpers.get_uf_role(contact.role)
- affiliation = helpers.get_uf_affiliation(contact.affiliation)
- self.echo("%s - %s - %s" % (nick, role, affiliation))
-
- @command('ignore', raw=True)
- @doc(_("Forbid an occupant to send you public or private messages"))
- def block(self, who):
- self.on_block(None, who)
-
- @command('unignore', raw=True)
- @doc(_("Allow an occupant to send you public or private messages"))
- def unblock(self, who):
- self.on_unblock(None, who)
-
- @command
- @doc(_("Send a ping to the contact"))
- def ping(self, nick):
- if self.account == gajim.ZEROCONF_ACC_NAME:
- raise CommandError(_('Command is not supported for zeroconf accounts'))
- gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- gajim.connections[self.account].sendPing(gc_c, self)
-
diff --git a/src/command_system/mapping.py b/src/command_system/mapping.py
deleted file mode 100644
index e2cd0c47b..000000000
--- a/src/command_system/mapping.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-
-"""
-The module contains routines to parse command arguments and map them to
-the command handler's positonal and keyword arguments.
-
-Mapping is done in two stages: 1) parse arguments into positional
-arguments and options; 2) adapt them to the specific command handler
-according to the command properties.
-"""
-
-import re
-from operator import itemgetter
-
-from .errors import DefinitionError, CommandError
-
-# Quite complex piece of regular expression logic to parse options and
-# arguments. Might need some tweaking along the way.
-ARG_PATTERN = re.compile(r'(\'|")?(?P<body>(?(1).+?|\S+))(?(1)\1)')
-OPT_PATTERN = re.compile(r'(?<!\w)--?(?P<key>[\w-]+)(?:(?:=|\s)(\'|")?(?P<value>(?(2)[^-]+?|[^-\s]+))(?(2)\2))?')
-
-# Option keys needs to be encoded to a specific encoding as Python does
-# not allow to expand dictionary with raw unicode strings as keys from a
-# **kwargs.
-KEY_ENCODING = 'UTF-8'
-
-# Defines how complete representation of command usage (generated based
-# on command handler argument specification) will be rendered.
-USAGE_PATTERN = 'Usage: %s %s'
-
-def parse_arguments(arguments):
- """
- Simple yet effective and sufficient in most cases parser which
- parses command arguments and returns them as two lists.
-
- First list represents positional arguments as (argument, position),
- and second representing options as (key, value, position) tuples,
- where position is a (start, end) span tuple of where it was found in
- the string.
-
- Options may be given in --long or -short format. As --option=value
- or --option value or -option value. Keys without values will get
- None as value.
-
- Arguments and option values that contain spaces may be given as 'one
- two three' or "one two three"; that is between single or double
- quotes.
- """
- args, opts = [], []
-
- def intersects_opts(given_start, given_end):
- """
- Check if given span intersects with any of options.
- """
- for key, value, (start, end) in opts:
- if given_start >= start and given_end <= end:
- return True
- return False
-
- def intersects_args(given_start, given_end):
- """
- Check if given span intersects with any of arguments.
- """
- for arg, (start, end) in args:
- if given_start >= start and given_end <= end:
- return True
- return False
-
- for match in re.finditer(OPT_PATTERN, arguments):
- if match:
- key = match.group('key')
- value = match.group('value') or None
- position = match.span()
- opts.append((key, value, position))
-
- for match in re.finditer(ARG_PATTERN, arguments):
- if match:
- body = match.group('body')
- position = match.span()
- args.append((body, position))
-
- # Primitive but sufficiently effective way of disposing of
- # conflicted sectors. Remove any arguments that intersect with
- # options.
- for arg, position in args[:]:
- if intersects_opts(*position):
- args.remove((arg, position))
-
- # Primitive but sufficiently effective way of disposing of
- # conflicted sectors. Remove any options that intersect with
- # arguments.
- for key, value, position in opts[:]:
- if intersects_args(*position):
- opts.remove((key, value, position))
-
- return args, opts
-
-def adapt_arguments(command, arguments, args, opts):
- """
- Adapt args and opts got from the parser to a specific handler by
- means of arguments specified on command definition. That is
- transform them to *args and **kwargs suitable for passing to a
- command handler.
-
- Dashes (-) in the option names will be converted to underscores. So
- you can map --one-more-option to a one_more_option=None.
-
- If the initial value of a keyword argument is a boolean (False in
- most cases) - then this option will be treated as a switch, that is
- an option which does not take an argument. If a switch is followed
- by an argument - then this argument will be treated just like a
- normal positional argument.
- """
- spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
- norm_kwargs = dict(spec_kwargs)
-
- # Quite complex piece of neck-breaking logic to extract raw
- # arguments if there is more, then one positional argument specified
- # by the command. In case if it's just one argument which is the
- # collector - this is fairly easy. But when it's more then one
- # argument - the neck-breaking logic of how to retrieve residual
- # arguments as a raw, all in one piece string, kicks in.
- if command.raw:
- if arguments:
- spec_fix = 1 if command.source else 0
- spec_len = len(spec_args) - spec_fix
- arguments_end = len(arguments) - 1
-
- # If there are any optional arguments given they should be
- # either an unquoted postional argument or part of the raw
- # argument. So we find all optional arguments that can
- # possibly be unquoted argument and append them as is to the
- # args.
- for key, value, (start, end) in opts[:spec_len]:
- if value:
- end -= len(value) + 1
- args.append((arguments[start:end], (start, end)))
- args.append((value, (end, end + len(value) + 1)))
- else:
- args.append((arguments[start:end], (start, end)))
-
- # We need in-place sort here because after manipulations
- # with options order of arguments might be wrong and we just
- # can't have more complex logic to not let that happen.
- args.sort(key=itemgetter(1))
-
- if spec_len > 1:
- try:
- stopper, (start, end) = args[spec_len - 2]
- except IndexError:
- raise CommandError(_("Missing arguments"), command)
-
- # The essential point of the whole play. After
- # boundaries are being determined (supposingly correct)
- # we separate raw part from the rest of arguments, which
- # should be normally processed.
- raw = arguments[end:]
- raw = raw.strip() or None
-
- if not raw and not command.empty:
- raise CommandError(_("Missing arguments"), command)
-
- # Discard residual arguments and all of the options as
- # raw command does not support options and if an option
- # is given it is rather a part of a raw argument.
- args = args[:spec_len - 1]
- opts = []
-
- args.append((raw, (end, arguments_end)))
- else:
- # Substitue all of the arguments with only one, which
- # contain raw and unprocessed arguments as a string. And
- # discard all the options, as raw command does not
- # support them.
- args = [(arguments, (0, arguments_end))]
- opts = []
- else:
- if command.empty:
- args.append((None, (0, 0)))
- else:
- raise CommandError(_("Missing arguments"), command)
-
- # The first stage of transforming options we have got to a format
- # that can be used to associate them with declared keyword
- # arguments. Substituting dashes (-) in their names with
- # underscores (_).
- for index, (key, value, position) in enumerate(opts):
- if '-' in key:
- opts[index] = (key.replace('-', '_'), value, position)
-
- # The second stage of transforming options to an associatable state.
- # Expanding short, one-letter options to a verbose ones, if
- # corresponding optin has been given.
- if command.expand:
- expanded = []
- for spec_key, spec_value in norm_kwargs.items():
- letter = spec_key[0] if len(spec_key) > 1 else None
- if letter and letter not in expanded:
- for index, (key, value, position) in enumerate(opts):
- if key == letter:
- expanded.append(letter)
- opts[index] = (spec_key, value, position)
- break
-
- # Detect switches and set their values accordingly. If any of them
- # carries a value - append it to args.
- for index, (key, value, position) in enumerate(opts):
- if isinstance(norm_kwargs.get(key), bool):
- opts[index] = (key, True, position)
- if value:
- args.append((value, position))
-
- # Sorting arguments and options (just to be sure) in regarding to
- # their positions in the string.
- args.sort(key=itemgetter(1))
- opts.sort(key=itemgetter(2))
-
- # Stripping down position information supplied with arguments and
- # options as it won't be needed again.
- args = list(map(lambda t: t[0], args))
- opts = list(map(lambda t: (t[0], t[1]), opts))
-
- # If command has extra option enabled - collect all extra arguments
- # and pass them to a last positional argument command defines as a
- # list.
- if command.extra:
- if not var_args:
- spec_fix = 1 if not command.source else 2
- spec_len = len(spec_args) - spec_fix
- extra = args[spec_len:]
- args = args[:spec_len]
- args.append(extra)
- else:
- raise DefinitionError("Can not have both, extra and *args")
-
- # Detect if positional arguments overlap keyword arguments. If so
- # and this is allowed by command options - then map them directly to
- # their options, so they can get propert further processings.
- spec_fix = 1 if command.source else 0
- spec_len = len(spec_args) - spec_fix
- if len(args) > spec_len:
- if command.overlap:
- overlapped = args[spec_len:]
- args = args[:spec_len]
- for arg, (spec_key, spec_value) in zip(overlapped, spec_kwargs):
- opts.append((spec_key, arg))
- else:
- raise CommandError(_("Too many arguments"), command)
-
- # Detect every switch and ensure it will not receive any arguments.
- # Normally this does not happen unless overlapping is enabled.
- for key, value in opts:
- initial = norm_kwargs.get(key)
- if isinstance(initial, bool):
- if not isinstance(value, bool):
- raise CommandError("%s: Switch can not take an argument" % key, command)
-
- # Inject the source arguments as a string as a first argument, if
- # command has enabled the corresponding option.
- if command.source:
- args.insert(0, arguments)
-
- # Return *args and **kwargs in the form suitable for passing to a
- # command handler and being expanded.
- return tuple(args), dict(opts)
-
-def generate_usage(command, complete=True):
- """
- Extract handler's arguments specification and wrap them in a
- human-readable format usage information. If complete is given - then
- USAGE_PATTERN will be used to render the specification completly.
- """
- spec_args, spec_kwargs, var_args, var_kwargs = command.extract_specification()
-
- # Remove some special positional arguments from the specifiaction,
- # but store their names so they can be used for usage info
- # generation.
- sp_source = spec_args.pop(0) if command.source else None
- sp_extra = spec_args.pop() if command.extra else None
-
- kwargs = []
- letters = []
-
- for key, value in spec_kwargs:
- letter = key[0]
- key = key.replace('_', '-')
-
- if isinstance(value, bool):
- value = str()
- else:
- value = '=%s' % value
-
- if letter not in letters:
- kwargs.append('-(-%s)%s%s' % (letter, key[1:], value))
- letters.append(letter)
- else:
- kwargs.append('--%s%s' % (key, value))
-
- usage = str()
- args = str()
-
- if command.raw:
- spec_len = len(spec_args) - 1
- if spec_len:
- args += ('<%s>' % ', '.join(spec_args[:spec_len])) + ' '
- args += ('(|%s|)' if command.empty else '|%s|') % spec_args[-1]
- else:
- if spec_args:
- args += '<%s>' % ', '.join(spec_args)
- if var_args or sp_extra:
- args += (' ' if spec_args else str()) + '<<%s>>' % (var_args or sp_extra)
-
- usage += args
-
- if kwargs or var_kwargs:
- if kwargs:
- usage += (' ' if args else str()) + '[%s]' % ', '.join(kwargs)
- if var_kwargs:
- usage += (' ' if args else str()) + '[[%s]]' % var_kwargs
-
- # Native name will be the first one if it is included. Otherwise,
- # names will be in the order they were specified.
- if len(command.names) > 1:
- names = '%s (%s)' % (command.first_name, ', '.join(command.names[1:]))
- else:
- names = command.first_name
-
- return USAGE_PATTERN % (names, usage) if complete else usage
diff --git a/src/command_system/tools.py b/src/command_system/tools.py
deleted file mode 100644
index 42a295dd0..000000000
--- a/src/command_system/tools.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-def remove(sequence, target):
- if isinstance(sequence, list):
- if target in sequence:
- sequence.remove(target)
- elif isinstance(sequence, dict):
- if target in sequence:
- del sequence[target]
diff --git a/src/common/__init__.py b/src/common/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/src/common/__init__.py
+++ /dev/null
diff --git a/src/common/account.py b/src/common/account.py
deleted file mode 100644
index 976b6bf43..000000000
--- a/src/common/account.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/contacts.py
-##
-## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-class Account(object):
-
- def __init__(self, name, contacts, gc_contacts):
- self.name = name
- self.contacts = contacts
- self.gc_contacts = gc_contacts
-
- def change_contact_jid(self, old_jid, new_jid):
- self.contacts.change_contact_jid(old_jid, new_jid)
-
- def __repr__(self):
- return self.name
-
- def __hash__(self):
- return hash(self.name)
diff --git a/src/common/atom.py b/src/common/atom.py
deleted file mode 100644
index 7cf606832..000000000
--- a/src/common/atom.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/common/atom.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-"""
-Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
-and services. Very simple. Actually implements only atom:entry. Implement more features
-if you need
-"""
-
-# suggestion: rewrite functions that return dates to return standard python time tuples,
-# exteneded to contain timezone
-
-import nbxmpp
-import time
-
-class PersonConstruct(nbxmpp.Node, object):
- """
- Not used for now, as we don't need authors/contributors in pubsub.com feeds.
- They rarely exist there
- """
-
- def __init__(self, node):
- ''' Create person construct from node. '''
- nbxmpp.Node.__init__(self, node=node)
-
- def get_name(self):
- return self.getTagData('name')
-
- name = property(get_name, None, None,
- '''Conveys a human-readable name for the person. Should not be None,
- although some badly generated atom feeds don't put anything here
- (this is non-standard behavior, still pubsub.com sometimes does that.)''')
-
- def get_uri(self):
- return self.getTagData('uri')
-
- uri = property(get_uri, None, None,
- '''Conveys an IRI associated with the person. Might be None when not set.''')
-
- def get_email(self):
- return self.getTagData('email')
-
- email = property(get_email, None, None,
- '''Conveys an e-mail address associated with the person. Might be None when
- not set.''')
-
-class Entry(nbxmpp.Node, object):
- def __init__(self, node=None):
- nbxmpp.Node.__init__(self, 'entry', node=node)
-
- def __repr__(self):
- return '<Atom:Entry object of id="%r">' % self.getAttr('id')
-
-class OldEntry(nbxmpp.Node, object):
- """
- Parser for feeds from pubsub.com. They use old Atom 0.3 format with their
- extensions
- """
-
- def __init__(self, node=None):
- ''' Create new Atom 0.3 entry object. '''
- nbxmpp.Node.__init__(self, 'entry', node=node)
-
- def __repr__(self):
- return '<Atom0.3:Entry object of id="%r">' % self.getAttr('id')
-
- def get_feed_title(self):
- """
- Return title of feed, where the entry was created. The result is the feed
- name concatenated with source-feed title
- """
- if self.parent is not None:
- main_feed = self.parent.getTagData('title')
- else:
- main_feed = None
-
- if self.getTag('feed') is not None:
- source_feed = self.getTag('feed').getTagData('title')
- else:
- source_feed = None
-
-
- if main_feed is not None and source_feed is not None:
- return '%s: %s' % (main_feed, source_feed)
- elif main_feed is not None:
- return main_feed
- elif source_feed is not None:
- return source_feed
- else:
- return ''
-
- feed_title = property(get_feed_title, None, None,
- ''' Title of feed. It is built from entry''s original feed title and title of feed
- which delivered this entry. ''')
-
- def get_feed_link(self):
- """
- Get source link
- """
- try:
- return self.getTag('feed').getTags('link', {'rel':'alternate'})[1].getData()
- except Exception:
- return None
-
- feed_link = property(get_feed_link, None, None,
- ''' Link to main webpage of the feed. ''')
-
- def get_title(self):
- """
- Get an entry's title
- """
- return self.getTagData('title')
-
- title = property(get_title, None, None,
- ''' Entry's title. ''')
-
- def get_uri(self):
- """
- Get the uri the entry points to (entry's first link element with
- rel='alternate' or without rel attribute)
- """
- for element in self.getTags('link'):
- if 'rel' in element.attrs and element.attrs['rel']!='alternate': continue
- try:
- return element.attrs['href']
- except AttributeError:
- pass
- return None
-
- uri = property(get_uri, None, None,
- ''' URI that is pointed by the entry. ''')
-
- def get_updated(self):
- """
- Get the time the entry was updated last time
-
- This should be standarized, but pubsub.com sends it in human-readable
- format. We won't try to parse it. (Atom 0.3 uses the word «modified» for
- that).
-
- If there's no time given in the entry, we try with <published>
- and <issued> elements.
- """
- for name in ('updated', 'modified', 'published', 'issued'):
- date = self.getTagData(name)
- if date is not None: break
-
- if date is None:
- # it is not in the standard format
- return time.asctime()
-
- return date
-
- updated = property(get_updated, None, None,
- ''' Last significant modification time. ''')
-
- feed_tagline = ''
diff --git a/src/common/caps_cache.py b/src/common/caps_cache.py
deleted file mode 100644
index 6a1fc7d15..000000000
--- a/src/common/caps_cache.py
+++ /dev/null
@@ -1,440 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/caps_cache.py
-##
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-## Copyright (C) 2008-2009 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-"""
-Module containing all XEP-115 (Entity Capabilities) related classes
-
-Basic Idea:
-CapsCache caches features to hash relationships. The cache is queried
-through ClientCaps objects which are hold by contact instances.
-"""
-
-import base64
-import hashlib
-
-import logging
-log = logging.getLogger('gajim.c.caps_cache')
-
-from nbxmpp import (NS_XHTML_IM, NS_ESESSION, NS_CHATSTATES,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
- NS_JINGLE_FILE_TRANSFER_5)
-# Features where we cannot safely assume that the other side supports them
-FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_ESESSION,
- NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
- NS_JINGLE_FILE_TRANSFER_5]
-
-# Query entry status codes
-NEW = 0
-QUERIED = 1
-CACHED = 2 # got the answer
-FAKED = 3 # allow NullClientCaps to behave as it has a cached item
-
-################################################################################
-### Public API of this module
-################################################################################
-
-capscache = None
-def initialize(logger):
- """
- Initialize this module
- """
- global capscache
- capscache = CapsCache(logger)
-
-def client_supports(client_caps, requested_feature):
- lookup_item = client_caps.get_cache_lookup_strategy()
- cache_item = lookup_item(capscache)
-
- supported_features = cache_item.features
- if requested_feature in supported_features:
- return True
- elif not supported_features and cache_item.status in (NEW, QUERIED, FAKED):
- # assume feature is supported, if we don't know yet, what the client
- # is capable of
- return requested_feature not in FEATURE_BLACKLIST
- else:
- return False
-
-def create_suitable_client_caps(node, caps_hash, hash_method, fjid=None):
- """
- Create and return a suitable ClientCaps object for the given node,
- caps_hash, hash_method combination.
- """
- if not node or not caps_hash:
- if fjid:
- client_caps = NoClientCaps(fjid)
- else:
- # improper caps, ignore client capabilities.
- client_caps = NullClientCaps()
- elif not hash_method:
- client_caps = OldClientCaps(caps_hash, node)
- else:
- client_caps = ClientCaps(caps_hash, node, hash_method)
- return client_caps
-
-def compute_caps_hash(identities, features, dataforms=None, hash_method='sha-1'):
- """
- Compute caps hash according to XEP-0115, V1.5
-
- dataforms are nbxmpp.DataForms objects as common.dataforms don't allow several
- values without a field type list-multi
- """
- if dataforms is None:
- dataforms = []
- def sort_identities_func(i1, i2):
- cat1 = i1['category']
- cat2 = i2['category']
- if cat1 < cat2:
- return -1
- if cat1 > cat2:
- return 1
- type1 = i1.get('type', '')
- type2 = i2.get('type', '')
- if type1 < type2:
- return -1
- if type1 > type2:
- return 1
- lang1 = i1.get('xml:lang', '')
- lang2 = i2.get('xml:lang', '')
- if lang1 < lang2:
- return -1
- if lang1 > lang2:
- return 1
- return 0
-
- def sort_dataforms_func(d1, d2):
- f1 = d1.getField('FORM_TYPE')
- f2 = d2.getField('FORM_TYPE')
- if f1 and f2 and (f1.getValue() < f2.getValue()):
- return -1
- return 1
-
- S = ''
- from functools import cmp_to_key
- identities.sort(key=cmp_to_key(sort_identities_func))
- for i in identities:
- c = i['category']
- type_ = i.get('type', '')
- lang = i.get('xml:lang', '')
- name = i.get('name', '')
- S += '%s/%s/%s/%s<' % (c, type_, lang, name)
- features.sort()
- for f in features:
- S += '%s<' % f
- dataforms.sort(key=cmp_to_key(sort_dataforms_func))
- for dataform in dataforms:
- # fields indexed by var
- fields = {}
- for f in dataform.getChildren():
- fields[f.getVar()] = f
- form_type = fields.get('FORM_TYPE')
- if form_type:
- S += form_type.getValue() + '<'
- del fields['FORM_TYPE']
- for var in sorted(fields.keys()):
- S += '%s<' % var
- values = sorted(fields[var].getValues())
- for value in values:
- S += '%s<' % value
-
- if hash_method == 'sha-1':
- hash_ = hashlib.sha1(S.encode('utf-8'))
- elif hash_method == 'md5':
- hash_ = hashlib.md5(S.encode('utf-8'))
- else:
- return ''
- return base64.b64encode(hash_.digest()).decode('utf-8')
-
-
-################################################################################
-### Internal classes of this module
-################################################################################
-
-class AbstractClientCaps(object):
- """
- Base class representing a client and its capabilities as advertised by a
- caps tag in a presence
- """
- def __init__(self, caps_hash, node):
- self._hash = caps_hash
- self._node = node
- self._hash_method = None
-
- def get_discover_strategy(self):
- return self._discover
-
- def _discover(self, connection, jid):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_cache_lookup_strategy(self):
- return self._lookup_in_cache
-
- def _lookup_in_cache(self, caps_cache):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
- def get_hash_validation_strategy(self):
- return self._is_hash_valid
-
- def _is_hash_valid(self, identities, features, dataforms):
- """
- To be implemented by subclassess
- """
- raise NotImplementedError
-
-
-class ClientCaps(AbstractClientCaps):
- """
- The current XEP-115 implementation
- """
- def __init__(self, caps_hash, node, hash_method):
- AbstractClientCaps.__init__(self, caps_hash, node)
- assert hash_method != 'old'
- self._hash_method = hash_method
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[(self._hash_method, self._hash)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid, '%s#%s' % (self._node, self._hash))
-
- def _is_hash_valid(self, identities, features, dataforms):
- computed_hash = compute_caps_hash(identities, features,
- dataforms=dataforms, hash_method=self._hash_method)
- return computed_hash == self._hash
-
-
-class OldClientCaps(AbstractClientCaps):
- """
- Old XEP-115 implemtation. Kept around for background competability
- """
- def __init__(self, caps_hash, node):
- AbstractClientCaps.__init__(self, caps_hash, node)
- self._hash_method = 'old'
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[('old', self._node + '#' + self._hash)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid)
-
- def _is_hash_valid(self, identities, features, dataforms):
- return True
-
-class NoClientCaps(AbstractClientCaps):
- """
- For clients that don't support XEP-0115
- """
- def __init__(self, fjid):
- AbstractClientCaps.__init__(self, fjid, fjid)
- self._hash_method = 'no'
-
- def _lookup_in_cache(self, caps_cache):
- return caps_cache[('no', self._node)]
-
- def _discover(self, connection, jid):
- connection.discoverInfo(jid)
-
- def _is_hash_valid(self, identities, features, dataforms):
- return True
-
-class NullClientCaps(AbstractClientCaps):
- """
- This is a NULL-Object to streamline caps handling if a client has not
- advertised any caps or has advertised them in an improper way
-
- Assumes (almost) everything is supported.
- """
- _instance = None
- def __new__(cls, *args, **kwargs):
- """
- Make it a singleton.
- """
- if not cls._instance:
- cls._instance = super(NullClientCaps, cls).__new__(
- cls, *args, **kwargs)
- return cls._instance
-
- def __init__(self):
- AbstractClientCaps.__init__(self, None, None)
- self._hash_method = 'dummy'
-
- def _lookup_in_cache(self, caps_cache):
- # lookup something which does not exist to get a new CacheItem created
- cache_item = caps_cache[('dummy', '')]
- # Mark the item as cached so that protocol/caps.py does not update it
- cache_item.status = FAKED
- return cache_item
-
- def _discover(self, connection, jid):
- pass
-
- def _is_hash_valid(self, identities, features, dataforms):
- return False
-
-
-class CapsCache(object):
- """
- This object keeps the mapping between caps data and real disco features they
- represent, and provides simple way to query that info
- """
- def __init__(self, logger=None):
- # our containers:
- # __cache is a dictionary mapping: pair of hash method and hash maps
- # to CapsCacheItem object
- # __CacheItem is a class that stores data about particular
- # client (hash method/hash pair)
- self.__cache = {}
-
- class CacheItem(object):
- # __names is a string cache; every string long enough is given
- # another object, and we will have plenty of identical long
- # strings. therefore we can cache them
- __names = {}
-
- def __init__(self, hash_method, hash_, logger):
- # cached into db
- self.hash_method = hash_method
- self.hash = hash_
- self._features = []
- self._identities = []
- self._logger = logger
-
- self.status = NEW
- self._recently_seen = False
-
- def _get_features(self):
- return self._features
-
- def _set_features(self, value):
- self._features = []
- for feature in value:
- self._features.append(self.__names.setdefault(feature, feature))
-
- features = property(_get_features, _set_features)
-
- def _get_identities(self):
- list_ = []
- for i in self._identities:
- # transforms it back in a dict
- d = dict()
- d['category'] = i[0]
- if i[1]:
- d['type'] = i[1]
- if i[2]:
- d['xml:lang'] = i[2]
- if i[3]:
- d['name'] = i[3]
- list_.append(d)
- return list_
-
- def _set_identities(self, value):
- self._identities = []
- for identity in value:
- # dict are not hashable, so transform it into a tuple
- t = (identity['category'], identity.get('type'),
- identity.get('xml:lang'), identity.get('name'))
- self._identities.append(self.__names.setdefault(t, t))
-
- identities = property(_get_identities, _set_identities)
-
- def set_and_store(self, identities, features):
- self.identities = identities
- self.features = features
- if self.hash_method != 'no':
- self._logger.add_caps_entry(self.hash_method, self.hash,
- identities, features)
- self.status = CACHED
-
- def update_last_seen(self):
- if not self._recently_seen:
- self._recently_seen = True
- if self.hash_method != 'no':
- self._logger.update_caps_time(self.hash_method,
- self.hash)
-
- def is_valid(self):
- """
- Returns True if identities and features for this cache item
- are known.
- """
- return self.status in (CACHED, FAKED)
-
- self.__CacheItem = CacheItem
- self.logger = logger
-
- def initialize_from_db(self):
- self._remove_outdated_caps()
- for hash_method, hash_, identities, features in \
- self.logger.iter_caps_data():
- x = self[(hash_method, hash_)]
- x.identities = identities
- x.features = features
- x.status = CACHED
-
- def _remove_outdated_caps(self):
- """
- Remove outdated values from the db
- """
- self.logger.clean_caps_table()
-
- def __getitem__(self, caps):
- if caps in self.__cache:
- return self.__cache[caps]
-
- hash_method, hash_ = caps
-
- x = self.__CacheItem(hash_method, hash_, self.logger)
- self.__cache[(hash_method, hash_)] = x
- return x
-
- def query_client_of_jid_if_unknown(self, connection, jid, client_caps):
- """
- Start a disco query to determine caps (node, ver, exts). Won't query if
- the data is already in cache
- """
- lookup_cache_item = client_caps.get_cache_lookup_strategy()
- q = lookup_cache_item(self)
-
- if q.status == NEW:
- # do query for bare node+hash pair
- # this will create proper object
- q.status = QUERIED
- discover = client_caps.get_discover_strategy()
- discover(connection, jid)
- else:
- q.update_last_seen()
-
- def forget_caps(self, client_caps):
- hash_method = client_caps._hash_method
- hash = client_caps._hash
- key = (hash_method, hash)
- if key in self.__cache:
- del self.__cache[key]
diff --git a/src/common/check_X509.py b/src/common/check_X509.py
deleted file mode 100644
index 4aec0a7e9..000000000
--- a/src/common/check_X509.py
+++ /dev/null
@@ -1,183 +0,0 @@
-import logging
-log = logging.getLogger('gajim.c.check_X509')
-
-try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- ver = OpenSSL.__version__
- if ver < '0.12':
- raise ImportError
- from pyasn1.type import univ, constraint, char, namedtype, tag
- from pyasn1.codec.der.decoder import decode
- from common.helpers import prep, InvalidFormat
-
- MAX = 64
- oid_xmppaddr = '1.3.6.1.5.5.7.8.5'
- oid_dnssrv = '1.3.6.1.5.5.7.8.7'
-
-
-
- class DirectoryString(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType(
- 'teletexString', char.TeletexString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'printableString', char.PrintableString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'universalString', char.UniversalString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'utf8String', char.UTF8String().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'bmpString', char.BMPString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'ia5String', char.IA5String().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- namedtype.NamedType(
- 'gString', univ.OctetString().subtype(
- subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
- )
-
- class AttributeValue(DirectoryString):
- pass
-
- class AttributeType(univ.ObjectIdentifier):
- pass
-
- class AttributeTypeAndValue(univ.Sequence):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('type', AttributeType()),
- namedtype.NamedType('value', AttributeValue()),
- )
-
- class RelativeDistinguishedName(univ.SetOf):
- componentType = AttributeTypeAndValue()
-
- class RDNSequence(univ.SequenceOf):
- componentType = RelativeDistinguishedName()
-
- class Name(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('', RDNSequence()),
- )
-
- class GeneralName(univ.Choice):
- componentType = namedtype.NamedTypes(
- namedtype.NamedType('otherName', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x0))),
- namedtype.NamedType('rfc822Name', char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 1))),
- namedtype.NamedType('dNSName', char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 2))),
- namedtype.NamedType('x400Address', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x3))),
- namedtype.NamedType('directoryName', Name().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x4))),
- namedtype.NamedType('ediPartyName', univ.Sequence().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatConstructed, 0x5))),
- namedtype.NamedType('uniformResourceIdentifier',
- char.IA5String().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 6))),
- namedtype.NamedType('iPAddress', univ.OctetString().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 7))),
- namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(
- implicitTag=tag.Tag(tag.tagClassContext,
- tag.tagFormatSimple, 8))),
- )
-
- class GeneralNames(univ.SequenceOf):
- componentType = GeneralName()
- sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
-
- def _parse_asn1(asn1):
- obj = decode(asn1, asn1Spec=GeneralNames())[0]
- r = {}
- for o in obj:
- name = o.getName()
- if name == 'dNSName':
- if name not in r:
- r[name] = []
- r[name].append(str(o.getComponent()))
- if name == 'otherName':
- if name not in r:
- r[name] = {}
- tag = str(tuple(o.getComponent())[0])
- val = str(tuple(o.getComponent())[1])
- if tag not in r[name]:
- r[name][tag] = []
- r[name][tag].append(val)
- if name == 'uniformResourceIdentifier':
- r['uniformResourceIdentifier'] = True
- return r
-
- def check_certificate(cert, domain):
- cnt = cert.get_extension_count()
- if '.' in domain:
- compared_domain = domain.split('.', 1)[1]
- else:
- compared_domain = ''
- srv_domain = '_xmpp-client.' + domain
- compared_srv_domain = '_xmpp-client.' + compared_domain
- for i in range(0, cnt):
- ext = cert.get_extension(i)
- if ext.get_short_name() == b'subjectAltName':
- try:
- r = _parse_asn1(ext.get_data())
- except:
- log.error('Wrong data in certificate: subjectAltName=%s' % \
- ext.get_data())
- continue
- if 'otherName' in r:
- if oid_xmppaddr in r['otherName']:
- for host in r['otherName'][oid_xmppaddr]:
- try:
- host = prep(None, host, None)
- except InvalidFormat:
- continue
- if host == domain:
- return True
- if oid_dnssrv in r['otherName']:
- for host in r['otherName'][oid_dnssrv]:
- if host.startswith('_xmpp-client.*.'):
- if host.replace('*.', '', 1) == compared_srv_domain:
- return True
- continue
- if host == srv_domain:
- return True
- if 'dNSName' in r:
- for host in r['dNSName']:
- if host.startswith('*.'):
- if host[2:] == compared_domain:
- return True
- continue
- if host == domain:
- return True
- if r:
- return False
- break
-
- subject = cert.get_subject()
- if subject.commonName == domain:
- return True
- return False
-except ImportError:
- log.warning('Import of PyOpenSSL or pyasn1 failed. Cannot correctly check '
- 'SSL certificate')
-
- def check_certificate(cert, domain):
- subject = cert.get_subject()
- if subject.commonName == domain:
- return True
- return False
diff --git a/src/common/check_paths.py b/src/common/check_paths.py
deleted file mode 100644
index 98d1a9cc1..000000000
--- a/src/common/check_paths.py
+++ /dev/null
@@ -1,364 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/check_paths.py
-##
-## Copyright (C) 2005-2006 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import shutil
-import sys
-
-from common import gajim
-from common import logger
-
-# DO NOT MOVE ABOVE OF import gajim
-import sqlite3 as sqlite
-
-def create_log_db():
- print(_('creating logs database'))
- con = sqlite.connect(logger.LOG_DB_PATH)
- os.chmod(logger.LOG_DB_PATH, 0o600) # rw only for us
- cur = con.cursor()
- # create the tables
- # kind can be
- # status, gcstatus, gc_msg, (we only recv for those 3),
- # single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
- # to meet all our needs
- # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
- # jids.jid text column will be JID if TC-related, room_jid if GC-related,
- # ROOM_JID/nick if pm-related.
- # also check optparser.py, which updates databases on gajim updates
- cur.executescript(
- '''
- CREATE TABLE jids(
- jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE unread_messages(
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- shown BOOLEAN default 0
- );
-
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
-
- CREATE TABLE logs(
- log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER,
- contact_name TEXT,
- time INTEGER,
- kind INTEGER,
- show INTEGER,
- message TEXT,
- subject TEXT,
- additional_data TEXT DEFAULT '{}',
- stanza_id TEXT,
- mam_id TEXT,
- encryption TEXT,
- encryption_state TEXT,
- marker INTEGER
- );
-
- CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
-
- con.commit()
- con.close()
-
-def create_cache_db():
- print(_('creating cache database'))
- con = sqlite.connect(logger.CACHE_DB_PATH)
- os.chmod(logger.CACHE_DB_PATH, 0o600) # rw only for us
- cur = con.cursor()
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
-
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB,
- last_seen INTEGER);
-
- CREATE TABLE rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
-
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
-
- con.commit()
- con.close()
-
-def split_db():
- print('spliting database')
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = '.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser('~/.gajim')
-
- tmp = logger.CACHE_DB_PATH
- logger.CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
- create_cache_db()
- back = os.getcwd()
- os.chdir(OLD_LOG_DB_FOLDER)
- con = sqlite.connect('logs.db')
- os.chdir(back)
- cur = con.cursor()
- cur.execute('''SELECT name FROM sqlite_master WHERE type = 'table';''')
- tables = cur.fetchall() # we get [('jids',), ('unread_messages',), ...
- tables = [t[0] for t in tables]
- cur.execute("ATTACH DATABASE '%s' AS cache" % logger.CACHE_DB_PATH)
- for table in ('caps_cache', 'rooms_last_message_time', 'roster_entry',
- 'roster_group', 'transports_cache'):
- if table not in tables:
- continue
- try:
- cur.executescript(
- 'INSERT INTO cache.%s SELECT * FROM %s;' % (table, table))
- con.commit()
- cur.executescript('DROP TABLE %s;' % table)
- con.commit()
- except sqlite.OperationalError as e:
- print('error moving table %s to cache.db: %s' % (table, str(e)),
- file=sys.stderr)
- con.close()
- logger.CACHE_DB_PATH = tmp
-
-def check_and_possibly_move_config():
- LOG_DB_PATH = logger.LOG_DB_PATH
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- vars = {}
- vars['VCARD_PATH'] = gajim.VCARD_PATH
- vars['AVATAR_PATH'] = gajim.AVATAR_PATH
- vars['MY_EMOTS_PATH'] = gajim.MY_EMOTS_PATH
- vars['MY_ICONSETS_PATH'] = gajim.MY_ICONSETS_PATH
- vars['MY_MOOD_ICONSETS_PATH'] = gajim.MY_MOOD_ICONSETS_PATH
- vars['MY_ACTIVITY_ICONSETS_PATH'] = gajim.MY_ACTIVITY_ICONSETS_PATH
- from common import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
-
- if os.path.exists(LOG_DB_PATH):
- # File already exists
- return
-
- if os.name == 'nt':
- try:
- OLD_LOG_DB_FOLDER = os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- OLD_LOG_DB_FOLDER = '.'
- else:
- OLD_LOG_DB_FOLDER = os.path.expanduser('~/.gajim')
- if not os.path.exists(OLD_LOG_DB_FOLDER):
- return
- OLD_LOG_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'logs.db')
- OLD_CACHE_DB_PATH = os.path.join(OLD_LOG_DB_FOLDER, 'cache.db')
- vars['OLD_VCARD_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'vcards')
- vars['OLD_AVATAR_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'avatars')
- vars['OLD_MY_EMOTS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'emoticons')
- vars['OLD_MY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'iconsets')
- vars['OLD_MY_MOOD_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER, 'moods')
- vars['OLD_MY_ACTIVITY_ICONSETS_PATH'] = os.path.join(OLD_LOG_DB_FOLDER,
- 'activities')
- OLD_CONFIG_FILES = []
- OLD_DATA_FILES = []
- for f in os.listdir(OLD_LOG_DB_FOLDER):
- if f == 'config' or f.startswith('config.'):
- OLD_CONFIG_FILES.append(f)
- if f == 'secrets' or f.startswith('secrets.'):
- OLD_DATA_FILES.append(f)
- if f == 'cacerts.pem':
- OLD_DATA_FILES.append(f)
-
- if not os.path.exists(OLD_LOG_DB_PATH):
- return
-
- if not os.path.exists(OLD_CACHE_DB_PATH):
- # split database
- split_db()
-
- to_move = {}
- to_move[OLD_LOG_DB_PATH] = LOG_DB_PATH
- to_move[OLD_CACHE_DB_PATH] = CACHE_DB_PATH
-
- for folder in ('VCARD_PATH', 'AVATAR_PATH', 'MY_EMOTS_PATH',
- 'MY_ICONSETS_PATH', 'MY_MOOD_ICONSETS_PATH', 'MY_ACTIVITY_ICONSETS_PATH'):
- src = vars['OLD_' + folder]
- dst = vars[folder]
- to_move[src] = dst
-
- # move config files
- for f in OLD_CONFIG_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_CONFIG, f)
- to_move[src] = dst
-
- # Move data files (secrets, cacert.pem)
- for f in OLD_DATA_FILES:
- src = os.path.join(OLD_LOG_DB_FOLDER, f)
- dst = os.path.join(MY_DATA, f)
- to_move[src] = dst
-
- for src, dst in to_move.items():
- if os.path.exists(dst):
- continue
- if not os.path.exists(src):
- continue
- print(_('moving %s to %s') % (src, dst))
- shutil.move(src, dst)
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
-
-def check_and_possibly_create_paths():
- LOG_DB_PATH = logger.LOG_DB_PATH
- LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
-
- CACHE_DB_PATH = logger.CACHE_DB_PATH
- CACHE_DB_FOLDER, CACHE_DB_FILE = os.path.split(CACHE_DB_PATH)
-
- VCARD_PATH = gajim.VCARD_PATH
- AVATAR_PATH = gajim.AVATAR_PATH
- from common import configpaths
- MY_DATA = configpaths.gajimpaths['MY_DATA']
- MY_CONFIG = configpaths.gajimpaths['MY_CONFIG']
- MY_CACHE = configpaths.gajimpaths['MY_CACHE']
- XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS']
- LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT']
-
- PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
-
- if not os.path.exists(MY_DATA):
- create_path(MY_DATA)
- elif os.path.isfile(MY_DATA):
- print(_('%s is a file but it should be a directory') % MY_DATA)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(MY_CONFIG):
- create_path(MY_CONFIG)
- elif os.path.isfile(MY_CONFIG):
- print(_('%s is a file but it should be a directory') % MY_CONFIG)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(MY_CACHE):
- create_path(MY_CACHE)
- elif os.path.isfile(MY_CACHE):
- print(_('%s is a file but it should be a directory') % MY_CACHE)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(VCARD_PATH):
- create_path(VCARD_PATH)
- elif os.path.isfile(VCARD_PATH):
- print(_('%s is a file but it should be a directory') % VCARD_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(AVATAR_PATH):
- create_path(AVATAR_PATH)
- elif os.path.isfile(AVATAR_PATH):
- print(_('%s is a file but it should be a directory') % AVATAR_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(LOG_DB_FOLDER):
- create_path(LOG_DB_FOLDER)
- elif os.path.isfile(LOG_DB_FOLDER):
- print(_('%s is a file but it should be a directory') % LOG_DB_FOLDER)
- 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()
-
- if not os.path.exists(CACHE_DB_FOLDER):
- create_path(CACHE_DB_FOLDER)
- elif os.path.isfile(CACHE_DB_FOLDER):
- print(_('%s is a file but it should be a directory') % CACHE_DB_FOLDER)
- print(_('Gajim will now exit'))
- sys.exit()
-
- check_and_possibly_move_config()
-
- if not os.path.exists(LOG_DB_PATH):
- if os.path.exists(CACHE_DB_PATH):
- os.remove(CACHE_DB_PATH)
- create_log_db()
- gajim.logger.init_vars()
- elif os.path.isdir(LOG_DB_PATH):
- print(_('%s is a directory but should be a file') % LOG_DB_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(CACHE_DB_PATH):
- create_cache_db()
- gajim.logger.attach_cache_database()
- elif os.path.isdir(CACHE_DB_PATH):
- print(_('%s is a directory but should be a file') % CACHE_DB_PATH)
- print(_('Gajim will now exit'))
- sys.exit()
-
- if not os.path.exists(XTLS_CERTS):
- create_path(XTLS_CERTS)
- if not os.path.exists(LOCAL_XTLS_CERTS):
- create_path(LOCAL_XTLS_CERTS)
-
-def create_path(directory):
- head, tail = os.path.split(directory)
- if not os.path.exists(head):
- create_path(head)
- if os.path.exists(directory):
- return
- print(('creating %s directory') % directory)
- os.mkdir(directory, 0o700)
diff --git a/src/common/commands.py b/src/common/commands.py
deleted file mode 100644
index 0eeb57c8e..000000000
--- a/src/common/commands.py
+++ /dev/null
@@ -1,495 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/commands.py
-##
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import nbxmpp
-from common import helpers
-from common import dataforms
-from common import gajim
-from common.connection_handlers_events import MessageOutgoingEvent
-
-import logging
-log = logging.getLogger('gajim.c.commands')
-
-class AdHocCommand:
- commandnode = 'command'
- commandname = 'The Command'
- commandfeatures = (nbxmpp.NS_DATA,)
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- This returns True if that command should be visible and invokable for
- others
-
- samejid - True when command is invoked by an entity with the same bare
- jid.
- """
- return True
-
- def __init__(self, conn, jid, sessionid):
- self.connection = conn
- self.jid = jid
- self.sessionid = sessionid
-
- def buildResponse(self, request, status = 'executing', defaultaction = None,
- actions = None):
- assert status in ('executing', 'completed', 'canceled')
-
- response = request.buildReply('result')
- cmd = response.getTag('command', namespace=nbxmpp.NS_COMMANDS)
- cmd.setAttr('sessionid', self.sessionid)
- cmd.setAttr('node', self.commandnode)
- cmd.setAttr('status', status)
- if defaultaction is not None or actions is not None:
- if defaultaction is not None:
- assert defaultaction in ('cancel', 'execute', 'prev', 'next',
- 'complete')
- attrs = {'action': defaultaction}
- else:
- attrs = {}
-
- cmd.addChild('actions', attrs, actions)
- return response, cmd
-
- def badRequest(self, stanza):
- self.connection.connection.send(nbxmpp.Error(stanza,
- nbxmpp.NS_STANZAS + ' bad-request'))
-
- def cancel(self, request):
- response = self.buildResponse(request, status = 'canceled')[0]
- self.connection.connection.send(response)
- return False # finish the session
-
-class ChangeStatusCommand(AdHocCommand):
- commandnode = 'change-status'
- commandname = _('Change status information')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions = ['execute'])
-
- cmd.addChild(node = dataforms.SimpleDataForm(
- title = _('Change status'),
- instructions = _('Set the presence type and description'),
- fields = [
- dataforms.Field('list-single',
- var = 'presence-type',
- label = 'Type of presence:',
- options = [
- ('chat', _('Free for chat')),
- ('online', _('Online')),
- ('away', _('Away')),
- ('xa', _('Extended away')),
- ('dnd', _('Do not disturb')),
- ('offline', _('Offline - disconnect'))],
- value = 'online',
- required = True),
- dataforms.Field('text-multi',
- var = 'presence-desc',
- label = _('Presence description:'))]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.changestatus
-
- return True # keep the session
-
- def changestatus(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- presencetype = form['presence-type'].value
- if not presencetype in \
- ('chat', 'online', 'away', 'xa', 'dnd', 'offline'):
- self.badRequest(request)
- return False
- except Exception: # KeyError if there's no presence-type field in form or
- # AttributeError if that field is of wrong type
- self.badRequest(request)
- return False
-
- try:
- presencedesc = form['presence-desc'].value
- except Exception: # same exceptions as in last comment
- presencedesc = ''
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = presencetype == 'offline')
-
- # send new status
- gajim.interface.roster.send_status(self.connection.name, presencetype,
- presencedesc)
-
- return False # finish the session
-
-def find_current_groupchats(account):
- import message_control
- rooms = []
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + gajim.interface.minimized_controls[account].\
- values():
- acct = gc_control.account
- # check if account is the good one
- if acct != account:
- continue
- room_jid = gc_control.room_jid
- nick = gc_control.nick
- if room_jid in gajim.gc_connected[acct] and \
- gajim.gc_connected[acct][room_jid]:
- rooms.append((room_jid, nick,))
- return rooms
-
-
-class LeaveGroupchatsCommand(AdHocCommand):
- commandnode = 'leave-groupchats'
- commandname = _('Leave Groupchats')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- # first query...
- response, cmd = self.buildResponse(request, defaultaction = 'execute',
- actions=['execute'])
- options = []
- account = self.connection.name
- for gc in find_current_groupchats(account):
- options.append(('%s' %(gc[0]), _('%(nickname)s on %(room_jid)s') % \
- {'nickname': gc[1], 'room_jid': gc[0]}))
- if not len(options):
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('You have not joined a groupchat.'))
-
- self.connection.connection.send(response)
- return False
-
- cmd.addChild(node=dataforms.SimpleDataForm(
- title = _('Leave Groupchats'),
- instructions = _('Choose the groupchats you want to leave'),
- fields=[
- dataforms.Field('list-multi',
- var = 'groupchats',
- label = _('Groupchats'),
- options = options,
- required = True)]))
-
- self.connection.connection.send(response)
-
- # for next invocation
- self.execute = self.leavegroupchats
-
- return True # keep the session
-
- def leavegroupchats(self, request):
- # check if the data is correct
- try:
- form = dataforms.SimpleDataForm(extend = request.getTag('command').\
- getTag('x'))
- except Exception:
- self.badRequest(request)
- return False
-
- try:
- gc = form['groupchats'].values
- except Exception: # KeyError if there's no groupchats in form
- self.badRequest(request)
- return False
- account = self.connection.name
- try:
- for room_jid in gc:
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if not gc_control:
- gc_control = gajim.interface.minimized_controls[account]\
- [room_jid]
- gc_control.shutdown()
- gajim.interface.roster.remove_groupchat(room_jid, account)
- continue
- gc_control.parent_win.remove_tab(gc_control, None, force = True)
- except Exception: # KeyError if there's no such room opened
- self.badRequest(request)
- return False
- response, cmd = self.buildResponse(request, status = 'completed')
- note = _('You left the following groupchats:')
- for room_jid in gc:
- note += '\n\t' + room_jid
- cmd.addChild('note', {}, note)
-
- self.connection.connection.send(response)
- return False
-
-
-class ForwardMessagesCommand(AdHocCommand):
- # http://www.xmpp.org/extensions/xep-0146.html#forward
- commandnode = 'forward-messages'
- commandname = _('Forward unread messages')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- account = self.connection.name
- # Forward messages
- events = gajim.events.get_events(account, types=['chat', 'normal',
- 'printed_chat'])
- j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
- for jid in events:
- for event in events[jid]:
- ev_typ = event.type_
- if ev_typ == 'printed_chat':
- ev_typ = 'chat'
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=account, jid=j, message=event.message, type_=ev_typ,
- subject=event.subject, resource=resource, forward_from=jid,
- delayed=event.time_))
-
- # Inform other client of completion
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('All unread messages have been forwarded.'))
-
- self.connection.connection.send(response)
-
- return False # finish the session
-
-class FwdMsgThenDisconnectCommand(AdHocCommand):
- commandnode = 'fwd-msd-disconnect'
- commandname = _('Forward unread message then disconnect')
-
- @staticmethod
- def isVisibleFor(samejid):
- """
- Change status is visible only if the entity has the same bare jid
- """
- return samejid
-
- def execute(self, request):
- account = self.connection.name
- # Forward messages
- events = gajim.events.get_events(account, types=['chat', 'normal'])
- j, resource = gajim.get_room_and_nick_from_fjid(self.jid)
- for jid in events:
- for event in events[jid]:
- ev_typ = event.type_
- if ev_typ == 'printed_chat':
- ev_typ = 'chat'
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=account, jid=j, message=event.message, type_=ev_typ,
- subject=event.subject, resource=resource, forward_from=jid,
- delayed=event.time_, now=True))
-
- response, cmd = self.buildResponse(request, status = 'completed')
- cmd.addChild('note', {}, _('The status has been changed.'))
-
- # if going offline, we need to push response so it won't go into
- # queue and disappear
- self.connection.connection.send(response, now = True)
-
- # send new status
- gajim.interface.roster.send_status(self.connection.name, 'offline', '')
- # finish the session
- return False
-
-class ConnectionCommands:
- """
- This class depends on that it is a part of Connection() class
- """
-
- def __init__(self):
- # a list of all commands exposed: node -> command class
- self.__commands = {}
- if gajim.config.get('remote_commands'):
- for cmdobj in (ChangeStatusCommand, ForwardMessagesCommand,
- LeaveGroupchatsCommand, FwdMsgThenDisconnectCommand):
- self.__commands[cmdobj.commandnode] = cmdobj
-
- # a list of sessions; keys are tuples (jid, sessionid, node)
- self.__sessions = {}
-
- def getOurBareJID(self):
- return gajim.get_jid_from_account(self.name)
-
- def isSameJID(self, jid):
- """
- Test if the bare jid given is the same as our bare jid
- """
- return nbxmpp.JID(jid).getStripped() == self.getOurBareJID()
-
- def commandListQuery(self, con, iq_obj):
- iq = iq_obj.buildReply('result')
- jid = helpers.get_full_jid_from_iq(iq_obj)
- q = iq.getTag('query')
- # buildReply don't copy the node attribute. Re-add it
- q.setAttr('node', nbxmpp.NS_COMMANDS)
-
- for node, cmd in self.__commands.items():
- if cmd.isVisibleFor(self.isSameJID(jid)):
- q.addChild('item', {
- # TODO: find the jid
- 'jid': self.getOurBareJID() + '/' + self.server_resource,
- 'node': node,
- 'name': cmd.commandname})
-
- self.connection.send(iq)
-
- def commandInfoQuery(self, con, iq_obj):
- """
- Send disco#info result for query for command (JEP-0050, example 6.).
- Return True if the result was sent, False if not
- """
- try:
- jid = helpers.get_full_jid_from_iq(iq_obj)
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % iq_obj.getFrom())
- return
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- q = iq.getTag('query')
- q.addChild('identity', attrs = {'type': 'command-node',
- 'category': 'automation',
- 'name': cmd.commandname})
- q.addChild('feature', attrs = {'var': nbxmpp.NS_COMMANDS})
- for feature in cmd.commandfeatures:
- q.addChild('feature', attrs = {'var': feature})
-
- self.connection.send(iq)
- return True
-
- return False
-
- def commandItemsQuery(self, con, iq_obj):
- """
- Send disco#items result for query for command. Return True if the result
- was sent, False if not.
- """
- jid = helpers.get_full_jid_from_iq(iq_obj)
- node = iq_obj.getTagAttr('query', 'node')
-
- if node not in self.__commands: return False
-
- cmd = self.__commands[node]
- if cmd.isVisibleFor(self.isSameJID(jid)):
- iq = iq_obj.buildReply('result')
- self.connection.send(iq)
- return True
-
- return False
-
- def _CommandExecuteCB(self, con, iq_obj):
- jid = helpers.get_full_jid_from_iq(iq_obj)
-
- cmd = iq_obj.getTag('command')
- if cmd is None: return
-
- node = cmd.getAttr('node')
- if node is None: return
-
- sessionid = cmd.getAttr('sessionid')
- if sessionid is None:
- # we start a new command session... only if we are visible for the jid
- # and command exist
- if node not in self.__commands.keys():
- self.connection.send(
- nbxmpp.Error(iq_obj, nbxmpp.NS_STANZAS + ' item-not-found'))
- raise nbxmpp.NodeProcessed
-
- newcmd = self.__commands[node]
- if not newcmd.isVisibleFor(self.isSameJID(jid)):
- return
-
- # generate new sessionid
- sessionid = self.connection.getAnID()
-
- # create new instance and run it
- obj = newcmd(conn = self, jid = jid, sessionid = sessionid)
- rc = obj.execute(iq_obj)
- if rc:
- self.__sessions[(jid, sessionid, node)] = obj
- raise nbxmpp.NodeProcessed
- else:
- # the command is already running, check for it
- magictuple = (jid, sessionid, node)
- if magictuple not in self.__sessions:
- # we don't have this session... ha!
- return
-
- action = cmd.getAttr('action')
- obj = self.__sessions[magictuple]
-
- try:
- if action == 'cancel':
- rc = obj.cancel(iq_obj)
- elif action == 'prev':
- rc = obj.prev(iq_obj)
- elif action == 'next':
- rc = obj.next(iq_obj)
- elif action == 'execute' or action is None:
- rc = obj.execute(iq_obj)
- elif action == 'complete':
- rc = obj.complete(iq_obj)
- else:
- # action is wrong. stop the session, send error
- raise AttributeError
- except AttributeError:
- # the command probably doesn't handle invoked action...
- # stop the session, return error
- del self.__sessions[magictuple]
- return
-
- # delete the session if rc is False
- if not rc:
- del self.__sessions[magictuple]
-
- raise nbxmpp.NodeProcessed
diff --git a/src/common/config.py b/src/common/config.py
deleted file mode 100644
index fe254558d..000000000
--- a/src/common/config.py
+++ /dev/null
@@ -1,826 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/config.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005 Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Alex Mauer <hawke AT hawkesnest.net>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-import re
-from common import defs
-from gi.repository import GLib
-from enum import IntEnum, unique
-
-@unique
-class Option(IntEnum):
- TYPE = 0
- VAL = 1
- DESC = 2
- # If Option.RESTART is True - we need restart to use our changed option
- # Option.DESC also should be there
- RESTART = 3
-
-opt_int = [ 'integer', 0 ]
-opt_str = [ 'string', 0 ]
-opt_bool = [ 'boolean', 0 ]
-opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
-opt_one_window_types = ['never', 'always', 'always_with_roster', 'peracct', 'pertype']
-opt_show_roster_on_startup = ['always', 'never', 'last_state']
-opt_treat_incoming_messages = ['', 'chat', 'normal']
-
-class Config:
-
- DEFAULT_ICONSET = 'dcraven'
- DEFAULT_MOOD_ICONSET = 'default'
- DEFAULT_ACTIVITY_ICONSET = 'default'
- DEFAULT_OPENWITH = 'xdg-open'
- DEFAULT_BROWSER = 'firefox'
- DEFAULT_MAILAPP = 'mozilla-thunderbird -compose'
- DEFAULT_FILE_MANAGER = 'xffm'
-
- __options = ({
- # name: [ type, default_value, help_string ]
- 'verbose': [ opt_bool, False, '', True ],
- 'autopopup': [ opt_bool, False ],
- 'notify_on_signin': [ opt_bool, True ],
- 'notify_on_signout': [ opt_bool, False ],
- 'notify_on_new_message': [ opt_bool, True ],
- 'autopopupaway': [ opt_bool, False ],
- 'autopopup_chat_opened': [ opt_bool, False, _('Show desktop notification even when a chat window is opened for this contact and does not have focus') ],
- 'sounddnd': [ opt_bool, False, _('Play sound when user is busy')],
- 'use_notif_daemon': [ opt_bool, True, _('Use D-Bus and Notification-Daemon to show notifications') ],
- 'showoffline': [ opt_bool, False ],
- 'show_only_chat_and_online': [ opt_bool, False, _('Show only online and free for chat contacts in roster.')],
- 'show_transports_group': [ opt_bool, True ],
- 'autoaway': [ opt_bool, True ],
- 'autoawaytime': [ opt_int, 5, _('Time in minutes, after which your status changes to away.') ],
- 'autoaway_message': [ opt_str, _('$S (Away as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoawaytime.') ],
- 'autoxa': [ opt_bool, True ],
- 'autoxatime': [ opt_int, 15, _('Time in minutes, after which your status changes to not available.') ],
- 'autoxa_message': [ opt_str, _('$S (Not available as a result of being idle more than $T min)'), _('$S will be replaced by current status message, $T by autoxatime.') ],
- 'ask_online_status': [ opt_bool, False ],
- 'ask_offline_status': [ opt_bool, False ],
- 'trayicon': [opt_str, 'always', _("When to show notification area icon. Can be 'never', 'on_event', 'always'."), False],
- 'allow_hide_roster': [opt_bool, False, _("Allow to hide the roster window even if the tray icon is not shown."), False],
- 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
- 'mood_iconset': [ opt_str, DEFAULT_MOOD_ICONSET, '', True ],
- 'activity_iconset': [ opt_str, DEFAULT_ACTIVITY_ICONSET, '', True ],
- 'use_transports_iconsets': [ opt_bool, True, '', True ],
- 'inmsgcolor': [ opt_color, '#a40000', _('Incoming nickname color.'), True ],
- 'outmsgcolor': [ opt_color, '#3465a4', _('Outgoing nickname color.'), True ],
- 'inmsgtxtcolor': [ opt_color, '', _('Incoming text color.'), True ],
- 'outmsgtxtcolor': [ opt_color, '#555753', _('Outgoing text color.'), True ],
- 'statusmsgcolor': [ opt_color, '#4e9a06', _('Status message text color.'), True ],
- 'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
- 'urlmsgcolor': [ opt_color, '#204a87', '', True ],
- 'notif_signin_color': [ opt_color, '#32CD32', _('Contact signed in notification color.') ], # limegreen
- 'notif_signout_color': [ opt_color, '#FF0000', _('Contact signout notification color') ], # red
- 'notif_message_color': [ opt_color, '#1E90FF', _('New message/email notification color.') ], # dodgerblue
- 'notif_ftrequest_color': [ opt_color, '#F0E68C', _('File transfer request notification color.') ], # khaki
- 'notif_fterror_color': [ opt_color, '#B22222', _('File transfer error notification color.') ], # firebrick
- 'notif_ftcomplete_color': [ opt_color, '#9ACD32', _('File transfer complete or stopped notification color.') ], # yellowgreen
- 'notif_invite_color': [ opt_color, '#D2B48C', _('Groupchat invitation notification color') ], # tan1
- 'notif_status_color': [ opt_color, '#D8BFD8', _('Background color of status changed notification') ], # thistle2
- 'notif_other_color': [ opt_color, '#FFFFFF', _('Other dialogs color.') ], # white
- 'inmsgfont': [ opt_str, '', _('Incoming nickname font.'), True ],
- 'outmsgfont': [ opt_str, '', _('Outgoing nickname font.'), True ],
- 'inmsgtxtfont': [ opt_str, '', _('Incoming text font.'), True ],
- 'outmsgtxtfont': [ opt_str, '', _('Outgoing text font.'), True ],
- 'statusmsgfont': [ opt_str, '', _('Status message text font.'), True ],
- 'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
- 'roster_theme': [ opt_str, _('default'), '', True ],
- 'mergeaccounts': [ opt_bool, False, '', True ],
- 'sort_by_show_in_roster': [ opt_bool, True, '', True ],
- 'sort_by_show_in_muc': [ opt_bool, False, '', True ],
- 'use_speller': [ opt_bool, False, ],
- 'ignore_incoming_xhtml': [ opt_bool, False, ],
- 'speller_language': [ opt_str, '', _('Language used by speller')],
- 'print_time': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
- 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ],
- 'emoticons_theme': [opt_str, 'static', '', True ],
- 'ascii_formatting': [ opt_bool, True,
- _('Treat * / _ pairs as possible formatting characters.'), True],
- 'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not '
- 'remove */_ . So *abc* will be bold but with * * not removed.')],
- 'rst_formatting_outgoing_messages': [ opt_bool, False,
- _('Uses ReStructured text markup to send HTML, plus ascii formatting if selected. For syntax, see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html (If you want to use this, install docutils)')],
- 'sounds_on': [ opt_bool, True ],
- # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
- 'soundplayer': [ opt_str, '' ],
- 'openwith': [ opt_str, DEFAULT_OPENWITH ],
- 'custombrowser': [ opt_str, DEFAULT_BROWSER ],
- 'custommailapp': [ opt_str, DEFAULT_MAILAPP ],
- 'custom_file_manager': [ opt_str, DEFAULT_FILE_MANAGER ],
- 'gc-hpaned-position': [opt_int, 430],
- 'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
- 'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
- 'msgwin-max-state': [opt_bool, False],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'msgwin-width': [opt_int, 500],
- 'msgwin-height': [opt_int, 440],
- 'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'chat-msgwin-width': [opt_int, 480],
- 'chat-msgwin-height': [opt_int, 440],
- 'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
- 'gc-msgwin-width': [opt_int, 600],
- 'gc-msgwin-height': [opt_int, 440],
- 'single-msg-x-position': [opt_int, 0],
- 'single-msg-y-position': [opt_int, 0],
- 'single-msg-width': [opt_int, 400],
- 'single-msg-height': [opt_int, 280],
- 'save-roster-position': [opt_bool, True, _('If True, Gajim will save roster position when hiding roster, and restore it when showing roster.')],
- 'roster_x-position': [ opt_int, 0 ],
- 'roster_y-position': [ opt_int, 0 ],
- 'roster_width': [ opt_int, 200 ],
- 'roster_height': [ opt_int, 400 ],
- 'roster_hpaned_position': [opt_int, 200],
- 'roster_on_the_right': [opt_bool, False, _('Place the roster on the right in single window mode'), True],
- 'history_window_width': [ opt_int, 650 ],
- 'history_window_height': [ opt_int, 450 ],
- 'history_window_x-position': [ opt_int, 0 ],
- 'history_window_y-position': [ opt_int, 0 ],
- 'latest_disco_addresses': [ opt_str, '' ],
- 'recently_groupchat': [ opt_str, '' ],
- 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ],
- 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ],
- 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ],
- 'notify_on_new_gmail_email': [ opt_bool, True ],
- 'notify_on_new_gmail_email_extra': [ opt_bool, False ],
- 'notify_on_new_gmail_email_command': [ opt_str, '', _('Specify the command to run when new mail arrives, e.g.: /usr/bin/getmail -q') ],
- 'use_gpg_agent': [ opt_bool, False ],
- 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
- 'restore_lines': [opt_int, 4, _('Amount of previous messages to include when reopening a chat')],
- 'restore_timeout': [opt_int, 60, _('How many minutes should last lines from previous conversation last.')],
- 'muc_restore_lines': [opt_int, 20, _('How many lines to request from server when entering a groupchat. -1 means no limit')],
- 'muc_restore_timeout': [opt_int, 60, _('Minutes of backlog to request when entering a groupchat. -1 means no limit')],
- 'muc_autorejoin_timeout': [opt_int, 1, _('How many seconds to wait before trying to autorejoin to a conference you are being disconnected from. Set to 0 to disable autorejoining.')],
- 'muc_autorejoin_on_kick': [opt_bool, False, _('Should autorejoin be activated when we are being kicked from a conference?')],
- 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
- 'last_roster_visible': [opt_bool, True],
- 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
- 'version': [ opt_str, defs.version ], # which version created the config
- 'search_engine': [opt_str, 'https://www.google.com/search?&q=%s&sourceid=gajim'],
- 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
- 'always_english_wikipedia': [opt_bool, False],
- 'always_english_wiktionary': [opt_bool, True],
- 'remote_control': [opt_bool, False, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
- 'outgoing_chat_state_notifications': [opt_str, 'all', _('Sent chat state notifications. Can be one of all, composing_only, disabled.')],
- 'displayed_chat_state_notifications': [opt_str, 'all', _('Displayed chat state notifications in chat windows. Can be one of all, composing_only, disabled.')],
- 'autodetect_browser_mailer': [opt_bool, True, '', True],
- 'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
- 'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
- 'confirm_close_muc_rooms': [opt_str, '', _('Always ask for confirmation before closing groupchats with any of the JIDs on this space separated list.')],
- 'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask for confirmation before closing groupchats with any of the JIDs on this space separated list.')],
- 'confirm_close_multiple_tabs': [opt_bool, True, _('Ask before closing tabbed chat window if there are controls that can lose data (chat, private chat, groupchat that will not be minimized)')],
- 'notify_on_file_complete': [opt_bool, True],
- 'file_transfers_port': [opt_int, 28011],
- 'ft_add_hosts_to_send': [opt_str, '', _('Comma separated list of hosts that we send, in addition of local interfaces, for File Transfer in case of address translation/port forwarding.')],
- 'conversation_font': [opt_str, ''],
- 'use_kib_mib': [opt_bool, False, _('IEC standard says KiB = 1024 bytes, KB = 1000 bytes.')],
- 'notify_on_all_muc_messages': [opt_bool, False],
- 'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the notification area.')],
- 'trayicon_blink': [opt_bool, True, _('If False, Gajim will display a static event icon instead of the blinking status icon in the notification area when notifying on event.')],
- 'last_save_dir': [opt_str, ''],
- 'last_send_dir': [opt_str, ''],
- 'last_emoticons_dir': [opt_str, ''],
- 'last_sounds_dir': [opt_str, ''],
- 'tabs_position': [opt_str, 'top'],
- 'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
- 'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
- 'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
- 'esession_modp': [opt_str, '15,16,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
- 'chat_avatar_width': [opt_int, 52],
- 'chat_avatar_height': [opt_int, 52],
- 'roster_avatar_width': [opt_int, 32],
- 'roster_avatar_height': [opt_int, 32],
- 'tooltip_avatar_width': [opt_int, 125],
- 'tooltip_avatar_height': [opt_int, 125],
- 'tooltip_status_online_color': [opt_color, '#73D216'],
- 'tooltip_status_free_for_chat_color': [opt_color, '#3465A4'],
- 'tooltip_status_away_color': [opt_color, '#EDD400'],
- 'tooltip_status_busy_color': [opt_color, '#F57900'],
- 'tooltip_status_na_color': [opt_color, '#CC0000'],
- 'tooltip_status_offline_color': [opt_color, '#555753'],
- 'tooltip_affiliation_none_color': [opt_color, '#555753'],
- 'tooltip_affiliation_member_color': [opt_color, '#73D216'],
- 'tooltip_affiliation_administrator_color': [opt_color, '#F57900'],
- 'tooltip_affiliation_owner_color': [opt_color, '#CC0000'],
- 'tooltip_account_name_color': [opt_color, '#888A85'],
- 'tooltip_idle_color': [opt_color, '#888A85'],
- 'vcard_avatar_width': [opt_int, 200],
- 'vcard_avatar_height': [opt_int, 200],
- 'notification_preview_message': [opt_bool, True, _('Preview new messages in notification popup?')],
- 'notification_position_x': [opt_int, -1],
- 'notification_position_y': [opt_int, -1],
- 'notification_avatar_width': [opt_int, 48],
- 'notification_avatar_height': [opt_int, 48],
- 'muc_highlight_words': [opt_str, '', _('A semicolon-separated list of words that will be highlighted in group chats.')],
- 'quit_on_roster_x_button': [opt_bool, False, _('If True, quits Gajim when X button of Window Manager is clicked. This setting is taken into account only if notification icon is used.')],
- 'show_unread_tab_icon': [opt_bool, False, _('If True, Gajim will display an icon on each tab containing unread messages. Depending on the theme, this icon may be animated.')],
- 'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
- 'show_avatars_in_roster': [opt_bool, True, '', True],
- 'show_mood_in_roster': [opt_bool, True, '', True],
- 'show_activity_in_roster': [opt_bool, True, '', True],
- 'show_tunes_in_roster': [opt_bool, True, '', True],
- 'show_location_in_roster': [opt_bool, True, '', True],
- 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True],
- 'ask_avatars_on_startup': [opt_bool, True, _('If True, Gajim will ask for avatar each contact that did not have an avatar last time or has one cached that is too old.')],
- 'print_status_in_chats': [opt_bool, False, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
- 'print_status_in_muc': [opt_str, 'none', _('Can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')],
- 'log_contact_status_changes': [opt_bool, False],
- 'log_xhtml_messages': [opt_bool, False, _('Log XHTML messages instead of plain text messages.')],
- 'just_connected_bg_color': [opt_str, '#adc3c6', _('Background color of contacts when they just signed in.')],
- 'just_disconnected_bg_color': [opt_str, '#ab6161', _('Background color of contacts when they just signed out.')],
- 'restored_messages_color': [opt_color, '#555753'],
- 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')],
- 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')],
- 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')],
- 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')],
- 'notification_timeout': [opt_int, 5],
- 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
- 'one_message_window': [opt_str, 'always',
-#always, never, peracct, pertype should not be translated
- _('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'always_with_roster\' - Like \'always\' but the messages are in a single window along with the roster.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g. chats vs. groupchats) is sent to a specific window.')],
- 'show_roster_on_startup':[opt_str, 'always', _('Show roster on startup.\n\'always\' - Always show roster.\n\'never\' - Never show roster.\n\'last_state\' - Restore the last state roster.')],
- 'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
- 'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
- 'compact_view': [opt_bool, False, _('Hides the buttons in chat windows.')],
- 'hide_groupchat_banner': [opt_bool, False, _('Hides the banner in a group chat window')],
- 'hide_chat_banner': [opt_bool, False, _('Hides the banner in two persons chat window')],
- 'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
- 'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
- 'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
- 'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
- 'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
- 'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
- 'confirm_block': [ opt_str, '', _('Should we show the confirm block contact dialog or not? Empty string means we never show the dialog.')],
- 'confirm_custom_status': [ opt_str, '', _('Should we show the confirm custom status dialog or not? Empty string means we never show the dialog.')],
- 'enable_negative_priority': [ opt_bool, False, _('If True, you will be able to set a negative priority to your account in account modification window. BE CAREFUL, when you are logged in with a negative priority, you will NOT receive any message from your server.')],
- 'show_contacts_number': [opt_bool, True, _('If True, Gajim will show number of online and total contacts in account and group rows.')],
- 'treat_incoming_messages': [ opt_str, '', _('Can be empty, \'chat\' or \'normal\'. If not empty, treat all incoming messages as if they were of this type')],
- 'scroll_roster_to_last_message': [opt_bool, True, _('If True, Gajim will scroll and select the contact who sent you the last message, if chat window is not already opened.')],
- 'change_status_window_timeout': [opt_int, 15, _('Time of inactivity needed before the change status window closes down.')],
- 'max_conversation_lines': [opt_int, 500, _('Maximum number of lines that are printed in conversations. Oldest lines are cleared.')],
- 'attach_notifications_to_systray': [opt_bool, False, _('If True, notification windows from notification-daemon will be attached to notification icon.')],
- 'check_idle_every_foo_seconds': [opt_int, 2, _('Choose interval between 2 checks of idleness.')],
- 'uri_schemes': [opt_str, 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal:// aesgcm://', _('Valid uri schemes. Only schemes in this list will be accepted as "real" uri. (mailto and xmpp are handled separately)'), True],
- 'ask_offline_status_on_connection': [ opt_bool, False, _('Request offline status messages from all contacts upon connecting. WARNING: This causes a lot of requests to be sent!') ],
- 'shell_like_completion': [ opt_bool, False, _('If True, completion in groupchats will be like a shell auto-completion')],
- 'show_self_contact': [opt_str, 'when_other_resource', _('When is self contact row displayed. Can be "always", "when_other_resource" or "never"'), True],
- 'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
- 'audio_output_device': [opt_str, 'autoaudiosink'],
- 'video_input_device': [opt_str, 'autovideosrc'],
- 'video_output_device': [opt_str, 'autovideosink'],
- 'video_framerate': [opt_str, '', _('Optionally fix jingle output video framerate. Example: 10/1 or 25/2')],
- 'video_size': [opt_str, '', _('Optionally resize jingle output video. Example: 320x240')],
- 'video_see_self': [opt_bool, True, _('If True, You will also see your webcam')],
- 'audio_input_volume': [opt_int, 50],
- 'audio_output_volume': [opt_int, 50],
- 'use_stun_server': [opt_bool, False, _('If True, Gajim will try to use a STUN server when using Jingle. The one in "stun_server" option, or the one given by the XMPP server.')],
- 'stun_server': [opt_str, '', _('STUN server to use when using Jingle')],
- 'show_affiliation_in_groupchat': [opt_bool, True, _('If True, Gajim will show affiliation of groupchat occupants by adding a colored square to the status icon')],
- 'global_proxy': [opt_str, '', _('Proxy used for all outgoing connections if the account does not have a specific proxy configured')],
- 'ignore_incoming_attention': [opt_bool, False, _('If True, Gajim will ignore incoming attention requestd ("wizz").')],
- 'remember_opened_chat_controls': [ opt_bool, True, _('If enabled, Gajim will reopen chat windows that were opened last time Gajim was closed.')],
- 'positive_184_ack': [ opt_bool, False, _('If enabled, Gajim will show an icon to show that sent message has been received by your contact')],
- 'show_avatar_in_tabs': [ opt_bool, False, _('Show a mini avatar in chat window tabs and in window icon')],
- 'use_keyring': [opt_bool, True, _('If True, Gajim will use the Systems Keyring to store account passwords.')],
- 'pgp_encoding': [ opt_str, '', _('Sets the encoding used by python-gnupg'), True],
- 'remote_commands': [opt_bool, False, _('If True, Gajim will execute XEP-0146 Commands.')],
- }, {})
-
- __options_per_key = {
- 'accounts': ({
- 'name': [ opt_str, '', '', True ],
- 'hostname': [ opt_str, '', '', True ],
- 'anonymous_auth': [ opt_bool, False ],
- 'client_cert': [ opt_str, '', '', True ],
- 'client_cert_encrypted': [ opt_bool, False, '', False ],
- 'savepass': [ opt_bool, False ],
- 'password': [ opt_str, '' ],
- 'resource': [ opt_str, 'gajim.$rand', '', True ],
- 'priority': [ opt_int, 5, '', True ],
- 'adjust_priority_with_status': [ opt_bool, True, _('Priority will change automatically according to your status. Priorities are defined in autopriority_* options.') ],
- 'autopriority_online': [ opt_int, 50],
- 'autopriority_chat': [ opt_int, 50],
- 'autopriority_away': [ opt_int, 40],
- 'autopriority_xa': [ opt_int, 30],
- 'autopriority_dnd': [ opt_int, 20],
- 'autopriority_invisible': [ opt_int, 10],
- 'autoconnect': [ opt_bool, False, '', True ],
- 'autoconnect_as': [ opt_str, 'online', _('Status used to autoconnect as. Can be online, chat, away, xa, dnd, invisible. NOTE: this option is used only if restore_last_status is disabled'), True ],
- 'restore_last_status': [ opt_bool, False, _('If enabled, restore the last status that was used.') ],
- 'autoreconnect': [ opt_bool, True ],
- 'autoauth': [ opt_bool, False, _('If True, Contacts requesting authorization will be automatically accepted.')],
- 'active': [ opt_bool, True, _('If False, this account will be disabled and will not appear in roster window.'), True],
- 'proxy': [ opt_str, '', '', True ],
- 'keyid': [ opt_str, '', '', True ],
- 'gpg_sign_presence': [ opt_bool, True, _('If disabled, don\'t sign presences with GPG key, even if GPG is configured.') ],
- 'keyname': [ opt_str, '', '', True ],
- 'enable_esessions': [opt_bool, True, _('Enable ESessions encryption for this account.'), True],
- 'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session when possible?')],
- #keep tls, ssl and plain lowercase
- 'connection_types': [ opt_str, 'tls', _('Ordered list (space separated) of connection type to try. Can contain tls, ssl or plain')],
- 'tls_version': [ opt_str, '1.0', '' ],
- 'cipher_list': [ opt_str, 'HIGH:!aNULL:RC4-SHA', '' ],
- 'authentication_mechanisms': [ opt_str, '', _('List (space separated) of authentication mechanisms to try. Can contain ANONYMOUS, EXTERNAL, GSSAPI, SCRAM-SHA-1-PLUS, SCRAM-SHA-1, DIGEST-MD5, PLAIN, X-MESSENGER-OAUTH2 or XEP-0078') ],
- 'action_when_plaintext_connection': [ opt_str, 'warn', _('Show a warning dialog before sending password on an plaintext connection. Can be \'warn\', \'connect\', \'disconnect\'') ],
- 'warn_when_insecure_ssl_connection': [ opt_bool, True, _('Show a warning dialog before using standard SSL library.') ],
- 'warn_when_insecure_password': [ opt_bool, True, _('Show a warning dialog before sending PLAIN password over a plain connection.') ],
- 'ssl_fingerprint_sha1': [ opt_str, '', '', True ],
- 'ssl_fingerprint_sha256': [ opt_str, '', '', True ],
- 'ignore_ssl_errors': [ opt_str, '', _('Space separated list of ssl errors to ignore.') ],
- 'use_srv': [ opt_bool, True, '', True ],
- 'use_custom_host': [ opt_bool, False, '', True ],
- 'custom_port': [ opt_int, 5222, '', True ],
- 'custom_host': [ opt_str, '', '', True ],
- 'sync_with_global_status': [ opt_bool, False, ],
- 'no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you do not want to store logs. You can also add account name to log nothing for this account.')],
- 'sync_logs_with_server': [ opt_bool, True, _('On startup, Gajim will download logs stored on server, provided it supports XEP-0136 or XEP-0313')],
- 'allow_no_log_for': [ opt_str, '', _('Space separated list of JIDs for which you accept to not log conversations if he does not want to.')],
- 'non_minimized_gc': [ opt_str, '' ],
- 'attached_gpg_keys': [ opt_str, '' ],
- 'keep_alives_enabled': [ opt_bool, True, _('Whitespace sent after inactivity')],
- 'ping_alives_enabled': [ opt_bool, True, _('XMPP ping sent after inactivity')],
- # send keepalive every N seconds of inactivity
- 'keep_alive_every_foo_secs': [ opt_int, 55 ],
- 'ping_alive_every_foo_secs': [ opt_int, 120 ],
- 'time_for_ping_alive_answer': [ opt_int, 60, _('How many seconds to wait for the answer of ping alive packet before we try to reconnect?') ],
- # try for 1 minutes before giving up (aka. timeout after those seconds)
- 'try_connecting_for_foo_secs': [ opt_int, 60 ],
- 'http_auth': [opt_str, 'ask'], # yes, no, ask
- 'dont_ack_subscription': [opt_bool, False, _('Jabberd2 workaround')],
- # proxy65 for FT
- 'file_transfer_proxies': [opt_str, ''],
- 'use_ft_proxies': [opt_bool, False, _('If checked, Gajim will use your IP and proxies defined in file_transfer_proxies option for file transfer.'), True],
- 'test_ft_proxies_on_startup': [opt_bool, False, _('If True, Gajim will test file transfer proxies on startup to be sure it works. Openfire\'s proxies are known to fail this test even if they work.')],
- 'msgwin-x-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
- 'msgwin-width': [opt_int, 480],
- 'msgwin-height': [opt_int, 440],
- 'is_zeroconf': [opt_bool, False],
- 'last_status': [opt_str, 'online'],
- 'last_status_msg': [opt_str, ''],
- 'zeroconf_first_name': [ opt_str, '', '', True ],
- 'zeroconf_last_name': [ opt_str, '', '', True ],
- 'zeroconf_jabber_id': [ opt_str, '', '', True ],
- 'zeroconf_email': [ opt_str, '', '', True ],
- 'use_env_http_proxy': [opt_bool, False],
- 'answer_receipts': [opt_bool, True, _('Answer to receipt requests')],
- 'request_receipt': [opt_bool, True, _('Sent receipt requests')],
- 'publish_tune': [opt_bool, False],
- 'publish_location': [opt_bool, False],
- 'subscribe_mood': [opt_bool, True],
- 'subscribe_activity': [opt_bool, True],
- 'subscribe_tune': [opt_bool, True],
- 'subscribe_nick': [opt_bool, True],
- 'subscribe_location': [opt_bool, True],
- 'ignore_unknown_contacts': [ opt_bool, False ],
- 'send_os_info': [ opt_bool, True, _("Allow Gajim to send information about the operating system you are running.") ],
- 'send_time_info': [ opt_bool, True, _("Allow Gajim to send your local time.") ],
- 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
- 'send_idle_time': [ opt_bool, True ],
- 'roster_version': [opt_str, ''],
- 'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
- 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
- 'enable_message_carbons': [ opt_bool, True, _('If enabled and if server supports this feature, Gajim will receive messages sent and received by other resources.')],
- 'ft_send_local_ips': [ opt_bool, True, _('If enabled, Gajim will send your local IPs so your contact can connect to your machine to transfer files.')],
- 'oauth2_refresh_token': [ opt_str, '', _('Latest token for OAuth 2.0 authentication.')],
- 'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for OAuth 2.0 authentication.')],
- 'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')],
- 'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which we want to re-open a chat window on next startup.')],
- 'last_mam_id': [opt_str, '', _('Last MAM id we are syncronized with')],
- }, {}),
- 'statusmsg': ({
- 'message': [ opt_str, '' ],
- 'activity': [ opt_str, '' ],
- 'subactivity': [ opt_str, '' ],
- 'activity_text': [ opt_str, '' ],
- 'mood': [ opt_str, '' ],
- 'mood_text': [ opt_str, '' ],
- }, {}),
- 'defaultstatusmsg': ({
- 'enabled': [ opt_bool, False ],
- 'message': [ opt_str, '' ],
- }, {}),
- 'soundevents': ({
- 'enabled': [ opt_bool, True ],
- 'path': [ opt_str, '' ],
- }, {}),
- 'proxies': ({
- 'type': [ opt_str, 'http' ],
- 'host': [ opt_str, '' ],
- 'port': [ opt_int, 3128 ],
- 'useauth': [ opt_bool, False ],
- 'user': [ opt_str, '' ],
- 'pass': [ opt_str, '' ],
- 'bosh_uri': [ opt_str, '' ],
- 'bosh_useproxy': [ opt_bool, False ],
- 'bosh_wait': [ opt_int, 30 ],
- 'bosh_hold': [ opt_int, 2 ],
- 'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
- 'bosh_http_pipelining': [ opt_bool, False ],
- 'bosh_wait_for_restart_response': [ opt_bool, False ],
- }, {}),
- 'themes': ({
- 'accounttextcolor': [ opt_color, 'black', '', True ],
- 'accountbgcolor': [ opt_color, 'white', '', True ],
- 'accountfont': [ opt_str, '', '', True ],
- 'accountfontattrs': [ opt_str, 'B', '', True ],
- 'grouptextcolor': [ opt_color, 'black', '', True ],
- 'groupbgcolor': [ opt_color, 'white', '', True ],
- 'groupfont': [ opt_str, '', '', True ],
- 'groupfontattrs': [ opt_str, 'I', '', True ],
- 'contacttextcolor': [ opt_color, 'black', '', True ],
- 'contactbgcolor': [ opt_color, 'white', '', True ],
- 'contactfont': [ opt_str, '', '', True ],
- 'contactfontattrs': [ opt_str, '', '', True ],
- 'bannertextcolor': [ opt_color, 'black', '', True ],
- 'bannerbgcolor': [ opt_color, '', '', True ],
- 'bannerfont': [ opt_str, '', '', True ],
- 'bannerfontattrs': [ opt_str, 'B', '', True ],
- 'msgcorrectingcolor': [opt_color, '#eee8aa'],
-
- # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
- 'state_inactive_color': [ opt_color, 'grey62' ],
- 'state_composing_color': [ opt_color, 'green4' ],
- 'state_paused_color': [ opt_color, 'mediumblue' ],
- 'state_gone_color': [ opt_color, 'grey' ],
-
- # MUC chat states
- 'state_muc_msg_color': [ opt_color, 'mediumblue' ],
- 'state_muc_directed_msg_color': [ opt_color, 'red2' ],
- }, {}),
- 'contacts': ({
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- }, {}),
- 'encryption': ({'encryption': [ opt_str, '', _('The currently active encryption for that contact')],
- },{}),
- 'rooms': ({
- 'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
- 'muc_restore_lines': [opt_int, -2, _('How many lines to request from server when entering a groupchat. -1 means no limit, -2 means global value')],
- 'muc_restore_timeout': [opt_int, -2, _('Minutes of backlog to request when entering a groupchat. -1 means no limit, -2 means global value')],
- }, {}),
- 'plugins': ({
- 'active': [opt_bool, False, _('State whether plugins should be activated on startup (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')],
- },{}),
- }
-
- statusmsg_default = {
- _('Sleeping'): [ 'ZZZZzzzzzZZZZZ', 'inactive', 'sleeping', '', 'sleepy', '' ],
- _('Back soon'): [ _('Back in some minutes.'), '', '', '', '', '' ],
- _('Eating'): [ _("I'm eating, so leave me a message."), 'eating', 'other', '', '', '' ],
- _('Movie'): [ _("I'm watching a movie."), 'relaxing', 'watching_a_movie', '', '', '' ],
- _('Working'): [ _("I'm working."), 'working', 'other', '', '', '' ],
- _('Phone'): [ _("I'm on the phone."), 'talking', 'on_the_phone', '', '', '' ],
- _('Out'): [ _("I'm out enjoying life."), 'relaxing', 'going_out', '', '', '' ],
- '_last_online': ['', '', '', '', '', ''],
- '_last_chat': ['', '', '', '', '', ''],
- '_last_away': ['', '', '', '', '', ''],
- '_last_xa': ['', '', '', '', '', ''],
- '_last_dnd': ['', '', '', '', '', ''],
- '_last_invisible': ['', '', '', '', '', ''],
- '_last_offline': ['', '', '', '', '', ''],
- }
-
- defaultstatusmsg_default = {
- 'online': [ False, _("I'm available.") ],
- 'chat': [ False, _("I'm free for chat.") ],
- 'away': [ False, _('Be right back.') ],
- 'xa': [ False, _("I'm not available.") ],
- 'dnd': [ False, _('Do not disturb.') ],
- 'invisible': [ False, _('Bye!') ],
- 'offline': [ False, _('Bye!') ],
- }
-
- soundevents_default = {
- 'attention_received': [True, 'attention.wav'],
- 'first_message_received': [ True, 'message1.wav' ],
- 'next_message_received_focused': [ True, 'message2.wav' ],
- 'next_message_received_unfocused': [ True, 'message2.wav' ],
- 'contact_connected': [ False, 'connected.wav' ],
- 'contact_disconnected': [ False, 'disconnected.wav' ],
- 'message_sent': [ False, 'sent.wav' ],
- 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
- 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
- 'gmail_received': [ False, 'message1.wav' ],
- }
-
- themes_default = {
- # sorted alphanum
- _('default'): [ '', '', '', 'B', '', '', '', 'I', '', '', '', '', '', '',
- '', 'B' ],
-
- _('green'): [ '', '#94aa8c', '', 'B', '#0000ff', '#eff3e7',
- '', 'I', '#000000', '', '', '', '',
- '#94aa8c', '', 'B' ],
-
- _('grocery'): [ '', '#6bbe18', '', 'B', '#12125a', '#ceefad',
- '', 'I', '#000000', '#efb26b', '', '', '',
- '#108abd', '', 'B' ],
-
- _('human'): [ '', '#996442', '', 'B', '#ab5920', '#e3ca94',
- '', 'I', '#000000', '', '', '', '',
- '#996442', '', 'B' ],
-
- _('marine'): [ '', '#918caa', '', 'B', '', '#e9e7f3',
- '', 'I', '#000000', '', '', '', '',
- '#918caa', '', 'B' ],
-
- }
-
- proxies_default = {
- _('Tor'): ['socks5', 'localhost', 9050],
- }
-
- def foreach(self, cb, data=None):
- for opt in self.__options[1]:
- cb(data, opt, None, self.__options[1][opt])
- for opt in self.__options_per_key:
- cb(data, opt, None, None)
- dict_ = self.__options_per_key[opt][1]
- for opt2 in dict_.keys():
- cb(data, opt2, [opt], None)
- for opt3 in dict_[opt2]:
- cb(data, opt3, [opt, opt2], dict_[opt2][opt3])
-
- def get_children(self, node=None):
- """
- Tree-like interface
- """
- if node is None:
- for child, option in self.__options[1].items():
- yield (child, ), option
- for grandparent in self.__options_per_key:
- yield (grandparent, ), None
- elif len(node) == 1:
- grandparent, = node
- for parent in self.__options_per_key[grandparent][1]:
- yield (grandparent, parent), None
- elif len(node) == 2:
- grandparent, parent = node
- children = self.__options_per_key[grandparent][1][parent]
- for child, option in children.items():
- yield (grandparent, parent, child), option
- else:
- raise ValueError('Invalid node')
-
- def is_valid_int(self, val):
- try:
- ival = int(val)
- except Exception:
- return None
- return ival
-
- def is_valid_bool(self, val):
- if val == 'True':
- return True
- if val == 'False':
- return False
- ival = self.is_valid_int(val)
- if ival:
- return True
- if ival is None:
- return None
- return False
-
- def is_valid_string(self, val):
- return val
-
- def is_valid(self, type_, val):
- if not type_:
- return None
- if type_[0] == 'boolean':
- return self.is_valid_bool(val)
- elif type_[0] == 'integer':
- return self.is_valid_int(val)
- elif type_[0] == 'string':
- return self.is_valid_string(val)
- else:
- if re.match(type_[1], val):
- return val
- else:
- return None
-
- def set(self, optname, value):
- if optname not in self.__options[1]:
- return
- value = self.is_valid(self.__options[0][optname][Option.TYPE], value)
- if value is None:
- return
-
- self.__options[1][optname] = value
- self._timeout_save()
-
- def get(self, optname=None):
- if not optname:
- return list(self.__options[1].keys())
- if optname not in self.__options[1]:
- return None
- return self.__options[1][optname]
-
- def get_default(self, optname):
- if optname not in self.__options[0]:
- return None
- return self.__options[0][optname][Option.VAL]
-
- def get_type(self, optname):
- if optname not in self.__options[0]:
- return None
- return self.__options[0][optname][Option.TYPE][0]
-
- def get_desc(self, optname):
- if optname not in self.__options[0]:
- return None
- if len(self.__options[0][optname]) > Option.DESC:
- return self.__options[0][optname][Option.DESC]
-
- def get_restart(self, optname):
- if optname not in self.__options[0]:
- return None
- if len(self.__options[0][optname]) > Option.RESTART:
- return self.__options[0][optname][Option.RESTART]
-
- def add_per(self, typename, name): # per_group_of_option
- if typename not in self.__options_per_key:
- return
-
- opt = self.__options_per_key[typename]
- if name in opt[1]:
- # we already have added group name before
- return 'you already have added %s before' % name
- opt[1][name] = {}
- for o in opt[0]:
- opt[1][name][o] = opt[0][o][Option.VAL]
- self._timeout_save()
-
- def del_per(self, typename, name, subname = None): # per_group_of_option
- if typename not in self.__options_per_key:
- return
-
- opt = self.__options_per_key[typename]
- if subname is None:
- del opt[1][name]
- # if subname is specified, delete the item in the group.
- elif subname in opt[1][name]:
- del opt[1][name][subname]
- self._timeout_save()
-
- def set_per(self, optname, key, subname, value): # per_group_of_option
- if optname not in self.__options_per_key:
- return
- if not key:
- return
- dict_ = self.__options_per_key[optname][1]
- if key not in dict_:
- self.add_per(optname, key)
- obj = dict_[key]
- if subname not in obj:
- return
- typ = self.__options_per_key[optname][0][subname][Option.TYPE]
- value = self.is_valid(typ, value)
- if value is None:
- return
- obj[subname] = value
- self._timeout_save()
-
- def get_per(self, optname, key=None, subname=None): # per_group_of_option
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][1]
- if not key:
- return list(dict_.keys())
- if key not in dict_:
- if subname in self.__options_per_key[optname][0]:
- return self.__options_per_key[optname][0][subname][1]
- return None
- obj = dict_[key]
- if not subname:
- return obj
- if subname not in obj:
- return None
- return obj[subname]
-
- def get_default_per(self, optname, subname):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if subname not in dict_:
- return None
- return dict_[subname][Option.VAL]
-
- def get_type_per(self, optname, subname):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if subname not in dict_:
- return None
- return dict_[subname][Option.TYPE][0]
-
- def get_desc_per(self, optname, key=None, subname=None):
- if optname not in self.__options_per_key:
- return None
- dict_ = self.__options_per_key[optname][0]
- if not key:
- return None
- if key not in dict_:
- return None
- obj = dict_[key]
- if not subname:
- return None
- if subname not in obj:
- return None
- if len(obj[subname]) > Option.DESC:
- return obj[subname][Option.DESC]
- return None
-
- def get_restart_per(self, optname, key=None, subname=None):
- if optname not in self.__options_per_key:
- return False
- dict_ = self.__options_per_key[optname][0]
- if not key:
- return False
- if key not in dict_:
- return False
- obj = dict_[key]
- if not subname:
- return False
- if subname not in obj:
- return False
- if len(obj[subname]) > Option.RESTART:
- return obj[subname][Option.RESTART]
- return False
-
- def should_log(self, account, jid):
- """
- Should conversations between a local account and a remote jid be logged?
- """
- no_log_for = self.get_per('accounts', account, 'no_log_for')
-
- if not no_log_for:
- no_log_for = ''
-
- no_log_for = no_log_for.split()
-
- return (account not in no_log_for) and (jid not in no_log_for)
-
- def _init_options(self):
- for opt in self.__options[0]:
- self.__options[1][opt] = self.__options[0][opt][Option.VAL]
-
- def _really_save(self):
- from common import gajim
- if gajim.interface:
- gajim.interface.save_config()
- self.save_timeout_id = None
- return False
-
- def _timeout_save(self):
- if self.save_timeout_id:
- return
- self.save_timeout_id = GLib.timeout_add(1000, self._really_save)
-
- def __init__(self):
- #init default values
- self._init_options()
- self.save_timeout_id = None
- for event in self.soundevents_default:
- default = self.soundevents_default[event]
- self.add_per('soundevents', event)
- self.set_per('soundevents', event, 'enabled', default[0])
- self.set_per('soundevents', event, 'path', default[1])
-
- for status in self.defaultstatusmsg_default:
- default = self.defaultstatusmsg_default[status]
- self.add_per('defaultstatusmsg', status)
- self.set_per('defaultstatusmsg', status, 'enabled', default[0])
- self.set_per('defaultstatusmsg', status, 'message', default[1])
diff --git a/src/common/configpaths.py b/src/common/configpaths.py
deleted file mode 100644
index 1edf3bbbf..000000000
--- a/src/common/configpaths.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/configpaths.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import sys
-import tempfile
-from common import defs
-from enum import Enum, unique
-
-@unique
-class Type(Enum):
- CONFIG = 0
- CACHE = 1
- DATA = 2
-
-# Note on path and filename encodings:
-#
-# In general it is very difficult to do this correctly.
-# We may pull information from environment variables, and what encoding that is
-# in is anyone's guess. Any information we request directly from the file
-# system will be in filesystemencoding, and (parts of) paths that we write in
-# this source code will be in whatever encoding the source is in. (I hereby
-# declare this file to be UTF-8 encoded.)
-#
-# To make things more complicated, modern Windows filesystems use UTF-16, but
-# the API tends to hide this from us.
-#
-# I tried to minimize problems by passing Unicode strings to OS functions as
-# much as possible. Hopefully this makes the function return an Unicode string
-# as well. If not, we get an 8-bit string in filesystemencoding, which we can
-# happily pass to functions that operate on files and directories, so we can
-# just leave it as is. Since these paths are meant to be internal to Gajim and
-# not displayed to the user, Unicode is not really necessary here.
-
-
-def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
-
-
-def get(key):
- return gajimpaths[key]
-
-
-class ConfigPaths:
- def __init__(self):
- # {'name': (type, path), } type can be Type.CONFIG, Type.CACHE, Type.DATA
- # or None
- self.paths = {}
-
- if os.name == 'nt':
- try:
- # Documents and Settings\[User Name]\Application Data\Gajim
-
- # How are we supposed to know what encoding the environment
- # variable 'appdata' is in? Assuming it to be in filesystem
- # encoding.
- self.config_root = self.cache_root = self.data_root = \
- os.path.join(os.environ['appdata'], 'Gajim')
- except KeyError:
- # win9x, in cwd
- self.config_root = self.cache_root = self.data_root = '.'
- else: # Unices
- # Pass in an Unicode string, and hopefully get one back.
- expand = os.path.expanduser
- base = os.getenv('XDG_CONFIG_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.config')
- self.config_root = os.path.join(base, 'gajim')
- base = os.getenv('XDG_CACHE_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.cache')
- self.cache_root = os.path.join(base, 'gajim')
- base = os.getenv('XDG_DATA_HOME')
- if base is None or base[0] != '/':
- base = expand('~/.local/share')
- self.data_root = os.path.join(base, 'gajim')
-
- basedir = os.environ.get('GAJIM_BASEDIR', defs.basedir)
- self.add('DATA', None, os.path.join(basedir, 'data'))
- self.add('GUI', None, os.path.join(basedir, 'data', 'gui'))
- self.add('ICONS', None, os.path.join(basedir, 'icons'))
- self.add('HOME', None, os.path.expanduser('~'))
- self.add('PLUGINS_BASE', None, os.path.join(basedir, 'plugins'))
-
- def add(self, name, type_, path):
- self.paths[name] = (type_, path)
-
- def __getitem__(self, key):
- type_, path = self.paths[key]
- if type_ == Type.CONFIG:
- return os.path.join(self.config_root, path)
- elif type_ == Type.CACHE:
- return os.path.join(self.cache_root, path)
- elif type_ == Type.DATA:
- return os.path.join(self.data_root, path)
- return path
-
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
-
- def items(self):
- for key in self.paths.keys():
- yield (key, self[key])
-
- def init(self, root=None, profile='', profile_separation=False):
- if root is not None:
- self.config_root = self.cache_root = self.data_root = root
-
- self.init_profile(profile)
-
- if len(profile) > 0 and profile_separation:
- profile = u'.' + profile
- else:
- profile = ''
-
- d = {'LOG_DB': 'logs.db', 'MY_CACERTS': 'cacerts.pem',
- 'MY_EMOTS': 'emoticons', 'MY_ICONSETS': 'iconsets',
- 'MY_MOOD_ICONSETS': 'moods', 'MY_ACTIVITY_ICONSETS': 'activities',
- 'PLUGINS_USER': 'plugins',
- 'RNG_SEED': 'rng_seed'}
- for name in d:
- d[name] += profile
- self.add(name, Type.DATA, windowsify(d[name]))
- if len(profile):
- self.add('MY_DATA', Type.DATA, 'data.dir')
- else:
- self.add('MY_DATA', Type.DATA, '')
-
- d = {'CACHE_DB': 'cache.db', 'VCARD': 'vcards',
- 'AVATAR': 'avatars'}
- for name in d:
- d[name] += profile
- self.add(name, Type.CACHE, windowsify(d[name]))
- if len(profile):
- self.add('MY_CACHE', Type.CACHE, 'cache.dir')
- else:
- self.add('MY_CACHE', Type.CACHE, '')
-
- if len(profile):
- self.add('MY_CONFIG', Type.CONFIG, 'config.dir')
- else:
- self.add('MY_CONFIG', Type.CONFIG, '')
-
- try:
- self.add('TMP', None, tempfile.gettempdir())
- except IOError as e:
- print('Error opening tmp folder: %s\nUsing %s' % (str(e),
- os.path.expanduser('~')), file=sys.stderr)
- self.add('TMP', None, os.path.expanduser('~'))
-
- def init_profile(self, profile):
- conffile = windowsify('config')
- secretsfile = windowsify('secrets')
- pluginsconfdir = windowsify('pluginsconfig')
- certsdir = windowsify(u'certs')
- localcertsdir = windowsify(u'localcerts')
-
- if len(profile) > 0:
- conffile += '.' + profile
- secretsfile += '.' + profile
- pluginsconfdir += '.' + profile
- certsdir += u'.' + profile
- localcertsdir += u'.' + profile
-
- self.add('SECRETS_FILE', Type.DATA, secretsfile)
- self.add('MY_PEER_CERTS', Type.DATA, certsdir)
- self.add('CONFIG_FILE', Type.CONFIG, conffile)
- self.add('PLUGINS_CONFIG_DIR', Type.CONFIG, pluginsconfdir)
- self.add('MY_CERT', Type.CONFIG, localcertsdir)
-
-gajimpaths = ConfigPaths()
diff --git a/src/common/connection.py b/src/common/connection.py
deleted file mode 100644
index d77cfa7cf..000000000
--- a/src/common/connection.py
+++ /dev/null
@@ -1,2982 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import random
-import socket
-import operator
-import string
-import time
-import locale
-import hmac
-import hashlib
-import json
-
-try:
- randomsource = random.SystemRandom()
-except Exception:
- randomsource = random.Random()
- randomsource.seed()
-
-import signal
-if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
-import nbxmpp
-from common import helpers
-from common import gajim
-from common import gpg
-from common import passwords
-from common import exceptions
-from common import check_X509
-from common.connection_handlers import *
-
-from gtkgui_helpers import get_action
-
-if gajim.HAVE_PYOPENSSL:
- import OpenSSL.crypto
-
-from nbxmpp import Smacks
-from string import Template
-import logging
-log = logging.getLogger('gajim.c.connection')
-
-ssl_error = {
- 2: _("Unable to get issuer certificate"),
- 3: _("Unable to get certificate CRL"),
- 4: _("Unable to decrypt certificate's signature"),
- 5: _("Unable to decrypt CRL's signature"),
- 6: _("Unable to decode issuer public key"),
- 7: _("Certificate signature failure"),
- 8: _("CRL signature failure"),
- 9: _("Certificate is not yet valid"),
- 10: _("Certificate has expired"),
- 11: _("CRL is not yet valid"),
- 12: _("CRL has expired"),
- 13: _("Format error in certificate's notBefore field"),
- 14: _("Format error in certificate's notAfter field"),
- 15: _("Format error in CRL's lastUpdate field"),
- 16: _("Format error in CRL's nextUpdate field"),
- 17: _("Out of memory"),
- 18: _("Self signed certificate"),
- 19: _("Self signed certificate in certificate chain"),
- 20: _("Unable to get local issuer certificate"),
- 21: _("Unable to verify the first certificate"),
- 22: _("Certificate chain too long"),
- 23: _("Certificate revoked"),
- 24: _("Invalid CA certificate"),
- 25: _("Path length constraint exceeded"),
- 26: _("Unsupported certificate purpose"),
- 27: _("Certificate not trusted"),
- 28: _("Certificate rejected"),
- 29: _("Subject issuer mismatch"),
- 30: _("Authority and subject key identifier mismatch"),
- 31: _("Authority and issuer serial number mismatch"),
- 32: _("Key usage does not include certificate signing"),
- 50: _("Application verification failure")
- #100 is for internal usage: host not correct
-}
-
-class CommonConnection:
- """
- Common connection class, can be derivated for normal connection or zeroconf
- connection
- """
-
- def __init__(self, name):
- self.name = name
- # self.connected:
- # 0=>offline,
- # 1=>connection in progress,
- # 2=>online
- # 3=>free for chat
- # ...
- self.connected = 0
- self.connection = None # xmpppy ClientCommon instance
- self.on_purpose = False
- self.is_zeroconf = False
- self.password = ''
- self.server_resource = self._compute_resource()
- self.gpg = None
- self.USE_GPG = False
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- self.status = ''
- self.old_show = ''
- self.priority = gajim.get_priority(name, 'offline')
- self.time_to_reconnect = None
- self.bookmarks = []
-
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_all = False
-
- self.seclabel_supported = False
- self.seclabel_catalogues = {}
-
- self.pep_supported = False
- self.pep = {}
- # Do we continue connection when we get roster (send presence,get vcard..)
- self.continue_connect_info = None
-
- # Remember where we are in the register agent process
- self.agent_registrations = {}
- # To know the groupchat jid associated with a sranza ID. Useful to
- # request vcard or os info... to a real JID but act as if it comes from
- # the fake jid
- self.groupchat_jids = {} # {ID : groupchat_jid}
-
- self.privacy_rules_supported = False
- self.vcard_supported = False
- self.private_storage_supported = False
- self.archiving_namespace = None
- self.archiving_supported = False
- self.archiving_313_supported = False
- self.archiving_136_supported = False
- self.archive_pref_supported = False
- self.roster_supported = True
- self.blocking_supported = False
- self.addressing_supported = False
- self.carbons_enabled = False
-
- self.muc_jid = {} # jid of muc server for each transport type
- self._stun_servers = [] # STUN servers of our jabber server
-
- self.awaiting_cids = {} # Used for XEP-0231
-
- self.nested_group_delimiter = '::'
-
- self.get_config_values_or_default()
-
- def _compute_resource(self):
- resource = gajim.config.get_per('accounts', self.name, 'resource')
- # All valid resource substitution strings should be added to this hash.
- if resource:
- rand = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
- resource = Template(resource).safe_substitute({
- 'hostname': socket.gethostname(),
- 'rand': rand
- })
- return resource
-
- def dispatch(self, event, data):
- """
- Always passes account name as first param
- """
- gajim.ged.raise_event(event, self.name, data)
-
- def reconnect(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def quit(self, kill_core):
- if kill_core and gajim.account_is_connected(self.name):
- self.disconnect(on_purpose=True)
-
- def test_gpg_passphrase(self, password):
- """
- Returns 'ok', 'bad_pass' or 'expired'
- """
- if not self.gpg:
- return False
- self.gpg.passphrase = password
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- signed = self.gpg.sign('test', keyID)
- self.gpg.password = None
- if signed == 'KEYEXPIRED':
- return 'expired'
- elif signed == 'BAD_PASSPHRASE':
- return 'bad_pass'
- return 'ok'
-
- def get_signed_msg(self, msg, callback = None):
- """
- Returns the signed message if possible or an empty string if gpg is not
- used or None if waiting for passphrase
-
- callback is the function to call when user give the passphrase
- """
- signed = ''
- keyID = gajim.config.get_per('accounts', self.name, 'keyid')
- if keyID and self.USE_GPG:
- if self.gpg.passphrase is None and not self.gpg.use_agent:
- # We didn't set a passphrase
- return None
- signed = self.gpg.sign(msg, keyID)
- if signed == 'BAD_PASSPHRASE':
- self.USE_GPG = False
- signed = ''
- gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
- conn=self))
- return signed
-
- def _on_disconnected(self):
- """
- Called when a disconnect request has completed successfully
- """
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
-
- def get_status(self):
- return gajim.SHOW_LIST[self.connected]
-
- def check_jid(self, jid):
- """
- This function must be implemented by derivated classes. It has to return
- the valid jid, or raise a helpers.InvalidFormat exception
- """
- raise NotImplementedError
-
- def _prepare_message(self, obj):
-
- if not self.connection or self.connected < 2:
- return 1
-
- if isinstance(obj.jid, list):
- for jid in obj.jid:
- try:
- self.check_jid(jid)
- except helpers.InvalidFormat:
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invalid JID'),
- sec_txt=_('It is not possible to send a message '
- 'to %s, this JID is not valid.') % jid))
- return
- else:
- try:
- self.check_jid(obj.jid)
- except helpers.InvalidFormat:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Invalid JID'), sec_txt=_(
- 'It is not possible to send a message to %s, this JID is not '
- 'valid.') % obj.jid))
- return
-
- if obj.message and not obj.xhtml and gajim.config.get(
- 'rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- obj.xhtml = create_xhtml(obj.message)
- if not obj.message and obj.chatstate is None and obj.form_node is None:
- return
-
- self._build_message_stanza(obj)
-
- def _build_message_stanza(self, obj):
- if obj.jid == gajim.get_jid_from_account(self.name):
- fjid = obj.jid
- else:
- fjid = obj.get_full_jid()
-
- if obj.type_ == 'chat':
- msg_iq = nbxmpp.Message(body=obj.message, typ=obj.type_,
- xhtml=obj.xhtml)
- else:
- if obj.subject:
- msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
- subject=obj.subject, xhtml=obj.xhtml)
- else:
- msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
- xhtml=obj.xhtml)
-
- if obj.correct_id:
- msg_iq.setTag('replace', attrs={'id': obj.correct_id},
- namespace=nbxmpp.NS_CORRECT)
-
- if obj.msg_id:
- msg_iq.setID(obj.msg_id)
-
- if obj.form_node:
- msg_iq.addChild(node=obj.form_node)
- if obj.label:
- msg_iq.addChild(node=obj.label)
-
- # XEP-0172: user_nickname
- if obj.user_nick:
- msg_iq.setTag('nick', namespace=nbxmpp.NS_NICK).setData(
- obj.user_nick)
-
- # XEP-0203
- if obj.delayed:
- our_jid = gajim.get_jid_from_account(self.name) + '/' + \
- self.server_resource
- timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(obj.delayed))
- msg_iq.addChild('delay', namespace=nbxmpp.NS_DELAY2,
- attrs={'from': our_jid, 'stamp': timestamp})
-
- # XEP-0224
- if obj.attention:
- msg_iq.setTag('attention', namespace=nbxmpp.NS_ATTENTION)
-
- if isinstance(obj.jid, list):
- if self.addressing_supported:
- msg_iq.setTo(gajim.config.get_per('accounts', self.name, 'hostname'))
- addresses = msg_iq.addChild('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- for j in obj.jid:
- addresses.addChild('address', attrs = {'type': 'to',
- 'jid': j})
- else:
- iqs = []
- for j in obj.jid:
- iq = nbxmpp.Message(node=msg_iq)
- iq.setTo(j)
- iqs.append(iq)
- msg_iq = iqs
- else:
- msg_iq.setTo(fjid)
- r_ = obj.resource
- if not r_ and obj.jid != fjid: # Only if we're not in a pm
- r_ = gajim.get_resource_from_jid(fjid)
- if r_:
- contact = gajim.contacts.get_contact(self.name, obj.jid, r_)
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.name, obj.jid)
-
- # chatstates - if peer supports xep85, send chatstates
- # please note that the only valid tag inside a message containing a
- # <body> tag is the active event
- if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
- msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
- only_chatste = False
- if not obj.message:
- only_chatste = True
- if only_chatste and not obj.session.enable_encryption:
- msg_iq.setTag('no-store',
- namespace=nbxmpp.NS_MSG_HINTS)
-
- # XEP-0184
- if obj.jid != gajim.get_jid_from_account(self.name):
- request = gajim.config.get_per('accounts', self.name,
- 'request_receipt')
- if obj.message and request:
- msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
-
- if obj.forward_from:
- addresses = msg_iq.addChild('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- addresses.addChild('address', attrs = {'type': 'ofrom',
- 'jid': obj.forward_from})
-
- if obj.session:
- # XEP-0201
- obj.session.last_send = time.time()
- msg_iq.setThread(obj.session.thread_id)
-
- self._push_stanza_message_outgoing(obj, msg_iq)
-
- def _push_stanza_message_outgoing(self, obj, msg_iq):
- obj.conn = self
- if isinstance(msg_iq, list):
- for iq in msg_iq:
- obj.msg_iq = iq
- gajim.nec.push_incoming_event(
- StanzaMessageOutgoingEvent(None, **vars(obj)))
- else:
- obj.msg_iq = msg_iq
- gajim.nec.push_incoming_event(
- StanzaMessageOutgoingEvent(None, **vars(obj)))
-
- def log_message(self, obj, jid):
- if not obj.is_loggable:
- return
-
- if obj.forward_from or not obj.session or not obj.session.is_loggable():
- return
-
- if not gajim.config.should_log(self.name, jid):
- return
-
- if obj.xhtml and gajim.config.get('log_xhtml_messages'):
- message = '<body xmlns="%s">%s</body>' % (nbxmpp.NS_XHTML,
- obj.xhtml)
- else:
- message = obj.original_message or obj.message
- if not message:
- return
-
- if obj.type_ == 'chat':
- kind = 'chat_msg_sent'
- else:
- kind = 'single_msg_sent'
-
- gajim.logger.write(
- kind, jid, message, subject=obj.subject,
- additional_data=obj.additional_data)
-
- def ack_subscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def ack_unsubscribed(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def request_subscription(self, jid, msg='', name='', groups=None,
- auto_auth=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def refuse_authorization(self, jid):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe(self, jid, remove_auth = True):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def unsubscribe_agent(self, agent):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def update_contact(self, jid, name, groups):
- if self.connection and self.roster_supported:
- self.connection.getRoster().setItem(jid=jid, name=name, groups=groups)
-
- def update_contacts(self, contacts):
- """
- Update multiple roster items
- """
- if self.connection and self.roster_supported:
- self.connection.getRoster().setItemMulti(contacts)
-
- def new_account(self, name, config, sync=False):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def _on_new_account(self, con=None, con_type=None):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def account_changed(self, new_name):
- self.name = new_name
-
- def request_last_status_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_LAST)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.last_ids.append(id_)
- self.connection.send(iq)
-
- def request_os_info(self, jid, resource):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_settings(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def store_bookmarks(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def get_metacontacts(self):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def send_agent_status(self, agent, ptype):
- """
- To be implemented by derivated classes
- """
- raise NotImplementedError
-
- def gpg_passphrase(self, passphrase):
- if self.gpg:
- if self.gpg.use_agent:
- self.gpg.passphrase = None
- else:
- self.gpg.passphrase = passphrase
-
- def ask_gpg_keys(self, keyID=None):
- if self.gpg:
- if keyID:
- return self.gpg.get_key(keyID)
- return self.gpg.get_keys()
- return None
-
- def ask_gpg_secrete_keys(self):
- if self.gpg:
- return self.gpg.get_secret_keys()
- return None
-
- def load_roster_from_db(self):
- # Do nothing by default
- return
-
- def _event_dispatcher(self, realm, event, data):
- if realm == '':
- if event == nbxmpp.transports_nb.DATA_RECEIVED:
- gajim.nec.push_incoming_event(StanzaReceivedEvent(None,
- conn=self, stanza_str=data))
- elif event == nbxmpp.transports_nb.DATA_SENT:
- gajim.nec.push_incoming_event(StanzaSentEvent(None, conn=self,
- stanza_str=data))
-
- def change_status(self, show, msg, auto=False):
- if not msg:
- msg = ''
- sign_msg = False
- if not auto and not show == 'offline':
- sign_msg = True
- if show != 'invisible':
- # We save it only when privacy list is accepted
- self.status = msg
- if show != 'offline' and self.connected < 1:
- # set old_show to requested 'show' in case we need to
- # recconect before we auth to server
- self.old_show = show
- self.on_purpose = False
- self.server_resource = self._compute_resource()
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- self.connect_and_init(show, msg, sign_msg)
- return
-
- if show == 'offline':
- self.connected = 0
- if self.connection:
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- p = nbxmpp.Presence(typ = 'unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
-
- self.connection.RegisterDisconnectHandler(self._on_disconnected)
- self.connection.send(p, now=True)
- self.connection.start_disconnect()
- else:
- self._on_disconnected()
- return
-
- if show != 'offline' and self.connected > 0:
- # dont'try to connect, when we are in state 'connecting'
- if self.connected == 1:
- return
- if show == 'invisible':
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- self._change_to_invisible(msg)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return -1
- was_invisible = self.connected == gajim.SHOW_LIST.index('invisible')
- self.connected = gajim.SHOW_LIST.index(show)
- gajim.nec.push_incoming_event(BeforeChangeShowEvent(None,
- conn=self, show=show, message=msg))
- if was_invisible:
- self._change_from_invisible()
- self._update_status(show, msg)
-
-class Connection(CommonConnection, ConnectionHandlers):
- def __init__(self, name):
- CommonConnection.__init__(self, name)
- ConnectionHandlers.__init__(self)
- # this property is used to prevent double connections
- self.last_connection = None # last ClientCommon instance
- # If we succeed to connect, remember it so next time we try (after a
- # disconnection) we try only this type.
- self.last_connection_type = None
- self.lang = None
- if locale.getdefaultlocale()[0]:
- self.lang = locale.getdefaultlocale()[0].split('_')[0]
- # increase/decrease default timeout for server responses
- self.try_connecting_for_foo_secs = 45
- # holds the actual hostname to which we are connected
- self.connected_hostname = None
- self.redirected = None
- self.last_time_to_reconnect = None
- self.new_account_info = None
- self.new_account_form = None
- self.annotations = {}
- self.last_io = gajim.idlequeue.current_time()
- self.last_sent = []
- self.last_history_time = {}
- self.password = passwords.get_password(name)
-
- self.music_track_info = 0
- self.location_info = {}
- self.pubsub_supported = False
- self.register_supported = False
- self.pubsub_publish_options_supported = False
- # Do we auto accept insecure connection
- self.connection_auto_accepted = False
- self.pasword_callback = None
-
- self.on_connect_success = None
- self.on_connect_failure = None
- self.retrycount = 0
- self.jids_for_auto_auth = [] # list of jid to auto-authorize
- self.available_transports = {} # list of available transports on this
- # server {'icq': ['icq.server.com', 'icq2.server.com'], }
- self.private_storage_supported = True
- self.privacy_rules_requested = False
- self.streamError = ''
- self.secret_hmac = str(random.random())[2:].encode('utf-8')
-
- self.sm = Smacks(self) # Stream Management
-
- gajim.ged.register_event_handler('privacy-list-received', ged.CORE,
- self._nec_privacy_list_received)
- gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error_received)
- gajim.ged.register_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info_received)
- gajim.ged.register_event_handler('message-outgoing', ged.OUT_CORE,
- self._nec_message_outgoing)
- gajim.ged.register_event_handler('gc-message-outgoing', ged.OUT_CORE,
- self._nec_gc_message_outgoing)
- gajim.ged.register_event_handler('gc-stanza-message-outgoing', ged.OUT_CORE,
- self._nec_gc_stanza_message_outgoing)
- gajim.ged.register_event_handler('stanza-message-outgoing',
- ged.OUT_CORE, self._nec_stanza_message_outgoing)
- # END __init__
-
- def cleanup(self):
- ConnectionHandlers.cleanup(self)
- gajim.ged.remove_event_handler('privacy-list-received', ged.CORE,
- self._nec_privacy_list_received)
- gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error_received)
- gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info_received)
- gajim.ged.remove_event_handler('message-outgoing', ged.OUT_CORE,
- self._nec_message_outgoing)
- gajim.ged.remove_event_handler('gc-message-outgoing', ged.OUT_CORE,
- self._nec_gc_message_outgoing)
- gajim.ged.remove_event_handler('gc-stanza-message-outgoing', ged.OUT_CORE,
- self._nec_gc_stanza_message_outgoing)
- gajim.ged.remove_event_handler('stanza-message-outgoing', ged.OUT_CORE,
- self._nec_stanza_message_outgoing)
-
- def get_config_values_or_default(self):
- if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'):
- self.keepalives = gajim.config.get_per('accounts', self.name,
- 'keep_alive_every_foo_secs')
- else:
- self.keepalives = 0
- if gajim.config.get_per('accounts', self.name, 'ping_alives_enabled'):
- self.pingalives = gajim.config.get_per('accounts', self.name,
- 'ping_alive_every_foo_secs')
- else:
- self.pingalives = 0
- self.client_cert = gajim.config.get_per('accounts', self.name,
- 'client_cert')
- self.client_cert_passphrase = ''
-
- def check_jid(self, jid):
- return helpers.parse_jid(jid)
-
- def reconnect(self):
- # Do not try to reco while we are already trying
- self.time_to_reconnect = None
- if self.connected < 2: # connection failed
- log.debug('reconnect')
- self.connected = 1
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='connecting'))
- self.retrycount += 1
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_init(self.old_show, self.status, self.USE_GPG)
- else:
- # reconnect succeeded
- self.time_to_reconnect = None
- self.retrycount = 0
-
- # We are doing disconnect at so many places, better use one function in all
- def disconnect(self, on_purpose=False):
- gajim.interface.music_track_changed(None, None, self.name)
- self.reset_awaiting_pep()
- self.on_purpose = on_purpose
- self.connected = 0
- self.time_to_reconnect = None
- self.privacy_rules_supported = False
- if on_purpose:
- self.sm = Smacks(self)
- if self.connection:
- # make sure previous connection is completely closed
- gajim.proxy65_manager.disconnect(self.connection)
- self.terminate_sessions()
- self.remove_all_transfers()
- self.connection.disconnect()
- self.last_connection = None
- self.connection = None
-
- def set_oldst(self): # Set old state
- if self.old_show:
- self.connected = gajim.SHOW_LIST.index(self.old_show)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=self.connected))
- else: # we default to online
- self.connected = 2
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=gajim.SHOW_LIST[self.connected]))
-
- def disconnectedReconnCB(self):
- """
- Called when we are disconnected
- """
- log.info('disconnectedReconnCB called')
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = gajim.SHOW_LIST[self.connected]
- self.connected = 0
- if not self.on_purpose:
- if not (self.sm and self.sm.resumption):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- else:
- self.sm.enabled = False
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='error'))
- if self.connection:
- self.connection.UnregisterDisconnectHandler(
- self.disconnectedReconnCB)
- self.disconnect()
- if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
- self.connected = -1
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='error'))
- if gajim.status_before_autoaway[self.name]:
- # We were auto away. So go back online
- self.status = gajim.status_before_autoaway[self.name]
- gajim.status_before_autoaway[self.name] = ''
- self.old_show = 'online'
- # this check has moved from reconnect method
- # do exponential backoff until less than 5 minutes
- if self.retrycount < 2 or self.last_time_to_reconnect is None:
- self.last_time_to_reconnect = 5
- self.last_time_to_reconnect += randomsource.randint(0, 5)
- if self.last_time_to_reconnect < 200:
- self.last_time_to_reconnect *= 1.5
- self.time_to_reconnect = int(self.last_time_to_reconnect)
- log.info("Reconnect to %s in %ss", self.name, self.time_to_reconnect)
- gajim.idlequeue.set_alarm(self._reconnect_alarm,
- self.time_to_reconnect)
- elif self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # show error dialog
- self._connection_lost()
- else:
- if self.redirected:
- self.disconnect(on_purpose=True)
- self.connect()
- return
- else:
- self.disconnect()
- self.on_purpose = False
- # END disconnectedReconnCB
-
- def _connection_lost(self):
- log.debug('_connection_lost')
- self.disconnect(on_purpose = False)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Connection with account "%s" has been lost') % self.name,
- msg=_('Reconnect manually.')))
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == nbxmpp.NS_REGISTER:
- if event == nbxmpp.features_nb.REGISTER_DATA_RECEIVED:
- # data is (agent, DataFrom, is_form, error_msg)
- if self.new_account_info and \
- self.new_account_info['hostname'] == data[0]:
- # it's a new account
- if not data[1]: # wrong answer
- reason = _('Server %(name)s answered wrongly to '
- 'register request: %(error)s') % {'name': data[0],
- 'error': data[3]}
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=reason))
- return
- is_form = data[2]
- conf = data[1]
- if data[4] is not '':
- helpers.replace_dataform_media(conf, data[4])
- if self.new_account_form:
- def _on_register_result(result):
- if not nbxmpp.isResultNode(result):
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=result.getError()))
- return
- if gajim.HAVE_GPG:
- self.USE_GPG = True
- self.gpg = gpg.GnuPG()
- gajim.nec.push_incoming_event(
- AccountCreatedEvent(None, conn=self,
- account_info = self.new_account_info))
- self.new_account_info = None
- self.new_account_form = None
- if self.connection:
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- # it's the second time we get the form, we have info user
- # typed, so send them
- if is_form:
- #TODO: Check if form has changed
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER,
- to=self._hostname)
- iq.setTag('query').addChild(node=self.new_account_form)
- self.connection.SendAndCallForResponse(iq,
- _on_register_result)
- else:
- if list(self.new_account_form.keys()).sort() != \
- list(conf.keys()).sort():
- # requested config has changed since first connection
- reason = _('Server %s provided a different '
- 'registration form') % data[0]
- gajim.nec.push_incoming_event(AccountNotCreatedEvent(
- None, conn=self, reason=reason))
- return
- nbxmpp.features_nb.register(self.connection,
- self._hostname, self.new_account_form,
- _on_register_result)
- return
- gajim.nec.push_incoming_event(NewAccountConnectedEvent(None,
- conn=self, config=conf, is_form=is_form))
- self.connection.UnregisterDisconnectHandler(
- self._on_new_account)
- self.disconnect(on_purpose=True)
- return
- if not data[1]: # wrong answer
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invalid answer'),
- sec_txt=_('Transport %(name)s answered wrongly to '
- 'register request: %(error)s') % {'name': data[0],
- 'error': data[3]}))
- return
- is_form = data[2]
- conf = data[1]
- gajim.nec.push_incoming_event(RegisterAgentInfoReceivedEvent(
- None, conn=self, agent=data[0], config=conf,
- is_form=is_form))
- elif realm == nbxmpp.NS_PRIVACY:
- if event == nbxmpp.features_nb.PRIVACY_LISTS_RECEIVED:
- # data is (list)
- gajim.nec.push_incoming_event(PrivacyListsReceivedEvent(None,
- conn=self, lists_list=data))
- elif event == nbxmpp.features_nb.PRIVACY_LIST_RECEIVED:
- # data is (resp)
- if not data:
- return
- rules = []
- name = data.getTag('query').getTag('list').getAttr('name')
- for child in data.getTag('query').getTag('list').getChildren():
- dict_item = child.getAttrs()
- childs = []
- if 'type' in dict_item:
- for scnd_child in child.getChildren():
- childs += [scnd_child.getName()]
- rules.append({'action':dict_item['action'],
- 'type':dict_item['type'], 'order':dict_item['order'],
- 'value':dict_item['value'], 'child':childs})
- else:
- for scnd_child in child.getChildren():
- childs.append(scnd_child.getName())
- rules.append({'action':dict_item['action'],
- 'order':dict_item['order'], 'child':childs})
- gajim.nec.push_incoming_event(PrivacyListReceivedEvent(None,
- conn=self, list_name=name, rules=rules))
- elif event == nbxmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
- # data is (dict)
- gajim.nec.push_incoming_event(PrivacyListActiveDefaultEvent(
- None, conn=self, active_list=data['active'],
- default_list=data['default']))
-
- def _select_next_host(self, hosts):
- """
- Selects the next host according to RFC2782 p.3 based on it's priority.
- Chooses between hosts with the same priority randomly, where the
- probability of being selected is proportional to the weight of the host
- """
- hosts_by_prio = sorted(hosts, key=operator.itemgetter('prio'))
-
- try:
- lowest_prio = hosts_by_prio[0]['prio']
- except IndexError:
- raise ValueError("No hosts to choose from!")
-
- hosts_lowest_prio = [h for h in hosts_by_prio if h['prio'] == lowest_prio]
-
- if len(hosts_lowest_prio) == 1:
- return hosts_lowest_prio[0]
- else:
- rndint = random.randint(0, sum(h['weight'] for h in hosts_lowest_prio))
- weightsum = 0
- for host in sorted(hosts_lowest_prio, key=operator.itemgetter(
- 'weight')):
- weightsum += host['weight']
- if weightsum >= rndint:
- return host
-
- def connect(self, data=None):
- """
- Start a connection to the XMPP server
-
- Returns connection, and connection type ('tls', 'ssl', 'plain', '') data
- MUST contain hostname, proxy, use_custom_host, custom_host (if
- use_custom_host), custom_port (if use_custom_host)
- """
- if self.connection:
- return self.connection, ''
-
- if self.sm.resuming and self.sm.location:
- # If resuming and server gave a location, connect from there
- hostname = self.sm.location
- self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
- self.name, 'try_connecting_for_foo_secs')
- use_custom = False
- proxy = helpers.get_proxy_info(self.name)
-
- elif data:
- hostname = data['hostname']
- self.try_connecting_for_foo_secs = 45
- p = data['proxy']
- if p and p in gajim.config.get_per('proxies'):
- proxy = {}
- proxyptr = gajim.config.get_per('proxies', p)
- for key in proxyptr.keys():
- proxy[key] = proxyptr[key]
- else:
- proxy = None
- use_srv = True
- use_custom = data['use_custom_host']
- if use_custom:
- custom_h = data['custom_host']
- custom_p = data['custom_port']
- else:
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- self.try_connecting_for_foo_secs = gajim.config.get_per('accounts',
- self.name, 'try_connecting_for_foo_secs')
- proxy = helpers.get_proxy_info(self.name)
- use_srv = gajim.config.get_per('accounts', self.name, 'use_srv')
- if self.redirected:
- use_custom = True
- custom_h = self.redirected['host']
- custom_p = self.redirected['port']
- else:
- use_custom = gajim.config.get_per('accounts', self.name,
- 'use_custom_host')
- if use_custom:
- custom_h = gajim.config.get_per('accounts', self.name,
- 'custom_host')
- custom_p = gajim.config.get_per('accounts', self.name,
- 'custom_port')
- try:
- helpers.idn_to_ascii(custom_h)
- except Exception:
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error',
- pri_txt=_('Wrong Custom Hostname'),
- sec_txt='Wrong custom hostname "%s". Ignoring it.' \
- % custom_h))
- use_custom = False
-
- # create connection if it doesn't already exist
- self.connected = 1
-
- h = hostname
- p = 5222
- ssl_p = 5223
- if use_custom:
- h = custom_h
- p = custom_p
- ssl_p = custom_p
- if not self.redirected:
- use_srv = False
-
- self.redirected = None
- # SRV resolver
- self._proxy = proxy
- self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
- 'weight': 10} ]
- self._hostname = hostname
- if use_srv:
- # add request for srv query to the resolve, on result '_on_resolve'
- # will be called
- gajim.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(
- h), self._on_resolve)
- else:
- self._on_resolve('', [])
-
- def _on_resolve(self, host, result_array):
- # SRV query returned at least one valid result, we put it in hosts dict
- if len(result_array) != 0:
- self._hosts = [i for i in result_array]
- # Add ssl port
- ssl_p = 5223
- if gajim.config.get_per('accounts', self.name, 'use_custom_host'):
- ssl_p = gajim.config.get_per('accounts', self.name,
- 'custom_port')
- for i in self._hosts:
- i['ssl_port'] = ssl_p
- self._connect_to_next_host()
-
-
- def _connect_to_next_host(self, retry=False):
- log.debug('Connection to next host')
- if len(self._hosts):
- # No config option exist when creating a new account
- if self.name in gajim.config.get_per('accounts'):
- self._connection_types = gajim.config.get_per('accounts', self.name,
- 'connection_types').split()
- else:
- self._connection_types = ['tls', 'ssl']
- if self.last_connection_type:
- if self.last_connection_type in self._connection_types:
- self._connection_types.remove(self.last_connection_type)
- self._connection_types.insert(0, self.last_connection_type)
-
-
- if self._proxy and self._proxy['type']=='bosh':
- # with BOSH, we can't do TLS negotiation with <starttls>, we do only "plain"
- # connection and TLS with handshake right after TCP connecting ("ssl")
- scheme = nbxmpp.transports_nb.urisplit(self._proxy['bosh_uri'])[0]
- if scheme=='https':
- self._connection_types = ['ssl']
- else:
- self._connection_types = ['plain']
-
- host = self._select_next_host(self._hosts)
- self._current_host = host
- self._hosts.remove(host)
- self.connect_to_next_type()
-
- else:
- if not retry and self.retrycount == 0:
- log.debug("Out of hosts, giving up connecting to %s", self.name)
- self.time_to_reconnect = None
- if self.on_connect_failure:
- self.on_connect_failure()
- self.on_connect_failure = None
- else:
- # shown error dialog
- self._connection_lost()
- else:
- # try reconnect if connection has failed before auth to server
- self.disconnectedReconnCB()
-
- def connect_to_next_type(self, retry=False):
- if self.redirected:
- self.disconnect(on_purpose=True)
- self.connect()
- return
- if len(self._connection_types):
- self._current_type = self._connection_types.pop(0)
- if self.last_connection:
- self.last_connection.socket.disconnect()
- self.last_connection = None
- self.connection = None
-
- if self._current_type == 'ssl':
- # SSL (force TLS on different port than plain)
- # If we do TLS over BOSH, port of XMPP server should be the standard one
- # and TLS should be negotiated because TLS on 5223 is deprecated
- if self._proxy and self._proxy['type']=='bosh':
- port = self._current_host['port']
- else:
- port = self._current_host['ssl_port']
- elif self._current_type == 'tls':
- # TLS - negotiate tls after XMPP stream is estabilished
- port = self._current_host['port']
- elif self._current_type == 'plain':
- # plain connection on defined port
- port = self._current_host['port']
-
- cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
- if not os.path.exists(cacerts):
- cacerts = ''
- mycerts = common.gajim.MY_CACERTS
- tls_version = gajim.config.get_per('accounts', self.name,
- 'tls_version')
- cipher_list = gajim.config.get_per('accounts', self.name,
- 'cipher_list')
- secure_tuple = (self._current_type, cacerts, mycerts, tls_version, cipher_list)
-
- con = nbxmpp.NonBlockingClient(
- domain=self._hostname,
- caller=self,
- idlequeue=gajim.idlequeue)
-
- self.last_connection = con
- # increase default timeout for server responses
- nbxmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = \
- self.try_connecting_for_foo_secs
- # FIXME: this is a hack; need a better way
- if self.on_connect_success == self._on_new_account:
- con.RegisterDisconnectHandler(self._on_new_account)
-
- if self.client_cert and gajim.config.get_per('accounts', self.name,
- 'client_cert_encrypted'):
- gajim.nec.push_incoming_event(ClientCertPassphraseEvent(
- None, conn=self, con=con, port=port,
- secure_tuple=secure_tuple))
- return
- self.on_client_cert_passphrase('', con, port, secure_tuple)
-
- else:
- self._connect_to_next_host(retry)
-
- def on_client_cert_passphrase(self, passphrase, con, port, secure_tuple):
- self.client_cert_passphrase = passphrase
-
- self.log_hosttype_info(port)
- con.connect(
- hostname=self._current_host['host'],
- port=port,
- on_connect=self.on_connect_success,
- on_proxy_failure=self.on_proxy_failure,
- on_connect_failure=self.connect_to_next_type,
- on_stream_error_cb=self._StreamCB,
- proxy=self._proxy,
- secure_tuple = secure_tuple)
-
- def log_hosttype_info(self, port):
- msg = '>>>>>> Connecting to %s [%s:%d], type = %s' % (self.name,
- self._current_host['host'], port, self._current_type)
- log.info(msg)
- if self._proxy:
- msg = '>>>>>> '
- if self._proxy['type']=='bosh':
- msg = '%s over BOSH %s' % (msg, self._proxy['bosh_uri'])
- if self._proxy['type'] in ['http', 'socks5'] or self._proxy['bosh_useproxy']:
- msg = '%s over proxy %s:%s' % (msg, self._proxy['host'], self._proxy['port'])
- log.info(msg)
-
- def _connect_failure(self, con_type=None):
- if not con_type:
- # we are not retrying, and not conecting
- if not self.retrycount and self.connected != 0:
- self.disconnect(on_purpose = True)
- if self._proxy:
- pritxt = _('Could not connect to "%(host)s" via proxy "%(proxy)s"') %\
- {'host': self._hostname, 'proxy': self._proxy['host']}
- else:
- pritxt = _('Could not connect to "%(host)s"') % {'host': \
- self._hostname}
- sectxt = _('Check your connection or try again later.')
- if self.streamError:
- # show error dialog
- key = nbxmpp.NS_XMPP_STREAMS + ' ' + self.streamError
- if key in nbxmpp.ERRORS:
- sectxt2 = _('Server replied: %s') % nbxmpp.ERRORS[key][2]
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=pritxt,
- sec_txt='%s\n%s' % (sectxt2, sectxt)))
- return
- # show popup
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=pritxt, msg=sectxt))
-
- def on_proxy_failure(self, reason):
- log.error('Connection to proxy failed: %s' % reason)
- self.time_to_reconnect = None
- self.on_connect_failure = None
- self.disconnect(on_purpose = True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Connection to proxy failed'), msg=reason))
-
- def _connect_success(self, con, con_type):
- if not self.connected: # We went offline during connecting process
- # FIXME - not possible, maybe it was when we used threads
- return
- _con_type = con_type
- if _con_type != self._current_type:
- log.info('Connecting to next type beacuse desired is %s and returned is %s'
- % (self._current_type, _con_type))
- self.connect_to_next_type()
- return
- con.RegisterDisconnectHandler(self._on_disconnected)
- if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
- 'action_when_plaintext_connection') == 'warn':
- gajim.nec.push_incoming_event(PlainConnectionEvent(None, conn=self,
- xmpp_client=con))
- return True
- if _con_type == 'plain' and gajim.config.get_per('accounts', self.name,
- 'action_when_plaintext_connection') == 'disconnect':
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- return False
- if _con_type in ('tls', 'ssl') and con.Connection.ssl_lib != 'PYOPENSSL' \
- and gajim.config.get_per('accounts', self.name,
- 'warn_when_insecure_ssl_connection') and \
- not self.connection_auto_accepted:
- # Pyopenssl is not used
- gajim.nec.push_incoming_event(InsecureSSLConnectionEvent(None,
- conn=self, xmpp_client=con, conn_type=_con_type))
- return True
- return self.connection_accepted(con, con_type)
-
- def connection_accepted(self, con, con_type):
- if not con or not con.Connection:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to account %s') % self.name,
- msg=_('Connection with account %s has been lost. Retry '
- 'connecting.') % self.name))
- return
- self.hosts = []
- self.connection_auto_accepted = False
- self.connected_hostname = self._current_host['host']
- self.on_connect_failure = None
- con.UnregisterDisconnectHandler(self._on_disconnected)
- con.RegisterDisconnectHandler(self.disconnectedReconnCB)
- log.debug('Connected to server %s:%s with %s' % (
- self._current_host['host'], self._current_host['port'], con_type))
-
- self.last_connection_type = con_type
- if con_type == 'tls' and 'ssl' in self._connection_types:
- # we were about to try ssl after tls, but tls succeed, so
- # definitively stop trying ssl
- con_types = gajim.config.get_per('accounts', self.name,
- 'connection_types').split()
- con_types.remove('ssl')
- gajim.config.set_per('accounts', self.name, 'connection_types',
- ' '.join(con_types))
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- name = None
- else:
- name = gajim.config.get_per('accounts', self.name, 'name')
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- self.connection = con
- try:
- errnum = con.Connection.ssl_errnum
- except AttributeError:
- errnum = 0
- cert = con.Connection.ssl_certificate
- if errnum > 0 and str(errnum) not in gajim.config.get_per('accounts',
- self.name, 'ignore_ssl_errors').split():
- text = _('The authenticity of the %s certificate could be invalid'
- ) % hostname
- if errnum in ssl_error:
- text += _('\nSSL Error: <b>%s</b>') % ssl_error[errnum]
- else:
- text += _('\nUnknown SSL error: %d') % errnum
- fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
- cert).decode('utf-8')
- gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
- error_text=text, error_num=errnum, cert=pem,
- fingerprint_sha1=fingerprint_sha1,
- fingerprint_sha256=fingerprint_sha256, certificate=cert))
- return True
- if cert:
- fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- saved_fingerprint_sha1 = gajim.config.get_per('accounts', self.name,
- 'ssl_fingerprint_sha1')
- if saved_fingerprint_sha1:
- # Check sha1 fingerprint
- if fingerprint_sha1 != saved_fingerprint_sha1:
- if not check_X509.check_certificate(cert, hostname):
- gajim.nec.push_incoming_event(FingerprintErrorEvent(
- None, conn=self, certificate=cert,
- new_fingerprint_sha1=fingerprint_sha1,
- new_fingerprint_sha256=fingerprint_sha256))
- return True
- gajim.config.set_per('accounts', self.name, 'ssl_fingerprint_sha1',
- fingerprint_sha1)
-
- saved_fingerprint_sha256 = gajim.config.get_per('accounts', self.name,
- 'ssl_fingerprint_sha256')
- if saved_fingerprint_sha256:
- # Check sha256 fingerprint
- if fingerprint_sha256 != saved_fingerprint_sha256:
- if not check_X509.check_certificate(cert, hostname):
- gajim.nec.push_incoming_event(FingerprintErrorEvent(
- None, conn=self, certificate=cert,
- new_fingerprint_sha1=fingerprint_sha1,
- new_fingerprint_sha256=fingerprint_sha256))
- return True
- gajim.config.set_per('accounts', self.name,
- 'ssl_fingerprint_sha256', fingerprint_sha256)
-
- if not check_X509.check_certificate(cert, hostname) and \
- '100' not in gajim.config.get_per('accounts', self.name,
- 'ignore_ssl_errors').split():
- pem = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
- txt = _('The authenticity of the %s certificate could be '
- 'invalid.\nThe certificate does not cover this domain.') %\
- hostname
- gajim.nec.push_incoming_event(SSLErrorEvent(None, conn=self,
- error_text=txt, error_num=100, cert=pem,
- fingerprint_sha1=fingerprint_sha1,
- fingerprint_sha256=fingerprint_sha256, certificate=cert))
- return True
-
- self._register_handlers(con, con_type)
- auth_mechs = gajim.config.get_per('accounts', self.name, 'authentication_mechanisms')
- auth_mechs = auth_mechs.split()
- for mech in auth_mechs:
- if mech not in nbxmpp.auth_nb.SASL_AUTHENTICATION_MECHANISMS | set(['XEP-0078']):
- log.warning("Unknown authentication mechanisms %s" % mech)
- if len(auth_mechs) == 0:
- auth_mechs = None
- con.auth(user=name, password=self.password,
- resource=self.server_resource, sasl=True, on_auth=self.__on_auth, auth_mechs=auth_mechs)
-
- def ssl_certificate_accepted(self):
- if not self.connection:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to account %s') % self.name,
- msg=_('Connection with account %s has been lost. Retry '
- 'connecting.') % self.name))
- return
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- name = None
- else:
- name = gajim.config.get_per('accounts', self.name, 'name')
- self._register_handlers(self.connection, 'ssl')
- self.connection.auth(name, self.password, self.server_resource, 1,
- self.__on_auth)
-
- def _register_handlers(self, con, con_type):
- self.peerhost = con.get_peerhost()
- gajim.con_types[self.name] = con_type
- # notify the gui about con_type
- gajim.nec.push_incoming_event(ConnectionTypeEvent(None,
- conn=self, connection_type=con_type))
- ConnectionHandlers._register_handlers(self, con, con_type)
-
- def __on_auth(self, con, auth):
- if not con:
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not connect to "%s"') % self._hostname,
- msg=_('Check your connection or try again later.')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if not self.connected: # We went offline during connecting process
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- return
- if hasattr(con, 'Resource'):
- self.server_resource = con.Resource
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth'):
- # Get jid given by server
- old_jid = gajim.get_jid_from_account(self.name)
- gajim.config.set_per('accounts', self.name, 'name', con.User)
- new_jid = gajim.get_jid_from_account(self.name)
- gajim.nec.push_incoming_event(AnonymousAuthEvent(None,
- conn=self, old_jid=old_jid, new_jid=new_jid))
- if auth:
- self.last_io = gajim.idlequeue.current_time()
- self.connected = 2
- self.retrycount = 0
- if self.on_connect_auth:
- self.on_connect_auth(con)
- self.on_connect_auth = None
- else:
- if not gajim.config.get_per('accounts', self.name, 'savepass'):
- # Forget password, it's wrong
- self.password = None
- gajim.log.debug("Couldn't authenticate to %s" % self._hostname)
- self.disconnect(on_purpose = True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Authentication failed with "%s"') % \
- self._hostname, sec_txt=_('Please check your login and password'
- ' for correctness.')))
- if self.on_connect_auth:
- self.on_connect_auth(None)
- self.on_connect_auth = None
- # END connect
-
- def add_lang(self, stanza):
- if self.lang:
- stanza.setAttr('xml:lang', self.lang)
-
- def get_privacy_lists(self):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getPrivacyLists(self.connection)
-
- def send_keepalive(self):
- # nothing received for the last foo seconds
- if self.connection:
- self.connection.send(' ')
-
- def _on_xmpp_ping_answer(self, iq_obj):
- id_ = iq_obj.getAttr('id')
- assert id_ == self.awaiting_xmpp_ping_id
- self.awaiting_xmpp_ping_id = None
-
- def sendPing(self, pingTo=None, control=None):
- """
- Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to
- server to detect connection failure at application level
- If control is set, display result there
- """
- if not gajim.account_is_connected(self.name):
- return
- id_ = self.connection.getAnID()
- if pingTo:
- to = pingTo.get_full_jid()
- gajim.nec.push_incoming_event(PingSentEvent(None, conn=self,
- contact=pingTo))
- else:
- to = gajim.config.get_per('accounts', self.name, 'hostname')
- self.awaiting_xmpp_ping_id = id_
- iq = nbxmpp.Iq('get', to=to)
- iq.addChild(name='ping', namespace=nbxmpp.NS_PING)
- iq.setID(id_)
- def _on_response(resp):
- timePong = time.time()
- if not nbxmpp.isResultNode(resp):
- gajim.nec.push_incoming_event(PingErrorEvent(None, conn=self,
- contact=pingTo))
- return
- timeDiff = round(timePong - timePing, 2)
- gajim.nec.push_incoming_event(PingReplyEvent(None, conn=self,
- contact=pingTo, seconds=timeDiff, control=control))
- if pingTo:
- timePing = time.time()
- self.connection.SendAndCallForResponse(iq, _on_response)
- else:
- self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer)
- gajim.idlequeue.set_alarm(self.check_pingalive, gajim.config.get_per(
- 'accounts', self.name, 'time_for_ping_alive_answer'))
-
- def get_active_default_lists(self):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
-
- def del_privacy_list(self, privacy_list):
- if not gajim.account_is_connected(self.name):
- return
- def _on_del_privacy_list_result(result):
- if result:
- gajim.nec.push_incoming_event(PrivacyListRemovedEvent(None,
- conn=self, list_name=privacy_list))
- else:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Error while removing privacy '
- 'list'), sec_txt=_('Privacy list %s has not been removed. '
- 'It is maybe active in one of your connected resources. '
- 'Deactivate it and try again.') % privacy_list))
- nbxmpp.features_nb.delPrivacyList(self.connection, privacy_list,
- _on_del_privacy_list_result)
-
- def get_privacy_list(self, title):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.getPrivacyList(self.connection, title)
-
- def set_privacy_list(self, listname, tags):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setPrivacyList(self.connection, listname, tags)
-
- def set_active_list(self, listname):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setActivePrivacyList(self.connection, listname,
- 'active')
-
- def set_default_list(self, listname):
- if not gajim.account_is_connected(self.name):
- return
- nbxmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
-
- def build_privacy_rule(self, name, action, order=1):
- """
- Build a Privacy rule stanza for invisibility
- """
- iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVACY, xmlns='')
- l = iq.setQuery().setTag('list', {'name': name})
- i = l.setTag('item', {'action': action, 'order': str(order)})
- i.setTag('presence-out')
- return iq
-
- def build_invisible_rule(self):
- iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVACY, xmlns='')
- l = iq.setQuery().setTag('list', {'name': 'invisible'})
- if self.name in gajim.interface.status_sent_to_groups and \
- len(gajim.interface.status_sent_to_groups[self.name]) > 0:
- for group in gajim.interface.status_sent_to_groups[self.name]:
- i = l.setTag('item', {'type': 'group', 'value': group,
- 'action': 'allow', 'order': '1'})
- i.setTag('presence-out')
- if self.name in gajim.interface.status_sent_to_users and \
- len(gajim.interface.status_sent_to_users[self.name]) > 0:
- for jid in gajim.interface.status_sent_to_users[self.name]:
- i = l.setTag('item', {'type': 'jid', 'value': jid,
- 'action': 'allow', 'order': '2'})
- i.setTag('presence-out')
- i = l.setTag('item', {'action': 'deny', 'order': '3'})
- i.setTag('presence-out')
- return iq
-
- def set_invisible_rule(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = self.build_invisible_rule()
- self.connection.send(iq)
-
- def get_max_blocked_list_order(self):
- max_order = 0
- for rule in self.blocked_list:
- order = int(rule['order'])
- if order > max_order:
- max_order = order
- return max_order
-
- def block_contacts(self, contact_list, message):
- if self.privacy_default_list is None:
- self.privacy_default_list = 'block'
- if not self.privacy_rules_supported:
- if self.blocking_supported: #XEP-0191
- iq = nbxmpp.Iq('set', xmlns='')
- query = iq.setQuery(name='block')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- self.connection.send(iq)
- return
- for contact in contact_list:
- self.send_custom_status('offline', message, contact.jid)
- max_order = self.get_max_blocked_list_order()
- new_rule = {'order': str(max_order + 1),
- 'type': 'jid',
- 'action': 'deny',
- 'value': contact.jid}
- self.blocked_list.append(new_rule)
- self.blocked_contacts.append(contact.jid)
- self.set_privacy_list(self.privacy_default_list, self.blocked_list)
- if len(self.blocked_list) == 1:
- self.set_default_list(self.privacy_default_list)
-
- def unblock_contacts(self, contact_list):
- if not self.privacy_rules_supported:
- if self.blocking_supported: #XEP-0191
- iq = nbxmpp.Iq('set', xmlns='')
- query = iq.setQuery(name='unblock')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- for contact in contact_list:
- query.addChild(name='item', attrs={'jid': contact.jid})
- self.connection.send(iq)
- return
- self.new_blocked_list = []
- self.to_unblock = []
- for contact in contact_list:
- self.to_unblock.append(contact.jid)
- if contact.jid in self.blocked_contacts:
- self.blocked_contacts.remove(contact.jid)
- for rule in self.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'jid' \
- or rule['value'] not in self.to_unblock:
- self.new_blocked_list.append(rule)
- if len(self.new_blocked_list) == 0:
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.set_default_list('')
- self.del_privacy_list(self.privacy_default_list)
- else:
- self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
- if not gajim.interface.roster.regroup:
- show = gajim.SHOW_LIST[self.connected]
- else: # accounts merged
- show = helpers.get_global_show()
- if show == 'invisible':
- return
- for contact in contact_list:
- self.send_custom_status(show, self.status, contact.jid)
-
- def block_group(self, group, contact_list, message):
- if not self.privacy_rules_supported:
- return
- self.blocked_groups.append(group)
- for contact in contact_list:
- self.send_custom_status('offline', message, contact.jid)
- max_order = self.get_max_blocked_list_order()
- new_rule = {'order': str(max_order + 1),
- 'type': 'group',
- 'action': 'deny',
- 'value': group}
- self.blocked_list.append(new_rule)
- self.set_privacy_list(self.privacy_default_list, self.blocked_list)
- if len(self.blocked_list) == 1:
- self.set_default_list(self.privacy_default_list)
-
- def unblock_group(self, group, contact_list):
- if not self.privacy_rules_supported:
- return
- if group in self.blocked_groups:
- self.blocked_groups.remove(group)
- self.new_blocked_list = []
- for rule in self.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'group' or \
- rule['value'] != group:
- self.new_blocked_list.append(rule)
- if len(self.new_blocked_list) == 0:
- self.blocked_list = []
- self.blocked_contacts = []
- self.blocked_groups = []
- self.set_default_list('')
- self.del_privacy_list(self.privacy_default_list)
- else:
- self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
- if not gajim.interface.roster.regroup:
- show = gajim.SHOW_LIST[self.connected]
- else: # accounts merged
- show = helpers.get_global_show()
- if show == 'invisible':
- return
- for contact in contact_list:
- self.send_custom_status(show, self.status, contact.jid)
-
- def send_invisible_presence(self, msg, signed, initial = False):
- if not gajim.account_is_connected(self.name):
- return
- if not self.privacy_rules_supported:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=gajim.SHOW_LIST[self.connected]))
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Invisibility not supported'),
- sec_txt=_('Account %s doesn\'t support invisibility.') % \
- self.name))
- return
- # If we are already connected, and privacy rules are supported, send
- # offline presence first as it's required by XEP-0126
- if self.connected > 1 and self.privacy_rules_supported:
- self.on_purpose = True
- p = nbxmpp.Presence(typ='unavailable')
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- self.remove_all_transfers()
- self.connection.send(p)
-
- # try to set the privacy rule
- iq = self.build_invisible_rule()
- self.connection.SendAndCallForResponse(iq, self._continue_invisible,
- {'msg': msg, 'signed': signed, 'initial': initial})
-
- def _continue_invisible(self, con, iq_obj, msg, signed, initial):
- if iq_obj.getType() == 'error': # server doesn't support privacy lists
- return
- # active the privacy rule
- self.set_active_list('invisible')
- self.connected = gajim.SHOW_LIST.index('invisible')
- self.status = msg
- priority = gajim.get_priority(self.name, 'invisible')
- p = nbxmpp.Presence(priority=priority)
- p = self.add_sha(p, True)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='invisible'))
- if initial:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations
- self.get_annotations()
-
- # Inform GUI we just signed in
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
-
- def get_signed_presence(self, msg, callback = None):
- if gajim.config.get_per('accounts', self.name, 'gpg_sign_presence'):
- return self.get_signed_msg(msg, callback)
- return ''
-
- def connect_and_auth(self):
- self.on_connect_success = self._connect_success
- self.on_connect_failure = self._connect_failure
- self.connect()
-
- def connect_and_init(self, show, msg, sign_msg):
- self.continue_connect_info = [show, msg, sign_msg]
- self.on_connect_auth = self._discover_server_at_connection
- self.connect_and_auth()
-
- def _discover_server_at_connection(self, con):
- self.connection = con
- if not gajim.account_is_connected(self.name):
- return
- self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
- self.connection.set_send_timeout2(self.pingalives, self.sendPing)
- self.connection.onreceive(None)
-
- self.privacy_rules_requested = False
-
- # If we are not resuming, we ask for discovery info
- # and archiving preferences
- if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled):
- self.discoverInfo(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
-
- self.sm.resuming = False # back to previous state
- # Discover Stun server(s)
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- gajim.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(hostname),
- self._on_stun_resolved)
-
- def _on_stun_resolved(self, host, result_array):
- if len(result_array) != 0:
- self._stun_servers = self._hosts = [i for i in result_array]
-
- def _request_privacy(self):
- if not gajim.account_is_connected(self.name) or not self.connection:
- return
- iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVACY, xmlns='')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (PRIVACY_ARRIVED, )
- self.connection.send(iq)
-
- def _continue_connection_request_privacy(self):
- if self.privacy_rules_supported:
- if not self.privacy_rules_requested:
- self.privacy_rules_requested = True
- self._request_privacy()
- else:
- if self.continue_connect_info and self.continue_connect_info[0]\
- == 'invisible':
- # Trying to login as invisible but privacy list not
- # supported
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error', pri_txt=_('Invisibility not '
- 'supported'), sec_txt=_('Account %s doesn\'t support '
- 'invisibility.') % self.name))
- return
- if self.blocking_supported:
- iq = nbxmpp.Iq('get', xmlns='')
- query = iq.setQuery(name='blocklist')
- query.setNamespace(nbxmpp.NS_BLOCKING)
- id2_ = self.connection.getAnID()
- iq.setID(id2_)
- self.awaiting_answers[id2_] = (BLOCKING_ARRIVED, )
- self.connection.send(iq)
- # Ask metacontacts before roster
- self.get_metacontacts()
-
- def _nec_agent_info_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_[:6] == 'Gajim_':
- self._continue_connection_request_privacy()
-
- def _nec_agent_info_received(self, obj):
- if obj.conn.name != self.name:
- return
- is_muc = False
- transport_type = ''
- for identity in obj.identities:
- if 'category' in identity and identity['category'] in ('gateway',
- 'headline') and 'type' in identity:
- transport_type = identity['type']
- if 'category' in identity and identity['category'] == 'server' and \
- 'type' in identity and identity['type'] == 'im':
- transport_type = 'jabber' # it's a jabber server
- if 'category' in identity and identity['category'] == 'conference' \
- and 'type' in identity and identity['type'] == 'text':
- is_muc = True
-
- if transport_type != '' and obj.fjid not in gajim.transport_type:
- gajim.transport_type[obj.fjid] = transport_type
- gajim.logger.save_transport_type(obj.fjid, transport_type)
-
- if obj.id_[:6] == 'Gajim_':
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- our_jid = gajim.get_jid_from_account(self.name)
- if obj.fjid == hostname:
- if nbxmpp.NS_GMAILNOTIFY in obj.features:
- gajim.gmail_domains.append(obj.fjid)
- self.request_gmail_notifications()
- if nbxmpp.NS_SECLABEL in obj.features:
- self.seclabel_supported = True
- for identity in obj.identities:
- if identity['category'] == 'pubsub' and identity.get(
- 'type') == 'pep':
- self.pep_supported = True
- break
- if nbxmpp.NS_VCARD in obj.features:
- self.vcard_supported = True
- get_action(self.name + '-profile').set_enabled(True)
- if nbxmpp.NS_REGISTER in obj.features:
- self.register_supported = True
- if nbxmpp.NS_PUBSUB in obj.features:
- self.pubsub_supported = True
- if nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS in obj.features:
- self.pubsub_publish_options_supported = True
- else:
- # Remove stored bookmarks accessible to everyone.
- self.send_pb_purge(our_jid, 'storage:bookmarks')
- self.send_pb_delete(our_jid, 'storage:bookmarks')
- if nbxmpp.NS_MAM_2 in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM_2
- elif nbxmpp.NS_MAM_1 in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM_1
- elif nbxmpp.NS_MAM in obj.features:
- self.archiving_namespace = nbxmpp.NS_MAM
- if self.archiving_namespace:
- self.archiving_supported = True
- self.archiving_313_supported = True
- get_action(self.name + '-archive').set_enabled(True)
- if nbxmpp.NS_ARCHIVE in obj.features:
- self.archiving_supported = True
- self.archiving_136_supported = True
- self.request_message_archiving_preferences()
- if nbxmpp.NS_ARCHIVE_AUTO in obj.features:
- self.archive_auto_supported = True
- if nbxmpp.NS_ARCHIVE_MANAGE in obj.features:
- self.archive_manage_supported = True
- if nbxmpp.NS_ARCHIVE_MANUAL in obj.features:
- self.archive_manual_supported = True
- if nbxmpp.NS_ARCHIVE_PREF in obj.features:
- self.archive_pref_supported = True
- if nbxmpp.NS_BLOCKING in obj.features:
- self.blocking_supported = True
- if nbxmpp.NS_ADDRESS in obj.features:
- self.addressing_supported = True
- if nbxmpp.NS_CARBONS in obj.features and gajim.config.get_per(
- 'accounts', self.name, 'enable_message_carbons'):
- self.carbons_enabled = True
- # Server supports carbons, activate it
- iq = nbxmpp.Iq('set')
- iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
- self.connection.send(iq)
- if nbxmpp.NS_PRIVACY in obj.features:
- self.privacy_rules_supported = True
- get_action(self.name + '-privacylists').set_enabled(True)
-
- if nbxmpp.NS_BYTESTREAM in obj.features and \
- gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
- our_fjid = helpers.parse_jid(our_jid + '/' + \
- self.server_resource)
- testit = gajim.config.get_per('accounts', self.name,
- 'test_ft_proxies_on_startup')
- gajim.proxy65_manager.resolve(obj.fjid, self.connection,
- our_fjid, default=self.name, testit=testit)
- if nbxmpp.NS_MUC in obj.features and is_muc:
- type_ = transport_type or 'jabber'
- self.muc_jid[type_] = obj.fjid
- if transport_type:
- if transport_type in self.available_transports:
- self.available_transports[transport_type].append(obj.fjid)
- else:
- self.available_transports[transport_type] = [obj.fjid]
- self._continue_connection_request_privacy()
-
- def send_custom_status(self, show, msg, jid):
- if not show in gajim.SHOW_LIST:
- return -1
- if not gajim.account_is_connected(self.name):
- return
- sshow = helpers.get_xmpp_show(show)
- if not msg:
- msg = ''
- if show == 'offline':
- p = nbxmpp.Presence(typ='unavailable', to=jid)
- p = self.add_sha(p, False)
- if msg:
- p.setStatus(msg)
- else:
- signed = self.get_signed_presence(msg)
- priority = gajim.get_priority(self.name, sshow)
- p = nbxmpp.Presence(typ=None, priority=priority, show=sshow, to=jid)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- self.connection.send(p)
-
- def _change_to_invisible(self, msg):
- signed = self.get_signed_presence(msg)
- self.send_invisible_presence(msg, signed)
-
- def _change_from_invisible(self):
- if self.privacy_rules_supported:
- self.set_active_list('')
-
- def _update_status(self, show, msg):
- xmpp_show = helpers.get_xmpp_show(show)
- priority = gajim.get_priority(self.name, xmpp_show)
- p = nbxmpp.Presence(typ=None, priority=priority, show=xmpp_show)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- signed = self.get_signed_presence(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
-
- def send_motd(self, jid, subject='', msg='', xhtml=None):
- if not gajim.account_is_connected(self.name):
- return
- msg_iq = nbxmpp.Message(to=jid, body=msg, subject=subject,
- xhtml=xhtml)
-
- self.connection.send(msg_iq)
-
- def _nec_message_outgoing(self, obj):
- if obj.account != self.name:
- return
-
- self._prepare_message(obj)
-
- def _nec_stanza_message_outgoing(self, obj):
- if obj.conn.name != self.name:
- return
-
- config_key = '%s-%s' % (self.name, obj.jid)
- encryption = gajim.config.get_per('encryption', config_key, 'encryption')
- if encryption:
- gajim.plugin_manager.extension_point(
- 'encrypt' + encryption, self, obj, self.send_message)
- else:
- self.send_message(obj)
-
- def send_message(self, obj):
- obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now)
-
- gajim.nec.push_incoming_event(MessageSentEvent(
- None, conn=self, jid=obj.jid, message=obj.message, keyID=obj.keyID,
- chatstate=obj.chatstate, automatic_message=obj.automatic_message,
- msg_id=obj.msg_id, additional_data=obj.additional_data))
- if obj.callback:
- obj.callback(obj, obj.msg_iq, *obj.callback_args)
-
- if isinstance(obj.jid, list):
- for j in obj.jid:
- if obj.session is None:
- obj.session = self.get_or_create_session(j, '')
- self.log_message(obj, j)
- else:
- self.log_message(obj, obj.jid)
-
- def send_contacts(self, contacts, fjid, type_='message'):
- """
- Send contacts with RosterX (Xep-0144)
- """
- if not gajim.account_is_connected(self.name):
- return
- if type_ == 'message':
- if len(contacts) == 1:
- msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(),
- contacts[0].get_shown_name())
- else:
- msg = _('Sent contacts:')
- for contact in contacts:
- msg += '\n "%s" (%s)' % (contact.get_full_jid(),
- contact.get_shown_name())
- stanza = nbxmpp.Message(to=gajim.get_jid_without_resource(fjid),
- body=msg)
- elif type_ == 'iq':
- stanza = nbxmpp.Iq(to=fjid, typ='set')
- x = stanza.addChild(name='x', namespace=nbxmpp.NS_ROSTERX)
- for contact in contacts:
- x.addChild(name='item', attrs={'action': 'add', 'jid': contact.jid,
- 'name': contact.get_shown_name()})
- self.connection.send(stanza)
-
- def send_stanza(self, stanza):
- """
- Send a stanza untouched
- """
- if not self.connection:
- return
- self.connection.send(stanza)
-
- def ack_subscribed(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- log.debug('ack\'ing subscription complete for %s' % jid)
- p = nbxmpp.Presence(jid, 'subscribe')
- self.connection.send(p)
-
- def ack_unsubscribed(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- log.debug('ack\'ing unsubscription complete for %s' % jid)
- p = nbxmpp.Presence(jid, 'unsubscribe')
- self.connection.send(p)
-
- def request_subscription(self, jid, msg='', name='', groups=None,
- auto_auth=False, user_nick=''):
- if not gajim.account_is_connected(self.name):
- return
- if groups is None:
- groups = []
- log.debug('subscription request for %s' % jid)
- if auto_auth:
- self.jids_for_auto_auth.append(jid)
- # RFC 3921 section 8.2
- infos = {'jid': jid}
- if name:
- infos['name'] = name
- iq = nbxmpp.Iq('set', nbxmpp.NS_ROSTER)
- q = iq.setQuery()
- item = q.addChild('item', attrs=infos)
- for g in groups:
- item.addChild('group').setData(g)
- self.connection.send(iq)
-
- p = nbxmpp.Presence(jid, 'subscribe')
- if user_nick:
- p.setTag('nick', namespace = nbxmpp.NS_NICK).setData(user_nick)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- self.connection.send(p)
-
- def send_authorization(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- p = nbxmpp.Presence(jid, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def refuse_authorization(self, jid):
- if not gajim.account_is_connected(self.name):
- return
- p = nbxmpp.Presence(jid, 'unsubscribed')
- p = self.add_sha(p)
- self.connection.send(p)
-
- def unsubscribe(self, jid, remove_auth = True):
- if not gajim.account_is_connected(self.name):
- return
- if remove_auth:
- self.connection.getRoster().delItem(jid)
- jid_list = gajim.config.get_per('contacts')
- for j in jid_list:
- if j.startswith(jid):
- gajim.config.del_per('contacts', j)
- else:
- self.connection.getRoster().Unsubscribe(jid)
- self.update_contact(jid, '', [])
-
- def unsubscribe_agent(self, agent):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER, to=agent)
- iq.setQuery().setTag('remove')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (AGENT_REMOVED, agent)
- self.connection.send(iq)
- self.connection.getRoster().delItem(agent)
-
- def send_new_account_infos(self, form, is_form):
- if is_form:
- # Get username and password and put them in new_account_info
- for field in form.iter_fields():
- if field.var == 'username':
- self.new_account_info['name'] = field.value
- if field.var == 'password':
- self.new_account_info['password'] = field.value
- else:
- # Get username and password and put them in new_account_info
- if 'username' in form:
- self.new_account_info['name'] = form['username']
- if 'password' in form:
- self.new_account_info['password'] = form['password']
- self.new_account_form = form
- self.new_account(self.name, self.new_account_info)
-
- def new_account(self, name, config, sync=False):
- # If a connection already exist we cannot create a new account
- if self.connection:
- return
- self._hostname = config['hostname']
- self.new_account_info = config
- self.name = name
- self.on_connect_success = self._on_new_account
- self.on_connect_failure = self._on_new_account
- self.connect(config)
-
- def _on_new_account(self, con=None, con_type=None):
- if not con_type:
- if len(self._connection_types) or len(self._hosts):
- # There are still other way to try to connect
- return
- reason = _('Could not connect to "%s"') % self._hostname
- gajim.nec.push_incoming_event(NewAccountNotConnectedEvent(None,
- conn=self, reason=reason))
- return
- self.on_connect_failure = None
- self.connection = con
- nbxmpp.features_nb.getRegInfo(con, self._hostname)
-
- def request_os_info(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get', queryNS=nbxmpp.NS_VERSION)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.version_ids.append(id_)
- self.connection.send(iq)
-
- def request_entity_time(self, jid, resource, groupchat_jid=None):
- """
- groupchat_jid is used when we want to send a request to a real jid and
- act as if the answer comes from the groupchat_jid
- """
- if not gajim.account_is_connected(self.name):
- return
- # If we are invisible, do not request
- if self.connected == gajim.SHOW_LIST.index('invisible'):
- self.dispatch('ENTITY_TIME', (jid, resource, _('Not fetched because of invisible status')))
- return
- to_whom_jid = jid
- if resource:
- to_whom_jid += '/' + resource
- iq = nbxmpp.Iq(to=to_whom_jid, typ='get')
- iq.addChild('time', namespace=nbxmpp.NS_TIME_REVISED)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- if groupchat_jid:
- self.groupchat_jids[id_] = groupchat_jid
- self.entity_time_ids.append(id_)
- self.connection.send(iq)
-
- def request_gateway_prompt(self, jid, prompt=None):
- def _on_prompt_result(resp):
- gajim.nec.push_incoming_event(GatewayPromptReceivedEvent(None,
- conn=self, stanza=resp))
- if prompt:
- typ_ = 'set'
- else:
- typ_ = 'get'
- iq = nbxmpp.Iq(typ=typ_, to=jid)
- query = iq.addChild(name='query', namespace=nbxmpp.NS_GATEWAY)
- if prompt:
- query.setTagData('prompt', prompt)
- self.connection.SendAndCallForResponse(iq, _on_prompt_result)
-
- def get_settings(self):
- """
- Get Gajim settings as described in XEP 0049
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='gajim', namespace='gajim:prefs')
- self.connection.send(iq)
-
- def seclabel_catalogue(self, to, callback):
- if not gajim.account_is_connected(self.name):
- return
- self.seclabel_catalogue_request(to, callback)
- server = gajim.get_jid_from_account(self.name).split("@")[1] # Really, no better way?
- iq = nbxmpp.Iq(typ='get', to=server)
- iq2 = iq.addChild(name='catalog', namespace=nbxmpp.NS_SECLABEL_CATALOG)
- iq2.setAttr('to', to)
- self.connection.send(iq)
-
- def _nec_privacy_list_received(self, obj):
- roster = gajim.interface.roster
- if obj.conn.name != self.name:
- return
- if obj.list_name != self.privacy_default_list:
- return
- self.blocked_contacts = []
- self.blocked_groups = []
- self.blocked_list = []
- self.blocked_all = False
- for rule in obj.rules:
- if rule['action'] == 'allow':
- if not 'type' in rule:
- self.blocked_all = False
- elif rule['type'] == 'jid' and rule['value'] in \
- self.blocked_contacts:
- self.blocked_contacts.remove(rule['value'])
- elif rule['type'] == 'group' and rule['value'] in \
- self.blocked_groups:
- self.blocked_groups.remove(rule['value'])
- elif rule['action'] == 'deny':
- if not 'type' in rule:
- self.blocked_all = True
- elif rule['type'] == 'jid' and rule['value'] not in \
- self.blocked_contacts:
- self.blocked_contacts.append(rule['value'])
- elif rule['type'] == 'group' and rule['value'] not in \
- self.blocked_groups:
- self.blocked_groups.append(rule['value'])
- self.blocked_list.append(rule)
-
- if 'type' in rule:
- if rule['type'] == 'jid':
- roster.draw_contact(rule['value'], self.name)
- if rule['type'] == 'group':
- roster.draw_group(rule['value'], self.name)
-
- def _request_bookmarks_xml(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:bookmarks')
- self.connection.send(iq)
-
- def _check_bookmarks_received(self):
- if not self.bookmarks:
- self._request_bookmarks_xml()
-
- def get_bookmarks(self, storage_type=None):
- """
- Get Bookmarks from storage or PubSub if supported as described in XEP
- 0048
-
- storage_type can be set to xml to force request to xml storage
- """
- if not gajim.account_is_connected(self.name):
- return
- if self.pubsub_supported and self.pubsub_publish_options_supported \
- and storage_type != 'xml':
- self.send_pb_retrieve('', 'storage:bookmarks')
- # some server (ejabberd) are so slow to answer that we request via XML
- # if we don't get answer in the next 30 seconds
- gajim.idlequeue.set_alarm(self._check_bookmarks_received, 30)
- else:
- self._request_bookmarks_xml()
-
- def store_bookmarks(self, storage_type=None):
- """
- Send bookmarks to the storage namespace or PubSub if supported
-
- storage_type can be set to 'pubsub' or 'xml' so store in only one method
- else it will be stored on both
- """
- NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Node(tag='storage', attrs={'xmlns': 'storage:bookmarks'})
- for bm in self.bookmarks:
- iq2 = iq.addChild(name="conference")
- iq2.setAttr('jid', bm['jid'])
- iq2.setAttr('autojoin', bm['autojoin'])
- iq2.setAttr('name', bm['name'])
- iq2.setTag('minimize', namespace=NS_GAJIM_BM). \
- setData(bm['minimize'])
- # Only add optional elements if not empty
- # Note: need to handle both None and '' as empty
- # thus shouldn't use "is not None"
- if bm.get('nick', None):
- iq2.setTagData('nick', bm['nick'])
- if bm.get('password', None):
- iq2.setTagData('password', bm['password'])
- if bm.get('print_status', None):
- iq2.setTag('print_status', namespace=NS_GAJIM_BM). \
- setData(bm['print_status'])
-
- if self.pubsub_supported and self.pubsub_publish_options_supported and\
- storage_type != 'xml':
- options = nbxmpp.Node(nbxmpp.NS_DATA + ' x',
- attrs={'type': 'submit'})
- f = options.addChild('field',
- attrs={'var': 'FORM_TYPE', 'type': 'hidden'})
- f.setTagData('value', nbxmpp.NS_PUBSUB_PUBLISH_OPTIONS)
- f = options.addChild('field',
- attrs={'var': 'pubsub#persist_items'})
- f.setTagData('value', 'true')
- f = options.addChild('field', attrs={'var': 'pubsub#access_model'})
- f.setTagData('value', 'whitelist')
- self.send_pb_publish('', 'storage:bookmarks', iq, 'current',
- options=options)
- if storage_type != 'pubsub':
- iqA = nbxmpp.Iq(typ='set')
- iqB = iqA.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iqB.addChild(node=iq)
- self.connection.send(iqA)
-
- def get_annotations(self):
- """
- Get Annonations from storage as described in XEP 0048, and XEP 0145
- """
- self.annotations = {}
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:rosternotes')
- self.connection.send(iq)
-
- def store_annotations(self):
- """
- Set Annonations in private storage as described in XEP 0048, and XEP 0145
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:rosternotes')
- for jid in self.annotations.keys():
- if self.annotations[jid]:
- iq4 = iq3.addChild(name = "note")
- iq4.setAttr('jid', jid)
- iq4.setData(self.annotations[jid])
- self.connection.send(iq)
-
- def get_roster_delimiter(self):
- """
- Get roster group delimiter from storage as described in XEP 0083
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='roster', namespace='roster:delimiter')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (DELIMITER_ARRIVED, )
- self.connection.send(iq)
-
- def set_roster_delimiter(self, delimiter='::'):
- """
- Set roster group delimiter to the storage namespace
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='roster', namespace='roster:delimiter')
- iq3.setData(delimiter)
-
- self.connection.send(iq)
-
- def get_metacontacts(self):
- """
- Get metacontacts list from storage as described in XEP 0049
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq2.addChild(name='storage', namespace='storage:metacontacts')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
- self.connection.send(iq)
-
- def store_metacontacts(self, tags_list):
- """
- Send meta contacts to the storage namespace
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
- iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
- for tag in tags_list:
- for data in tags_list[tag]:
- jid = data['jid']
- dict_ = {'jid': jid, 'tag': tag}
- if 'order' in data:
- dict_['order'] = data['order']
- iq3.addChild(name='meta', attrs=dict_)
- self.connection.send(iq)
-
- def request_roster(self):
- version = None
- features = self.connection.Dispatcher.Stream.features
- if features and features.getTag('ver',
- namespace=nbxmpp.NS_ROSTER_VER):
- version = gajim.config.get_per('accounts', self.name,
- 'roster_version')
- if version and not gajim.contacts.get_contacts_jid_list(
- self.name):
- gajim.config.set_per('accounts', self.name, 'roster_version',
- '')
- version = None
-
- iq_id = self.connection.initRoster(version=version)
- self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
-
- def send_agent_status(self, agent, ptype):
- if not gajim.account_is_connected(self.name):
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = nbxmpp.Presence(to=agent, typ=ptype, show=show)
- p = self.add_sha(p, ptype != 'unavailable')
- self.connection.send(p)
-
- def send_captcha(self, jid, form_node):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=jid)
- captcha = iq.addChild(name='captcha', namespace=nbxmpp.NS_CAPTCHA)
- captcha.addChild(node=form_node)
- self.connection.send(iq)
-
- def check_unique_room_id_support(self, server, instance):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', to=server)
- iq.setAttr('id', 'unique1')
- iq.addChild('unique', namespace=nbxmpp.NS_MUC_UNIQUE)
- def _on_response(resp):
- if not nbxmpp.isResultNode(resp):
- gajim.nec.push_incoming_event(UniqueRoomIdNotSupportedEvent(
- None, conn=self, instance=instance, server=server))
- return
- gajim.nec.push_incoming_event(UniqueRoomIdSupportedEvent(None,
- conn=self, instance=instance, server=server,
- room_id=resp.getTag('unique').getData()))
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def join_gc(self, nick, room_jid, password, change_nick=False,
- rejoin=False):
- # FIXME: This room JID needs to be normalized; see #1364
- if not gajim.account_is_connected(self.name):
- return
- show = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- if show == 'invisible':
- # Never join a room when invisible
- return
-
- # last date/time in history to avoid duplicate
- if room_jid not in self.last_history_time:
- # Not in memory, get it from DB
- last_log = None
- # Do not check if we are not logging for this room
- if gajim.config.should_log(self.name, room_jid):
- # Check time first in the FAST table
- last_log = gajim.logger.get_room_last_message_time(room_jid)
- if last_log is None:
- # Not in special table, get it from messages DB
- last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
- is_room=True)
- # Create self.last_history_time[room_jid] even if not logging,
- # could be used in connection_handlers
- if last_log is None:
- last_log = 0
- self.last_history_time[room_jid] = last_log
-
- p = nbxmpp.Presence(to='%s/%s' % (room_jid, nick),
- show=show, status=self.status)
- h = hmac.new(self.secret_hmac, room_jid.encode('utf-8'), hashlib.md5).\
- hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence'):
- p = self.add_sha(p)
- self.add_lang(p)
- if not change_nick:
- t = p.setTag(nbxmpp.NS_MUC + ' x')
- tags = {}
- timeout = gajim.config.get_per('rooms', room_jid,
- 'muc_restore_timeout')
- if timeout is None or timeout == -2:
- timeout = gajim.config.get('muc_restore_timeout')
- timeout *= 60
- if timeout >= 0:
- last_date = self.last_history_time[room_jid]
- if last_date == 0:
- last_date = time.time() - timeout
- elif not rejoin:
- last_date = min(last_date, time.time() - timeout)
- last_date = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(
- last_date))
- tags['since'] = last_date
- nb = gajim.config.get_per('rooms', room_jid, 'muc_restore_lines')
- if nb is None or nb == -2:
- nb = gajim.config.get('muc_restore_lines')
- if nb >= 0:
- tags['maxstanzas'] = nb
- if tags:
- t.setTag('history', tags)
- if password:
- t.setTagData('password', password)
- self.connection.send(p)
-
- def _nec_gc_message_outgoing(self, obj):
- if obj.account != self.name:
- return
- if not gajim.account_is_connected(self.name):
- return
-
- if not obj.xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
- from common.rst_xhtml_generator import create_xhtml
- obj.xhtml = create_xhtml(obj.message)
-
- msg_iq = nbxmpp.Message(obj.jid, obj.message, typ='groupchat',
- xhtml=obj.xhtml)
-
- if obj.correct_id:
- msg_iq.setTag('replace', attrs={'id': obj.correct_id},
- namespace=nbxmpp.NS_CORRECT)
-
- if obj.chatstate:
- msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
- if obj.label is not None:
- msg_iq.addChild(node=obj.label)
-
- obj.msg_iq = msg_iq
- obj.conn = self
- gajim.nec.push_incoming_event(GcStanzaMessageOutgoingEvent(None, **vars(obj)))
-
- def _nec_gc_stanza_message_outgoing(self, obj):
- if obj.conn.name != self.name:
- return
-
- config_key = '%s-%s' % (self.name, obj.jid)
- encryption = gajim.config.get_per('encryption', config_key, 'encryption')
- if encryption:
- gajim.plugin_manager.extension_point(
- 'gc_encrypt' + encryption, self, obj, self.send_gc_message)
- else:
- self.send_gc_message(obj)
-
- def send_gc_message(self, obj):
- obj.msg_id = self.connection.send(obj.msg_iq)
- gajim.nec.push_incoming_event(MessageSentEvent(
- None, conn=self, jid=obj.jid, message=obj.message, keyID=None,
- chatstate=None, automatic_message=obj.automatic_message,
- msg_id=obj.msg_id, additional_data=obj.additional_data))
- if obj.callback:
- obj.callback(obj)
-
- def send_gc_subject(self, jid, subject):
- if not gajim.account_is_connected(self.name):
- return
- msg_iq = nbxmpp.Message(jid, typ='groupchat', subject=subject)
- self.connection.send(msg_iq)
-
- def request_gc_config(self, room_jid):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', queryNS=nbxmpp.NS_MUC_OWNER,
- to=room_jid)
- self.add_lang(iq)
- self.connection.send(iq)
-
- def destroy_gc_room(self, room_jid, reason = '', jid = ''):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', queryNS=nbxmpp.NS_MUC_OWNER,
- to=room_jid)
- destroy = iq.setQuery().setTag('destroy')
- if reason:
- destroy.setTagData('reason', reason)
- if jid:
- destroy.setAttr('jid', jid)
- self.connection.send(iq)
-
- def send_gc_status(self, nick, jid, show, status):
- if not gajim.account_is_connected(self.name):
- return
- if show == 'invisible':
- show = 'offline'
- ptype = None
- if show == 'offline':
- ptype = 'unavailable'
- xmpp_show = helpers.get_xmpp_show(show)
- p = nbxmpp.Presence(to='%s/%s' % (jid, nick), typ=ptype,
- show=xmpp_show, status=status)
- h = hmac.new(self.secret_hmac, jid.encode('utf-8'), hashlib.md5).\
- hexdigest()[:6]
- id_ = self.connection.getAnID()
- id_ = 'gajim_muc_' + id_ + '_' + h
- p.setID(id_)
- if gajim.config.get('send_sha_in_gc_presence') and show != 'offline':
- p = self.add_sha(p, ptype != 'unavailable')
- self.add_lang(p)
- # send instantly so when we go offline, status is sent to gc before we
- # disconnect from jabber server
- self.connection.send(p)
-
- def gc_got_disconnected(self, room_jid):
- """
- A groupchat got disconnected. This can be or purpose or not
-
- Save the time we had last message to avoid duplicate logs AND be faster
- than get that date from DB. Save time that we have in mem in a small
- table (with fast access)
- """
- gajim.logger.set_room_last_message_time(room_jid, self.last_history_time[room_jid])
-
- def gc_set_role(self, room_jid, nick, role, reason=''):
- """
- Role is for all the life of the room so it's based on nick
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('nick', nick)
- item.setAttr('role', role)
- if reason:
- item.addChild(name='reason', payload=reason)
- self.connection.send(iq)
-
- def gc_set_affiliation(self, room_jid, jid, affiliation, reason = ''):
- """
- Affiliation is for all the life of the room so it's based on jid
- """
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('jid', jid)
- item.setAttr('affiliation', affiliation)
- if reason:
- item.addChild(name = 'reason', payload = reason)
- self.connection.send(iq)
-
- def send_gc_affiliation_list(self, room_jid, users_dict):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery()
- for jid in users_dict:
- item_tag = item.addChild('item', {'jid': jid,
- 'affiliation': users_dict[jid]['affiliation']})
- if 'reason' in users_dict[jid] and users_dict[jid]['reason']:
- item_tag.setTagData('reason', users_dict[jid]['reason'])
- self.connection.send(iq)
-
- def get_affiliation_list(self, room_jid, affiliation):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get', to=room_jid, queryNS=nbxmpp.NS_MUC_ADMIN)
- item = iq.setQuery().setTag('item')
- item.setAttr('affiliation', affiliation)
- self.connection.send(iq)
-
- def send_gc_config(self, room_jid, form):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set', to=room_jid, queryNS=nbxmpp.NS_MUC_OWNER)
- query = iq.setQuery()
- form.setAttr('type', 'submit')
- query.addChild(node = form)
- self.connection.send(iq)
-
- def change_password(self, password):
- if not gajim.account_is_connected(self.name):
- return
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- username = gajim.config.get_per('accounts', self.name, 'name')
- iq = nbxmpp.Iq(typ='set', to=hostname)
- q = iq.setTag(nbxmpp.NS_REGISTER + ' query')
- q.setTagData('username', username)
- q.setTagData('password', password)
- self.connection.send(iq)
-
- def get_password(self, callback, type_):
- if gajim.config.get_per('accounts', self.name, 'anonymous_auth') and \
- type_ != 'ANONYMOUS':
- gajim.nec.push_incoming_event(NonAnonymousServerErrorEvent(None,
- conn=self))
- self._on_disconnected()
- return
- self.pasword_callback = (callback, type_)
- if type_ == 'X-MESSENGER-OAUTH2':
- client_id = gajim.config.get_per('accounts', self.name,
- 'oauth2_client_id')
- refresh_token = gajim.config.get_per('accounts', self.name,
- 'oauth2_refresh_token')
- if refresh_token:
- renew_URL = 'https://oauth.live.com/token?client_id=' \
- '%(client_id)s&redirect_uri=https%%3A%%2F%%2Foauth.live.' \
- 'com%%2Fdesktop&grant_type=refresh_token&refresh_token=' \
- '%(refresh_token)s' % locals()
- result = helpers.download_image(self.name, {'src': renew_URL})[0]
- if result:
- dict_ = json.loads(result)
- if 'access_token' in dict_:
- self.set_password(dict_['access_token'])
- return
- script_url = gajim.config.get_per('accounts', self.name,
- 'oauth2_redirect_url')
- token_URL = 'https://oauth.live.com/authorize?client_id=' \
- '%(client_id)s&scope=wl.messenger%%20wl.offline_access&' \
- 'response_type=code&redirect_uri=%(script_url)s' % locals()
- helpers.launch_browser_mailer('url', token_URL)
- self.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(Oauth2CredentialsRequiredEvent(None,
- conn=self))
- return
- if self.password:
- self.set_password(self.password)
- return
- gajim.nec.push_incoming_event(PasswordRequiredEvent(None, conn=self))
-
- def set_password(self, password):
- self.password = password
- if self.pasword_callback:
- callback, type_ = self.pasword_callback
- if self._current_type == 'plain' and type_ == 'PLAIN' and \
- gajim.config.get_per('accounts', self.name,
- 'warn_when_insecure_password'):
- gajim.nec.push_incoming_event(InsecurePasswordEvent(None,
- conn=self))
- return
- callback(password)
- self.pasword_callback = None
-
- def accept_insecure_password(self):
- if self.pasword_callback:
- callback, type_ = self.pasword_callback
- callback(self.password)
- self.pasword_callback = None
-
- def unregister_account(self, on_remove_success):
- # no need to write this as a class method and keep the value of
- # on_remove_success as a class property as pass it as an argument
- def _on_unregister_account_connect(con):
- self.on_connect_auth = None
- if gajim.account_is_connected(self.name):
- hostname = gajim.config.get_per('accounts', self.name, 'hostname')
- iq = nbxmpp.Iq(typ='set', to=hostname)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- iq.setTag(nbxmpp.NS_REGISTER + ' query').setTag('remove')
- def _on_answer(con, result):
- if result.getID() == id_:
- on_remove_success(True)
- return
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=self, level='error',
- pri_txt=_('Unregister failed'),
- sec_txt=_('Unregistration with server %(server)s '
- 'failed: %(error)s') % {'server': hostname,
- 'error': result.getErrorMsg()}))
- on_remove_success(False)
- con.RegisterHandler('iq', _on_answer, 'result', system=True)
- con.SendAndWaitForResponse(iq)
- return
- on_remove_success(False)
- if self.connected == 0:
- self.on_connect_auth = _on_unregister_account_connect
- self.connect_and_auth()
- else:
- _on_unregister_account_connect(self.connection)
-
- def send_invite(self, room, to, reason='', continue_tag=False):
- """
- Send invitation
- """
- if not gajim.account_is_connected(self.name):
- return
- contact = gajim.contacts.get_contact_from_full_jid(self.name, to)
- if contact and contact.supports(nbxmpp.NS_CONFERENCE):
- # send direct invite
- message=nbxmpp.Message(to=to)
- attrs = {'jid': room}
- if reason:
- attrs['reason'] = reason
- if continue_tag:
- attrs['continue'] = 'true'
- password = gajim.gc_passwords.get(room, '')
- if password:
- attrs['password'] = password
- c = message.addChild(name='x', attrs=attrs,
- namespace=nbxmpp.NS_CONFERENCE)
- self.connection.send(message)
- return
- message=nbxmpp.Message(to=room)
- c = message.addChild(name='x', namespace=nbxmpp.NS_MUC_USER)
- c = c.addChild(name='invite', attrs={'to': to})
- if continue_tag:
- c.addChild(name='continue')
- if reason != '':
- c.setTagData('reason', reason)
- self.connection.send(message)
-
- def decline_invitation(self, room, to, reason=''):
- """
- decline a groupchat invitation
- """
- if not gajim.account_is_connected(self.name):
- return
- message=nbxmpp.Message(to=room)
- c = message.addChild(name='x', namespace=nbxmpp.NS_MUC_USER)
- c = c.addChild(name='decline', attrs={'to': to})
- if reason != '':
- c.setTagData('reason', reason)
- self.connection.send(message)
-
- def request_voice(self, room):
- """
- Request voice in a moderated room
- """
- if not gajim.account_is_connected(self.name):
- return
- message = nbxmpp.Message(to=room)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value=nbxmpp.NS_MUC + '#request'))
- x.addChild(node=nbxmpp.DataField(name='muc#role', value='participant',
- typ='text-single'))
-
- message.addChild(node=x)
-
- self.connection.send(message)
-
- def check_pingalive(self):
- if not gajim.config.get_per('accounts', self.name, 'active'):
- # Account may have been disabled
- return
- if self.awaiting_xmpp_ping_id:
- # We haven't got the pong in time, disco and reconnect
- log.warning("No reply received for keepalive ping. Reconnecting.")
- self.disconnectedReconnCB()
-
- def _reconnect_alarm(self):
- if not gajim.config.get_per('accounts', self.name, 'active'):
- # Account may have been disabled
- return
- if self.time_to_reconnect:
- if self.connected < 2:
- self.reconnect()
- else:
- self.time_to_reconnect = None
-
- def request_search_fields(self, jid):
- iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_SEARCH)
- self.connection.send(iq)
-
- def send_search_form(self, jid, form, is_form):
- iq = nbxmpp.Iq(typ='set', to=jid, queryNS=nbxmpp.NS_SEARCH)
- item = iq.setQuery()
- if is_form:
- item.addChild(node=form)
- else:
- for i in form.keys():
- item.setTagData(i, form[i])
- def _on_response(resp):
- gajim.nec.push_incoming_event(SearchResultReceivedEvent(None,
- conn=self, stanza=resp))
-
- self.connection.SendAndCallForResponse(iq, _on_response)
-
- def load_roster_from_db(self):
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self))
-
-# END Connection
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
deleted file mode 100644
index fe024b54b..000000000
--- a/src/common/connection_handlers.py
+++ /dev/null
@@ -1,2297 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import base64
-import operator
-import hashlib
-
-from time import (altzone, daylight, gmtime, localtime, strftime,
- time as time_time, timezone, tzname)
-
-from gi.repository import GLib
-
-import nbxmpp
-from common import caps_cache as capscache
-
-from common.pep import LOCATION_DATA
-from common import helpers
-from common import gajim
-from common import exceptions
-from common import dataforms
-from common import jingle_xtls
-from common.commands import ConnectionCommands
-from common.pubsub import ConnectionPubSub
-from common.protocol.caps import ConnectionCaps
-from common.protocol.bytestream import ConnectionSocks5Bytestream
-from common.protocol.bytestream import ConnectionIBBytestream
-from common.message_archiving import ConnectionArchive136
-from common.message_archiving import ConnectionArchive313
-from common.connection_handlers_events import *
-
-from common import ged
-from common import nec
-from common.nec import NetworkEvent
-
-from common.jingle import ConnectionJingle
-
-import logging
-log = logging.getLogger('gajim.c.connection_handlers')
-
-# kind of events we can wait for an answer
-VCARD_PUBLISHED = 'vcard_published'
-VCARD_ARRIVED = 'vcard_arrived'
-AGENT_REMOVED = 'agent_removed'
-METACONTACTS_ARRIVED = 'metacontacts_arrived'
-ROSTER_ARRIVED = 'roster_arrived'
-DELIMITER_ARRIVED = 'delimiter_arrived'
-PRIVACY_ARRIVED = 'privacy_arrived'
-BLOCKING_ARRIVED = 'blocking_arrived'
-PEP_CONFIG = 'pep_config'
-HAS_IDLE = True
-try:
-# import idle
- import common.sleepy
-except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
-
-
-class ConnectionDisco:
- """
- Holds xmpppy handlers and public methods for discover services
- """
-
- def discoverItems(self, jid, node=None, id_prefix=None):
- """
- According to XEP-0030:
- jid is mandatory;
- name, node, action is optional.
- """
- id_ = self._discover(nbxmpp.NS_DISCO_ITEMS, jid, node, id_prefix)
- self.disco_items_ids.append(id_)
-
- def discoverInfo(self, jid, node=None, id_prefix=None):
- """
- According to XEP-0030:
- For identity: category, type is mandatory, name is optional.
- For feature: var is mandatory.
- """
- id_ = self._discover(nbxmpp.NS_DISCO_INFO, jid, node, id_prefix)
- self.disco_info_ids.append(id_)
-
- def request_register_agent_info(self, agent):
- if not self.connection or self.connected < 2:
- return None
- iq = nbxmpp.Iq('get', nbxmpp.NS_REGISTER, to=agent)
- id_ = self.connection.getAnID()
- iq.setID(id_)
- # Wait the answer during 30 secondes
- self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id_,
- _('Registration information for transport %s has not arrived in '
- 'time') % agent)
- self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
- {'agent': agent})
-
- def _agent_registered_cb(self, con, resp, agent):
- if resp.getType() == 'result':
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='info', pri_txt=_('Registration succeeded'), sec_txt=_(
- 'Registration with agent %s succeeded') % agent))
- self.request_subscription(agent, auto_auth=True)
- self.agent_registrations[agent]['roster_push'] = True
- if self.agent_registrations[agent]['sub_received']:
- p = nbxmpp.Presence(agent, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if resp.getType() == 'error':
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Registration failed'), sec_txt=_(
- 'Registration with agent %(agent)s failed with error %(error)s:'
- ' %(error_msg)s') % {'agent': agent, 'error': resp.getError(),
- 'error_msg': resp.getErrorMsg()}))
-
- def register_agent(self, agent, info, is_form=False):
- if not self.connection or self.connected < 2:
- return
- if is_form:
- iq = nbxmpp.Iq('set', nbxmpp.NS_REGISTER, to=agent)
- query = iq.setQuery()
- info.setAttr('type', 'submit')
- query.addChild(node=info)
- self.connection.SendAndCallForResponse(iq,
- self._agent_registered_cb, {'agent': agent})
- else:
- # fixed: blocking
- nbxmpp.features_nb.register(self.connection, agent, info,
- self._agent_registered_cb, {'agent': agent})
- self.agent_registrations[agent] = {'roster_push': False,
- 'sub_received': False}
-
- def _discover(self, ns, jid, node=None, id_prefix=None):
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='get', to=jid, queryNS=ns)
- id_ = self.connection.getAnID()
- if id_prefix:
- id_ = id_prefix + id_
- iq.setID(id_)
- if node:
- iq.setQuerynode(node)
- self.connection.send(iq)
- return id_
-
- def _ReceivedRegInfo(self, con, resp, agent):
- nbxmpp.features_nb._ReceivedRegInfo(con, resp, agent)
- self._IqCB(con, resp)
-
- def _discoGetCB(self, con, iq_obj):
- """
- Get disco info
- """
- if not self.connection or self.connected < 2:
- return
- frm = helpers.get_full_jid_from_iq(iq_obj)
- to = iq_obj.getAttr('to')
- id_ = iq_obj.getAttr('id')
- iq = nbxmpp.Iq(to=frm, typ='result', queryNS=nbxmpp.NS_DISCO, frm=to)
- iq.setAttr('id', id_)
- query = iq.setTag('query')
- query.setAttr('node', 'http://gajim.org#' + gajim.version.split('-', 1)[
- 0])
- for f in (nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
- nbxmpp.NS_COMMANDS, nbxmpp.NS_JINGLE_FILE_TRANSFER_5,
- nbxmpp.NS_JINGLE_XTLS, nbxmpp.NS_PUBKEY_PUBKEY, nbxmpp.NS_PUBKEY_REVOKE,
- nbxmpp.NS_PUBKEY_ATTEST):
- feature = nbxmpp.Node('feature')
- feature.setAttr('var', f)
- query.addChild(node=feature)
-
- self.connection.send(iq)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverItemsErrorCB(self, con, iq_obj):
- log.debug('DiscoverItemsErrorCB')
- gajim.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _DiscoverItemsCB(self, con, iq_obj):
- log.debug('DiscoverItemsCB')
- gajim.nec.push_incoming_event(AgentItemsReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise nbxmpp.NodeProcessed
- if node == nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverInfoGetCB(self, con, iq_obj):
- log.debug('DiscoverInfoGetCB')
- if not self.connection or self.connected < 2:
- return
- node = iq_obj.getQuerynode()
-
- if self.commandInfoQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
-
- id_ = iq_obj.getAttr('id')
- if id_[:6] == 'Gajim_':
- # We get this request from echo.server
- raise nbxmpp.NodeProcessed
-
- iq = iq_obj.buildReply('result')
- q = iq.setQuery()
- if node:
- q.setAttr('node', node)
- q.addChild('identity', attrs=gajim.gajim_identity)
- client_version = 'http://gajim.org#' + gajim.caps_hash[self.name]
-
- if node in (None, client_version):
- for f in gajim.gajim_common_features:
- q.addChild('feature', attrs={'var': f})
- for f in gajim.gajim_optional_features[self.name]:
- q.addChild('feature', attrs={'var': f})
-
- if q.getChildren():
- self.connection.send(iq)
- raise nbxmpp.NodeProcessed
-
- def _DiscoverInfoErrorCB(self, con, iq_obj):
- log.debug('DiscoverInfoErrorCB')
- gajim.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _DiscoverInfoCB(self, con, iq_obj):
- log.debug('DiscoverInfoCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(AgentInfoReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
-class ConnectionVcard:
- def __init__(self):
- self.vcard_sha = None
- self.vcard_shas = {} # sha of contacts
- # list of gc jids so that vcard are saved in a folder
- self.room_jids = []
-
- def add_sha(self, p, send_caps=True):
- c = p.setTag('x', namespace=nbxmpp.NS_VCARD_UPDATE)
- if self.vcard_sha is not None:
- c.setTagData('photo', self.vcard_sha)
- if send_caps:
- return self._add_caps(p)
- return p
-
- def _add_caps(self, p):
- ''' advertise our capabilities in presence stanza (xep-0115)'''
- c = p.setTag('c', namespace=nbxmpp.NS_CAPS)
- c.setAttr('hash', 'sha-1')
- c.setAttr('node', 'http://gajim.org')
- c.setAttr('ver', gajim.caps_hash[self.name])
- return p
-
- def _node_to_dict(self, node):
- dict_ = {}
- for info in node.getChildren():
- name = info.getName()
- if name in ('ADR', 'TEL', 'EMAIL'): # we can have several
- dict_.setdefault(name, [])
- entry = {}
- for c in info.getChildren():
- entry[c.getName()] = c.getData()
- dict_[name].append(entry)
- elif info.getChildren() == []:
- dict_[name] = info.getData()
- else:
- dict_[name] = {}
- for c in info.getChildren():
- dict_[name][c.getName()] = c.getData()
- return dict_
-
- def _save_vcard_to_hd(self, full_jid, card):
- jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
- puny_jid = helpers.sanitize_filename(jid)
- path = os.path.join(gajim.VCARD_PATH, puny_jid)
- if jid in self.room_jids or os.path.isdir(path):
- if not nick:
- return
- # remove room_jid file if needed
- if os.path.isfile(path):
- os.remove(path)
- # create folder if needed
- if not os.path.isdir(path):
- os.mkdir(path, 0o700)
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = path
- try:
- fil = open(path_to_file, 'w', encoding='utf-8')
- fil.write(str(card))
- fil.close()
- except IOError as e:
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Disk Write Error'), sec_txt=str(e)))
-
- def get_cached_vcard(self, fjid, is_fake_jid=False):
- """
- Return the vcard as a dict.
- Return {} if vcard was too old.
- Return None if we don't have cached vcard.
- """
- jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
- puny_jid = helpers.sanitize_filename(jid)
- if is_fake_jid:
- puny_nick = helpers.sanitize_filename(nick)
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- else:
- path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid)
- if not os.path.isfile(path_to_file):
- return None
- # We have the vcard cached
- f = open(path_to_file, encoding='utf-8')
- c = f.read()
- f.close()
- try:
- card = nbxmpp.Node(node=c)
- except Exception:
- # We are unable to parse it. Remove it
- os.remove(path_to_file)
- return None
- vcard = self._node_to_dict(card)
- if 'PHOTO' in vcard:
- if not isinstance(vcard['PHOTO'], dict):
- del vcard['PHOTO']
- elif 'SHA' in vcard['PHOTO']:
- cached_sha = vcard['PHOTO']['SHA']
- if jid in self.vcard_shas and self.vcard_shas[jid] != \
- cached_sha:
- # user change his vcard so don't use the cached one
- return {}
- vcard['jid'] = jid
- vcard['resource'] = gajim.get_resource_from_jid(fjid)
- return vcard
-
- def request_vcard(self, jid=None, groupchat_jid=None):
- """
- Request the VCARD
-
- If groupchat_jid is not null, it means we request a vcard to a fake jid,
- like in private messages in groupchat. jid can be the real jid of the
- contact, but we want to consider it comes from a fake jid
- """
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='get')
- if jid:
- iq.setTo(jid)
- iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD)
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- j = jid
- if not j:
- j = gajim.get_jid_from_account(self.name)
- self.awaiting_answers[id_] = (VCARD_ARRIVED, j, groupchat_jid)
- if groupchat_jid:
- room_jid = gajim.get_room_and_nick_from_fjid(groupchat_jid)[0]
- if not room_jid in self.room_jids:
- self.room_jids.append(room_jid)
- self.groupchat_jids[id_] = groupchat_jid
- self.connection.send(iq)
-
- def send_vcard(self, vcard):
- if not self.connection or self.connected < 2:
- return
- iq = nbxmpp.Iq(typ='set')
- iq2 = iq.setTag(nbxmpp.NS_VCARD + ' vCard')
- for i in vcard:
- if i == 'jid':
- continue
- if isinstance(vcard[i], dict):
- iq3 = iq2.addChild(i)
- for j in vcard[i]:
- iq3.addChild(j).setData(vcard[i][j])
- elif isinstance(vcard[i], list):
- for j in vcard[i]:
- iq3 = iq2.addChild(i)
- for k in j:
- iq3.addChild(k).setData(j[k])
- else:
- iq2.addChild(i).setData(vcard[i])
-
- id_ = self.connection.getAnID()
- iq.setID(id_)
- self.connection.send(iq)
-
- our_jid = gajim.get_jid_from_account(self.name)
- # Add the sha of the avatar
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- photo_decoded = base64.b64decode(photo.encode('utf-8'))
- gajim.interface.save_avatar_files(our_jid, photo_decoded)
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
- else:
- gajim.interface.remove_avatar_files(our_jid)
-
- self.awaiting_answers[id_] = (VCARD_PUBLISHED, iq2)
-
- def _IqCB(self, con, iq_obj):
- id_ = iq_obj.getID()
-
- gajim.nec.push_incoming_event(NetworkEvent('raw-iq-received',
- conn=self, stanza=iq_obj))
-
- # Check if we were waiting a timeout for this id
- found_tim = None
- for tim in self.awaiting_timeouts:
- if id_ == self.awaiting_timeouts[tim][0]:
- found_tim = tim
- break
- if found_tim:
- del self.awaiting_timeouts[found_tim]
-
- if id_ not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[id_][0] == VCARD_PUBLISHED:
- if iq_obj.getType() == 'result':
- vcard_iq = self.awaiting_answers[id_][1]
- # Save vcard to HD
- if vcard_iq.getTag('PHOTO') and vcard_iq.getTag('PHOTO').getTag(
- 'SHA'):
- new_sha = vcard_iq.getTag('PHOTO').getTagData('SHA')
- else:
- new_sha = ''
-
- # Save it to file
- our_jid = gajim.get_jid_from_account(self.name)
- self._save_vcard_to_hd(our_jid, vcard_iq)
-
- # Send new presence if sha changed and we are not invisible
- if self.vcard_sha != new_sha and gajim.SHOW_LIST[
- self.connected] != 'invisible':
- if not self.connection or self.connected < 2:
- del self.awaiting_answers[id_]
- return
- self.vcard_sha = new_sha
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[
- self.connected])
- p = nbxmpp.Presence(typ=None, priority=self.priority,
- show=sshow, status=self.status)
- p = self.add_sha(p)
- self.connection.send(p)
- gajim.nec.push_incoming_event(VcardPublishedEvent(None,
- conn=self))
- elif iq_obj.getType() == 'error':
- gajim.nec.push_incoming_event(VcardNotPublishedEvent(None,
- conn=self))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == VCARD_ARRIVED:
- # If vcard is empty, we send to the interface an empty vcard so that
- # it knows it arrived
- jid = self.awaiting_answers[id_][1]
- groupchat_jid = self.awaiting_answers[id_][2]
- frm = jid
- if groupchat_jid:
- # We do as if it comes from the fake_jid
- frm = groupchat_jid
- our_jid = gajim.get_jid_from_account(self.name)
- if (not iq_obj.getTag('vCard') and iq_obj.getType() == 'result') or\
- iq_obj.getType() == 'error':
- if id_ in self.groupchat_jids:
- frm = self.groupchat_jids[id_]
- del self.groupchat_jids[id_]
- if frm:
- # Write an empty file
- self._save_vcard_to_hd(frm, '')
- jid, resource = gajim.get_room_and_nick_from_fjid(frm)
- vcard = {'jid': jid, 'resource': resource}
- gajim.nec.push_incoming_event(VcardReceivedEvent(None,
- conn=self, vcard_dict=vcard))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == AGENT_REMOVED:
- jid = self.awaiting_answers[id_][1]
- gajim.nec.push_incoming_event(AgentRemovedEvent(None, conn=self,
- agent=jid))
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
- if not self.connection:
- return
- if iq_obj.getType() == 'result':
- gajim.nec.push_incoming_event(MetacontactsReceivedEvent(None,
- conn=self, stanza=iq_obj))
- else:
- if iq_obj.getErrorCode() not in ('403', '406', '404'):
- self.private_storage_supported = False
- self.get_roster_delimiter()
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == DELIMITER_ARRIVED:
- del self.awaiting_answers[id_]
- if not self.connection:
- return
- if iq_obj.getType() == 'result':
- query = iq_obj.getTag('query')
- if not query:
- return
- delimiter = query.getTagData('roster')
- if delimiter:
- self.nested_group_delimiter = delimiter
- else:
- self.set_roster_delimiter('::')
- else:
- self.private_storage_supported = False
-
- # We can now continue connection by requesting the roster
- self.request_roster()
- elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
- if iq_obj.getType() == 'result':
- if not iq_obj.getTag('query'):
- account_jid = gajim.get_jid_from_account(self.name)
- roster_data = gajim.logger.get_roster(account_jid)
- roster = self.connection.getRoster(force=True)
- roster.setRaw(roster_data)
- self._getRoster()
- elif iq_obj.getType() == 'error':
- self.roster_supported = False
- self.discoverItems(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
- if gajim.config.get_per('accounts', self.name,
- 'use_ft_proxies'):
- self.discover_ft_proxies()
- gajim.nec.push_incoming_event(RosterReceivedEvent(None,
- conn=self))
- GLib.timeout_add_seconds(10, self.discover_servers)
- del self.awaiting_answers[id_]
- elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
- del self.awaiting_answers[id_]
- if iq_obj.getType() != 'error':
- for list_ in iq_obj.getQueryPayload():
- if list_.getName() == 'default':
- self.privacy_default_list = list_.getAttr('name')
- self.get_privacy_list(self.privacy_default_list)
- break
- # Ask metacontacts before roster
- self.get_metacontacts()
- else:
- # That should never happen, but as it's blocking in the
- # connection process, we don't take the risk
- self.privacy_rules_supported = False
- self._continue_connection_request_privacy()
- elif self.awaiting_answers[id_][0] == BLOCKING_ARRIVED:
- del self.awaiting_answers[id_]
- if iq_obj.getType() == 'result':
- list_node = iq_obj.getTag('blocklist')
- if not list_node:
- return
- self.blocked_contacts = []
- for i in list_node.iterTags('item'):
- self.blocked_contacts.append(i.getAttr('jid'))
- elif self.awaiting_answers[id_][0] == PEP_CONFIG:
- del self.awaiting_answers[id_]
- if iq_obj.getType() == 'error':
- return
- if not iq_obj.getTag('pubsub'):
- return
- conf = iq_obj.getTag('pubsub').getTag('configure')
- if not conf:
- return
- node = conf.getAttr('node')
- form_tag = conf.getTag('x', namespace=nbxmpp.NS_DATA)
- if form_tag:
- form = dataforms.ExtendForm(node=form_tag)
- gajim.nec.push_incoming_event(PEPConfigReceivedEvent(None,
- conn=self, node=node, form=form))
-
- def _vCardCB(self, con, vc):
- """
- Called when we receive a vCard Parse the vCard and send it to plugins
- """
- if not vc.getTag('vCard'):
- return
- if not vc.getTag('vCard').getNamespace() == nbxmpp.NS_VCARD:
- return
- id_ = vc.getID()
- frm_iq = vc.getFrom()
- our_jid = gajim.get_jid_from_account(self.name)
- resource = ''
- if id_ in self.groupchat_jids:
- who = self.groupchat_jids[id_]
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- del self.groupchat_jids[id_]
- elif frm_iq:
- who = helpers.get_full_jid_from_iq(vc)
- frm, resource = gajim.get_room_and_nick_from_fjid(who)
- else:
- who = frm = our_jid
- card = vc.getChildren()[0]
- vcard = self._node_to_dict(card)
- photo_decoded = None
- if 'PHOTO' in vcard and isinstance(vcard['PHOTO'], dict) and \
- 'BINVAL' in vcard['PHOTO']:
- photo = vcard['PHOTO']['BINVAL']
- try:
- photo_decoded = base64.b64decode(photo.encode('utf-8'))
- avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
- except Exception:
- avatar_sha = ''
- else:
- avatar_sha = ''
-
- if avatar_sha:
- card.getTag('PHOTO').setTagData('SHA', avatar_sha)
-
- # Save it to file
- self._save_vcard_to_hd(who, card)
- # Save the decoded avatar to a separate file too, and generate files
- # for dbus notifications
- puny_jid = helpers.sanitize_filename(frm)
- puny_nick = None
- begin_path = os.path.join(gajim.AVATAR_PATH, puny_jid)
- frm_jid = frm
- if frm in self.room_jids:
- puny_nick = helpers.sanitize_filename(resource)
- # create folder if needed
- if not os.path.isdir(begin_path):
- os.mkdir(begin_path, 0o700)
- begin_path = os.path.join(begin_path, puny_nick)
- frm_jid += '/' + resource
- if photo_decoded:
- avatar_file = begin_path + '_notif_size_colored.png'
- if frm_jid == our_jid and avatar_sha != self.vcard_sha:
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- elif frm_jid != our_jid and (not os.path.exists(avatar_file) or \
- frm_jid not in self.vcard_shas or \
- avatar_sha != self.vcard_shas[frm_jid]):
- gajim.interface.save_avatar_files(frm, photo_decoded, puny_nick)
- if avatar_sha:
- self.vcard_shas[frm_jid] = avatar_sha
- elif frm in self.vcard_shas:
- del self.vcard_shas[frm]
- else:
- for ext in ('.jpeg', '.png', '_notif_size_bw.png',
- '_notif_size_colored.png'):
- path = begin_path + ext
- if os.path.isfile(path):
- os.remove(path)
-
- vcard['jid'] = frm
- vcard['resource'] = resource
- gajim.nec.push_incoming_event(VcardReceivedEvent(None, conn=self,
- vcard_dict=vcard))
- if frm_jid == our_jid:
- # we re-send our presence with sha if has changed and if we are
- # not invisible
- if self.vcard_sha == avatar_sha:
- return
- self.vcard_sha = avatar_sha
- if gajim.SHOW_LIST[self.connected] == 'invisible':
- return
- if not self.connection:
- return
- sshow = helpers.get_xmpp_show(gajim.SHOW_LIST[self.connected])
- p = nbxmpp.Presence(typ=None, priority=self.priority,
- show=sshow, status=self.status)
- p = self.add_sha(p)
- self.connection.send(p)
-
-
-class ConnectionPEP(object):
-
- def __init__(self, account, dispatcher, pubsub_connection):
- self._account = account
- self._dispatcher = dispatcher
- self._pubsub_connection = pubsub_connection
- self.reset_awaiting_pep()
-
- def pep_change_account_name(self, new_name):
- self._account = new_name
-
- def reset_awaiting_pep(self):
- self.to_be_sent_activity = None
- self.to_be_sent_mood = None
- self.to_be_sent_tune = None
- self.to_be_sent_nick = None
- self.to_be_sent_location = None
-
- def send_awaiting_pep(self):
- """
- Send pep info that were waiting for connection
- """
- if self.to_be_sent_activity:
- self.send_activity(*self.to_be_sent_activity)
- if self.to_be_sent_mood:
- self.send_mood(*self.to_be_sent_mood)
- if self.to_be_sent_tune:
- self.send_tune(*self.to_be_sent_tune)
- if self.to_be_sent_nick:
- self.send_nick(self.to_be_sent_nick)
- if self.to_be_sent_location:
- self.send_location(self.to_be_sent_location)
- self.reset_awaiting_pep()
-
- def _pubsubEventCB(self, xmpp_dispatcher, msg):
- ''' Called when we receive <message /> with pubsub event. '''
- gajim.nec.push_incoming_event(PEPReceivedEvent(None, conn=self,
- stanza=msg))
-
- def send_activity(self, activity, subactivity=None, message=None):
- if self.connected == 1:
- # We are connecting, keep activity in mem and send it when we'll be
- # connected
- self.to_be_sent_activity = (activity, subactivity, message)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('activity', {'xmlns': nbxmpp.NS_ACTIVITY})
- if activity:
- i = item.addChild(activity)
- if subactivity:
- i.addChild(subactivity)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_ACTIVITY, item,
- '0')
-
- def retract_activity(self):
- if not self.pep_supported:
- return
- self.send_activity(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_ACTIVITY, '0')
-
- def send_mood(self, mood, message=None):
- if self.connected == 1:
- # We are connecting, keep mood in mem and send it when we'll be
- # connected
- self.to_be_sent_mood = (mood, message)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('mood', {'xmlns': nbxmpp.NS_MOOD})
- if mood:
- item.addChild(mood)
- if message:
- i = item.addChild('text')
- i.addData(message)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_MOOD, item, '0')
-
- def retract_mood(self):
- if not self.pep_supported:
- return
- self.send_mood(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_MOOD, '0')
-
- def send_tune(self, artist='', title='', source='', track=0, length=0,
- items=None):
- if self.connected == 1:
- # We are connecting, keep tune in mem and send it when we'll be
- # connected
- self.to_be_sent_tune = (artist, title, source, track, length, items)
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('tune', {'xmlns': nbxmpp.NS_TUNE})
- if artist:
- i = item.addChild('artist')
- i.addData(artist)
- if title:
- i = item.addChild('title')
- i.addData(title)
- if source:
- i = item.addChild('source')
- i.addData(source)
- if track:
- i = item.addChild('track')
- i.addData(track)
- if length:
- i = item.addChild('length')
- i.addData(length)
- if items:
- item.addChild(payload=items)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_TUNE, item, '0')
-
- def retract_tune(self):
- if not self.pep_supported:
- return
- self.send_tune(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_TUNE, '0')
-
- def send_nickname(self, nick):
- if self.connected == 1:
- # We are connecting, keep nick in mem and send it when we'll be
- # connected
- self.to_be_sent_nick = nick
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('nick', {'xmlns': nbxmpp.NS_NICK})
- item.addData(nick)
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_NICK, item, '0')
-
- def retract_nickname(self):
- if not self.pep_supported:
- return
- self.send_nickname(None)
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_NICK, '0')
-
- def send_location(self, info):
- if self.connected == 1:
- # We are connecting, keep location in mem and send it when we'll be
- # connected
- self.to_be_sent_location = info
- return
- if not self.pep_supported:
- return
- item = nbxmpp.Node('geoloc', {'xmlns': nbxmpp.NS_LOCATION})
- for field in LOCATION_DATA:
- if info.get(field, None):
- i = item.addChild(field)
- i.addData(info[field])
- self._pubsub_connection.send_pb_publish('', nbxmpp.NS_LOCATION, item, '0')
-
- def retract_location(self):
- if not self.pep_supported:
- return
- self.send_location({})
- # not all client support new XEP, so we still retract
- self._pubsub_connection.send_pb_retract('', nbxmpp.NS_LOCATION, '0')
-
-# basic connection handlers used here and in zeroconf
-class ConnectionHandlersBase:
- def __init__(self):
- # List of IDs we are waiting answers for {id: (type_of_request, data), }
- self.awaiting_answers = {}
- # List of IDs that will produce a timeout is answer doesn't arrive
- # {time_of_the_timeout: (id, message to send to gui), }
- self.awaiting_timeouts = {}
- # keep the jids we auto added (transports contacts) to not send the
- # SUBSCRIBED event to gui
- self.automatically_added = []
- # IDs of jabber:iq:last requests
- self.last_ids = []
-
- # keep track of sessions this connection has with other JIDs
- self.sessions = {}
-
- # IDs of sent messages (https://trac.gajim.org/ticket/8222)
- self.sent_message_ids = []
-
- self.received_message_hashes = []
-
- # We decrypt GPG messages one after the other. Keep queue in mem
- self.gpg_messages_to_decrypt = []
-
- gajim.ged.register_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.register_event_handler('presence-received', ged.CORE,
- self._nec_presence_received)
- gajim.ged.register_event_handler('gc-presence-received', ged.CORE,
- self._nec_gc_presence_received)
- gajim.ged.register_event_handler('message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.register_event_handler('mam-message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.register_event_handler('decrypted-message-received', ged.CORE,
- self._nec_decrypted_message_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.remove_event_handler('presence-received', ged.CORE,
- self._nec_presence_received)
- gajim.ged.remove_event_handler('gc-presence-received', ged.CORE,
- self._nec_gc_presence_received)
- gajim.ged.remove_event_handler('message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.remove_event_handler('mam-message-received', ged.CORE,
- self._nec_message_received)
- gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE,
- self._nec_decrypted_message_received)
-
- def _nec_iq_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_ in self.last_ids:
- gajim.nec.push_incoming_event(LastResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
-
- def _nec_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- jid = obj.jid
- resource = obj.resource or ''
-
- statuss = ['offline', 'error', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
- obj.old_show = 0
- obj.new_show = statuss.index(obj.show)
-
- obj.contact_list = []
-
- highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
- obj.was_highest = (highest and highest.resource == resource)
-
- # Update contact
- obj.contact_list = gajim.contacts.get_contacts(account, jid)
- obj.contact = None
- resources = []
- for c in obj.contact_list:
- resources.append(c.resource)
- if c.resource == resource:
- obj.contact = c
- break
-
- if obj.avatar_sha is not None and obj.ptype != 'error':
- if obj.jid not in self.vcard_shas:
- cached_vcard = self.get_cached_vcard(obj.jid)
- if cached_vcard and 'PHOTO' in cached_vcard and \
- 'SHA' in cached_vcard['PHOTO']:
- self.vcard_shas[obj.jid] = cached_vcard['PHOTO']['SHA']
- else:
- self.vcard_shas[obj.jid] = ''
- if obj.avatar_sha != self.vcard_shas[obj.jid]:
- # avatar has been updated
- self.request_vcard(obj.jid)
-
- if obj.contact:
- if obj.contact.show in statuss:
- obj.old_show = statuss.index(obj.contact.show)
- # nick changed
- if obj.contact_nickname is not None and \
- obj.contact.contact_name != obj.contact_nickname:
- obj.contact.contact_name = obj.contact_nickname
- obj.need_redraw = True
-
- if obj.old_show == obj.new_show and obj.contact.status == \
- obj.status and obj.contact.priority == obj.prio: # no change
- return True
- else:
- obj.contact = gajim.contacts.get_first_contact_from_jid(account,
- jid)
- if not obj.contact:
- # Presence of another resource of our jid
- # Create self contact and add to roster
- if resource == obj.conn.server_resource:
- return
- # Ignore offline presence of unknown self resource
- if obj.new_show < 2:
- return
- obj.contact = gajim.contacts.create_self_contact(jid=jid,
- account=account, show=obj.show, status=obj.status,
- priority=obj.prio, keyID=obj.keyID,
- resource=obj.resource)
- gajim.contacts.add_contact(account, obj.contact)
- obj.contact_list.append(obj.contact)
- elif obj.contact.show in statuss:
- obj.old_show = statuss.index(obj.contact.show)
- if (resources != [''] and (len(obj.contact_list) != 1 or \
- obj.contact_list[0].show not in ('not in roster', 'offline'))) and \
- not gajim.jid_is_transport(jid):
- # Another resource of an existing contact connected
- obj.old_show = 0
- obj.contact = gajim.contacts.copy_contact(obj.contact)
- obj.contact_list.append(obj.contact)
- obj.contact.resource = resource
-
- obj.need_add_in_roster = True
-
- if not gajim.jid_is_transport(jid) and len(obj.contact_list) == 1:
- # It's not an agent
- if obj.old_show == 0 and obj.new_show > 1:
- if not jid in gajim.newly_added[account]:
- gajim.newly_added[account].append(jid)
- if jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].remove(jid)
- elif obj.old_show > 1 and obj.new_show == 0 and \
- obj.conn.connected > 1:
- if not jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].append(jid)
- if jid in gajim.newly_added[account]:
- gajim.newly_added[account].remove(jid)
- obj.need_redraw = True
-
- obj.contact.show = obj.show
- obj.contact.status = obj.status
- obj.contact.priority = obj.prio
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- obj.contact.keyID = attached_keys[attached_keys.index(jid) + 1]
- else:
- # Do not override assigned key
- obj.contact.keyID = obj.keyID
- if obj.timestamp:
- obj.contact.last_status_time = localtime(obj.timestamp)
- elif not gajim.block_signed_in_notifications[account]:
- # We're connected since more that 30 seconds
- obj.contact.last_status_time = localtime()
- obj.contact.contact_nickname = obj.contact_nickname
-
- if gajim.jid_is_transport(jid):
- return
-
- # It isn't an agent
- # reset chatstate if needed:
- # (when contact signs out or has errors)
- if obj.show in ('offline', 'error'):
- obj.contact.our_chatstate = obj.contact.chatstate = None
-
- # TODO: This causes problems when another
- # resource signs off!
- self.stop_all_active_file_transfers(obj.contact)
-
- # disable encryption, since if any messages are
- # lost they'll be not decryptable (note that
- # this contradicts XEP-0201 - trying to get that
- # in the XEP, though)
-
- # there won't be any sessions here if the contact terminated
- # their sessions before going offline (which we do)
- for sess in self.get_sessions(jid):
- sess_fjid = sess.jid.getStripped()
- if sess.resource:
- sess_fjid += '/' + sess.resource
- if obj.fjid != sess_fjid:
- continue
- if sess.control:
- sess.control.no_autonegotiation = False
- if sess.enable_encryption:
- sess.terminate_e2e()
-
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.name, obj.jid):
- gajim.logger.write('status', obj.jid, obj.status, obj.show)
-
- def _nec_gc_presence_received(self, obj):
- if obj.conn.name != self.name:
- return
- for sess in self.get_sessions(obj.fjid):
- if obj.fjid != sess.jid:
- continue
- if sess.enable_encryption:
- sess.terminate_e2e()
-
- def _nec_message_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- gajim.plugin_manager.extension_point(
- 'decrypt', self, obj, self._on_message_received)
- if not obj.encrypted:
- self._on_message_received(obj)
-
- def _on_message_received(self, obj):
- if isinstance(obj, MessageReceivedEvent):
- gajim.nec.push_incoming_event(
- DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
- else:
- gajim.nec.push_incoming_event(
- MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
-
- def _nec_decrypted_message_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- # Receipt requested
- # TODO: We shouldn't answer if we're invisible!
- contact = gajim.contacts.get_contact(self.name, obj.jid)
- nick = obj.resource
- gc_contact = gajim.contacts.get_gc_contact(self.name, obj.jid, nick)
- if obj.sent:
- jid_to = obj.stanza.getFrom()
- else:
- jid_to = obj.stanza.getTo()
- reply = False
- if not jid_to:
- reply = True
- else:
- fjid_to = helpers.parse_jid(str(jid_to))
- jid_to = gajim.get_jid_without_resource(fjid_to)
- if jid_to == gajim.get_jid_from_account(self.name):
- reply = True
-
- if obj.jid != gajim.get_jid_from_account(self.name):
- if obj.receipt_request_tag and gajim.config.get_per('accounts',
- self.name, 'answer_receipts') and ((contact and contact.sub \
- not in ('to', 'none')) or gc_contact) and obj.mtype != 'error' and \
- reply:
- receipt = nbxmpp.Message(to=obj.fjid, typ='chat')
- receipt.setTag('received', namespace='urn:xmpp:receipts',
- attrs={'id': obj.id_})
-
- if obj.thread_id:
- receipt.setThread(obj.thread_id)
- self.connection.send(receipt)
-
- # We got our message's receipt
- if obj.receipt_received_tag and gajim.config.get_per('accounts',
- self.name, 'request_receipt'):
- ctrl = obj.session.control
- if not ctrl:
- # Received <message> doesn't have the <thread> element
- # or control is not bound to session?
- # --> search for it
- ctrl = gajim.interface.msg_win_mgr.search_control(obj.jid,
- obj.conn.name, obj.resource)
-
- if ctrl:
- id_ = obj.receipt_received_tag.getAttr('id')
- if not id_:
- # old XEP implementation
- id_ = obj.id_
- ctrl.conv_textview.show_xep0184_ack(id_)
-
- if obj.mtype == 'error':
- if not obj.msgtxt:
- obj.msgtxt = _('message')
- self.dispatch_error_message(obj.stanza, obj.msgtxt,
- obj.session, obj.fjid, obj.timestamp)
- return True
- elif obj.mtype == 'groupchat':
- gajim.nec.push_incoming_event(GcMessageReceivedEvent(None,
- conn=self, msg_obj=obj))
- return True
-
- # process and dispatch an error message
- def dispatch_error_message(self, msg, msgtxt, session, frm, tim):
- error_msg = msg.getErrorMsg()
-
- if not error_msg:
- error_msg = msgtxt
- msgtxt = None
-
- subject = msg.getSubject()
-
- if session.is_loggable():
- gajim.logger.write('error', frm, error_msg, tim=tim,
- subject=subject)
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=frm, error_code=msg.getErrorCode(), error_msg=error_msg,
- msg=msgtxt, time_=tim, session=session, stanza=msg))
-
- def _LastResultCB(self, con, iq_obj):
- log.debug('LastResultCB')
- gajim.nec.push_incoming_event(LastResultReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def get_sessions(self, jid):
- """
- Get all sessions for the given full jid
- """
- if not gajim.interface.is_pm_contact(jid, self.name):
- jid = gajim.get_jid_without_resource(jid)
-
- try:
- return list(self.sessions[jid].values())
- except KeyError:
- return []
-
- def get_or_create_session(self, fjid, thread_id):
- """
- Return an existing session between this connection and 'jid', returns a
- new one if none exist
- """
- pm = True
- jid = fjid
-
- if not gajim.interface.is_pm_contact(fjid, self.name):
- pm = False
- jid = gajim.get_jid_without_resource(fjid)
-
- session = self.find_session(jid, thread_id)
-
- if session:
- return session
-
- if pm:
- return self.make_new_session(fjid, thread_id, type_='pm')
- else:
- return self.make_new_session(fjid, thread_id)
-
- def find_session(self, jid, thread_id):
- try:
- if not thread_id:
- return self.find_null_session(jid)
- else:
- return self.sessions[jid][thread_id]
- except KeyError:
- return None
-
- def terminate_sessions(self, send_termination=False):
- """
- Send termination messages and delete all active sessions
- """
- for jid in self.sessions:
- for thread_id in self.sessions[jid]:
- self.sessions[jid][thread_id].terminate(send_termination)
-
- self.sessions = {}
-
- def delete_session(self, jid, thread_id):
- if not jid in self.sessions:
- jid = gajim.get_jid_without_resource(jid)
- if not jid in self.sessions:
- return
-
- del self.sessions[jid][thread_id]
-
- if not self.sessions[jid]:
- del self.sessions[jid]
-
- def find_null_session(self, jid):
- """
- Find all of the sessions between us and a remote jid in which we haven't
- received a thread_id yet and returns the session that we last sent a
- message to
- """
- sessions = list(self.sessions[jid].values())
-
- # sessions that we haven't received a thread ID in
- idless = [s for s in sessions if not s.received_thread_id]
-
- # filter out everything except the default session type
- chat_sessions = [s for s in idless if isinstance(s,
- gajim.default_session_type)]
-
- if chat_sessions:
- # return the session that we last sent a message in
- return sorted(chat_sessions, key=operator.attrgetter('last_send'))[
- -1]
- else:
- return None
-
- def get_latest_session(self, jid):
- """
- Get the session that we last sent a message to
- """
- if jid not in self.sessions:
- return None
- sessions = self.sessions[jid].values()
- if not sessions:
- return None
- return sorted(sessions, key=operator.attrgetter('last_send'))[-1]
-
- def find_controlless_session(self, jid, resource=None):
- """
- Find an active session that doesn't have a control attached
- """
- try:
- sessions = list(self.sessions[jid].values())
-
- # filter out everything except the default session type
- chat_sessions = [s for s in sessions if isinstance(s,
- gajim.default_session_type)]
-
- orphaned = [s for s in chat_sessions if not s.control]
-
- if resource:
- orphaned = [s for s in orphaned if s.resource == resource]
-
- return orphaned[0]
- except (KeyError, IndexError):
- return None
-
- def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
- """
- Create and register a new session
-
- thread_id=None to generate one.
- type_ should be 'chat' or 'pm'.
- """
- if not cls:
- cls = gajim.default_session_type
-
- sess = cls(self, nbxmpp.JID(jid), thread_id, type_)
-
- # determine if this session is a pm session
- # if not, discard the resource so that all sessions are stored bare
- if not type_ == 'pm':
- jid = gajim.get_jid_without_resource(jid)
-
- if not jid in self.sessions:
- self.sessions[jid] = {}
-
- self.sessions[jid][sess.thread_id] = sess
-
- return sess
-
-class ConnectionHandlers(ConnectionArchive136, ConnectionArchive313,
-ConnectionVcard, ConnectionSocks5Bytestream, ConnectionDisco,
-ConnectionCommands, ConnectionPubSub, ConnectionPEP, ConnectionCaps,
-ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
- def __init__(self):
- global HAS_IDLE
- ConnectionArchive136.__init__(self)
- ConnectionArchive313.__init__(self)
- ConnectionVcard.__init__(self)
- ConnectionSocks5Bytestream.__init__(self)
- ConnectionIBBytestream.__init__(self)
- ConnectionCommands.__init__(self)
- ConnectionPubSub.__init__(self)
- ConnectionPEP.__init__(self, account=self.name, dispatcher=self,
- pubsub_connection=self)
-
- # Handle presences BEFORE caps
- gajim.nec.register_incoming_event(PresenceReceivedEvent)
-
- ConnectionCaps.__init__(self, account=self.name,
- capscache=capscache.capscache,
- client_caps_factory=capscache.create_suitable_client_caps)
- ConnectionJingle.__init__(self)
- ConnectionHandlersBase.__init__(self)
- self.gmail_url = None
-
- # keep the latest subscribed event for each jid to prevent loop when we
- # acknowledge presences
- self.subscribed_events = {}
- # IDs of jabber:iq:version requests
- self.version_ids = []
- # IDs of urn:xmpp:time requests
- self.entity_time_ids = []
- # IDs of disco#items requests
- self.disco_items_ids = []
- # IDs of disco#info requests
- self.disco_info_ids = []
- # ID of urn:xmpp:ping requests
- self.awaiting_xmpp_ping_id = None
- self.continue_connect_info = None
-
- self.privacy_default_list = None
-
- try:
- self.sleeper = common.sleepy.Sleepy()
- HAS_IDLE = True
- except Exception:
- HAS_IDLE = False
-
- self.gmail_last_tid = None
- self.gmail_last_time = None
-
- gajim.nec.register_incoming_event(PrivateStorageBookmarksReceivedEvent)
- gajim.nec.register_incoming_event(BookmarksReceivedEvent)
- gajim.nec.register_incoming_event(
- PrivateStorageRosternotesReceivedEvent)
- gajim.nec.register_incoming_event(RosternotesReceivedEvent)
- gajim.nec.register_incoming_event(StreamConflictReceivedEvent)
- gajim.nec.register_incoming_event(StreamOtherHostReceivedEvent)
- gajim.nec.register_incoming_event(MessageReceivedEvent)
- gajim.nec.register_incoming_event(ArchivingErrorReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingPreferencesChangedReceivedEvent)
- gajim.nec.register_incoming_event(
- Archiving313PreferencesChangedReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingFinishedLegacyReceivedEvent)
- gajim.nec.register_incoming_event(
- ArchivingFinishedReceivedEvent)
- gajim.nec.register_incoming_event(NotificationEvent)
-
- gajim.ged.register_event_handler('http-auth-received', ged.CORE,
- self._nec_http_auth_received)
- gajim.ged.register_event_handler('version-request-received', ged.CORE,
- self._nec_version_request_received)
- gajim.ged.register_event_handler('last-request-received', ged.CORE,
- self._nec_last_request_received)
- gajim.ged.register_event_handler('time-request-received', ged.CORE,
- self._nec_time_request_received)
- gajim.ged.register_event_handler('time-revised-request-received',
- ged.CORE, self._nec_time_revised_request_received)
- gajim.ged.register_event_handler('roster-set-received',
- ged.CORE, self._nec_roster_set_received)
- gajim.ged.register_event_handler('private-storage-bookmarks-received',
- ged.CORE, self._nec_private_storate_bookmarks_received)
- gajim.ged.register_event_handler('private-storage-rosternotes-received',
- ged.CORE, self._nec_private_storate_rosternotes_received)
- gajim.ged.register_event_handler('roster-received', ged.CORE,
- self._nec_roster_received)
- gajim.ged.register_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.register_event_handler('gmail-new-mail-received', ged.CORE,
- self._nec_gmail_new_mail_received)
- gajim.ged.register_event_handler('ping-received', ged.CORE,
- self._nec_ping_received)
- gajim.ged.register_event_handler('subscribe-presence-received',
- ged.CORE, self._nec_subscribe_presence_received)
- gajim.ged.register_event_handler('subscribed-presence-received',
- ged.CORE, self._nec_subscribed_presence_received)
- gajim.ged.register_event_handler('subscribed-presence-received',
- ged.POSTGUI, self._nec_subscribed_presence_received_end)
- gajim.ged.register_event_handler('unsubscribed-presence-received',
- ged.CORE, self._nec_unsubscribed_presence_received)
- gajim.ged.register_event_handler('unsubscribed-presence-received',
- ged.POSTGUI, self._nec_unsubscribed_presence_received_end)
- gajim.ged.register_event_handler('agent-removed', ged.CORE,
- self._nec_agent_removed)
- gajim.ged.register_event_handler('stream-other-host-received', ged.CORE,
- self._nec_stream_other_host_received)
- gajim.ged.register_event_handler('blocking', ged.CORE,
- self._nec_blocking)
-
- def cleanup(self):
- ConnectionHandlersBase.cleanup(self)
- ConnectionCaps.cleanup(self)
- ConnectionArchive136.cleanup(self)
- ConnectionArchive313.cleanup(self)
- ConnectionPubSub.cleanup(self)
- gajim.ged.remove_event_handler('http-auth-received', ged.CORE,
- self._nec_http_auth_received)
- gajim.ged.remove_event_handler('version-request-received', ged.CORE,
- self._nec_version_request_received)
- gajim.ged.remove_event_handler('last-request-received', ged.CORE,
- self._nec_last_request_received)
- gajim.ged.remove_event_handler('time-request-received', ged.CORE,
- self._nec_time_request_received)
- gajim.ged.remove_event_handler('time-revised-request-received',
- ged.CORE, self._nec_time_revised_request_received)
- gajim.ged.remove_event_handler('roster-set-received',
- ged.CORE, self._nec_roster_set_received)
- gajim.ged.remove_event_handler('private-storage-bookmarks-received',
- ged.CORE, self._nec_private_storate_bookmarks_received)
- gajim.ged.remove_event_handler('private-storage-rosternotes-received',
- ged.CORE, self._nec_private_storate_rosternotes_received)
- gajim.ged.remove_event_handler('roster-received', ged.CORE,
- self._nec_roster_received)
- gajim.ged.remove_event_handler('iq-error-received', ged.CORE,
- self._nec_iq_error_received)
- gajim.ged.remove_event_handler('gmail-new-mail-received', ged.CORE,
- self._nec_gmail_new_mail_received)
- gajim.ged.remove_event_handler('ping-received', ged.CORE,
- self._nec_ping_received)
- gajim.ged.remove_event_handler('subscribe-presence-received',
- ged.CORE, self._nec_subscribe_presence_received)
- gajim.ged.remove_event_handler('subscribed-presence-received',
- ged.CORE, self._nec_subscribed_presence_received)
- gajim.ged.remove_event_handler('subscribed-presence-received',
- ged.POSTGUI, self._nec_subscribed_presence_received_end)
- gajim.ged.remove_event_handler('unsubscribed-presence-received',
- ged.CORE, self._nec_unsubscribed_presence_received)
- gajim.ged.remove_event_handler('unsubscribed-presence-received',
- ged.POSTGUI, self._nec_unsubscribed_presence_received_end)
- gajim.ged.remove_event_handler('agent-removed', ged.CORE,
- self._nec_agent_removed)
- gajim.ged.remove_event_handler('stream-other-host-received', ged.CORE,
- self._nec_stream_other_host_received)
- gajim.ged.remove_event_handler('blocking', ged.CORE, self._nec_blocking)
-
- def build_http_auth_answer(self, iq_obj, answer):
- if not self.connection or self.connected < 2:
- return
- if answer == 'yes':
- confirm = iq_obj.getTag('confirm')
- reply = iq_obj.buildReply('result')
- if iq_obj.getName() == 'message':
- reply.addChild(node=confirm)
- self.connection.send(reply)
- elif answer == 'no':
- err = nbxmpp.Error(iq_obj, nbxmpp.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.stanza, obj.opt)
- return True
-
- def _HttpAuthCB(self, con, iq_obj):
- log.debug('HttpAuthCB')
- gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _ErrorCB(self, con, iq_obj):
- log.debug('ErrorCB')
- gajim.nec.push_incoming_event(IqErrorReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _nec_iq_error_received(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.id_ in self.version_ids:
- gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.entity_time_ids:
- gajim.nec.push_incoming_event(TimeResultReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.disco_items_ids:
- gajim.nec.push_incoming_event(AgentItemsErrorReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
- if obj.id_ in self.disco_info_ids:
- gajim.nec.push_incoming_event(AgentInfoErrorReceivedEvent(None,
- conn=self, stanza=obj.stanza))
- return True
-
- def _nec_private_storate_bookmarks_received(self, obj):
- if obj.conn.name != self.name:
- return
- resend_to_pubsub = False
- bm_jids = [b['jid'] for b in self.bookmarks]
- for bm in obj.bookmarks:
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
- # We got a bookmark that was not in pubsub
- resend_to_pubsub = True
- if self.pubsub_supported and resend_to_pubsub:
- self.store_bookmarks('pubsub')
-
- def _nec_private_storate_rosternotes_received(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.annotations:
- self.annotations[jid] = obj.annotations[jid]
-
- def _PrivateCB(self, con, iq_obj):
- """
- Private Data (XEP 048 and 049)
- """
- log.debug('PrivateCB')
- gajim.nec.push_incoming_event(PrivateStorageReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _SecLabelCB(self, con, iq_obj):
- """
- Security Label callback, used for catalogues.
- """
- log.debug('SecLabelCB')
- query = iq_obj.getTag('catalog')
- to = query.getAttr('to')
- items = query.getTags('item')
- labels = {}
- ll = []
- default = None
- for item in items:
- label = item.getAttr('selector')
- labels[label] = item.getTag('securitylabel')
- ll.append(label)
- if item.getAttr('default') == 'true':
- default = label
- if to not in self.seclabel_catalogues:
- self.seclabel_catalogues[to] = [[], None, None, None]
- self.seclabel_catalogues[to][1] = labels
- self.seclabel_catalogues[to][2] = ll
- self.seclabel_catalogues[to][3] = default
- for callback in self.seclabel_catalogues[to][0]:
- callback()
- self.seclabel_catalogues[to][0] = []
-
- def seclabel_catalogue_request(self, to, callback):
- if to not in self.seclabel_catalogues:
- self.seclabel_catalogues[to] = [[], None, None, None]
- self.seclabel_catalogues[to][0].append(callback)
-
- def _rosterSetCB(self, con, iq_obj):
- log.debug('rosterSetCB')
- gajim.nec.push_incoming_event(RosterSetReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_roster_set_received(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.items:
- item = obj.items[jid]
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=item['name'], sub=item['sub'],
- ask=item['ask'], groups=item['groups']))
- account_jid = gajim.get_jid_from_account(self.name)
- gajim.logger.add_or_update_contact(account_jid, jid, item['name'],
- item['sub'], item['ask'], item['groups'])
- if obj.version:
- gajim.config.set_per('accounts', self.name, 'roster_version',
- obj.version)
-
- def _VersionCB(self, con, iq_obj):
- log.debug('VersionCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(VersionRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_version_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- send_os = gajim.config.get_per('accounts', self.name, 'send_os_info')
- if send_os:
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.getQuery()
- qp.setTagData('name', 'Gajim')
- qp.setTagData('version', gajim.version)
- qp.setTagData('os', helpers.get_os_info())
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _LastCB(self, con, iq_obj):
- log.debug('LastCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(LastRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_last_request_received(self, obj):
- global HAS_IDLE
- if obj.conn.name != self.name:
- return
- if HAS_IDLE and gajim.config.get_per('accounts', self.name,
- 'send_idle_time'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setQuery()
- qp.attrs['seconds'] = int(self.sleeper.getIdleSec())
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _VersionResultCB(self, con, iq_obj):
- log.debug('VersionResultCB')
- gajim.nec.push_incoming_event(VersionResultReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _TimeCB(self, con, iq_obj):
- log.debug('TimeCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(TimeRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_time_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- if gajim.config.get_per('accounts', self.name, 'send_time_info'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setQuery()
- qp.setTagData('utc', strftime('%Y%m%dT%H:%M:%S', gmtime()))
- qp.setTagData('tz', tzname[daylight])
- qp.setTagData('display', strftime('%c', localtime()))
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _TimeRevisedCB(self, con, iq_obj):
- log.debug('TimeRevisedCB')
- if not self.connection or self.connected < 2:
- return
- gajim.nec.push_incoming_event(TimeRevisedRequestEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_time_revised_request_received(self, obj):
- if obj.conn.name != self.name:
- return
- if gajim.config.get_per('accounts', self.name, 'send_time_info'):
- iq_obj = obj.stanza.buildReply('result')
- qp = iq_obj.setTag('time', namespace=nbxmpp.NS_TIME_REVISED)
- qp.setTagData('utc', strftime('%Y-%m-%dT%H:%M:%SZ', gmtime()))
- isdst = localtime().tm_isdst
- zone = -(timezone, altzone)[isdst] / 60.0
- tzo = (zone / 60, abs(zone % 60))
- qp.setTagData('tzo', '%+03d:%02d' % (tzo))
- else:
- iq_obj = obj.stanza.buildReply('error')
- err = nbxmpp.ErrorNode(name=nbxmpp.NS_STANZAS + \
- ' service-unavailable')
- iq_obj.addChild(node=err)
- self.connection.send(iq_obj)
-
- def _TimeRevisedResultCB(self, con, iq_obj):
- log.debug('TimeRevisedResultCB')
- gajim.nec.push_incoming_event(TimeResultReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _gMailNewMailCB(self, con, iq_obj):
- """
- Called when we get notified of new mail messages in gmail account
- """
- log.debug('gMailNewMailCB')
- gajim.nec.push_incoming_event(GmailNewMailReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_gmail_new_mail_received(self, obj):
- if obj.conn.name != self.name:
- return
- if not self.connection or self.connected < 2:
- return
- # we'll now ask the server for the exact number of new messages
- jid = gajim.get_jid_from_account(self.name)
- log.debug('Got notification of new gmail e-mail on %s. Asking the '
- 'server for more info.' % jid)
- iq = nbxmpp.Iq(typ='get')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
- # we want only be notified about newer mails
- if self.gmail_last_tid:
- query.setAttr('newer-than-tid', self.gmail_last_tid)
- if self.gmail_last_time:
- query.setAttr('newer-than-time', self.gmail_last_time)
- self.connection.send(iq)
-
- def _gMailQueryCB(self, con, iq_obj):
- """
- Called when we receive results from Querying the server for mail messages
- in gmail account
- """
- log.debug('gMailQueryCB')
- gajim.nec.push_incoming_event(GMailQueryReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _rosterItemExchangeCB(self, con, msg):
- """
- XEP-0144 Roster Item Echange
- """
- log.debug('rosterItemExchangeCB')
- gajim.nec.push_incoming_event(RosterItemExchangeEvent(None, conn=self,
- stanza=msg))
- raise nbxmpp.NodeProcessed
-
- def _messageCB(self, con, msg):
- """
- Called when we receive a message
- """
- log.debug('MessageCB')
-
- gajim.nec.push_incoming_event(NetworkEvent('raw-message-received',
- conn=self, stanza=msg, account=self.name))
-
- def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
- msg_obj.stanza = stanza
- gajim.nec.push_incoming_event(GcMessageReceivedEvent(None,
- conn=self, msg_obj=msg_obj))
-
- def _on_bob_received(self, conn, result, cid):
- """
- Called when we receive BoB data
- """
- if cid not in self.awaiting_cids:
- return
-
- if result.getType() == 'result':
- data = msg.getTags('data', namespace=nbxmpp.NS_BOB)
- if data.getAttr('cid') == cid:
- for func in self.awaiting_cids[cid]:
- cb = func[0]
- args = func[1]
- pos = func[2]
- bob_data = data.getData()
- def recurs(node, cid, data):
- if node.getData() == 'cid:' + cid:
- node.setData(data)
- else:
- for child in node.getChildren():
- recurs(child, cid, data)
- recurs(args[pos], cid, bob_data)
- cb(*args)
- del self.awaiting_cids[cid]
- return
-
- # An error occured, call callback without modifying data.
- for func in self.awaiting_cids[cid]:
- cb = func[0]
- args = func[1]
- cb(*args)
- del self.awaiting_cids[cid]
-
- def get_bob_data(self, cid, to, callback, args, position):
- """
- Request for BoB (XEP-0231) and when data will arrive, call callback
- with given args, after having replaced cid by it's data in
- args[position]
- """
- if cid in self.awaiting_cids:
- self.awaiting_cids[cid].appends((callback, args, position))
- else:
- self.awaiting_cids[cid] = [(callback, args, position)]
- iq = nbxmpp.Iq(to=to, typ='get')
- data = iq.addChild(name='data', attrs={'cid': cid},
- namespace=nbxmpp.NS_BOB)
- self.connection.SendAndCallForResponse(iq, self._on_bob_received,
- {'cid': cid})
-
- def _presenceCB(self, con, prs):
- """
- Called when we receive a presence
- """
- log.debug('PresenceCB')
- gajim.nec.push_incoming_event(NetworkEvent('raw-pres-received',
- conn=self, stanza=prs))
-
- def _nec_subscribe_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if gajim.jid_is_transport(obj.fjid) and obj.fjid in \
- self.agent_registrations:
- self.agent_registrations[obj.fjid]['sub_received'] = True
- if not self.agent_registrations[obj.fjid]['roster_push']:
- # We'll reply after roster push result
- return True
- if gajim.config.get_per('accounts', self.name, 'autoauth') or \
- gajim.jid_is_transport(obj.fjid) or obj.jid in self.jids_for_auto_auth \
- or obj.transport_auto_auth:
- if self.connection:
- p = nbxmpp.Presence(obj.fjid, 'subscribed')
- p = self.add_sha(p)
- self.connection.send(p)
- if gajim.jid_is_transport(obj.fjid) or obj.transport_auto_auth:
- #TODO!?!?
- #self.show = 'offline'
- #self.status = 'offline'
- #emit NOTIFY
- pass
- if obj.transport_auto_auth:
- self.automatically_added.append(obj.jid)
- self.request_subscription(obj.jid, name=obj.user_nick)
- return True
- if not obj.status:
- obj.status = _('I would like to add you to my roster.')
-
- def _nec_subscribed_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- # BE CAREFUL: no con.updateRosterItem() in a callback
- if obj.jid in self.automatically_added:
- self.automatically_added.remove(obj.jid)
- return True
- # detect a subscription loop
- if obj.jid not in self.subscribed_events:
- self.subscribed_events[obj.jid] = []
- self.subscribed_events[obj.jid].append(time_time())
- block = False
- if len(self.subscribed_events[obj.jid]) > 5:
- if time_time() - self.subscribed_events[obj.jid][0] < 5:
- block = True
- self.subscribed_events[obj.jid] = \
- self.subscribed_events[obj.jid][1:]
- if block:
- gajim.config.set_per('account', self.name, 'dont_ack_subscription',
- True)
- return True
-
- def _nec_subscribed_presence_received_end(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if not gajim.config.get_per('accounts', account,
- 'dont_ack_subscription'):
- self.ack_subscribed(obj.jid)
-
- def _nec_unsubscribed_presence_received(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- # detect a unsubscription loop
- if obj.jid not in self.subscribed_events:
- self.subscribed_events[obj.jid] = []
- self.subscribed_events[obj.jid].append(time_time())
- block = False
- if len(self.subscribed_events[obj.jid]) > 5:
- if time_time() - self.subscribed_events[obj.jid][0] < 5:
- block = True
- self.subscribed_events[obj.jid] = \
- self.subscribed_events[obj.jid][1:]
- if block:
- gajim.config.set_per('account', self.name, 'dont_ack_subscription',
- True)
- return True
-
- def _nec_unsubscribed_presence_received_end(self, obj):
- account = obj.conn.name
- if account != self.name:
- return
- if not gajim.config.get_per('accounts', account,
- 'dont_ack_subscription'):
- self.ack_unsubscribed(obj.jid)
-
- def _nec_agent_removed(self, obj):
- if obj.conn.name != self.name:
- return
- for jid in obj.jid_list:
- log.debug('Removing contact %s due to unregistered transport %s' % \
- (jid, obj.agent))
- self.unsubscribe(jid)
- # Transport contacts can't have 2 resources
- if jid in gajim.to_be_removed[self.name]:
- # This way we'll really remove it
- gajim.to_be_removed[self.name].remove(jid)
-
- def _StanzaArrivedCB(self, con, obj):
- self.last_io = gajim.idlequeue.current_time()
-
- def _MucOwnerCB(self, con, iq_obj):
- log.debug('MucOwnerCB')
- gajim.nec.push_incoming_event(MucOwnerReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _MucAdminCB(self, con, iq_obj):
- log.debug('MucAdminCB')
- gajim.nec.push_incoming_event(MucAdminReceivedEvent(None, conn=self,
- stanza=iq_obj))
-
- def _IqPingCB(self, con, iq_obj):
- log.debug('IqPingCB')
- gajim.nec.push_incoming_event(PingReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_ping_received(self, obj):
- if obj.conn.name != self.name:
- return
- if not self.connection or self.connected < 2:
- return
- iq_obj = obj.stanza.buildReply('result')
- q = iq_obj.getTag('ping')
- if q:
- iq_obj.delChild(q)
- self.connection.send(iq_obj)
-
- def _PrivacySetCB(self, con, iq_obj):
- """
- Privacy lists (XEP 016)
-
- A list has been set.
- """
- log.debug('PrivacySetCB')
- if not self.connection or self.connected < 2:
- return
- result = iq_obj.buildReply('result')
- q = result.getTag('query')
- if q:
- result.delChild(q)
- self.connection.send(result)
-
- for list_ in iq_obj.getQueryPayload():
- if list_.getName() == 'list':
- self.get_privacy_list(list_.getAttr('name'))
-
- raise nbxmpp.NodeProcessed
-
- def _getRoster(self):
- log.debug('getRosterCB')
- if not self.connection:
- return
- self.connection.getRoster(self._on_roster_set)
- self.discoverItems(gajim.config.get_per('accounts', self.name,
- 'hostname'), id_prefix='Gajim_')
- if gajim.config.get_per('accounts', self.name, 'use_ft_proxies'):
- self.discover_ft_proxies()
-
- def discover_ft_proxies(self):
- cfg_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) + \
- '/' + self.server_resource)
- testit = gajim.config.get_per('accounts', self.name,
- 'test_ft_proxies_on_startup')
- if cfg_proxies:
- proxies = [e.strip() for e in cfg_proxies.split(',')]
- for proxy in proxies:
- gajim.proxy65_manager.resolve(proxy, self.connection, our_jid,
- testit=testit)
-
- def discover_servers(self):
- if not self.connection:
- return
- servers = []
- for c in gajim.contacts.iter_contacts(self.name):
- s = gajim.get_server_from_jid(c.jid)
- if s not in servers and s not in gajim.transport_type:
- servers.append(s)
- for s in servers:
- self.discoverInfo(s)
-
- def _on_roster_set(self, roster):
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
- xmpp_roster=roster))
-
- def _nec_roster_received(self, obj):
- if obj.conn.name != self.name:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- if self.connected > 1 and self.continue_connect_info:
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- signed = ''
- send_first_presence = True
- if sign_msg:
- signed = self.get_signed_presence(msg,
- self._send_first_presence)
- if signed is None:
- gajim.nec.push_incoming_event(GPGPasswordRequiredEvent(None,
- conn=self, callback=self._send_first_presence))
- # _send_first_presence will be called when user enter
- # passphrase
- send_first_presence = False
- if send_first_presence:
- self._send_first_presence(signed)
-
- if obj.received_from_server:
- for jid in obj.roster:
- if jid != our_jid and gajim.jid_is_transport(jid) and \
- not gajim.get_transport_name_from_jid(jid):
- # we can't determine which iconset to use
- self.discoverInfo(jid)
-
- gajim.logger.replace_roster(self.name, obj.version, obj.roster)
-
- for contact in gajim.contacts.iter_contacts(self.name):
- if not contact.is_groupchat() and contact.jid not in obj.roster\
- and contact.jid != our_jid:
- gajim.nec.push_incoming_event(RosterInfoEvent(None,
- conn=self, jid=contact.jid, nickname=None, sub=None,
- ask=None, groups=()))
- for jid, info in obj.roster.items():
- gajim.nec.push_incoming_event(RosterInfoEvent(None,
- conn=self, jid=jid, nickname=info['name'],
- sub=info['subscription'], ask=info['ask'],
- groups=info['groups']))
-
- def _send_first_presence(self, signed=''):
- show = self.continue_connect_info[0]
- msg = self.continue_connect_info[1]
- sign_msg = self.continue_connect_info[2]
- if sign_msg and not signed:
- signed = self.get_signed_presence(msg)
- if signed is None:
- gajim.nec.push_incoming_event(BadGPGPassphraseEvent(None,
- conn=self))
- self.USE_GPG = False
- signed = ''
- self.connected = gajim.SHOW_LIST.index(show)
- sshow = helpers.get_xmpp_show(show)
- # send our presence
- if show == 'invisible':
- self.send_invisible_presence(msg, signed, True)
- return
- if show not in ['offline', 'online', 'chat', 'away', 'xa', 'dnd']:
- return
- priority = gajim.get_priority(self.name, sshow)
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- vcard = self.get_cached_vcard(our_jid)
- if vcard and 'PHOTO' in vcard and 'SHA' in vcard['PHOTO']:
- self.vcard_sha = vcard['PHOTO']['SHA']
- p = nbxmpp.Presence(typ=None, priority=priority, show=sshow)
- p = self.add_sha(p)
- if msg:
- p.setStatus(msg)
- if signed:
- p.setTag(nbxmpp.NS_SIGNED + ' x').setData(signed)
-
- if self.connection:
- self.connection.send(p)
- self.priority = priority
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- if self.vcard_supported:
- # ask our VCard
- self.request_vcard(None)
-
- # Get bookmarks from private namespace
- self.get_bookmarks()
-
- # Get annotations from private namespace
- self.get_annotations()
-
- # Inform GUI we just signed in
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
- self.send_awaiting_pep()
- self.continue_connect_info = None
- # hashes of already received messages
- self.received_message_hashes = []
-
- def request_gmail_notifications(self):
- if not self.connection or self.connected < 2:
- return
- # It's a gmail account,
- # inform the server that we want e-mail notifications
- our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
- log.debug(('%s is a gmail account. Setting option '
- 'to get e-mail notifications on the server.') % (our_jid))
- iq = nbxmpp.Iq(typ='set', to=our_jid)
- iq.setAttr('id', 'MailNotify')
- query = iq.setTag('usersetting')
- query.setNamespace(nbxmpp.NS_GTALKSETTING)
- query = query.setTag('mailnotifications')
- query.setAttr('value', 'true')
- self.connection.send(iq)
- # Ask how many messages there are now
- iq = nbxmpp.Iq(typ='get')
- iq.setID(self.connection.getAnID())
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
- self.connection.send(iq)
-
- def _SearchCB(self, con, iq_obj):
- log.debug('SearchCB')
- gajim.nec.push_incoming_event(SearchFormReceivedEvent(None,
- conn=self, stanza=iq_obj))
-
- def _search_fields_received(self, con, iq_obj):
- jid = jid = helpers.get_jid_from_iq(iq_obj)
- tag = iq_obj.getTag('query', namespace = nbxmpp.NS_SEARCH)
- if not tag:
- self.dispatch('SEARCH_FORM', (jid, None, False))
- return
- df = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if df:
- self.dispatch('SEARCH_FORM', (jid, df, True))
- return
- df = {}
- for i in iq_obj.getQueryPayload():
- df[i.getName()] = i.getData()
- self.dispatch('SEARCH_FORM', (jid, df, False))
-
- def _PubkeyGetCB(self, con, iq_obj):
- log.info('PubkeyGetCB')
- jid_from = helpers.get_full_jid_from_iq(iq_obj)
- sid = iq_obj.getAttr('id')
- jingle_xtls.send_cert(con, jid_from, sid)
- raise nbxmpp.NodeProcessed
-
- def _PubkeyResultCB(self, con, iq_obj):
- log.info('PubkeyResultCB')
- jid_from = helpers.get_full_jid_from_iq(iq_obj)
- jingle_xtls.handle_new_cert(con, iq_obj, jid_from)
-
- def _BlockingSetCB(self, con, iq_obj):
- log.debug('_BlockingSetCB')
- gajim.nec.push_incoming_event(BlockingEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_blocking(self, obj):
- if obj.conn.name != self.name:
- return
- if obj.unblock_all:
- self.blocked_contacts = []
- else:
- for jid in obj.blocked_jids:
- if jid not in self.blocked_contacts:
- self.blocked_contacts.append(jid)
- for jid in obj.unblocked_jids:
- if jid in self.blocked_contacts:
- self.blocked_contacts.remove(jid)
-
- def _nec_stream_other_host_received(self, obj):
- if obj.conn.name != self.name:
- return
- self.redirected = obj.redirected
-
- def _StreamCB(self, con, obj):
- log.debug('StreamCB')
- gajim.nec.push_incoming_event(StreamReceivedEvent(None,
- conn=self, stanza=obj))
-
- def _register_handlers(self, con, con_type):
- # try to find another way to register handlers in each class
- # that defines handlers
- con.RegisterHandler('message', self._messageCB)
- con.RegisterHandler('presence', self._presenceCB)
- # We use makefirst so that this handler is called before _messageCB, and
- # can prevent calling it when it's not needed.
- # We also don't check for namespace, else it cannot stop _messageCB to
- # be called
- con.RegisterHandler('message', self._pubsubEventCB, makefirst=True)
- con.RegisterHandler('iq', self._vCardCB, 'result', nbxmpp.NS_VCARD)
- con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER)
- con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._rosterItemExchangeCB, 'set',
- nbxmpp.NS_ROSTERX)
- con.RegisterHandler('iq', self._siErrorCB, 'error', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._siResultCB, 'result', nbxmpp.NS_SI)
- con.RegisterHandler('iq', self._discoGetCB, 'get', nbxmpp.NS_DISCO)
- con.RegisterHandler('iq', self._bytestreamSetCB, 'set',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamResultCB, 'result',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandler('iq', self._bytestreamErrorCB, 'error',
- nbxmpp.NS_BYTESTREAM)
- con.RegisterHandlerOnce('iq', self.IBBAllIqHandler)
- con.RegisterHandler('iq', self.IBBIqHandler, ns=nbxmpp.NS_IBB)
- con.RegisterHandler('message', self.IBBMessageHandler, ns=nbxmpp.NS_IBB)
- con.RegisterHandler('iq', self._DiscoverItemsCB, 'result',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverItemsErrorCB, 'error',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._DiscoverInfoCB, 'result',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverInfoErrorCB, 'error',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._VersionCB, 'get', nbxmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeCB, 'get', nbxmpp.NS_TIME)
- con.RegisterHandler('iq', self._TimeRevisedCB, 'get',
- nbxmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._LastCB, 'get', nbxmpp.NS_LAST)
- con.RegisterHandler('iq', self._LastResultCB, 'result', nbxmpp.NS_LAST)
- con.RegisterHandler('iq', self._VersionResultCB, 'result',
- nbxmpp.NS_VERSION)
- con.RegisterHandler('iq', self._TimeRevisedResultCB, 'result',
- nbxmpp.NS_TIME_REVISED)
- con.RegisterHandler('iq', self._MucOwnerCB, 'result',
- nbxmpp.NS_MUC_OWNER)
- con.RegisterHandler('iq', self._MucAdminCB, 'result',
- nbxmpp.NS_MUC_ADMIN)
- con.RegisterHandler('iq', self._PrivateCB, 'result', nbxmpp.NS_PRIVATE)
- con.RegisterHandler('iq', self._SecLabelCB, 'result',
- nbxmpp.NS_SECLABEL_CATALOG)
- con.RegisterHandler('iq', self._HttpAuthCB, 'get', nbxmpp.NS_HTTP_AUTH)
- con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
- nbxmpp.NS_COMMANDS)
- con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
- nbxmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._gMailQueryCB, 'result',
- nbxmpp.NS_GMAILNOTIFY)
- con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
- nbxmpp.NS_DISCO_INFO)
- con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
- nbxmpp.NS_DISCO_ITEMS)
- con.RegisterHandler('iq', self._IqPingCB, 'get', nbxmpp.NS_PING)
- con.RegisterHandler('iq', self._SearchCB, 'result', nbxmpp.NS_SEARCH)
- con.RegisterHandler('iq', self._PrivacySetCB, 'set', nbxmpp.NS_PRIVACY)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_ARCHIVE)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1)
- con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2)
- con.RegisterHandler('iq', self._PubSubCB, 'result')
- con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'result')
- con.RegisterHandler('iq', self._JingleCB, 'error')
- con.RegisterHandler('iq', self._JingleCB, 'set', nbxmpp.NS_JINGLE)
- con.RegisterHandler('iq', self._ErrorCB, 'error')
- con.RegisterHandler('iq', self._IqCB)
- con.RegisterHandler('iq', self._StanzaArrivedCB)
- con.RegisterHandler('iq', self._ResultCB, 'result')
- con.RegisterHandler('presence', self._StanzaArrivedCB)
- con.RegisterHandler('message', self._StanzaArrivedCB)
- con.RegisterHandler('unknown', self._StreamCB,
- nbxmpp.NS_XMPP_STREAMS, xmlns=nbxmpp.NS_STREAMS)
- con.RegisterHandler('iq', self._PubkeyGetCB, 'get',
- nbxmpp.NS_PUBKEY_PUBKEY)
- con.RegisterHandler('iq', self._PubkeyResultCB, 'result',
- nbxmpp.NS_PUBKEY_PUBKEY)
- con.RegisterHandler('iq', self._BlockingSetCB, 'set',
- nbxmpp.NS_BLOCKING)
diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py
deleted file mode 100644
index f820db879..000000000
--- a/src/common/connection_handlers_events.py
+++ /dev/null
@@ -1,2834 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers_events.py
-##
-## Copyright (C) 2010-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from calendar import timegm
-import datetime
-import hashlib
-import hmac
-import logging
-import sys
-import os
-from time import time as time_time
-
-import nbxmpp
-from nbxmpp.protocol import NS_CHATSTATES
-from common import atom
-from common import nec
-from common import helpers
-from common import gajim
-from common import i18n
-from common import dataforms
-from common import exceptions
-from common.zeroconf.zeroconf import Constant
-from common.logger import LOG_DB_PATH
-from common.pep import SUPPORTED_PERSONAL_USER_EVENTS
-from common.jingle_transport import JingleTransportSocks5
-from common.file_props import FilesProp
-
-if gajim.HAVE_PYOPENSSL:
- import OpenSSL.crypto
-
-log = logging.getLogger('gajim.c.connection_handlers_events')
-
-CONDITION_TO_CODE = {
- 'realjid-public': 100,
- 'affiliation-changed': 101,
- 'unavailable-shown': 102,
- 'unavailable-not-shown': 103,
- 'configuration-changed': 104,
- 'self-presence': 110,
- 'logging-enabled': 170,
- 'logging-disabled': 171,
- 'non-anonymous': 172,
- 'semi-anonymous': 173,
- 'fully-anonymous': 174,
- 'room-created': 201,
- 'nick-assigned': 210,
- 'banned': 301,
- 'new-nick': 303,
- 'kicked': 307,
- 'removed-affiliation': 321,
- 'removed-membership': 322,
- 'removed-shutdown': 332,
-}
-
-class HelperEvent:
- def get_jid_resource(self, check_fake_jid=False):
- if check_fake_jid and hasattr(self, 'id_') and \
- self.id_ in self.conn.groupchat_jids:
- self.fjid = self.conn.groupchat_jids[self.id_]
- del self.conn.groupchat_jids[self.id_]
- else:
- self.fjid = helpers.get_full_jid_from_iq(self.stanza)
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
-
- def get_id(self):
- self.id_ = self.stanza.getID()
-
- def get_gc_control(self):
- self.gc_control = gajim.interface.msg_win_mgr.get_gc_control(self.jid,
- self.conn.name)
-
- # If gc_control is missing - it may be minimized. Try to get it
- # from there. If it's not there - then it's missing anyway and
- # will remain set to None.
- if not self.gc_control:
- minimized = gajim.interface.minimized_controls[self.conn.name]
- self.gc_control = minimized.get(self.jid)
-
- def _generate_timestamp(self, tag):
- # Make sure we use only int/float Epoch time
- if not isinstance(tag, str):
- self.timestamp = time_time()
- return
- try:
- tim = helpers.datetime_tuple(tag)
- self.timestamp = timegm(tim)
- except Exception:
- log.error('wrong timestamp, ignoring it: ' + tag)
- self.timestamp = time_time()
-
- def get_chatstate(self):
- """
- Extract chatstate from a <message/> stanza
- Requires self.stanza and self.msgtxt
- """
- self.chatstate = None
-
- # chatstates - look for chatstate tags in a message if not delayed
- delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not None
- if not delayed:
- children = self.stanza.getChildren()
- for child in children:
- if child.getNamespace() == NS_CHATSTATES:
- self.chatstate = child.getName()
- break
-
-class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
- name = 'http-auth-received'
- base_network_events = []
-
- def generate(self):
- self.opt = gajim.config.get_per('accounts', self.conn.name, 'http_auth')
- self.iq_id = self.stanza.getTagAttr('confirm', 'id')
- self.method = self.stanza.getTagAttr('confirm', 'method')
- self.url = self.stanza.getTagAttr('confirm', 'url')
- # In case it's a message with a body
- self.msg = self.stanza.getTagData('body')
- return True
-
-class LastResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'last-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.last_ids:
- self.conn.last_ids.remove(self.id_)
-
- self.status = ''
- self.seconds = -1
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.getTag('query')
- if not qp:
- return
- 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):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.version_ids:
- self.conn.version_ids.remove(self.id_)
-
- self.client_info = ''
- self.os_info = ''
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.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 = 'time-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- if self.id_ in self.conn.entity_time_ids:
- self.conn.entity_time_ids.remove(self.id_)
-
- self.time_info = ''
-
- if self.stanza.getType() == 'error':
- return True
-
- qp = self.stanza.getTag('time')
- if not qp:
- # wrong answer
- return
- tzo = qp.getTag('tzo').getData()
- if tzo.lower() == 'z':
- tzo = '0:0'
- try:
- tzoh, tzom = tzo.split(':')
- except Exception as e:
- # wrong tzo
- return
- 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
-
- if utc_time[-1:] == 'Z':
- # Remove the trailing 'Z'
- utc_time = utc_time[:-1]
- elif utc_time[-6:] == "+00:00":
- # Remove the trailing "+00:00"
- utc_time = utc_time[:-6]
- else:
- log.info("Wrong timezone defintion: %s" % utc_time)
- return
- try:
- t = datetime.datetime.strptime(utc_time, '%Y-%m-%dT%H:%M:%S')
- except ValueError:
- try:
- t = datetime.datetime.strptime(utc_time,
- '%Y-%m-%dT%H:%M:%S.%f')
- except ValueError as e:
- log.info('Wrong time format: %s' % str(e))
- return
-
- t = t.replace(tzinfo=UTC())
- self.time_info = t.astimezone(contact_tz()).strftime('%c')
- return True
-
-class GMailQueryReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gmail-notify'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('mailbox'):
- return
- mb = self.stanza.getTag('mailbox')
- if not mb.getAttr('url'):
- return
- self.conn.gmail_url = mb.getAttr('url')
- if mb.getNamespace() != nbxmpp.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
-
-class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'roster-item-exchange-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource()
- self.exchange_items_list = {}
- items_list = self.stanza.getTag('x').getChildren()
- if not items_list:
- return
- self.action = items_list[0].getAttr('action')
- if self.action is None:
- self.action = 'add'
- for item in self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX).\
- getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- contact = gajim.contacts.get_contact(self.conn.name, jid)
- groups = []
- same_groups = True
- for group in item.getTags('group'):
- groups.append(group.getData())
- # check that all suggested groups are in the groups we have for
- # this contact
- if not contact or group not in contact.groups:
- same_groups = False
- if contact:
- # check that all groups we have for this contact are in the
- # suggested groups
- for group in contact.groups:
- if group not in groups:
- same_groups = False
- if contact.sub in ('both', 'to') and same_groups:
- continue
- self.exchange_items_list[jid] = []
- self.exchange_items_list[jid].append(name)
- self.exchange_items_list[jid].append(groups)
- if self.exchange_items_list:
- return True
-
-class VersionRequestEvent(nec.NetworkIncomingEvent):
- name = 'version-request-received'
- base_network_events = []
-
-class LastRequestEvent(nec.NetworkIncomingEvent):
- name = 'last-request-received'
- base_network_events = []
-
-class TimeRequestEvent(nec.NetworkIncomingEvent):
- name = 'time-request-received'
- base_network_events = []
-
-class TimeRevisedRequestEvent(nec.NetworkIncomingEvent):
- name = 'time-revised-request-received'
- base_network_events = []
-
-class RosterReceivedEvent(nec.NetworkIncomingEvent):
- name = 'roster-received'
- base_network_events = []
-
- def generate(self):
- if hasattr(self, 'xmpp_roster'):
- self.version = self.xmpp_roster.version
- self.received_from_server = self.xmpp_roster.received_from_server
- self.roster = {}
- raw_roster = self.xmpp_roster.getRaw()
- our_jid = gajim.get_jid_from_account(self.conn.name)
-
- for jid in raw_roster:
- try:
- j = helpers.parse_jid(jid)
- except Exception:
- print(_('JID %s is not RFC compliant. It will not be added '
- 'to your roster. Use roster management tools such as '
- 'http://jru.jabberstudio.org/ to remove it') % jid,
- file=sys.stderr)
- else:
- infos = raw_roster[jid]
- if jid != our_jid and (not infos['subscription'] or \
- infos['subscription'] == 'none') and (not infos['ask'] or \
- infos['ask'] == 'none') and not infos['name'] and \
- not infos['groups']:
- # remove this useless item, it won't be shown in roster
- # anyway
- self.conn.connection.getRoster().delItem(jid)
- elif jid != our_jid: # don't add our jid
- self.roster[j] = raw_roster[jid]
- else:
- # Roster comes from DB
- self.received_from_server = False
- self.version = gajim.config.get_per('accounts', self.conn.name,
- 'roster_version')
- self.roster = gajim.logger.get_roster(gajim.get_jid_from_account(
- self.conn.name))
- return True
-
-class RosterSetReceivedEvent(nec.NetworkIncomingEvent):
- name = 'roster-set-received'
- base_network_events = []
-
- def generate(self):
- frm = helpers.get_jid_from_iq(self.stanza)
- our_jid = gajim.get_jid_from_account(self.conn.name)
- if frm and frm != our_jid and frm != gajim.get_server_from_jid(our_jid):
- return
- self.version = self.stanza.getTagAttr('query', 'ver')
- self.items = {}
- for item in self.stanza.getTag('query').getChildren():
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % item.getAttr('jid'))
- continue
- name = item.getAttr('name')
- sub = item.getAttr('subscription')
- ask = item.getAttr('ask')
- groups = []
- for group in item.getTags('group'):
- groups.append(group.getData())
- self.items[jid] = {'name': name, 'sub': sub, 'ask': ask,
- 'groups': groups}
- if len(self.items) > 1:
- reply = nbxmpp.Iq(typ='error', attrs={'id': self.stanza.getID()},
- to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
- self.conn.connection.send(reply)
- return
- if self.conn.connection and self.conn.connected > 1:
- reply = nbxmpp.Iq(typ='result', attrs={'id': self.stanza.getID()},
- to=self.stanza.getFrom(), frm=self.stanza.getTo(), xmlns=None)
- self.conn.connection.send(reply)
- return True
-
-class RosterInfoEvent(nec.NetworkIncomingEvent):
- name = 'roster-info'
- base_network_events = []
-
-class MucOwnerReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'muc-owner-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- qp = self.stanza.getQueryPayload()
- self.form_node = None
- for q in qp:
- if q.getNamespace() == nbxmpp.NS_DATA:
- self.form_node = q
- self.dataform = dataforms.ExtendForm(node=self.form_node)
- return True
-
-class MucAdminReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'muc-admin-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- items = self.stanza.getTag('query',
- namespace=nbxmpp.NS_MUC_ADMIN).getTags('item')
- self.users_dict = {}
- for item in items:
- if item.has_attr('jid') and item.has_attr('affiliation'):
- try:
- jid = helpers.parse_jid(item.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % \
- item.getAttr('jid'))
- continue
- affiliation = item.getAttr('affiliation')
- self.users_dict[jid] = {'affiliation': affiliation}
- if item.has_attr('nick'):
- self.users_dict[jid]['nick'] = item.getAttr('nick')
- if item.has_attr('role'):
- self.users_dict[jid]['role'] = item.getAttr('role')
- reason = item.getTagData('reason')
- if reason:
- self.users_dict[jid]['reason'] = reason
- return True
-
-class PrivateStorageReceivedEvent(nec.NetworkIncomingEvent):
- name = 'private-storage-received'
- base_network_events = []
-
- def generate(self):
- query = self.stanza.getTag('query')
- self.storage_node = query.getTag('storage')
- if self.storage_node:
- self.namespace = self.storage_node.getNamespace()
- return True
-
-
-class BookmarksHelper:
- def parse_bookmarks(self):
- self.bookmarks = []
- NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
- confs = self.storage_node.getTags('conference')
- for conf in confs:
- autojoin_val = conf.getAttr('autojoin')
- if not autojoin_val: # not there (it's optional)
- autojoin_val = False
- minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM)
- if not minimize_val: # not there, try old Gajim behaviour
- minimize_val = conf.getAttr('minimize')
- if not minimize_val: # not there (it's optional)
- minimize_val = False
- else:
- minimize_val = minimize_val.getData()
-
- print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM)
- if not print_status: # not there, try old Gajim behaviour
- print_status = conf.getTagData('print_status')
- if not print_status: # not there, try old Gajim behaviour
- print_status = conf.getTagData('show_status')
- else:
- print_status = print_status.getData()
-
- try:
- jid = helpers.parse_jid(conf.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it'
- % conf.getAttr('jid'))
- continue
-
- bm = {'name': conf.getAttr('name'),
- 'jid': jid,
- 'autojoin': autojoin_val,
- 'minimize': minimize_val,
- 'password': conf.getTagData('password'),
- 'nick': conf.getTagData('nick'),
- 'print_status': print_status}
-
- bm_jids = [b['jid'] for b in self.bookmarks]
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
-
-class PrivateStorageBookmarksReceivedEvent(nec.NetworkIncomingEvent,
-BookmarksHelper):
- name = 'private-storage-bookmarks-received'
- base_network_events = ['private-storage-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.storage_node = self.base_event.storage_node
- if self.base_event.namespace != nbxmpp.NS_BOOKMARKS:
- return
- self.parse_bookmarks()
- return True
-
-class BookmarksReceivedEvent(nec.NetworkIncomingEvent):
- name = 'bookmarks-received'
- base_network_events = ['private-storage-bookmarks-received',
- 'pubsub-bookmarks-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.bookmarks = self.base_event.bookmarks
- return True
-
-class PrivateStorageRosternotesReceivedEvent(nec.NetworkIncomingEvent):
- name = 'private-storage-rosternotes-received'
- base_network_events = ['private-storage-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- if self.base_event.namespace != nbxmpp.NS_ROSTERNOTES:
- return
- notes = self.base_event.storage_node.getTags('note')
- self.annotations = {}
- for note in notes:
- try:
- jid = helpers.parse_jid(note.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it' % note.getAttr('jid'))
- continue
- annotation = note.getData()
- self.annotations[jid] = annotation
- if self.annotations:
- return True
-
-class RosternotesReceivedEvent(nec.NetworkIncomingEvent):
- name = 'rosternotes-received'
- base_network_events = ['private-storage-rosternotes-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.annotations = self.base_event.annotations
- return True
-
-class PubsubReceivedEvent(nec.NetworkIncomingEvent):
- name = 'pubsub-received'
- base_network_events = []
-
- def generate(self):
- self.pubsub_node = self.stanza.getTag('pubsub')
- if not self.pubsub_node:
- return
- self.items_node = self.pubsub_node.getTag('items')
- if not self.items_node:
- return
- self.item_node = self.items_node.getTag('item')
- if not self.item_node:
- return
- children = self.item_node.getChildren()
- if not children:
- return
- self.node = children[0]
- return True
-
-class PubsubBookmarksReceivedEvent(nec.NetworkIncomingEvent, BookmarksHelper):
- name = 'pubsub-bookmarks-received'
- base_network_events = ['pubsub-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.storage_node = self.base_event.node
- ns = self.storage_node.getNamespace()
- if ns != nbxmpp.NS_BOOKMARKS:
- return
- self.parse_bookmarks()
- return True
-
-class SearchFormReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'search-form-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.data = None
- self.is_dataform = False
- tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
- if not tag:
- return True
- self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if self.data:
- self.is_dataform = True
- return True
- self.data = {}
- for i in self.stanza.getQueryPayload():
- self.data[i.getName()] = i.getData()
- return True
-
-
-class SearchResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'search-result-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.data = None
- self.is_dataform = False
- tag = self.stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
- if not tag:
- return True
- self.data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if self.data:
- self.is_dataform = True
- return True
- self.data = []
- for item in tag.getTags('item'):
- # We also show attributes. jid is there
- f = item.attrs
- for i in item.getPayload():
- f[i.getName()] = i.getData()
- self.data.append(f)
- return True
-
-class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'iq-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- self.get_jid_resource(check_fake_jid=True)
- self.errmsg = self.stanza.getErrorMsg()
- self.errcode = self.stanza.getErrorCode()
- return True
-
-class GmailNewMailReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gmail-new-mail-received'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('new-mail'):
- return
- if self.stanza.getTag('new-mail').getNamespace() != \
- nbxmpp.NS_GMAILNOTIFY:
- return
- return True
-
-class PingReceivedEvent(nec.NetworkIncomingEvent):
- name = 'ping-received'
- base_network_events = []
-
-class StreamReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-received'
- base_network_events = []
-
-class StreamConflictReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-conflict-received'
- base_network_events = ['stream-received']
-
- def generate(self):
- if self.base_event.stanza.getTag('conflict'):
- self.conn = self.base_event.conn
- return True
-
-class StreamOtherHostReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stream-other-host-received'
- base_network_events = ['stream-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- other_host = self.stanza.getTag('see-other-host')
- if other_host and self.conn._current_type in ('ssl', 'tls'):
- host = other_host.getData()
- if ':' in host:
- host_l = host.split(':', 1)
- h = host_l[0]
- p = host_l[1]
- else:
- h = host
- p = 5222
- if h.startswith('[') and h.endswith(']'):
- h = h[1:-1]
- self.redirected = {'host': h, 'port': p}
- return True
-
-class PresenceHelperEvent:
- def _generate_show(self):
- self.show = self.stanza.getShow()
- if self.show not in ('chat', 'away', 'xa', 'dnd'):
- self.show = '' # We ignore unknown show
- if not self.ptype and not self.show:
- self.show = 'online'
- elif self.ptype == 'unavailable':
- self.show = 'offline'
-
- def _generate_ptype(self):
- self.ptype = self.stanza.getType()
- if self.ptype == 'available':
- self.ptype = None
- rfc_types = ('unavailable', 'error', 'subscribe', 'subscribed',
- 'unsubscribe', 'unsubscribed')
- if self.ptype and not self.ptype in rfc_types:
- self.ptype = None
-
-class PresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
-PresenceHelperEvent):
- name = 'presence-received'
- base_network_events = ['raw-pres-received']
-
- def _generate_keyID(self, sig_tag):
- self.keyID = ''
- if sig_tag and self.conn.USE_GPG and self.ptype != 'error':
- # error presences contain our own signature
- # verify
- sig_msg = sig_tag.getData()
- self.keyID = self.conn.gpg.verify(self.status, sig_msg)
- self.keyID = helpers.prepare_and_validate_gpg_keyID(self.conn.name,
- self.jid,
- self.keyID)
-
- def _generate_prio(self):
- self.prio = self.stanza.getPriority()
- try:
- self.prio = int(self.prio)
- except Exception:
- self.prio = 0
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
-
- self.need_add_in_roster = False
- self.need_redraw = False
-
- self.popup = False # Do we want to open chat window ?
-
- if not self.conn or self.conn.connected < 2:
- log.debug('account is no more connected')
- return
-
- self._generate_ptype()
- try:
- self.get_jid_resource()
- except Exception:
- log.warning('Invalid JID: %s, ignoring it' % self.stanza.getFrom())
- return
- jid_list = gajim.contacts.get_jid_list(self.conn.name)
- self.timestamp = None
- self.get_id()
- self.is_gc = False # is it a GC presence ?
- sig_tag = None
- self.avatar_sha = None
- # XEP-0172 User Nickname
- self.user_nick = self.stanza.getTagData('nick') or ''
- self.contact_nickname = None
- self.transport_auto_auth = False
- # XEP-0203
- delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- if delay_tag:
- self._generate_timestamp(self.stanza.getTimestamp2())
- xtags = self.stanza.getTags('x')
- for x in xtags:
- namespace = x.getNamespace()
- if namespace.startswith(nbxmpp.NS_MUC):
- self.is_gc = True
- elif namespace == nbxmpp.NS_SIGNED:
- sig_tag = x
- elif namespace == nbxmpp.NS_VCARD_UPDATE:
- self.avatar_sha = x.getTagData('photo')
- self.contact_nickname = x.getTagData('nickname')
- elif namespace == nbxmpp.NS_DELAY and not self.timestamp:
- # XEP-0091
- self._generate_timestamp(self.stanza.getTimestamp())
- elif namespace == 'http://delx.cjb.net/protocol/roster-subsync':
- # see http://trac.gajim.org/ticket/326
- agent = gajim.get_server_from_jid(self.jid)
- if self.conn.connection.getRoster().getItem(agent):
- # to be sure it's a transport contact
- self.transport_auto_auth = True
-
- if not self.is_gc and self.id_ and self.id_.startswith('gajim_muc_') \
- and self.ptype == 'error':
- # Error presences may not include sent stanza, so we don't detect
- # it's a muc presence. So detect it by ID
- h = hmac.new(self.conn.secret_hmac, self.jid.encode('utf-8'),
- hashlib.md5).hexdigest()[:6]
- if self.id_.split('_')[-1] == h:
- self.is_gc = True
- self.status = self.stanza.getStatus() or ''
- self._generate_show()
- self._generate_prio()
- self._generate_keyID(sig_tag)
-
- self.errcode = self.stanza.getErrorCode()
- self.errmsg = self.stanza.getErrorMsg()
-
- if self.is_gc:
- gajim.nec.push_incoming_event(
- GcPresenceReceivedEvent(
- None, conn=self.conn, stanza=self.stanza,
- presence_obj=self))
- return
-
- if self.ptype == 'subscribe':
- gajim.nec.push_incoming_event(SubscribePresenceReceivedEvent(None,
- conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'subscribed':
- # BE CAREFUL: no con.updateRosterItem() in a callback
- gajim.nec.push_incoming_event(SubscribedPresenceReceivedEvent(None,
- conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'unsubscribe':
- log.debug(_('unsubscribe request from %s') % self.jid)
- elif self.ptype == 'unsubscribed':
- gajim.nec.push_incoming_event(UnsubscribedPresenceReceivedEvent(
- None, conn=self.conn, stanza=self.stanza, presence_obj=self))
- elif self.ptype == 'error':
- return
-
- if not self.ptype or self.ptype == 'unavailable':
- our_jid = gajim.get_jid_from_account(self.conn.name)
- if self.jid == our_jid and self.resource == self.conn.server_resource:
- # We got our own presence
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
- show=self.show))
- elif self.jid in jid_list or self.jid == our_jid:
- return True
-
-class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.resource = 'local'
- self.prio = 0
- self.keyID = None
- self.timestamp = 0
- self.contact_nickname = None
- self.avatar_sha = None
- self.need_add_in_roster = False
- self.need_redraw = False
- if self.show == 'offline':
- self.ptype = 'unavailable'
- else:
- self.ptype = None
- self.is_gc = False
- self.user_nick = ''
- self.transport_auto_auth = False
- self.errcode = None
- self.errmsg = ''
- self.popup = False # Do we want to open chat window ?
- return True
-
-class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'gc-presence-received'
- base_network_events = []
-
- def generate(self):
- self.ptype = self.presence_obj.ptype
- self.fjid = self.presence_obj.fjid
- self.jid = self.presence_obj.jid
- self.room_jid = self.presence_obj.jid
- self.nick = self.presence_obj.resource
- self.show = self.presence_obj.show
- self.status = self.presence_obj.status
- self.avatar_sha = self.presence_obj.avatar_sha
- self.errcode = self.presence_obj.errcode
- self.errmsg = self.presence_obj.errmsg
- self.errcon = self.stanza.getError()
- self.get_gc_control()
- self.gc_contact = gajim.contacts.get_gc_contact(self.conn.name,
- self.room_jid, self.nick)
-
- if self.ptype == 'error':
- return True
-
- if self.ptype and self.ptype != 'unavailable':
- return
- if gajim.config.get('log_contact_status_changes') and \
- gajim.config.should_log(self.conn.name, self.room_jid):
- if self.gc_contact:
- jid = self.gc_contact.jid
- else:
- jid = self.stanza.getJid()
- st = self.status
- if jid:
- # we know real jid, save it in db
- st += ' (%s)' % jid
- gajim.logger.write('gcstatus', self.fjid, st, self.show)
- if self.avatar_sha == '':
- # contact has no avatar
- puny_nick = helpers.sanitize_filename(self.nick)
- gajim.interface.remove_avatar_files(self.room_jid, puny_nick)
- # NOTE: if it's a gc presence, don't ask vcard here.
- # We may ask it to real jid in gui part.
- self.status_code = []
- ns_muc_user_x = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
- if ns_muc_user_x:
- destroy = ns_muc_user_x.getTag('destroy')
- else:
- destroy = None
- if ns_muc_user_x and destroy:
- # Room has been destroyed. see
- # http://www.xmpp.org/extensions/xep-0045.html#destroyroom
- self.reason = _('Room has been destroyed')
- r = destroy.getTagData('reason')
- if r:
- self.reason += ' (%s)' % r
- if destroy.getAttr('jid'):
- try:
- jid = helpers.parse_jid(destroy.getAttr('jid'))
- self.reason += '\n' + \
- _('You can join this room instead: %s') % jid
- except helpers.InvalidFormat:
- pass
- self.status_code = ['destroyed']
- else:
- self.reason = self.stanza.getReason()
- conditions = self.stanza.getStatusConditions()
- if conditions:
- self.status_code = []
- for condition in conditions:
- if condition in CONDITION_TO_CODE:
- self.status_code.append(CONDITION_TO_CODE[condition])
- else:
- self.status_code = self.stanza.getStatusCode()
-
- self.role = self.stanza.getRole()
- self.affiliation = self.stanza.getAffiliation()
- self.real_jid = self.stanza.getJid()
- self.actor = self.stanza.getActor()
- self.new_nick = self.stanza.getNewNick()
- return True
-
-class SubscribePresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'subscribe-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- self.fjid = self.presence_obj.fjid
- self.status = self.presence_obj.status
- self.transport_auto_auth = self.presence_obj.transport_auto_auth
- self.user_nick = self.presence_obj.user_nick
- return True
-
-class SubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'subscribed-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- self.resource = self.presence_obj.resource
- return True
-
-class UnsubscribedPresenceReceivedEvent(nec.NetworkIncomingEvent):
- name = 'unsubscribed-presence-received'
- base_network_events = []
-
- def generate(self):
- self.jid = self.presence_obj.jid
- return True
-
-class OurShowEvent(nec.NetworkIncomingEvent):
- name = 'our-show'
- base_network_events = []
-
-class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
- name = 'before-change-show'
- base_network_events = []
-
-class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'mam-message-received'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.encrypted = False
-
- def generate(self):
- if not self.stanza:
- return
- account = self.conn.name
- self.msg_ = self.stanza.getTag('message')
- # use timestamp of archived message, if available and archive timestamp otherwise
- delay = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- delay2 = self.msg_.getTag('delay', namespace=nbxmpp.NS_DELAY2)
- if delay2:
- delay = delay2
- if not delay:
- return
- tim = delay.getAttr('stamp')
- tim = helpers.datetime_tuple(tim)
- self.tim = timegm(tim)
- to_ = self.msg_.getAttr('to')
- if to_:
- to_ = gajim.get_jid_without_resource(to_)
- else:
- to_ = gajim.get_jid_from_account(account)
- frm_ = gajim.get_jid_without_resource(self.msg_.getAttr('from'))
- self.msgtxt = self.msg_.getTagData('body')
- if to_ == gajim.get_jid_from_account(account):
- self.with_ = frm_
- self.direction = 'from'
- self.resource = gajim.get_resource_from_jid(
- self.msg_.getAttr('from'))
- else:
- self.with_ = to_
- self.direction = 'to'
- self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('to'))
- return True
-
-class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'mam-decrypted-message-received'
- base_network_events = []
-
- def generate(self):
- self.nick = None
- msg_ = self.msg_obj.msg_
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.with_ = self.msg_obj.with_
- self.direction = self.msg_obj.direction
- self.tim = self.msg_obj.tim
- res = self.msg_obj.resource
- self.msgtxt = self.msg_obj.msgtxt
- is_pm = gajim.logger.jid_is_room_jid(self.with_)
- if msg_.getAttr('type') == 'groupchat':
- if is_pm == False:
- log.warn('JID %s is marked as normal contact in database '
- 'but we got a groupchat message from it.')
- return
- if is_pm == None:
- gajim.logger.get_jid_id(self.with_, 'ROOM')
- self.nick = res
- else:
- if is_pm == None:
- # we don't know this JID, we need to disco it.
- server = gajim.get_server_from_jid(self.with_)
- if server not in self.conn.mam_awaiting_disco_result:
- self.conn.mam_awaiting_disco_result[server] = [
- [self.with_, self.direction, self.tim, self.msgtxt,
- res]]
- self.conn.discoverInfo(server)
- else:
- self.conn.mam_awaiting_disco_result[server].append(
- [self.with_, self.direction, self.tim, self.msgtxt,
- res])
- return
- return True
-
-class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'message-received'
- base_network_events = ['raw-message-received']
-
- def init(self):
- self.additional_data = {}
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.get_id()
- self.forwarded = False
- self.sent = False
- self.encrypted = False
- account = self.conn.name
-
- our_full_jid = gajim.get_jid_from_account(account, full=True)
- if self.stanza.getFrom() == our_full_jid:
- # Drop messages sent from our own full jid
- # It can happen that when we sent message to our own bare jid
- # that the server routes that message back to us
- log.info('Received message from self: %s, message is dropped'
- % self.stanza.getFrom())
- return
-
- # check if the message is a roster item exchange (XEP-0144)
- if self.stanza.getTag('x', namespace=nbxmpp.NS_ROSTERX):
- gajim.nec.push_incoming_event(RosterItemExchangeEvent(None,
- conn=self.conn, stanza=self.stanza))
- return
-
- # check if the message is a XEP-0070 confirmation request
- if self.stanza.getTag('confirm', namespace=nbxmpp.NS_HTTP_AUTH):
- gajim.nec.push_incoming_event(HttpAuthReceivedEvent(None,
- conn=self.conn, stanza=self.stanza))
- return
-
- try:
- self.get_jid_resource()
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getFrom())
- return
-
- address_tag = self.stanza.getTag('addresses',
- namespace=nbxmpp.NS_ADDRESS)
- # Be sure it comes from one of our resource, else ignore address element
- if address_tag and self.jid == gajim.get_jid_from_account(account):
- address = address_tag.getTag('address', attrs={'type': 'ofrom'})
- if address:
- try:
- self.fjid = helpers.parse_jid(address.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- address.getAttr('jid'))
- return
- self.jid = gajim.get_jid_without_resource(self.fjid)
-
- carbon_marker = self.stanza.getTag('sent', namespace=nbxmpp.NS_CARBONS)
- if not carbon_marker:
- carbon_marker = self.stanza.getTag('received',
- namespace=nbxmpp.NS_CARBONS)
- # Be sure it comes from one of our resource, else ignore forward element
- if carbon_marker and self.jid == gajim.get_jid_from_account(account):
- forward_tag = carbon_marker.getTag('forwarded',
- namespace=nbxmpp.NS_FORWARD)
- if forward_tag:
- msg = forward_tag.getTag('message')
- self.stanza = nbxmpp.Message(node=msg)
- self.get_id()
- if carbon_marker.getName() == 'sent':
- to = self.stanza.getTo()
- frm = self.stanza.getFrom()
- if not frm:
- frm = gajim.get_jid_from_account(account)
- self.stanza.setTo(frm)
- if not to:
- to = gajim.get_jid_from_account(account)
- self.stanza.setFrom(to)
- self.sent = True
- elif carbon_marker.getName() == 'received':
- full_frm = str(self.stanza.getFrom())
- frm = gajim.get_jid_without_resource(full_frm)
- if frm == gajim.get_jid_from_account(account):
- # Drop 'received' Carbons from ourself, we already
- # got the message with the 'sent' Carbon or via the
- # message itself
- log.info(
- 'Drop "received"-Carbon from ourself: %s'
- % full_frm)
- return
- try:
- self.get_jid_resource()
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getFrom())
- return
- self.forwarded = True
-
- result = self.stanza.getTag('result')
- if result and result.getNamespace() in (nbxmpp.NS_MAM,
- nbxmpp.NS_MAM_1,
- nbxmpp.NS_MAM_2):
- forwarded = result.getTag('forwarded', namespace=nbxmpp.NS_FORWARD)
- gajim.nec.push_incoming_event(MamMessageReceivedEvent(None,
- conn=self.conn, stanza=forwarded))
- return
-
- # Mediated invitation?
- muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
- if muc_user:
- if muc_user.getTag('decline'):
- gajim.nec.push_incoming_event(
- GcDeclineReceivedEvent(
- None, conn=self.conn,
- room_jid=self.fjid, stanza=muc_user))
- return
- if muc_user.getTag('invite'):
- gajim.nec.push_incoming_event(
- GcInvitationReceivedEvent(
- None, conn=self.conn, jid_from=self.fjid,
- mediated=True, stanza=muc_user))
- return
- else:
- # Direct invitation?
- direct = self.stanza.getTag(
- 'x', namespace=nbxmpp.NS_CONFERENCE)
- if direct:
- gajim.nec.push_incoming_event(
- GcInvitationReceivedEvent(
- None, conn=self.conn, jid_from=self.fjid,
- mediated=False, stanza=direct))
- return
-
- self.thread_id = self.stanza.getThread()
- self.mtype = self.stanza.getType()
- if not self.mtype or self.mtype not in ('chat', 'groupchat', 'error'):
- self.mtype = 'normal'
-
- self.msgtxt = self.stanza.getBody()
-
- self.get_gc_control()
-
- if self.gc_control and self.jid == self.fjid:
- if self.mtype == 'error':
- self.msgtxt = _('error while sending %(message)s ( %(error)s )'\
- ) % {'message': self.msgtxt,
- 'error': self.stanza.getErrorMsg()}
- if self.stanza.getTag('html'):
- self.stanza.delChild('html')
- # message from a gc without a resource
- self.mtype = 'groupchat'
-
- self.session = None
- if self.mtype != 'groupchat':
- if gajim.interface.is_pm_contact(self.fjid, account) and \
- self.mtype == 'error':
- self.session = self.conn.find_session(self.fjid, self.thread_id)
- if not self.session:
- self.session = self.conn.get_latest_session(self.fjid)
- if not self.session:
- self.session = self.conn.make_new_session(self.fjid,
- self.thread_id,
- type_='pm')
- else:
- self.session = self.conn.get_or_create_session(self.fjid,
- self.thread_id)
-
- if self.thread_id and not self.session.received_thread_id:
- self.session.received_thread_id = True
-
- self.session.last_receive = time_time()
-
- self._generate_timestamp(self.stanza.getTimestamp())
-
- return True
-
-class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
- name = 'message-received'
- base_network_events = []
-
- def get_jid_resource(self):
- self.fjid =self.stanza.getFrom()
-
- if self.fjid is None:
- for key in self.conn.connection.zeroconf.contacts:
- if self.ip == self.conn.connection.zeroconf.contacts[key][
- Constant.ADDRESS]:
- self.fjid = key
- break
-
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
-
- def generate(self):
- self.base_event = nec.NetworkIncomingEvent(None, conn=self.conn,
- stanza=self.stanza)
- return super(ZeroconfMessageReceivedEvent, self).generate()
-
-class GcInvitationReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-invitation-received'
- base_network_events = []
-
- def generate(self):
- account = self.conn.name
- if not self.mediated:
- # direct invitation
- try:
- self.room_jid = helpers.parse_jid(self.stanza.getAttr('jid'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.stanza.getAttr('jid'))
- return
- self.reason = self.stanza.getAttr('reason')
- self.password = self.stanza.getAttr('password')
- self.is_continued = False
- self.is_continued = self.stanza.getAttr('continue') == 'true'
- else:
- self.invite = self.stanza.getTag('invite')
- self.room_jid = self.jid_from
- try:
- self.jid_from = helpers.parse_jid(self.invite.getAttr('from'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- self.invite.getAttr('from'))
- return
-
- self.reason = self.invite.getTagData('reason')
- self.password = self.stanza.getTagData('password')
- self.is_continued = self.stanza.getTag('continue') is not None
-
- if self.room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][self.room_jid]:
- # We are already in groupchat. Ignore invitation
- return
- jid = gajim.get_jid_without_resource(self.jid_from)
-
- ignore = gajim.config.get_per(
- 'accounts', account, 'ignore_unknown_contacts')
- if ignore and not gajim.contacts.get_contacts(account, jid):
- return
-
- return True
-
-class GcDeclineReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-decline-received'
- base_network_events = []
-
- def generate(self):
- account = self.conn.name
- decline = self.stanza.getTag('decline')
- try:
- self.jid_from = helpers.parse_jid(decline.getAttr('from'))
- except helpers.InvalidFormat:
- log.warning('Invalid JID: %s, ignoring it',
- decline.getAttr('from'))
- return
- jid = gajim.get_jid_without_resource(self.jid_from)
- ignore = gajim.config.get_per(
- 'accounts', account, 'ignore_unknown_contacts')
- if ignore and not gajim.contacts.get_contacts(account, jid):
- return
- self.reason = decline.getTagData('reason')
-
- return True
-
-class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'decrypted-message-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.id_ = self.msg_obj.id_
- self.jid = self.msg_obj.jid
- self.fjid = self.msg_obj.fjid
- self.resource = self.msg_obj.resource
- self.mtype = self.msg_obj.mtype
- self.thread_id = self.msg_obj.thread_id
- self.msgtxt = self.msg_obj.msgtxt
- self.gc_control = self.msg_obj.gc_control
- self.session = self.msg_obj.session
- self.timestamp = self.msg_obj.timestamp
- self.encrypted = self.msg_obj.encrypted
- self.forwarded = self.msg_obj.forwarded
- self.sent = self.msg_obj.sent
- self.conn = self.msg_obj.conn
- self.popup = False
- self.msg_log_id = None # id in log database
- self.attention = False # XEP-0224
- self.correct_id = None # XEP-0308
- self.msghash = None
-
- self.receipt_request_tag = self.stanza.getTag('request',
- namespace=nbxmpp.NS_RECEIPTS)
- self.receipt_received_tag = self.stanza.getTag('received',
- namespace=nbxmpp.NS_RECEIPTS)
-
- self.subject = self.stanza.getSubject()
-
- self.displaymarking = None
- self.seclabel = self.stanza.getTag('securitylabel',
- namespace=nbxmpp.NS_SECLABEL)
- if self.seclabel:
- self.displaymarking = self.seclabel.getTag('displaymarking')
-
- if self.stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION):
- delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not\
- None
- if not delayed:
- self.attention = True
-
- self.form_node = self.stanza.getTag('x', namespace=nbxmpp.NS_DATA)
-
- if gajim.config.get('ignore_incoming_xhtml'):
- self.xhtml = None
- else:
- self.xhtml = self.stanza.getXHTML()
-
- # XEP-0172 User Nickname
- self.user_nick = self.stanza.getTagData('nick') or ''
-
- self.get_chatstate()
-
- oob_node = self.stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
- self.oob_url = None
- self.oob_desc = None
- if oob_node:
- self.oob_url = oob_node.getTagData('url')
- self.oob_desc = oob_node.getTagData('desc')
- if self.oob_url:
- self.msgtxt += '\n'
- if self.oob_desc:
- self.msgtxt += self.oob_desc
- else:
- self.msgtxt += _('URL:')
- self.msgtxt += ' ' + self.oob_url
-
- replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
- if replace:
- self.correct_id = replace.getAttr('id')
-
- # ignore message duplicates
- if self.msgtxt and self.id_ and self.jid:
- self.msghash = hashlib.sha256(("%s|%s|%s" % (
- hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(),
- hashlib.sha256(self.id_.encode('utf-8')).hexdigest(),
- hashlib.sha256(self.jid.encode('utf-8')).hexdigest())).encode(
- 'utf-8')).digest()
- if self.msghash in self.conn.received_message_hashes:
- log.info("Ignoring duplicated message from '%s' with id '%s'" % (self.jid, self.id_))
- return False
- else:
- log.debug("subhashes: msgtxt, id_, jid = ('%s', '%s', '%s')" % (hashlib.sha256(self.msgtxt.encode('utf-8')).hexdigest(), hashlib.sha256(self.id_.encode('utf-8')).hexdigest(), hashlib.sha256(self.jid.encode('utf-8')).hexdigest()))
- self.conn.received_message_hashes.append(self.msghash)
- # only record the last 20000 hashes (should be about 1MB [32 bytes per hash]
- # and about 24 hours if you receive a message every 5 seconds)
- self.conn.received_message_hashes = self.conn.received_message_hashes[-20000:]
- return True
-
-class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
- name = 'chatstate-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- self.jid = self.msg_obj.jid
- self.fjid = self.msg_obj.fjid
- self.resource = self.msg_obj.resource
- self.chatstate = self.msg_obj.chatstate
- return True
-
-class GcMessageReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-message-received'
- base_network_events = []
-
- def generate(self):
- self.stanza = self.msg_obj.stanza
- if not hasattr(self, 'additional_data'):
- self.additional_data = self.msg_obj.additional_data
- self.id_ = self.msg_obj.stanza.getID()
- self.fjid = self.msg_obj.fjid
- self.msgtxt = self.msg_obj.msgtxt
- self.jid = self.msg_obj.jid
- self.room_jid = self.msg_obj.jid
- self.nickname = self.msg_obj.resource
- self.timestamp = self.msg_obj.timestamp
- self.xhtml_msgtxt = self.stanza.getXHTML()
- self.encrypted = self.msg_obj.encrypted
- self.correct_id = None # XEP-0308
-
- if gajim.config.get('ignore_incoming_xhtml'):
- self.xhtml_msgtxt = None
-
- if self.msg_obj.resource:
- # message from someone
- self.nick = self.msg_obj.resource
- else:
- # message from server
- self.nick = ''
-
- self.has_timestamp = bool(self.stanza.timestamp)
-
- self.subject = self.stanza.getSubject()
-
- if self.subject is not None:
- gajim.nec.push_incoming_event(GcSubjectReceivedEvent(None,
- conn=self.conn, msg_event=self))
- return
-
- conditions = self.stanza.getStatusConditions()
- if conditions:
- self.status_code = []
- for condition in conditions:
- if condition in CONDITION_TO_CODE:
- self.status_code.append(CONDITION_TO_CODE[condition])
- else:
- self.status_code = self.stanza.getStatusCode()
-
- if not self.stanza.getTag('body'): # no <body>
- # It could be a config change. See
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- if self.stanza.getTag('x'):
- if self.status_code != []:
- gajim.nec.push_incoming_event(GcConfigChangedReceivedEvent(
- None, conn=self.conn, msg_event=self))
- if self.msg_obj.form_node:
- return True
- return
-
- self.displaymarking = None
- seclabel = self.stanza.getTag('securitylabel')
- if seclabel and seclabel.getNamespace() == nbxmpp.NS_SECLABEL:
- # Ignore message from room in which we are not
- self.displaymarking = seclabel.getTag('displaymarking')
-
- if self.jid not in self.conn.last_history_time:
- return
-
- self.captcha_form = None
- captcha_tag = self.stanza.getTag('captcha', namespace=nbxmpp.NS_CAPTCHA)
- if captcha_tag:
- self.captcha_form = captcha_tag.getTag('x',
- namespace=nbxmpp.NS_DATA)
- for field in self.captcha_form.getTags('field'):
- for media in field.getTags('media'):
- for uri in media.getTags('uri'):
- uri_data = uri.getData()
- if uri_data.startswith('cid:'):
- uri_data = uri_data[4:]
- found = False
- for data in self.stanza.getTags('data',
- namespace=nbxmpp.NS_BOB):
- if data.getAttr('cid') == uri_data:
- uri.setData(data.getData())
- found = True
- if not found:
- self.conn.get_bob_data(uri_data, self.fjid,
- self.conn._dispatch_gc_msg_with_captcha,
- [self.stanza, self.msg_obj], 0)
- return
-
- replace = self.stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
- if replace:
- self.correct_id = replace.getAttr('id')
-
- return True
-
-class GcSubjectReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-subject-received'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_event.conn
- self.stanza = self.msg_event.stanza
- self.room_jid = self.msg_event.room_jid
- self.nickname = self.msg_event.nickname
- self.fjid = self.msg_event.fjid
- self.subject = self.msg_event.subject
- self.msgtxt = self.msg_event.msgtxt
- self.has_timestamp = self.msg_event.has_timestamp
- return True
-
-class GcConfigChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'gc-config-changed-received'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_event.conn
- self.stanza = self.msg_event.stanza
- self.room_jid = self.msg_event.room_jid
- self.status_code = self.msg_event.status_code
- return True
-
-class MessageSentEvent(nec.NetworkIncomingEvent):
- name = 'message-sent'
- base_network_events = []
-
- def generate(self):
- if not self.automatic_message:
- self.conn.sent_message_ids.append(self.msg_id)
- # only record the last 20000 message ids (should be about 1MB [36 byte per uuid]
- # and about 24 hours if you send out a message every 5 seconds)
- self.conn.sent_message_ids = self.conn.sent_message_ids[-20000:]
- return True
-
-class MessageNotSentEvent(nec.NetworkIncomingEvent):
- name = 'message-not-sent'
- base_network_events = []
-
-class MessageErrorEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'message-error'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- #only alert for errors of explicitly sent messages (see https://trac.gajim.org/ticket/8222)
- if self.id_ in self.conn.sent_message_ids:
- self.conn.sent_message_ids.remove(self.id_)
- return True
- return False
-
-class AnonymousAuthEvent(nec.NetworkIncomingEvent):
- name = 'anonymous-auth'
- base_network_events = []
-
-class JingleRequestReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-request-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleConnectedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-connected-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleDisconnectedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-disconnected-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleTransferCancelledEvent(nec.NetworkIncomingEvent):
- name = 'jingleFT-cancelled-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
- name = 'jingle-error-received'
- base_network_events = []
-
- def generate(self):
- self.fjid = self.jingle_session.peerjid
- self.jid, self.resource = gajim.get_room_and_nick_from_fjid(self.fjid)
- self.sid = self.jingle_session.sid
- return True
-
-class ArchivingReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-received'
- base_network_events = []
-
- def generate(self):
- self.type_ = self.stanza.getType()
- if self.type_ not in ('result', 'set', 'error'):
- return
- return True
-
-class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-error-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
-
- if self.type_ == 'error':
- self.error_msg = self.stanza.getErrorMsg()
- return True
-
-class ArchivingPreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-preferences-changed-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
-
- if self.type_ not in ('result', 'set'):
- return
-
- self.conf = {}
- self.new_items = {}
- self.removed_items = []
- pref = self.stanza.getTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- if pref:
- if pref.getTag('auto'):
- self.conf['auto'] = pref.getTagAttr('auto', 'save')
-
- method_auto = pref.getTag('method', attrs={'type': 'auto'})
- if method_auto:
- self.conf['method_auto'] = method_auto.getAttr('use')
-
- method_local = pref.getTag('method', attrs={'type': 'local'})
- if method_local:
- self.conf['method_local'] = method_local.getAttr('use')
-
- method_manual = pref.getTag('method', attrs={'type': 'manual'})
- if method_manual:
- self.conf['method_manual'] = method_manual.getAttr('use')
-
- default = pref.getTag('default')
- if default:
- self.conf['default'] = {
- 'expire': default.getAttr('expire'),
- 'otr': default.getAttr('otr'),
- 'save': default.getAttr('save'),
- 'unset': default.getAttr('unset')}
-
- for item in pref.getTags('item'):
- self.new_items[item.getAttr('jid')] = {
- 'expire': item.getAttr('expire'),
- 'otr': item.getAttr('otr'),
- 'save': item.getAttr('save')}
-
- elif self.stanza.getTag('itemremove'):
- for item in pref.getTags('item'):
- self.removed_items.append(item.getAttr('jid'))
- else:
- return
- return True
-
-class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-313-preferences-changed-received'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
- self.items = []
- self.default = None
- self.id = self.stanza.getID()
- self.answer = None
- prefs = self.stanza.getTag('prefs')
-
- if self.type_ != 'result' or not prefs:
- return
-
- self.default = prefs.getAttr('default')
-
- for item in prefs.getTag('always').getTags('jid'):
- self.items.append((item.getData(), 'Always'))
-
- for item in prefs.getTag('never').getTags('jid'):
- self.items.append((item.getData(), 'Never'))
-
- return True
-
-class ArchivingFinishedReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-finished'
- base_network_events = ['archiving-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.type_ = self.base_event.type_
- self.fin = self.stanza.getTag('fin')
-
- if self.type_ != 'result' or not self.fin:
- return
-
- self.queryid = self.fin.getAttr('queryid')
- if not self.queryid:
- return
-
- return True
-
-class ArchivingFinishedLegacyReceivedEvent(nec.NetworkIncomingEvent):
- name = 'archiving-finished-legacy'
- base_network_events = ['raw-message-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- self.fin = self.stanza.getTag('fin', namespace=nbxmpp.NS_MAM)
-
- if not self.fin:
- return
-
- self.queryid = self.fin.getAttr('queryid')
- if not self.queryid:
- return
-
- return True
-
-class AccountCreatedEvent(nec.NetworkIncomingEvent):
- name = 'account-created'
- base_network_events = []
-
-class AccountNotCreatedEvent(nec.NetworkIncomingEvent):
- name = 'account-not-created'
- base_network_events = []
-
-class NewAccountConnectedEvent(nec.NetworkIncomingEvent):
- name = 'new-account-connected'
- base_network_events = []
-
- def generate(self):
- try:
- self.errnum = self.conn.connection.Connection.ssl_errnum
- except AttributeError:
- self.errnum = 0 # we don't have an errnum
- self.ssl_msg = ''
- if self.errnum > 0:
- from common.connection import ssl_error
- self.ssl_msg = ssl_error.get(self.errnum,
- _('Unknown SSL error: %d') % self.errnum)
- self.ssl_cert = ''
- self.ssl_fingerprint_sha1 = ''
- self.ssl_fingerprint_sha256 = ''
- if self.conn.connection.Connection.ssl_certificate:
- cert = self.conn.connection.Connection.ssl_certificate
- self.ssl_cert = OpenSSL.crypto.dump_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert).decode('utf-8')
- self.ssl_fingerprint_sha1 = cert.digest('sha1').decode('utf-8')
- self.ssl_fingerprint_sha256 = cert.digest('sha256').decode('utf-8')
- return True
-
-class NewAccountNotConnectedEvent(nec.NetworkIncomingEvent):
- name = 'new-account-not-connected'
- base_network_events = []
-
-class ConnectionTypeEvent(nec.NetworkIncomingEvent):
- name = 'connection-type'
- base_network_events = []
-
-class VcardPublishedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-published'
- base_network_events = []
-
-class VcardNotPublishedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-not-published'
- base_network_events = []
-
-class StanzaReceivedEvent(nec.NetworkIncomingEvent):
- name = 'stanza-received'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
-
- def generate(self):
- return True
-
-class StanzaSentEvent(nec.NetworkIncomingEvent):
- name = 'stanza-sent'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
-
-class AgentRemovedEvent(nec.NetworkIncomingEvent):
- name = 'agent-removed'
- base_network_events = []
-
- def generate(self):
- self.jid_list = []
- for jid in gajim.contacts.get_jid_list(self.conn.name):
- if jid.endswith('@' + self.agent):
- self.jid_list.append(jid)
- return True
-
-class BadGPGPassphraseEvent(nec.NetworkIncomingEvent):
- name = 'bad-gpg-passphrase'
- base_network_events = []
-
- def generate(self):
- self.account = self.conn.name
- self.use_gpg_agent = gajim.config.get('use_gpg_agent')
- self.keyID = gajim.config.get_per('accounts', self.conn.name, 'keyid')
- return True
-
-class ConnectionLostEvent(nec.NetworkIncomingEvent):
- name = 'connection-lost'
- base_network_events = []
-
- def generate(self):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self.conn,
- show='offline'))
- return True
-
-class PingSentEvent(nec.NetworkIncomingEvent):
- name = 'ping-sent'
- base_network_events = []
-
-class PingReplyEvent(nec.NetworkIncomingEvent):
- name = 'ping-reply'
- base_network_events = []
-
-class PingErrorEvent(nec.NetworkIncomingEvent):
- name = 'ping-error'
- base_network_events = []
-
-class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent,
-PresenceHelperEvent):
- name = 'caps-presence-received'
- base_network_events = ['raw-pres-received']
-
- def _extract_caps_from_presence(self):
- caps_tag = self.stanza.getTag('c', namespace=nbxmpp.NS_CAPS)
- if caps_tag:
- self.hash_method = caps_tag['hash']
- self.node = caps_tag['node']
- self.caps_hash = caps_tag['ver']
- else:
- self.hash_method = self.node = self.caps_hash = None
-
- def generate(self):
- self.conn = self.base_event.conn
- self.stanza = self.base_event.stanza
- try:
- self.get_jid_resource()
- except Exception:
- return
- self._generate_ptype()
- self._generate_show()
- self._extract_caps_from_presence()
- return True
-
-class CapsDiscoReceivedEvent(nec.NetworkIncomingEvent):
- name = 'caps-disco-received'
- base_network_events = []
-
-class CapsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'caps-received'
- base_network_events = ['caps-presence-received', 'caps-disco-received']
-
- def generate(self):
- self.conn = self.base_event.conn
- self.fjid = self.base_event.fjid
- self.jid = self.base_event.jid
- self.resource = self.base_event.resource
- self.client_caps = self.base_event.client_caps
- return True
-
-class GPGTrustKeyEvent(nec.NetworkIncomingEvent):
- name = 'gpg-trust-key'
- base_network_events = []
-
-class GPGPasswordRequiredEvent(nec.NetworkIncomingEvent):
- name = 'gpg-password-required'
- base_network_events = []
-
- def generate(self):
- self.keyid = gajim.config.get_per('accounts', self.conn.name, 'keyid')
- return True
-
-class PEPReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'pep-received'
- base_network_events = []
-
- def generate(self):
- if not self.stanza.getTag('event'):
- return
- if self.stanza.getTag('error'):
- log.debug('PEPReceivedEvent received error stanza. Ignoring')
- return
-
- try:
- self.get_jid_resource()
- except Exception:
- return
-
- self.event_tag = self.stanza.getTag('event')
-
- for pep_class in SUPPORTED_PERSONAL_USER_EVENTS:
- pep = pep_class.get_tag_as_PEP(self.fjid, self.conn.name,
- self.event_tag)
- if pep:
- self.pep_type = pep.type_
- return True
-
- items = self.event_tag.getTag('items')
- if items:
- # for each entry in feed (there shouldn't be more than one, but to
- # be sure...
- for item in items.getTags('item'):
- entry = item.getTag('entry', namespace=nbxmpp.NS_ATOM)
- if entry:
- gajim.nec.push_incoming_event(AtomEntryReceived(None,
- conn=self.conn, node=entry))
- raise nbxmpp.NodeProcessed
-
-class AtomEntryReceived(nec.NetworkIncomingEvent):
- name = 'atom-entry-received'
- base_network_events = []
-
- def generate(self):
- self.atom_entry = atom.OldEntry(node=self.node)
- return True
-
-class PlainConnectionEvent(nec.NetworkIncomingEvent):
- name = 'plain-connection'
- base_network_events = []
-
-class InsecurePasswordEvent(nec.NetworkIncomingEvent):
- name = 'insecure-password'
- base_network_events = []
-
-class InsecureSSLConnectionEvent(nec.NetworkIncomingEvent):
- name = 'insecure-ssl-connection'
- base_network_events = []
-
-class SSLErrorEvent(nec.NetworkIncomingEvent):
- name = 'ssl-error'
- base_network_events = []
-
-class FingerprintErrorEvent(nec.NetworkIncomingEvent):
- name = 'fingerprint-error'
- base_network_events = []
-
-class UniqueRoomIdSupportedEvent(nec.NetworkIncomingEvent):
- name = 'unique-room-id-supported'
- base_network_events = []
-
-class UniqueRoomIdNotSupportedEvent(nec.NetworkIncomingEvent):
- name = 'unique-room-id-not-supported'
- base_network_events = []
-
-class PrivacyListsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-lists-received'
- base_network_events = []
-
-class PrivacyListReceivedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-received'
- base_network_events = []
-
-class PrivacyListRemovedEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-removed'
- base_network_events = []
-
-class PrivacyListActiveDefaultEvent(nec.NetworkIncomingEvent):
- name = 'privacy-list-active-default'
- base_network_events = []
-
-class NonAnonymousServerErrorEvent(nec.NetworkIncomingEvent):
- name = 'non-anonymous-server-error'
- base_network_events = []
-
-class VcardReceivedEvent(nec.NetworkIncomingEvent):
- name = 'vcard-received'
- base_network_events = []
-
- def generate(self):
- self.nickname = None
- if 'NICKNAME' in self.vcard_dict:
- self.nickname = self.vcard_dict['NICKNAME']
- elif 'FN' in self.vcard_dict:
- self.nickname = self.vcard_dict['FN']
- self.jid = self.vcard_dict['jid']
- self.resource = self.vcard_dict['resource']
- self.fjid = self.jid
- if self.resource:
- self.fjid += '/' + self.resource
- return True
-
-class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
- name = 'pep-config-received'
- base_network_events = []
-
-class MetacontactsReceivedEvent(nec.NetworkIncomingEvent):
- name = 'metacontacts-received'
- base_network_events = []
-
- def generate(self):
- # Metacontact tags
- # http://www.xmpp.org/extensions/xep-0209.html
- self.meta_list = {}
- query = self.stanza.getTag('query')
- storage = query.getTag('storage')
- metas = storage.getTags('meta')
- for meta in metas:
- try:
- jid = helpers.parse_jid(meta.getAttr('jid'))
- except helpers.InvalidFormat:
- continue
- tag = meta.getAttr('tag')
- data = {'jid': jid}
- order = meta.getAttr('order')
- try:
- order = int(order)
- except Exception:
- order = 0
- if order is not None:
- data['order'] = order
- if tag in self.meta_list:
- self.meta_list[tag].append(data)
- else:
- self.meta_list[tag] = [data]
- return True
-
-class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent):
- name = 'zeroconf-name-conflict'
- base_network_events = []
-
-class PasswordRequiredEvent(nec.NetworkIncomingEvent):
- name = 'password-required'
- base_network_events = []
-
-class Oauth2CredentialsRequiredEvent(nec.NetworkIncomingEvent):
- name = 'oauth2-credentials-required'
- base_network_events = []
-
-class FailedDecryptEvent(nec.NetworkIncomingEvent):
- name = 'failed-decrypt'
- base_network_events = []
-
- def generate(self):
- self.conn = self.msg_obj.conn
- self.fjid = self.msg_obj.fjid
- self.timestamp = self.msg_obj.timestamp
- self.session = self.msg_obj.session
- return True
-
-class SignedInEvent(nec.NetworkIncomingEvent):
- name = 'signed-in'
- base_network_events = []
-
-class RegisterAgentInfoReceivedEvent(nec.NetworkIncomingEvent):
- name = 'register-agent-info-received'
- base_network_events = []
-
-class AgentItemsReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-items-received'
- base_network_events = []
-
- def generate(self):
- q = self.stanza.getTag('query')
- self.node = q.getAttr('node')
- if not self.node:
- self.node = ''
- qp = self.stanza.getQueryPayload()
- self.items = []
- if not qp:
- qp = []
- for i in qp:
- # CDATA payload is not processed, only nodes
- if not isinstance(i, nbxmpp.simplexml.Node):
- continue
- attr = {}
- for key in i.getAttrs():
- attr[key] = i.getAttrs()[key]
- if 'jid' not in attr:
- continue
- try:
- attr['jid'] = helpers.parse_jid(attr['jid'])
- except helpers.InvalidFormat:
- # jid is not conform
- continue
- self.items.append(attr)
- self.get_jid_resource()
- hostname = gajim.config.get_per('accounts', self.conn.name, 'hostname')
- self.get_id()
- if self.id_ in self.conn.disco_items_ids:
- self.conn.disco_items_ids.remove(self.id_)
- if self.fjid == hostname and self.id_[:6] == 'Gajim_':
- for item in self.items:
- self.conn.discoverInfo(item['jid'], id_prefix='Gajim_')
- else:
- return True
-
-class AgentItemsErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-items-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.get_id()
- if self.id_ in self.conn.disco_items_ids:
- self.conn.disco_items_ids.remove(self.id_)
- return True
-
-class AgentInfoReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-info-received'
- base_network_events = []
-
- def generate(self):
- self.get_id()
- if self.id_ in self.conn.disco_info_ids:
- self.conn.disco_info_ids.remove(self.id_)
- if self.id_ is None:
- log.warning('Invalid IQ received without an ID. Ignoring it: %s' % \
- self.stanza)
- return
- # According to XEP-0030:
- # For identity: category, type is mandatory, name is optional.
- # For feature: var is mandatory
- self.identities, self.features, self.data = [], [], []
- q = self.stanza.getTag('query')
- self.node = q.getAttr('node')
- if not self.node:
- self.node = ''
- qc = self.stanza.getQueryChildren()
- if not qc:
- qc = []
-
- for i in qc:
- if i.getName() == 'identity':
- attr = {}
- for key in i.getAttrs().keys():
- attr[key] = i.getAttr(key)
- self.identities.append(attr)
- elif i.getName() == 'feature':
- var = i.getAttr('var')
- if var:
- self.features.append(var)
- elif i.getName() == 'x' and i.getNamespace() == nbxmpp.NS_DATA:
- self.data.append(nbxmpp.DataForm(node=i))
-
- if not self.identities:
- # ejabberd doesn't send identities when we browse online users
- # see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
- self.identities = [{'category': 'server', 'type': 'im',
- 'name': self.node}]
- self.get_jid_resource()
- return True
-
-class AgentInfoErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'agent-info-error-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- self.get_id()
- if self.id_ in self.conn.disco_info_ids:
- self.conn.disco_info_ids.remove(self.id_)
- return True
-
-class FileRequestReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'file-request-received'
- base_network_events = []
-
- def init(self):
- self.jingle_content = None
- self.FT_content = None
-
- def generate(self):
- self.get_id()
- self.fjid = self.conn._ft_get_from(self.stanza)
- self.jid = gajim.get_jid_without_resource(self.fjid)
- if self.jingle_content:
- secu = self.jingle_content.getTag('security')
- self.FT_content.use_security = bool(secu)
- if secu:
- fingerprint = secu.getTag('fingerprint')
- if fingerprint:
- self.FT_content.x509_fingerprint = fingerprint.getData()
- if not self.FT_content.transport:
- self.FT_content.transport = JingleTransportSocks5()
- self.FT_content.transport.set_our_jid(
- self.FT_content.session.ourjid)
- self.FT_content.transport.set_connection(
- self.FT_content.session.connection)
- sid = self.stanza.getTag('jingle').getAttr('sid')
- self.file_props = FilesProp.getNewFileProp(self.conn.name, sid)
- self.file_props.transport_sid = self.FT_content.transport.sid
- self.FT_content.file_props = self.file_props
- self.FT_content.transport.set_file_props(self.file_props)
- self.file_props.streamhosts.extend(
- self.FT_content.transport.remote_candidates)
- for host in self.file_props.streamhosts:
- host['initiator'] = self.FT_content.session.initiator
- host['target'] = self.FT_content.session.responder
- self.file_props.session_type = 'jingle'
- self.file_props.stream_methods = nbxmpp.NS_BYTESTREAM
- desc = self.jingle_content.getTag('description')
- if self.jingle_content.getAttr('creator') == 'initiator':
- file_tag = desc.getTag('file')
- self.file_props.sender = self.fjid
- self.file_props.receiver = self.conn._ft_get_our_jid()
- else:
- file_tag = desc.getTag('file')
- h = file_tag.getTag('hash')
- h = h.getData() if h else None
- n = file_tag.getTag('name')
- n = n.getData() if n else None
- pjid = gajim.get_jid_without_resource(self.fjid)
- file_info = self.conn.get_file_info(pjid, hash_=h,
- name=n,account=self.conn.name)
- self.file_props.file_name = file_info['file-name']
- self.file_props.sender = self.conn._ft_get_our_jid()
- self.file_props.receiver = self.fjid
- self.file_props.type_ = 's'
- for child in file_tag.getChildren():
- name = child.getName()
- val = child.getData()
- if val is None:
- continue
- if name == 'name':
- self.file_props.name = val
- if name == 'size':
- self.file_props.size = int(val)
- if name == 'hash':
- self.file_props.algo = child.getAttr('algo')
- self.file_props.hash_ = val
- if name == 'date':
- self.file_props.date = val
- else:
- si = self.stanza.getTag('si')
- self.file_props = FilesProp.getNewFileProp(self.conn.name,
- si.getAttr('id'))
- profile = si.getAttr('profile')
- if profile != nbxmpp.NS_FILE:
- self.conn.send_file_rejection(self.file_props, code='400',
- typ='profile')
- raise nbxmpp.NodeProcessed
- feature_tag = si.getTag('feature', namespace=nbxmpp.NS_FEATURE)
- if not feature_tag:
- return
- form_tag = feature_tag.getTag('x', namespace=nbxmpp.NS_DATA)
- if not form_tag:
- return
- self.dataform = dataforms.ExtendForm(node=form_tag)
- for f in self.dataform.iter_fields():
- if f.var == 'stream-method' and f.type_ == 'list-single':
- values = [o[1] for o in f.options]
- self.file_props.stream_methods = ' '.join(values)
- if nbxmpp.NS_BYTESTREAM in values or \
- nbxmpp.NS_IBB in values:
- break
- else:
- self.conn.send_file_rejection(self.file_props, code='400',
- typ='stream')
- raise nbxmpp.NodeProcessed
- file_tag = si.getTag('file')
- for name, val in file_tag.getAttrs().items():
- if val is None:
- continue
- if name == 'name':
- self.file_props.name = val
- if name == 'size':
- self.file_props.size = int(val)
- mime_type = si.getAttr('mime-type')
- if mime_type is not None:
- self.file_props.mime_type = mime_type
- self.file_props.sender = self.fjid
- self.file_props.receiver = self.conn._ft_get_our_jid()
- self.file_props.request_id = self.id_
- file_desc_tag = file_tag.getTag('desc')
- if file_desc_tag is not None:
- self.file_props.desc = file_desc_tag.getData()
- self.file_props.transfered_size = []
- return True
-
-class FileRequestErrorEvent(nec.NetworkIncomingEvent):
- name = 'file-request-error'
- base_network_events = []
-
- def generate(self):
- self.jid = gajim.get_jid_without_resource(self.jid)
- return True
-
-class FileTransferCompletedEvent(nec.NetworkIncomingEvent):
- name = 'file-transfer-completed'
- base_network_events = []
-
- def generate(self):
- jid = str(self.file_props.receiver)
- self.jid = gajim.get_jid_without_resource(jid)
- return True
-
-class GatewayPromptReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
- name = 'gateway-prompt-received'
- base_network_events = []
-
- def generate(self):
- self.get_jid_resource()
- query = self.stanza.getTag('query')
- if query:
- self.desc = query.getTagData('desc')
- self.prompt = query.getTagData('prompt')
- self.prompt_jid = query.getTagData('jid')
- else:
- self.desc = None
- self.prompt = None
- self.prompt_jid = None
- return True
-
-class NotificationEvent(nec.NetworkIncomingEvent):
- name = 'notification'
- base_network_events = ['decrypted-message-received', 'gc-message-received',
- 'presence-received']
-
- def detect_type(self):
- if self.base_event.name == 'decrypted-message-received':
- self.notif_type = 'msg'
- if self.base_event.name == 'gc-message-received':
- self.notif_type = 'gc-msg'
- if self.base_event.name == 'presence-received':
- self.notif_type = 'pres'
-
- def get_focused(self):
- self.control_focused = False
- if self.control:
- parent_win = self.control.parent_win
- if parent_win and self.control == parent_win.get_active_control() \
- and parent_win.window.get_property('has-toplevel-focus'):
- self.control_focused = True
-
- def handle_incoming_msg_event(self, msg_obj):
- # don't alert for carbon copied messages from ourselves
- if msg_obj.sent:
- return
- if not msg_obj.msgtxt:
- return
- self.jid = msg_obj.jid
- if msg_obj.session:
- self.control = msg_obj.session.control
- else:
- self.control = None
- self.get_focused()
- # This event has already been added to event list
- if not self.control and len(gajim.events.get_events(self.conn.name, \
- self.jid, [msg_obj.mtype])) <= 1:
- self.first_unread = True
-
- if msg_obj.mtype == 'pm':
- nick = msg_obj.resource
- else:
- nick = gajim.get_name_from_jid(self.conn.name, self.jid)
-
- if self.first_unread:
- self.sound_event = 'first_message_received'
- elif self.control_focused:
- self.sound_event = 'next_message_received_focused'
- else:
- self.sound_event = 'next_message_received_unfocused'
-
- if gajim.config.get('notification_preview_message'):
- self.popup_text = msg_obj.msgtxt
- if self.popup_text and (self.popup_text.startswith('/me ') or \
- self.popup_text.startswith('/me\n')):
- self.popup_text = '* ' + nick + self.popup_text[3:]
- else:
- # We don't want message preview, do_preview = False
- self.popup_text = ''
- if msg_obj.mtype == 'normal': # single message
- self.popup_msg_type = 'normal'
- self.popup_event_type = _('New Single Message')
- self.popup_image = 'gajim-single_msg_recv'
- self.popup_title = _('New Single Message from %(nickname)s') % \
- {'nickname': nick}
- elif msg_obj.mtype == 'pm':
- self.popup_msg_type = 'pm'
- self.popup_event_type = _('New Private Message')
- self.popup_image = 'gajim-priv_msg_recv'
- self.popup_title = _('New Private Message from group chat %s') % \
- msg_obj.jid
- if self.popup_text:
- self.popup_text = _('%(nickname)s: %(message)s') % \
- {'nickname': nick, 'message': self.popup_text}
- else:
- self.popup_text = _('Messaged by %(nickname)s') % \
- {'nickname': nick}
- else: # chat message
- self.popup_msg_type = 'chat'
- self.popup_event_type = _('New Message')
- self.popup_image = 'gajim-chat_msg_recv'
- self.popup_title = _('New Message from %(nickname)s') % \
- {'nickname': nick}
-
-
- if gajim.config.get('notify_on_new_message'):
- if self.first_unread or (gajim.config.get('autopopup_chat_opened') \
- and not self.control_focused):
- if gajim.config.get('autopopupaway'):
- # always show notification
- self.do_popup = True
- if gajim.connections[self.conn.name].connected in (2, 3):
- # we're online or chat
- self.do_popup = True
-
- if msg_obj.attention and not gajim.config.get(
- 'ignore_incoming_attention'):
- self.popup_timeout = 0
- self.do_popup = True
- else:
- self.popup_timeout = gajim.config.get('notification_timeout')
-
- if msg_obj.attention and not gajim.config.get(
- 'ignore_incoming_attention') and gajim.config.get_per('soundevents',
- 'attention_received', 'enabled'):
- self.sound_event = 'attention_received'
- self.do_sound = True
- elif self.first_unread and helpers.allow_sound_notification(
- self.conn.name, 'first_message_received'):
- self.do_sound = True
- elif not self.first_unread and self.control_focused and \
- helpers.allow_sound_notification(self.conn.name,
- 'next_message_received_focused'):
- self.do_sound = True
- elif not self.first_unread and not self.control_focused and \
- helpers.allow_sound_notification(self.conn.name,
- 'next_message_received_unfocused'):
- self.do_sound = True
-
- def handle_incoming_gc_msg_event(self, msg_obj):
- if not msg_obj.msg_obj.gc_control:
- # we got a message from a room we're not in? ignore it
- return
- self.jid = msg_obj.jid
- sound = msg_obj.msg_obj.gc_control.highlighting_for_message(
- msg_obj.msgtxt, msg_obj.timestamp)[1]
-
- if msg_obj.nickname != msg_obj.msg_obj.gc_control.nick:
- self.do_sound = True
- if sound == 'received':
- self.sound_event = 'muc_message_received'
- elif sound == 'highlight':
- self.sound_event = 'muc_message_highlight'
- else:
- self.do_sound = False
- else:
- self.do_sound = False
-
- self.do_popup = False
-
- def get_path_to_generic_or_avatar(self, generic, jid=None, suffix=None):
- """
- Choose between avatar image and default image
-
- Returns full path to the avatar image if it exists, otherwise returns full
- path to the image. generic must be with extension and suffix without
- """
- if jid:
- # we want an avatar
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid) + suffix
- path_to_local_file = path_to_file + '_local'
- for extension in ('.png', '.jpeg'):
- path_to_local_file_full = path_to_local_file + extension
- if os.path.exists(path_to_local_file_full):
- return path_to_local_file_full
- for extension in ('.png', '.jpeg'):
- path_to_file_full = path_to_file + extension
- if os.path.exists(path_to_file_full):
- return path_to_file_full
- return os.path.abspath(generic)
-
- def handle_incoming_pres_event(self, pres_obj):
- if gajim.jid_is_transport(pres_obj.jid):
- return True
- account = pres_obj.conn.name
- self.jid = pres_obj.jid
- resource = pres_obj.resource or ''
- # It isn't an agent
- for c in pres_obj.contact_list:
- if c.resource == resource:
- # we look for other connected resources
- continue
- if c.show not in ('offline', 'error'):
- return True
-
-
- # no other resource is connected, let's look in metacontacts
- family = gajim.contacts.get_metacontacts_family(account, self.jid)
- for info in family:
- acct_ = info['account']
- jid_ = info['jid']
- c_ = gajim.contacts.get_contact_with_highest_priority(acct_, jid_)
- if not c_:
- continue
- if c_.jid == self.jid:
- continue
- if c_.show not in ('offline', 'error'):
- return True
-
- if pres_obj.old_show < 2 and pres_obj.new_show > 1:
- event = 'contact_connected'
- show_image = 'online.png'
- suffix = '_notif_size_colored'
- server = gajim.get_server_from_jid(self.jid)
- account_server = account + '/' + server
- block_transport = False
- if account_server in gajim.block_signed_in_notifications and \
- gajim.block_signed_in_notifications[account_server]:
- block_transport = True
- if helpers.allow_showing_notification(account, 'notify_on_signin') \
- and not gajim.block_signed_in_notifications[account] and \
- not block_transport:
- self.do_popup = True
- if gajim.config.get_per('soundevents', 'contact_connected',
- 'enabled') and not gajim.block_signed_in_notifications[account] and\
- not block_transport and helpers.allow_sound_notification(account,
- 'contact_connected'):
- self.sound_event = event
- self.do_sound = True
-
- elif pres_obj.old_show > 1 and pres_obj.new_show < 2:
- event = 'contact_disconnected'
- show_image = 'offline.png'
- suffix = '_notif_size_bw'
- if helpers.allow_showing_notification(account, 'notify_on_signout'):
- self.do_popup = True
- if gajim.config.get_per('soundevents', 'contact_disconnected',
- 'enabled') and helpers.allow_sound_notification(account, event):
- self.sound_event = event
- self.do_sound = True
- # Status change (not connected/disconnected or error (<1))
- elif pres_obj.new_show > 1:
- event = 'status_change'
- # FIXME: we don't always 'online.png', but we first need 48x48 for
- # all status
- show_image = 'online.png'
- suffix = '_notif_size_colored'
- else:
- return True
-
- transport_name = gajim.get_transport_name_from_jid(self.jid)
- img_path = None
- if transport_name:
- img_path = os.path.join(helpers.get_transport_path(
- transport_name), '48x48', show_image)
- if not img_path or not os.path.isfile(img_path):
- iconset = gajim.config.get('iconset')
- img_path = os.path.join(helpers.get_iconset_path(iconset),
- '48x48', show_image)
- self.popup_image_path = self.get_path_to_generic_or_avatar(img_path,
- jid=self.jid, suffix=suffix)
-
- self.popup_timeout = gajim.config.get('notification_timeout')
-
- nick = i18n.direction_mark + gajim.get_name_from_jid(account, self.jid)
- if event == 'status_change':
- self.popup_title = _('%(nick)s Changed Status') % \
- {'nick': nick}
- self.popup_text = _('%(nick)s is now %(status)s') % \
- {'nick': nick, 'status': helpers.get_uf_show(pres_obj.show)}
- if pres_obj.status:
- self.popup_text = self.popup_text + " : " + pres_obj.status
- self.popup_event_type = _('Contact Changed Status')
- elif event == 'contact_connected':
- self.popup_title = _('%(nickname)s Signed In') % {'nickname': nick}
- self.popup_text = ''
- if pres_obj.status:
- self.popup_text = pres_obj.status
- self.popup_event_type = _('Contact Signed In')
- elif event == 'contact_disconnected':
- self.popup_title = _('%(nickname)s Signed Out') % {'nickname': nick}
- self.popup_text = ''
- if pres_obj.status:
- self.popup_text = pres_obj.status
- self.popup_event_type = _('Contact Signed Out')
-
- def generate(self):
- # what's needed to compute output
- self.conn = self.base_event.conn
- self.jid = ''
- self.control = None
- self.control_focused = False
- self.first_unread = False
-
- # For output
- self.do_sound = False
- self.sound_file = ''
- self.sound_event = '' # gajim sound played if not sound_file is set
- self.show_popup = False
-
- self.do_popup = False
- self.popup_title = ''
- self.popup_text = ''
- self.popup_event_type = ''
- self.popup_msg_type = ''
- self.popup_image = ''
- self.popup_image_path = ''
- self.popup_timeout = -1
-
- self.do_command = False
- self.command = ''
-
- self.show_in_notification_area = False
- self.show_in_roster = False
-
- self.detect_type()
-
- if self.notif_type == 'msg':
- self.handle_incoming_msg_event(self.base_event)
- elif self.notif_type == 'gc-msg':
- self.handle_incoming_gc_msg_event(self.base_event)
- elif self.notif_type == 'pres':
- self.handle_incoming_pres_event(self.base_event)
- return True
-
-class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name = 'message-outgoing'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.message = ''
- self.keyID = None
- self.type_ = 'chat'
- self.subject = ''
- self.chatstate = None
- self.msg_id = None
- self.resource = None
- self.user_nick = None
- self.xhtml = None
- self.label = None
- self.session = None
- self.forward_from = None
- self.form_node = None
- self.original_message = None
- self.delayed = None
- self.callback = None
- self.callback_args = []
- self.now = False
- self.is_loggable = True
- self.control = None
- self.attention = False
- self.correct_id = None
- self.automatic_message = True
- self.encryption = ''
-
- def get_full_jid(self):
- if self.resource:
- return self.jid + '/' + self.resource
- if self.session:
- return self.session.get_to()
- return self.jid
-
- def generate(self):
- return True
-
-class StanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name='stanza-message-outgoing'
- base_network_events = []
-
- def generate(self):
- return True
-
-class GcStanzaMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name='gc-stanza-message-outgoing'
- base_network_events = []
-
- def generate(self):
- return True
-
-class GcMessageOutgoingEvent(nec.NetworkOutgoingEvent):
- name = 'gc-message-outgoing'
- base_network_events = []
-
- def init(self):
- self.additional_data = {}
- self.message = ''
- self.chatstate = None
- self.xhtml = None
- self.label = None
- self.callback = None
- self.callback_args = []
- self.is_loggable = True
- self.control = None
- self.correct_id = None
- self.automatic_message = True
-
- def generate(self):
- return True
-
-
-class ClientCertPassphraseEvent(nec.NetworkIncomingEvent):
- name = 'client-cert-passphrase'
- base_network_events = []
-
-class InformationEvent(nec.NetworkIncomingEvent):
- name = 'information'
- base_network_events = []
-
- def init(self):
- self.popup = True
-
-class BlockingEvent(nec.NetworkIncomingEvent):
- name = 'blocking'
- base_network_events = []
-
- def init(self):
- self.blocked_jids = []
- self.unblocked_jids = []
- self.unblock_all = False
-
- def generate(self):
- block_tag = self.stanza.getTag('block', namespace=nbxmpp.NS_BLOCKING)
- if block_tag:
- for item in block_tag.getTags('item'):
- self.blocked_jids.append(item.getAttr('jid'))
- unblock_tag = self.stanza.getTag('unblock',
- namespace=nbxmpp.NS_BLOCKING)
- if unblock_tag:
- if not unblock_tag.getTags('item'): # unblock all
- self.unblock_all = True
- for item in unblock_tag.getTags('item'):
- self.unblocked_jids.append(item.getAttr('jid'))
- return True
diff --git a/src/common/contacts.py b/src/common/contacts.py
deleted file mode 100644
index 483131e12..000000000
--- a/src/common/contacts.py
+++ /dev/null
@@ -1,868 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/contacts.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from functools import cmp_to_key
-
-try:
- from common import caps_cache
- from common.account import Account
- import common.gajim
-except ImportError as e:
- if __name__ != "__main__":
- raise ImportError(str(e))
-
-class XMPPEntity(object):
- """
- Base representation of entities in XMPP
- """
-
- def __init__(self, jid, account, resource):
- self.jid = jid
- self.resource = resource
- self.account = account
-
-class CommonContact(XMPPEntity):
-
- def __init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate, client_caps=None):
-
- XMPPEntity.__init__(self, jid, account, resource)
-
- self.show = show
- self.status = status
- self.name = name
-
- self.client_caps = client_caps or caps_cache.NullClientCaps()
-
- # please read xep-85 http://www.xmpp.org/extensions/xep-0085.html
- # this holds what WE SEND to contact (our current chatstate)
- self.our_chatstate = our_chatstate
- # this is contact's chatstate
- self.chatstate = chatstate
-
- def get_full_jid(self):
- raise NotImplementedError
-
- def get_shown_name(self):
- raise NotImplementedError
-
- def supports(self, requested_feature):
- """
- Return True if the contact has advertised to support the feature
- identified by the given namespace. False otherwise.
- """
- if self.show == 'offline':
- # Unfortunately, if all resources are offline, the contact
- # includes the last resource that was online. Check for its
- # show, so we can be sure it's existant. Otherwise, we still
- # return caps for a contact that has no resources left.
- return False
- else:
- return caps_cache.client_supports(self.client_caps, requested_feature)
-
-
-class Contact(CommonContact):
- """
- Information concerning a contact
- """
- def __init__(self, jid, account, name='', groups=None, show='', status='',
- sub='', ask='', resource='', priority=0, keyID='', client_caps=None,
- our_chatstate=None, chatstate=None, last_status_time=None, msg_log_id=None,
- last_activity_time=None):
- if not isinstance(jid, str):
- print('no str')
- if groups is None:
- groups = []
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate, client_caps=client_caps)
-
- self.contact_name = '' # nick choosen by contact
- self.groups = [i if i else _('General') for i in set(groups)] # filter duplicate values
-
- self.sub = sub
- self.ask = ask
-
- self.priority = priority
- self.keyID = keyID
- self.msg_log_id = msg_log_id
- self.last_status_time = last_status_time
- self.last_activity_time = last_activity_time
-
- self.pep = {}
-
- def get_full_jid(self):
- if self.resource:
- return self.jid + '/' + self.resource
- return self.jid
-
- def get_shown_name(self):
- if self.name:
- return self.name
- if self.contact_name:
- return self.contact_name
- return self.jid.split('@')[0]
-
- def get_shown_groups(self):
- if self.is_observer():
- return [_('Observers')]
- elif self.is_groupchat():
- return [_('Groupchats')]
- elif self.is_transport():
- return [_('Transports')]
- elif not self.groups:
- return [_('General')]
- else:
- return self.groups
-
- def is_hidden_from_roster(self):
- """
- If contact should not be visible in roster
- """
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- if self.is_transport():
- return False
- if self.sub in ('both', 'to'):
- return False
- if self.sub in ('none', 'from') and self.ask == 'subscribe':
- return False
- if self.sub in ('none', 'from') and (self.name or len(self.groups)):
- return False
- if _('Not in Roster') in self.groups:
- return False
- return True
-
- def is_observer(self):
- # XEP-0162: http://www.xmpp.org/extensions/xep-0162.html
- is_observer = False
- if self.sub == 'from' and not self.is_transport()\
- and self.is_hidden_from_roster():
- is_observer = True
- return is_observer
-
- def is_groupchat(self):
- for account in common.gajim.gc_connected:
- if self.jid in common.gajim.gc_connected[account]:
- return True
- return False
-
- def is_transport(self):
- # if not '@' or '@' starts the jid then contact is transport
- return self.jid.find('@') <= 0
-
-
-class GC_Contact(CommonContact):
- """
- Information concerning each groupchat contact
- """
-
- def __init__(self, room_jid, account, name='', show='', status='', role='',
- affiliation='', jid='', resource='', our_chatstate=None,
- chatstate=None):
-
- CommonContact.__init__(self, jid, account, resource, show, status, name,
- our_chatstate, chatstate)
-
- self.room_jid = room_jid
- self.role = role
- self.affiliation = affiliation
-
- def get_full_jid(self):
- return self.room_jid + '/' + self.name
-
- def get_shown_name(self):
- return self.name
-
- def as_contact(self):
- """
- Create a Contact instance from this GC_Contact instance
- """
- return Contact(jid=self.get_full_jid(), account=self.account,
- name=self.name, groups=[], show=self.show, status=self.status,
- sub='none', client_caps=self.client_caps)
-
-
-class LegacyContactsAPI:
- """
- This is a GOD class for accessing contact and groupchat information.
- The API has several flaws:
-
- * it mixes concerns because it deals with contacts, groupchats,
- groupchat contacts and metacontacts
- * some methods like get_contact() may return None. This leads to
- a lot of duplication all over Gajim because it is not sure
- if we receive a proper contact or just None.
-
- It is a long way to cleanup this API. Therefore just stick with it
- and use it as before. We will try to figure out a migration path.
- """
- def __init__(self):
- self._metacontact_manager = MetacontactManager(self)
- self._accounts = {}
-
- def change_account_name(self, old_name, new_name):
- self._accounts[new_name] = self._accounts[old_name]
- self._accounts[new_name].name = new_name
- del self._accounts[old_name]
-
- self._metacontact_manager.change_account_name(old_name, new_name)
-
- def add_account(self, account_name):
- self._accounts[account_name] = Account(account_name, Contacts(),
- GC_Contacts())
- self._metacontact_manager.add_account(account_name)
-
- def get_accounts(self):
- return list(self._accounts.keys())
-
- def remove_account(self, account):
- del self._accounts[account]
- self._metacontact_manager.remove_account(account)
-
- def create_contact(self, jid, account, name='', groups=None, show='',
- status='', sub='', ask='', resource='', priority=0, keyID='',
- client_caps=None, our_chatstate=None, chatstate=None, last_status_time=None,
- last_activity_time=None):
- if groups is None:
- groups = []
- # Use Account object if available
- account = self._accounts.get(account, account)
- return Contact(jid=jid, account=account, name=name, groups=groups,
- show=show, status=status, sub=sub, ask=ask, resource=resource,
- priority=priority, keyID=keyID, client_caps=client_caps,
- our_chatstate=our_chatstate, chatstate=chatstate,
- last_status_time=last_status_time,
- last_activity_time=last_activity_time)
-
- def create_self_contact(self, jid, account, resource, show, status, priority,
- name='', keyID=''):
- conn = common.gajim.connections[account]
- nick = name or common.gajim.nicks[account]
- account = self._accounts.get(account, account) # Use Account object if available
- self_contact = self.create_contact(jid=jid, account=account,
- name=nick, groups=['self_contact'], show=show, status=status,
- sub='both', ask='none', priority=priority, keyID=keyID,
- resource=resource)
- self_contact.pep = conn.pep
- return self_contact
-
- def create_not_in_roster_contact(self, jid, account, resource='', name='',
- keyID=''):
- # Use Account object if available
- account = self._accounts.get(account, account)
- return self.create_contact(jid=jid, account=account, resource=resource,
- name=name, groups=[_('Not in Roster')], show='not in roster',
- status='', sub='none', keyID=keyID)
-
- def copy_contact(self, contact):
- return self.create_contact(contact.jid, contact.account,
- name=contact.name, groups=contact.groups, show=contact.show,
- status=contact.status, sub=contact.sub, ask=contact.ask,
- resource=contact.resource, priority=contact.priority,
- keyID=contact.keyID, client_caps=contact.client_caps,
- our_chatstate=contact.our_chatstate, chatstate=contact.chatstate,
- last_status_time=contact.last_status_time,
- last_activity_time=contact.last_activity_time)
-
- def add_contact(self, account, contact):
- if account not in self._accounts:
- self.add_account(account)
- return self._accounts[account].contacts.add_contact(contact)
-
- def remove_contact(self, account, contact):
- if account not in self._accounts:
- return
- return self._accounts[account].contacts.remove_contact(contact)
-
- def remove_jid(self, account, jid, remove_meta=True):
- self._accounts[account].contacts.remove_jid(jid)
- if remove_meta:
- self._metacontact_manager.remove_metacontact(account, jid)
-
- def get_contacts(self, account, jid):
- return self._accounts[account].contacts.get_contacts(jid)
-
- def get_contact(self, account, jid, resource=None):
- return self._accounts[account].contacts.get_contact(jid, resource=resource)
-
- def iter_contacts(self, account):
- for contact in self._accounts[account].contacts.iter_contacts():
- yield contact
-
- def get_contact_from_full_jid(self, account, fjid):
- return self._accounts[account].contacts.get_contact_from_full_jid(fjid)
-
- def get_first_contact_from_jid(self, account, jid):
- return self._accounts[account].contacts.get_first_contact_from_jid(jid)
-
- def get_contacts_from_group(self, account, group):
- return self._accounts[account].contacts.get_contacts_from_group(group)
-
- def get_contacts_jid_list(self, account):
- return self._accounts[account].contacts.get_contacts_jid_list()
-
- def get_jid_list(self, account):
- return self._accounts[account].contacts.get_jid_list()
-
- def change_contact_jid(self, old_jid, new_jid, account):
- return self._accounts[account].change_contact_jid(old_jid, new_jid)
-
- def get_highest_prio_contact_from_contacts(self, contacts):
- if not contacts:
- return None
- prim_contact = contacts[0]
- for contact in contacts[1:]:
- if int(contact.priority) > int(prim_contact.priority):
- prim_contact = contact
- return prim_contact
-
- def get_contact_with_highest_priority(self, account, jid):
- contacts = self.get_contacts(account, jid)
- if not contacts and '/' in jid:
- # jid may be a fake jid, try it
- room, nick = jid.split('/', 1)
- contact = self.get_gc_contact(account, room, nick)
- return contact
- return self.get_highest_prio_contact_from_contacts(contacts)
-
- def get_nb_online_total_contacts(self, accounts=None, groups=None):
- """
- Return the number of online contacts and the total number of contacts
- """
- if not accounts:
- accounts = self.get_accounts()
- if groups is None:
- groups = []
- nbr_online = 0
- nbr_total = 0
- for account in accounts:
- our_jid = common.gajim.get_jid_from_account(account)
- for jid in self.get_jid_list(account):
- if jid == our_jid:
- continue
- if common.gajim.jid_is_transport(jid) and not \
- _('Transports') in groups:
- # do not count transports
- continue
- if self.has_brother(account, jid, accounts) and not \
- self.is_big_brother(account, jid, accounts):
- # count metacontacts only once
- continue
- contact = self._accounts[account].contacts._contacts[jid][0]
- if _('Not in roster') in contact.groups:
- continue
- in_groups = False
- if groups == []:
- in_groups = True
- else:
- for group in groups:
- if group in contact.get_shown_groups():
- in_groups = True
- break
-
- if in_groups:
- if contact.show not in ('offline', 'error'):
- nbr_online += 1
- nbr_total += 1
- return nbr_online, nbr_total
-
- def __getattr__(self, attr_name):
- # Only called if self has no attr_name
- if hasattr(self._metacontact_manager, attr_name):
- return getattr(self._metacontact_manager, attr_name)
- else:
- raise AttributeError(attr_name)
-
- def create_gc_contact(self, room_jid, account, name='', show='', status='',
- role='', affiliation='', jid='', resource=''):
- account = self._accounts.get(account, account) # Use Account object if available
- return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid,
- resource)
-
- def add_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.add_gc_contact(gc_contact)
-
- def remove_gc_contact(self, account, gc_contact):
- return self._accounts[account].gc_contacts.remove_gc_contact(gc_contact)
-
- def remove_room(self, account, room_jid):
- return self._accounts[account].gc_contacts.remove_room(room_jid)
-
- def get_gc_list(self, account):
- return self._accounts[account].gc_contacts.get_gc_list()
-
- def get_nick_list(self, account, room_jid):
- return self._accounts[account].gc_contacts.get_nick_list(room_jid)
-
- def get_gc_contact(self, account, room_jid, nick):
- return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick)
-
- def is_gc_contact(self, account, jid):
- return self._accounts[account].gc_contacts.is_gc_contact(jid)
-
- def get_nb_role_total_gc_contacts(self, account, room_jid, role):
- return self._accounts[account].gc_contacts.get_nb_role_total_gc_contacts(room_jid, role)
-
-
-class Contacts():
- """
- This is a breakout of the contact related behavior of the old
- Contacts class (which is not called LegacyContactsAPI)
- """
- def __init__(self):
- # list of contacts {jid1: [C1, C2]}, } one Contact per resource
- self._contacts = {}
-
- def add_contact(self, contact):
- if contact.jid not in self._contacts:
- self._contacts[contact.jid] = [contact]
- return
- contacts = self._contacts[contact.jid]
- # We had only one that was offline, remove it
- if len(contacts) == 1 and contacts[0].show == 'offline':
- # Do not use self.remove_contact: it deteles
- # self._contacts[account][contact.jid]
- contacts.remove(contacts[0])
- # If same JID with same resource already exists, use the new one
- for c in contacts:
- if c.resource == contact.resource:
- self.remove_contact(c)
- break
- contacts.append(contact)
-
- def remove_contact(self, contact):
- if contact.jid not in self._contacts:
- return
- if contact in self._contacts[contact.jid]:
- self._contacts[contact.jid].remove(contact)
- if len(self._contacts[contact.jid]) == 0:
- del self._contacts[contact.jid]
-
- def remove_jid(self, jid):
- """
- Remove all contacts for a given jid
- """
- if jid in self._contacts:
- del self._contacts[jid]
-
- def get_contacts(self, jid):
- """
- Return the list of contact instances for this jid
- """
- return self._contacts.get(jid, [])
-
- def get_contact(self, jid, resource=None):
- ### WARNING ###
- # This function returns a *RANDOM* resource if resource = None!
- # Do *NOT* use if you need to get the contact to which you
- # send a message for example, as a bare JID in Jabber means
- # highest available resource, which this function ignores!
- """
- Return the contact instance for the given resource if it's given else the
- first contact is no resource is given or None if there is not
- """
- if jid in self._contacts:
- if not resource:
- return self._contacts[jid][0]
- for c in self._contacts[jid]:
- if c.resource == resource:
- return c
- return self._contacts[jid][0]
-
- def iter_contacts(self):
- for jid in list(self._contacts.keys()):
- for contact in self._contacts[jid][:]:
- yield contact
-
- def get_jid_list(self):
- return list(self._contacts.keys())
-
- def get_contacts_jid_list(self):
- return [jid for jid, contact in self._contacts.items() if not
- contact[0].is_groupchat()]
-
- def get_contact_from_full_jid(self, fjid):
- """
- Get Contact object for specific resource of given jid
- """
- barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid)
- return self.get_contact(barejid, resource)
-
- def get_first_contact_from_jid(self, jid):
- if jid in self._contacts:
- return self._contacts[jid][0]
-
- def get_contacts_from_group(self, group):
- """
- Return all contacts in the given group
- """
- group_contacts = []
- for jid in self._contacts:
- contacts = self.get_contacts(jid)
- if group in contacts[0].groups:
- group_contacts += contacts
- return group_contacts
-
- def change_contact_jid(self, old_jid, new_jid):
- if old_jid not in self._contacts:
- return
- self._contacts[new_jid] = []
- for _contact in self._contacts[old_jid]:
- _contact.jid = new_jid
- self._contacts[new_jid].append(_contact)
- del self._contacts[old_jid]
-
-
-class GC_Contacts():
-
- def __init__(self):
- # list of contacts that are in gc {room_jid: {nick: C}}}
- self._rooms = {}
-
- def add_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- self._rooms[gc_contact.room_jid] = {gc_contact.name: gc_contact}
- else:
- self._rooms[gc_contact.room_jid][gc_contact.name] = gc_contact
-
- def remove_gc_contact(self, gc_contact):
- if gc_contact.room_jid not in self._rooms:
- return
- if gc_contact.name not in self._rooms[gc_contact.room_jid]:
- return
- del self._rooms[gc_contact.room_jid][gc_contact.name]
- # It was the last nick in room ?
- if not len(self._rooms[gc_contact.room_jid]):
- del self._rooms[gc_contact.room_jid]
-
- def remove_room(self, room_jid):
- if room_jid in self._rooms:
- del self._rooms[room_jid]
-
- def get_gc_list(self):
- return self._rooms.keys()
-
- def get_nick_list(self, room_jid):
- gc_list = self.get_gc_list()
- if not room_jid in gc_list:
- return []
- return list(self._rooms[room_jid].keys())
-
- def get_gc_contact(self, room_jid, nick):
- nick_list = self.get_nick_list(room_jid)
- if not nick in nick_list:
- return None
- return self._rooms[room_jid][nick]
-
- def is_gc_contact(self, jid):
- """
- >>> gc = GC_Contacts()
- >>> gc._rooms = {'gajim@conference.gajim.org' : {'test' : True}}
- >>> gc.is_gc_contact('gajim@conference.gajim.org/test')
- True
- >>> gc.is_gc_contact('test@jabbim.com')
- False
- """
- jid = jid.split('/')
- if len(jid) != 2:
- return False
- gcc = self.get_gc_contact(jid[0], jid[1])
- return gcc != None
-
- def get_nb_role_total_gc_contacts(self, room_jid, role):
- """
- Return the number of group chat contacts for the given role and the total
- number of group chat contacts
- """
- if room_jid not in self._rooms:
- return 0, 0
- nb_role = nb_total = 0
- for nick in self._rooms[room_jid]:
- if self._rooms[room_jid][nick].role == role:
- nb_role += 1
- nb_total += 1
- return nb_role, nb_total
-
-
-class MetacontactManager():
-
- def __init__(self, contacts):
- self._metacontacts_tags = {}
- self._contacts = contacts
-
- def change_account_name(self, old_name, new_name):
- self._metacontacts_tags[new_name] = self._metacontacts_tags[old_name]
- del self._metacontacts_tags[old_name]
-
- def add_account(self, account):
- if account not in self._metacontacts_tags:
- self._metacontacts_tags[account] = {}
-
- def remove_account(self, account):
- del self._metacontacts_tags[account]
-
- def define_metacontacts(self, account, tags_list):
- self._metacontacts_tags[account] = tags_list
-
- def _get_new_metacontacts_tag(self, jid):
- if not jid in self._metacontacts_tags:
- return jid
- #FIXME: can this append ?
- assert False
-
- def iter_metacontacts_families(self, account):
- for tag in self._metacontacts_tags[account]:
- family = self._get_metacontacts_family_from_tag(account, tag)
- yield family
-
- def _get_metacontacts_tag(self, account, jid):
- """
- Return the tag of a jid
- """
- if not account in self._metacontacts_tags:
- return None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- return tag
- return None
-
- def add_metacontact(self, brother_account, brother_jid, account, jid, order=None):
- tag = self._get_metacontacts_tag(brother_account, brother_jid)
- if not tag:
- tag = self._get_new_metacontacts_tag(brother_jid)
- self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
- 'tag': tag}]
- if brother_account != account:
- common.gajim.connections[brother_account].store_metacontacts(
- self._metacontacts_tags[brother_account])
- # be sure jid has no other tag
- old_tag = self._get_metacontacts_tag(account, jid)
- while old_tag:
- self.remove_metacontact(account, jid)
- old_tag = self._get_metacontacts_tag(account, jid)
- if tag not in self._metacontacts_tags[account]:
- self._metacontacts_tags[account][tag] = [{'jid': jid, 'tag': tag}]
- else:
- if order:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag, 'order': order})
- else:
- self._metacontacts_tags[account][tag].append({'jid': jid,
- 'tag': tag})
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
-
- def remove_metacontact(self, account, jid):
- if not account in self._metacontacts_tags:
- return
-
- found = None
- for tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- if data['jid'] == jid:
- found = data
- break
- if found:
- self._metacontacts_tags[account][tag].remove(found)
- common.gajim.connections[account].store_metacontacts(
- self._metacontacts_tags[account])
- break
-
- def has_brother(self, account, jid, accounts):
- tag = self._get_metacontacts_tag(account, jid)
- if not tag:
- return False
- meta_jids = self._get_metacontacts_jids(tag, accounts)
- return len(meta_jids) > 1 or len(meta_jids[account]) > 1
-
- def is_big_brother(self, account, jid, accounts):
- family = self.get_metacontacts_family(account, jid)
- if family:
- nearby_family = [data for data in family
- if account in accounts]
- bb_data = self._get_metacontacts_big_brother(nearby_family)
- if bb_data['jid'] == jid and bb_data['account'] == account:
- return True
- return False
-
- def _get_metacontacts_jids(self, tag, accounts):
- """
- Return all jid for the given tag in the form {acct: [jid1, jid2],.}
- """
- answers = {}
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- if account not in accounts:
- continue
- answers[account] = []
- for data in self._metacontacts_tags[account][tag]:
- answers[account].append(data['jid'])
- return answers
-
- def get_metacontacts_family(self, account, jid):
- """
- Return the family of the given jid, including jid in the form:
- [{'account': acct, 'jid': jid, 'order': order}, ] 'order' is optional
- """
- tag = self._get_metacontacts_tag(account, jid)
- return self._get_metacontacts_family_from_tag(account, tag)
-
- def _get_metacontacts_family_from_tag(self, account, tag):
- if not tag:
- return []
- answers = []
- for account in self._metacontacts_tags:
- if tag in self._metacontacts_tags[account]:
- for data in self._metacontacts_tags[account][tag]:
- data['account'] = account
- answers.append(data)
- return answers
-
- def _compare_metacontacts(self, data1, data2):
- """
- Compare 2 metacontacts
-
- Data is {'jid': jid, 'account': account, 'order': order} order is
- optional
- """
- jid1 = data1['jid']
- jid2 = data2['jid']
- account1 = data1['account']
- account2 = data2['account']
- contact1 = self._contacts.get_contact_with_highest_priority(account1, jid1)
- contact2 = self._contacts.get_contact_with_highest_priority(account2, jid2)
- show_list = ['not in roster', 'error', 'offline', 'invisible', 'dnd',
- 'xa', 'away', 'chat', 'online', 'requested', 'message']
- # contact can be null when a jid listed in the metacontact data
- # is not in our roster
- if not contact1:
- if contact2:
- return -1 # prefer the known contact
- else:
- show1 = 0
- priority1 = 0
- else:
- show1 = show_list.index(contact1.show)
- priority1 = contact1.priority
- if not contact2:
- if contact1:
- return 1 # prefer the known contact
- else:
- show2 = 0
- priority2 = 0
- else:
- show2 = show_list.index(contact2.show)
- priority2 = contact2.priority
- # If only one is offline, it's always second
- if show1 > 2 and show2 < 3:
- return 1
- if show2 > 2 and show1 < 3:
- return -1
- if 'order' in data1 and 'order' in data2:
- if data1['order'] > data2['order']:
- return 1
- if data1['order'] < data2['order']:
- return -1
- if 'order' in data1:
- return 1
- if 'order' in data2:
- return -1
- transport1 = common.gajim.get_transport_name_from_jid(jid1)
- transport2 = common.gajim.get_transport_name_from_jid(jid2)
- if transport2 and not transport1:
- return 1
- if transport1 and not transport2:
- return -1
- if show1 > show2:
- return 1
- if show2 > show1:
- return -1
- if priority1 > priority2:
- return 1
- if priority2 > priority1:
- return -1
- server1 = common.gajim.get_server_from_jid(jid1)
- server2 = common.gajim.get_server_from_jid(jid2)
- myserver1 = common.gajim.config.get_per('accounts', account1, 'hostname')
- myserver2 = common.gajim.config.get_per('accounts', account2, 'hostname')
- if server1 == myserver1:
- if server2 != myserver2:
- return 1
- elif server2 == myserver2:
- return -1
- if jid1 > jid2:
- return 1
- if jid2 > jid1:
- return -1
- # If all is the same, compare accounts, they can't be the same
- if account1 > account2:
- return 1
- if account2 > account1:
- return -1
- return 0
-
- def get_nearby_family_and_big_brother(self, family, account):
- """
- Return the nearby family and its Big Brother
-
- Nearby family is the part of the family that is grouped with the
- metacontact. A metacontact may be over different accounts. If accounts
- are not merged then the given family is split account wise.
-
- (nearby_family, big_brother_jid, big_brother_account)
- """
- if common.gajim.config.get('mergeaccounts'):
- # group all together
- nearby_family = family
- else:
- # we want one nearby_family per account
- nearby_family = [data for data in family if account == data['account']]
-
- if not nearby_family:
- return (None, None, None)
- big_brother_data = self._get_metacontacts_big_brother(nearby_family)
- big_brother_jid = big_brother_data['jid']
- big_brother_account = big_brother_data['account']
-
- return (nearby_family, big_brother_jid, big_brother_account)
-
- def _get_metacontacts_big_brother(self, family):
- """
- Which of the family will be the big brother under wich all others will be
- ?
- """
- family.sort(key=cmp_to_key(self._compare_metacontacts))
- return family[-1]
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/src/common/crypto.py b/src/common/crypto.py
deleted file mode 100644
index 2e99cde1a..000000000
--- a/src/common/crypto.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# common crypto functions (mostly specific to XEP-0116, but useful elsewhere)
-# -*- coding:utf-8 -*-
-## src/common/crypto.py
-##
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import sys
-import os
-import math
-
-from hashlib import sha256 as SHA256
-
-# convert a large integer to a big-endian bitstring
-def encode_mpi(n):
- if n >= 256:
- return encode_mpi(n // 256) + bytes([n % 256])
- else:
- return bytes([n])
-
-# convert a large integer to a big-endian bitstring, padded with \x00s to
-# a multiple of 16 bytes
-def encode_mpi_with_padding(n):
- return pad_to_multiple(encode_mpi(n), 16, '\x00', True)
-
-# pad 'string' to a multiple of 'multiple_of' with 'char'.
-# pad on the left if 'left', otherwise pad on the right.
-def pad_to_multiple(string, multiple_of, char, left):
- mod = len(string) % multiple_of
- if mod == 0:
- return string
- else:
- padding = (multiple_of - mod) * char
-
- if left:
- return padding + string
- else:
- return string + padding
-
-# convert a big-endian bitstring to an integer
-def decode_mpi(s):
- if len(s) == 0:
- return 0
- else:
- return 256 * decode_mpi(s[:-1]) + s[-1]
-
-def sha256(string):
- sh = SHA256()
- sh.update(string)
- return sh.digest()
-
-base28_chr = "acdefghikmopqruvwxy123456789"
-
-def sas_28x5(m_a, form_b):
- sha = sha256(m_a + form_b + b'Short Authentication String')
- lsb24 = decode_mpi(sha[-3:])
- return base28(lsb24)
-
-def base28(n):
- if n >= 28:
- return base28(n // 28) + base28_chr[n % 28]
- else:
- return base28_chr[n]
-
-def add_entropy_sources_OpenSSL():
- # Other possibly variable data. This are very low quality sources of
- # entropy, but some of them are installation dependent and can be hard
- # to guess for the attacker.
- # Data available on all platforms Unix, Windows
- sources = [sys.argv, sys.builtin_module_names,
- sys.copyright, sys.getfilesystemencoding(), sys.hexversion,
- sys.modules, sys.path, sys.version, sys.api_version,
- os.environ, os.getcwd(), os.getpid()]
-
- for s in sources:
- OpenSSL.rand.add(str(s).encode('utf-8'), 1)
-
- # On Windows add the current contents of the screen to the PRNG state.
-# if os.name == 'nt':
-# OpenSSL.rand.screen()
- # The /proc filesystem on POSIX systems contains many random variables:
- # memory statistics, interrupt counts, network packet counts
- if os.name == 'posix':
- dirs = ['/proc', '/proc/net', '/proc/self']
- for d in dirs:
- if os.access(d, os.R_OK):
- for filename in os.listdir(d):
- OpenSSL.rand.add(filename.encode('utf-8'), 0)
- try:
- with open(d + os.sep + filename, "r") as fp:
- # Limit the ammount of read bytes, in case a memory
- # file was opened
- OpenSSL.rand.add(str(fp.read(5000)).encode('utf-8'),
- 1)
- except:
- # Ignore all read and access errors
- pass
-
-PYOPENSSL_PRNG_PRESENT = False
-try:
- import OpenSSL.rand
- PYOPENSSL_PRNG_PRESENT = True
-except ImportError:
- # PyOpenSSL PRNG not available
- pass
-
-def random_bytes(bytes_):
- if PYOPENSSL_PRNG_PRESENT:
- OpenSSL.rand.add(os.urandom(bytes_), bytes_)
- return OpenSSL.rand.bytes(bytes_)
- else:
- return os.urandom(bytes_)
-
-def generate_nonce():
- return random_bytes(8)
-
-# generate a random number between 'bottom' and 'top'
-def srand(bottom, top):
- # minimum number of bytes needed to represent that range
- bytes = int(math.ceil(math.log(top - bottom, 256)))
-
- # in retrospect, this is horribly inadequate.
- return (decode_mpi(random_bytes(bytes)) % (top - bottom)) + bottom
-
-# a faster version of (base ** exp) % mod
-# taken from <http://lists.danga.com/pipermail/yadis/2005-September/001445.html>
-def powmod(base, exp, mod):
- square = base % mod
- result = 1
-
- while exp > 0:
- if exp & 1: # exponent is odd
- result = (result * square) % mod
-
- square = (square * square) % mod
- exp //= 2
- return result
diff --git a/src/common/dataforms.py b/src/common/dataforms.py
deleted file mode 100644
index 6dd6b896c..000000000
--- a/src/common/dataforms.py
+++ /dev/null
@@ -1,754 +0,0 @@
-# this will go to src/common/xmpp later, for now it is in src/common
-# -*- coding:utf-8 -*-
-## src/common/dataforms.py
-##
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-"""
-This module contains wrappers for different parts of data forms (JEP 0004). For
-information how to use them, read documentation
-"""
-
-import nbxmpp
-from common import helpers
-
-# exceptions used in this module
-# base class
-class Error(Exception): pass
-# when we get nbxmpp.Node which we do not understand
-class UnknownDataForm(Error): pass
-# when we get nbxmpp.Node which contains bad fields
-class WrongFieldValue(Error): pass
-
-# helper class to change class of already existing object
-class ExtendedNode(nbxmpp.Node, object):
- @classmethod
- def __new__(cls, *a, **b):
- if 'extend' not in b.keys() or not b['extend']:
- return object.__new__(cls)
-
- extend = b['extend']
- assert issubclass(cls, extend.__class__)
- extend.__class__ = cls
- return extend
-
-# helper decorator to create properties in cleaner way
-def nested_property(f):
- ret = f()
- p = {'doc': f.__doc__}
- for v in ('fget', 'fset', 'fdel', 'doc'):
- if v in ret.keys(): p[v]=ret[v]
- return property(**p)
-
-# helper to create fields from scratch
-def Field(typ, **attrs):
- ''' Helper function to create a field of given type. '''
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': JidMultiField,
- 'jid-single': JidSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }[typ](typ=typ, **attrs)
- return f
-
-def ExtendField(node):
- """
- Helper function to extend a node to field of appropriate type
- """
- # when validation (XEP-122) will go in, we could have another classes
- # like DateTimeField - so that dicts in Field() and ExtendField() will
- # be different...
- typ=node.getAttr('type')
- f = {
- 'boolean': BooleanField,
- 'fixed': StringField,
- 'hidden': StringField,
- 'text-private': StringField,
- 'text-single': StringField,
- 'jid-multi': JidMultiField,
- 'jid-single': JidSingleField,
- 'list-multi': ListMultiField,
- 'list-single': ListSingleField,
- 'text-multi': TextMultiField,
- }
- if typ not in f:
- typ = 'text-single'
- return f[typ](extend=node)
-
-def ExtendForm(node):
- """
- Helper function to extend a node to form of appropriate type
- """
- if node.getTag('reported') is not None:
- return MultipleDataForm(extend=node)
- else:
- return SimpleDataForm(extend=node)
-
-class DataField(ExtendedNode):
- """
- Keeps data about one field - var, field type, labels, instructions... Base
- class for different kinds of fields. Use Field() function to construct one
- of these
- """
-
- def __init__(self, typ=None, var=None, value=None, label=None, desc=None,
- required=False, options=None, extend=None):
-
- if extend is None:
- ExtendedNode.__init__(self, 'field')
-
- self.type_ = typ
- self.var = var
- if value is not None:
- self.value = value
- if label is not None:
- self.label = label
- if desc is not None:
- self.desc = desc
- self.required = required
- self.options = options
-
- @nested_property
- def type_():
- """
- Type of field. Recognized values are: 'boolean', 'fixed', 'hidden',
- 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi',
- 'text-private', 'text-single'. If you set this to something different,
- DataField will store given name, but treat all data as text-single
- """
- def fget(self):
- t = self.getAttr('type')
- if t is None:
- return 'text-single'
- return t
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('type', value)
-
- return locals()
-
- @nested_property
- def var():
- """
- Field identifier
- """
- def fget(self):
- return self.getAttr('var')
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('var', value)
-
- def fdel(self):
- self.delAttr('var')
-
- return locals()
-
- @nested_property
- def label():
- """
- Human-readable field name
- """
- def fget(self):
- l = self.getAttr('label')
- if not l:
- l = self.var
- return l
-
- def fset(self, value):
- assert isinstance(value, str)
- self.setAttr('label', value)
-
- def fdel(self):
- if self.getAttr('label'):
- self.delAttr('label')
-
- return locals()
-
- @nested_property
- def description():
- """
- Human-readable description of field meaning
- """
- def fget(self):
- return self.getTagData('desc') or ''
-
- def fset(self, value):
- assert isinstance(value, str)
- if value == '':
- fdel(self)
- else:
- self.setTagData('desc', value)
-
- def fdel(self):
- t = self.getTag('desc')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
- @nested_property
- def required():
- """
- Controls whether this field required to fill. Boolean
- """
- def fget(self):
- return bool(self.getTag('required'))
-
- def fset(self, value):
- t = self.getTag('required')
- if t and not value:
- self.delChild(t)
- elif not t and value:
- self.addChild('required')
-
- return locals()
-
- @nested_property
- def media():
- """
- Media data
- """
- def fget(self):
- media = self.getTag('media', namespace=nbxmpp.NS_DATA_MEDIA)
- if media:
- return Media(media)
-
- def fset(self, value):
- fdel(self)
- self.addChild(node=value)
-
- def fdel(self):
- t = self.getTag('media')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
- def is_valid(self):
- return True
-
-class Uri(nbxmpp.Node):
- def __init__(self, uri_tag):
- nbxmpp.Node.__init__(self, node=uri_tag)
-
- @nested_property
- def type_():
- """
- uri type
- """
- def fget(self):
- return self.getAttr('type')
-
- def fset(self, value):
- self.setAttr('type', value)
-
- def fdel(self):
- self.delAttr('type')
-
- return locals()
-
- @nested_property
- def uri_data():
- """
- uri data
- """
- def fget(self):
- return self.getData()
-
- def fset(self, value):
- self.setData(value)
-
- def fdel(self):
- self.setData(None)
-
- return locals()
-
-class Media(nbxmpp.Node):
- def __init__(self, media_tag):
- nbxmpp.Node.__init__(self, node=media_tag)
-
- @nested_property
- def uris():
- """
- URIs of the media element.
- """
- def fget(self):
- return map(Uri, self.getTags('uri'))
-
- def fset(self, value):
- fdel(self)
- for uri in value:
- self.addChild(node=uri)
-
- def fdel(self, value):
- for element in self.getTags('uri'):
- self.delChild(element)
-
- return locals()
-
-class BooleanField(DataField):
- @nested_property
- def value():
- """
- Value of field. May contain True, False or None
- """
- def fget(self):
- v = self.getTagData('value')
- if v in ('0', 'false'):
- return False
- if v in ('1', 'true'):
- return True
- if v is None:
- return False # default value is False
- raise WrongFieldValue
-
- def fset(self, value):
- self.setTagData('value', value and '1' or '0')
-
- def fdel(self, value):
- t = self.getTag('value')
- if t is not None:
- self.delChild(t)
-
- return locals()
-
-class StringField(DataField):
- """
- Covers fields of types: fixed, hidden, text-private, text-single
- """
-
- @nested_property
- def value():
- """
- Value of field. May be any string
- """
- def fget(self):
- return self.getTagData('value') or ''
-
- def fset(self, value):
- assert isinstance(value, str)
- if value == '' and not self.required:
- return fdel(self)
- self.setTagData('value', value)
-
- def fdel(self):
- try:
- self.delChild(self.getTag('value'))
- except ValueError: # if there already were no value tag
- pass
-
- return locals()
-
-class ListField(DataField):
- """
- Covers fields of types: jid-multi, jid-single, list-multi, list-single
- """
-
- @nested_property
- def options():
- """
- Options
- """
- def fget(self):
- options = []
- for element in self.getTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- options.append((l, v))
- return options
-
- def fset(self, values):
- fdel(self)
- for value, label in values:
- self.addChild('option', {'label': label}).setTagData('value', value)
-
- def fdel(self):
- for element in self.getTags('option'):
- self.delChild(element)
-
- return locals()
-
- def iter_options(self):
- for element in self.iterTags('option'):
- v = element.getTagData('value')
- if v is None:
- raise WrongFieldValue
- l = element.getAttr('label')
- if not l:
- l = v
- yield (v, l)
-
-class ListSingleField(ListField, StringField):
- """
- Covers list-single field
- """
- def is_valid(self):
- if not self.required:
- return True
- if not self.value:
- return False
- return True
-
-class JidSingleField(ListSingleField):
- """
- Covers jid-single fields
- """
- def is_valid(self):
- if self.value:
- try:
- helpers.parse_jid(self.value)
- return True
- except:
- return False
- if self.required:
- return False
- return True
-
-class ListMultiField(ListField):
- """
- Covers list-multi fields
- """
-
- @nested_property
- def values():
- """
- Values held in field
- """
- def fget(self):
- values = []
- for element in self.getTags('value'):
- values.append(element.getData())
- return values
-
- def fset(self, values):
- fdel(self)
- for value in values:
- self.addChild('value').setData(value)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
-
- def iter_values(self):
- for element in self.getTags('value'):
- yield element.getData()
-
- def is_valid(self):
- if not self.required:
- return True
- if not self.values:
- return False
- return True
-
-class JidMultiField(ListMultiField):
- """
- Covers jid-multi fields
- """
- def is_valid(self):
- if len(self.values):
- for value in self.values:
- try:
- helpers.parse_jid(value)
- except:
- return False
- return True
- if self.required:
- return False
- return True
-
-class TextMultiField(DataField):
- @nested_property
- def value():
- """
- Value held in field
- """
- def fget(self):
- value = ''
- for element in self.iterTags('value'):
- value += '\n' + element.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '':
- return
- for line in value.split('\n'):
- self.addChild('value').setData(line)
-
- def fdel(self):
- for element in self.getTags('value'):
- self.delChild(element)
-
- return locals()
-
-class DataRecord(ExtendedNode):
- """
- The container for data fields - an xml element which has DataField elements
- as children
- """
- def __init__(self, fields=None, associated=None, extend=None):
- self.associated = associated
- self.vars = {}
- if extend is None:
- # we have to build this object from scratch
- nbxmpp.Node.__init__(self)
-
- if fields is not None:
- self.fields = fields
- else:
- # we already have nbxmpp.Node inside - try to convert all
- # fields into DataField objects
- if fields is None:
- for field in self.iterTags('field'):
- if not isinstance(field, DataField):
- ExtendField(field)
- self.vars[field.var] = field
- else:
- for field in self.getTags('field'):
- self.delChild(field)
- self.fields = fields
-
- @nested_property
- def fields():
- """
- List of fields in this record
- """
- def fget(self):
- return self.getTags('field')
-
- def fset(self, fields):
- fdel(self)
- for field in fields:
- if not isinstance(field, DataField):
- ExtendField(extend=field)
- self.addChild(node=field)
-
- def fdel(self):
- for element in self.getTags('field'):
- self.delChild(element)
-
- return locals()
-
- def iter_fields(self):
- """
- Iterate over fields in this record. Do not take associated into account
- """
- for field in self.iterTags('field'):
- yield field
-
- def iter_with_associated(self):
- """
- Iterate over associated, yielding both our field and associated one
- together
- """
- for field in self.associated.iter_fields():
- yield self[field.var], field
-
- def __getitem__(self, item):
- return self.vars[item]
-
- def is_valid(self):
- for f in self.iter_fields():
- if not f.is_valid():
- return False
- return True
-
-class DataForm(ExtendedNode):
- def __init__(self, type_=None, title=None, instructions=None, extend=None):
- if extend is None:
- # we have to build form from scratch
- nbxmpp.Node.__init__(self, 'x', attrs={'xmlns': nbxmpp.NS_DATA})
-
- if type_ is not None:
- self.type_=type_
- if title is not None:
- self.title=title
- if instructions is not None:
- self.instructions=instructions
-
- @nested_property
- def type_():
- """
- Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
- 'form' - this form is to be filled in; you will be able soon to do:
- filledform = DataForm(replyto=thisform)
- """
- def fget(self):
- return self.getAttr('type')
-
- def fset(self, type_):
- assert type_ in ('form', 'submit', 'cancel', 'result')
- self.setAttr('type', type_)
-
- return locals()
-
- @nested_property
- def title():
- """
- Title of the form
-
- Human-readable, should not contain any \\r\\n.
- """
- def fget(self):
- return self.getTagData('title')
-
- def fset(self, title):
- self.setTagData('title', title)
-
- def fdel(self):
- try:
- self.delChild('title')
- except ValueError:
- pass
-
- return locals()
-
- @nested_property
- def instructions():
- """
- Instructions for this form
-
- Human-readable, may contain \\r\\n.
- """
- # TODO: the same code is in TextMultiField. join them
- def fget(self):
- value = ''
- for valuenode in self.getTags('instructions'):
- value += '\n' + valuenode.getData()
- return value[1:]
-
- def fset(self, value):
- fdel(self)
- if value == '': return
- for line in value.split('\n'):
- self.addChild('instructions').setData(line)
-
- def fdel(self):
- for value in self.getTags('instructions'):
- self.delChild(value)
-
- return locals()
-
-class SimpleDataForm(DataForm, DataRecord):
- def __init__(self, type_=None, title=None, instructions=None, fields=None, \
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- DataRecord.__init__(self, fields=fields, extend=self, associated=self)
-
- def get_purged(self):
- c = SimpleDataForm(extend=self)
- del c.title
- c.instructions = ''
- to_be_removed = []
- for f in c.iter_fields():
- if f.required:
- # add <value> if there is not
- if hasattr(f, 'value') and not f.value:
- f.value = ''
- # Keep all required fields
- continue
- if (hasattr(f, 'value') and not f.value and f.value != 0) or (
- hasattr(f, 'values') and len(f.values) == 0):
- to_be_removed.append(f)
- else:
- del f.label
- del f.description
- del f.media
- for f in to_be_removed:
- c.delChild(f)
- return c
-
-class MultipleDataForm(DataForm):
- def __init__(self, type_=None, title=None, instructions=None, items=None,
- extend=None):
- DataForm.__init__(self, type_=type_, title=title,
- instructions=instructions, extend=extend)
- # all records, recorded into DataRecords
- if extend is None:
- if items is not None:
- self.items = items
- else:
- # we already have nbxmpp.Node inside - try to convert all
- # fields into DataField objects
- if items is None:
- self.items = list(self.iterTags('item'))
- else:
- for item in self.getTags('item'):
- self.delChild(item)
- self.items = items
- reported_tag = self.getTag('reported')
- self.reported = DataRecord(extend=reported_tag)
-
- @nested_property
- def items():
- """
- A list of all records
- """
- def fget(self):
- return list(self.iter_records())
-
- def fset(self, records):
- fdel(self)
- for record in records:
- if not isinstance(record, DataRecord):
- DataRecord(extend=record)
- self.addChild(node=record)
-
- def fdel(self):
- for record in self.getTags('item'):
- self.delChild(record)
-
- return locals()
-
- def iter_records(self):
- for record in self.getTags('item'):
- yield record
-
-# @nested_property
-# def reported():
-# """
-# DataRecord that contains descriptions of fields in records
-# """
-# def fget(self):
-# return self.getTag('reported')
-# def fset(self, record):
-# try:
-# self.delChild('reported')
-# except:
-# pass
-#
-# record.setName('reported')
-# self.addChild(node=record)
-# return locals()
diff --git a/src/common/dbus_support.py b/src/common/dbus_support.py
deleted file mode 100644
index cd4611152..000000000
--- a/src/common/dbus_support.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/dbus_support.py
-##
-## Copyright (C) 2005 Andrew Sayman <lorien420 AT myrealbox.com>
-## Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-
-from common import gajim
-from common import exceptions
-
-_GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
-
-try:
- import dbus
- from dbus.mainloop.glib import DBusGMainLoop
- DBusGMainLoop(set_as_default=True)
-except ImportError:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print(_('D-Bus python bindings are missing in this computer'))
- print(_('D-Bus capabilities of Gajim cannot be used'))
-else:
- try:
- # test if dbus-x11 is installed
- bus = dbus.SystemBus()
- bus = dbus.SessionBus()
- supported = True # does user have D-Bus bindings?
- except dbus.DBusException:
- supported = False
- if not os.name == 'nt': # only say that to non Windows users
- print(_('D-Bus does not run correctly on this machine'))
- print(_('D-Bus capabilities of Gajim cannot be used'))
- except exceptions.SystemBusNotPresent:
- print(_('D-Bus does not run correctly on this machine: system bus not '
- 'present'))
- except exceptions.SessionBusNotPresent:
- print(_('D-Bus does not run correctly on this machine: session bus not '
- 'present'))
-
-class SystemBus:
- """
- A Singleton for the DBus SystemBus
- """
-
- def __init__(self):
- self.system_bus = None
-
- def SystemBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SystemBusNotPresent
- return self.system_bus
-
- def bus(self):
- return self.SystemBus()
-
- def present(self):
- if not supported:
- return False
- if self.system_bus is None:
- try:
- self.system_bus = dbus.SystemBus()
- except dbus.DBusException:
- self.system_bus = None
- return False
- if self.system_bus is None:
- return False
- # Don't exit Gajim when dbus is stopped
- self.system_bus.set_exit_on_disconnect(False)
- return True
-
-system_bus = SystemBus()
-
-class SessionBus:
- """
- A Singleton for the D-Bus SessionBus
- """
-
- def __init__(self):
- self.session_bus = None
-
- def SessionBus(self):
- if not supported:
- raise exceptions.DbusNotSupported
-
- if not self.present():
- raise exceptions.SessionBusNotPresent
- return self.session_bus
-
- def bus(self):
- return self.SessionBus()
-
- def present(self):
- if not supported:
- return False
- if self.session_bus is None:
- try:
- self.session_bus = dbus.SessionBus()
- except dbus.DBusException:
- self.session_bus = None
- return False
- if self.session_bus is None:
- return False
- return True
-
-session_bus = SessionBus()
-
-def get_interface(interface, path, start_service=True):
- """
- Get an interface on the current SessionBus. If the interface isn't running,
- try to start it first
- """
- if not supported:
- return None
- if session_bus.present():
- bus = session_bus.SessionBus()
- else:
- return None
- try:
- obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
- dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
- running_services = dbus_iface.ListNames()
- started = True
- if interface not in running_services:
- # try to start the service
- if start_service and dbus_iface.StartServiceByName(interface, dbus.UInt32(0)) == 1:
- started = True
- else:
- started = False
- if not started:
- return None
- obj = bus.get_object(interface, path)
- return dbus.Interface(obj, interface)
- except Exception as e:
- gajim.log.debug(str(e))
- return None
-
-
-def get_notifications_interface(notif=None):
- """
- Get the notifications interface
-
- :param notif: DesktopNotification instance
- """
- # try to see if KDE notifications are available
- iface = get_interface('org.kde.VisualNotifications', '/VisualNotifications',
- start_service=False)
- if iface != None:
- if notif != None:
- notif.kde_notifications = True
- return iface
- # KDE notifications don't seem to be available, falling back to
- # notification-daemon
- else:
- if notif != None:
- notif.kde_notifications = False
- return get_interface('org.freedesktop.Notifications',
- '/org/freedesktop/Notifications')
-
-if supported:
- class MissingArgument(dbus.DBusException):
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.MissingArgument'
-
- class InvalidArgument(dbus.DBusException):
- '''Raised when one of the provided arguments is invalid.'''
- _dbus_error_name = _GAJIM_ERROR_IFACE + '.InvalidArgument'
diff --git a/src/common/defs.py b/src/common/defs.py
deleted file mode 100644
index c5212348b..000000000
--- a/src/common/defs.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/defs.py
-##
-## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-## Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import subprocess
-import sys
-import os.path
-
-docdir = '../'
-basedir = '../'
-localedir = '../po'
-version = '0.16.10.3'
-
-try:
- node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
- stdout=subprocess.PIPE).communicate()[0]
- if node:
- version += '-' + node.decode('utf-8').strip()
-except Exception:
- pass
-
-for base in ('.', 'common'):
- sys.path.append(os.path.join(base, '.libs'))
diff --git a/src/common/dh.py b/src/common/dh.py
deleted file mode 100644
index aeba2d288..000000000
--- a/src/common/dh.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/dh.py
-##
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-"""
-This module defines a number of constants; specifically, large primes suitable
-for use with the Diffie-Hellman key exchange
-
-These constants have been obtained from RFC2409 and RFC3526.
-"""
-
-import string
-
-generators = [
- None, # one to get the right offset
- 2,
- 2,
- None,
- None,
- 2,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- 2, # group 14
- 2,
- 2,
- 2,
- 2
-]
-
-_HEX_PRIMES = [
- None,
-
- # group 1
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A63A3620 FFFFFFFF FFFFFFFF''',
-
- # group 2
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381
- FFFFFFFF FFFFFFFF''',
-
- # XXX how do I obtain these?
- None,
- None,
-
- # group 5
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA237327 FFFFFFFF FFFFFFFF''',
-
- None,
- None,
- None,
- None,
- None,
- None,
- None,
- None,
-
- # group 14
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AACAA68 FFFFFFFF FFFFFFFF''',
-
- # group 15
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF''',
-
- # group 16
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7
- 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA
- 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6
- 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED
- 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9
- 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
- FFFFFFFF FFFFFFFF''',
-
- # group 17
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08
- 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B
- 302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9
- A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6
- 49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8
- FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C
- 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718
- 3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D
- 04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D
- B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226
- 1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC
- E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26
- 99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB
- 04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2
- 233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127
- D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
- 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406
- AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918
- DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151
- 2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03
- F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F
- BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
- CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B
- B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632
- 387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E
- 6DCC4024 FFFFFFFF FFFFFFFF''',
-
- # group 18
- '''FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
- 29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
- EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
- E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
- EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
- C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
- 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
- 670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
- E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
- DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
- 15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
- ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
- ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
- F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
- BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
- 43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7
- 88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA
- 2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6
- 287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED
- 1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9
- 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492
- 36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD
- F8FF9406 AD9E530E E5DB382F 413001AE B06A53ED 9027D831
- 179727B0 865A8918 DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B
- DB7F1447 E6CC254B 33205151 2BD7AF42 6FB8F401 378CD2BF
- 5983CA01 C64B92EC F032EA15 D1721D03 F482D7CE 6E74FEF6
- D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F BEC7E8F3
- 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA
- CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328
- 06A1D58B B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C
- DA56C9EC 2EF29632 387FE8D7 6E3C0468 043E8F66 3F4860EE
- 12BF2D5B 0B7474D6 E694F91E 6DBE1159 74A3926F 12FEE5E4
- 38777CB6 A932DF8C D8BEC4D0 73B931BA 3BC832B6 8D9DD300
- 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C 5AE4F568
- 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9
- 22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B
- 4BCBC886 2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A
- 062B3CF5 B3A278A6 6D2A13F8 3F44F82D DF310EE0 74AB6A36
- 4597E899 A0255DC1 64F31CC5 0846851D F9AB4819 5DED7EA1
- B1D510BD 7EE74D73 FAF36BC3 1ECFA268 359046F4 EB879F92
- 4009438B 481C6CD7 889A002E D5EE382B C9190DA6 FC026E47
- 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71
- 60C980DD 98EDD3DF FFFFFFFF FFFFFFFF'''
-]
-
-_ALL_ASCII = ''.join(map(chr, range(256)))
-
-def hex_to_decimal(stripee):
- if not stripee:
- return None
-
- return int(stripee.translate(_ALL_ASCII).translate(
- str.maketrans("", "", string.whitespace)), 16)
-
-primes = list(map(hex_to_decimal, _HEX_PRIMES))
diff --git a/src/common/events.py b/src/common/events.py
deleted file mode 100644
index 3896cc69b..000000000
--- a/src/common/events.py
+++ /dev/null
@@ -1,454 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/events.py
-##
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import time
-
-class Event:
- """
- Information concerning each event
- """
-
- def __init__(self, time_=None, show_in_roster=False, show_in_systray=True):
- """
- type_ in chat, normal, file-request, file-error, file-completed,
- file-request-error, file-send-error, file-stopped, gc_msg, pm,
- printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
- gc-invitation, subscription_request, unsubscribedm jingle-incoming
-
- parameters is (per type_):
- chat, normal, pm: [message, subject, kind, time, encrypted, resource,
- msg_log_id]
- where kind in error, incoming
- file-*: file_props
- gc_msg: None
- printed_chat: [message, subject, control, msg_log_id]
- printed_*: None
- messages that are already printed in chat, but not read
- gc-invitation: [room_jid, reason, password, is_continued, jid_from]
- subscription_request: [text, nick]
- unsubscribed: contact
- jingle-incoming: (fulljid, sessionid, content_types)
- """
- if time_:
- self.time_ = time_
- else:
- self.time_ = time.time()
- self.show_in_roster = show_in_roster
- self.show_in_systray = show_in_systray
- # Set when adding the event
- self.jid = None
- self.account = None
-
-class ChatEvent(Event):
- type_ = 'chat'
- def __init__ (self, message, subject, kind, time, encrypted, resource,
- msg_log_id, correct_id=None, xhtml=None, session=None, form_node=None,
- displaymarking=None, sent_forwarded=False, time_=None, show_in_roster=False,
- show_in_systray=True, additional_data=None):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.message = message
- self.subject = subject
- self.kind = kind
- self.time = time
- self.encrypted = encrypted
- self.resource = resource
- self.msg_log_id = msg_log_id
- self.correct_id = correct_id
- self.xhtml = xhtml
- self.session = session
- self.form_node = form_node
- self.displaymarking = displaymarking
- self.sent_forwarded = sent_forwarded
- self.additional_data = additional_data
-
-class NormalEvent(ChatEvent):
- type_ = 'normal'
-
-class PmEvent(ChatEvent):
- type_ = 'pm'
-
-class PrintedChatEvent(Event):
- type_ = 'printed_chat'
- def __init__(self, message, subject, control, msg_log_id, time_=None,
- show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.message = message
- self.subject = subject
- self.control = control
- self.msg_log_id = msg_log_id
-
-class PrintedGcMsgEvent(PrintedChatEvent):
- type_ = 'printed_gc_msg'
-
-class PrintedMarkedGcMsgEvent(PrintedChatEvent):
- type_ = 'printed_marked_gc_msg'
-
-class PrintedPmEvent(PrintedChatEvent):
- type_ = 'printed_pm'
-
-class SubscriptionRequestEvent(Event):
- type_ = 'subscription_request'
- def __init__(self, text, nick, time_=None, show_in_roster=False,
- show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.text = text
- self.nick = nick
-
-class UnsubscribedEvent(Event):
- type_ = 'unsubscribed'
- def __init__(self, contact, time_=None, show_in_roster=False,
- show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.contact = contact
-
-class GcInvitationtEvent(Event):
- type_ = 'gc-invitation'
- def __init__(self, room_jid, reason, password, is_continued, jid_from,
- time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.room_jid = room_jid
- self.reason = reason
- self.password = password
- self.is_continued = is_continued
- self.jid_from = jid_from
-
-class FileRequestEvent(Event):
- type_ = 'file-request'
- def __init__(self, file_props, time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.file_props = file_props
-
-class FileSendErrorEvent(FileRequestEvent):
- type_ = 'file-send-error'
-
-class FileErrorEvent(FileRequestEvent):
- type_ = 'file-error'
-
-class FileRequestErrorEvent(FileRequestEvent):
- type_ = 'file-request-error'
-
-class FileCompletedEvent(FileRequestEvent):
- type_ = 'file-completed'
-
-class FileStoppedEvent(FileRequestEvent):
- type_ = 'file-stopped'
-
-class FileHashErrorEvent(FileRequestEvent):
- type_ = 'file-hash-error'
-
-class JingleIncomingEvent(Event):
- type_ = 'jingle-incoming'
- def __init__(self, peerjid, sid, content_types, time_=None, show_in_roster=False, show_in_systray=True):
- Event.__init__(self, time_, show_in_roster=show_in_roster,
- show_in_systray=show_in_systray)
- self.peerjid = peerjid
- self.sid = sid
- self.content_types = content_types
-
-class Events:
- """
- Information concerning all events
- """
-
- def __init__(self):
- self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
- self._event_added_listeners = []
- self._event_removed_listeners = []
-
- def event_added_subscribe(self, listener):
- """
- Add a listener when an event is added to the queue
- """
- if not listener in self._event_added_listeners:
- self._event_added_listeners.append(listener)
-
- def event_added_unsubscribe(self, listener):
- """
- Remove a listener when an event is added to the queue
- """
- if listener in self._event_added_listeners:
- self._event_added_listeners.remove(listener)
-
- def event_removed_subscribe(self, listener):
- """
- Add a listener when an event is removed from the queue
- """
- if not listener in self._event_removed_listeners:
- self._event_removed_listeners.append(listener)
-
- def event_removed_unsubscribe(self, listener):
- """
- Remove a listener when an event is removed from the queue
- """
- if listener in self._event_removed_listeners:
- self._event_removed_listeners.remove(listener)
-
- def fire_event_added(self, event):
- for listener in self._event_added_listeners:
- listener(event)
-
- def fire_event_removed(self, event_list):
- for listener in self._event_removed_listeners:
- listener(event_list)
-
- def change_account_name(self, old_name, new_name):
- if old_name in self._events:
- self._events[new_name] = self._events[old_name]
- del self._events[old_name]
-
- def add_account(self, account):
- self._events[account] = {}
-
- def get_accounts(self):
- return self._events.keys()
-
- def remove_account(self, account):
- del self._events[account]
-
- def add_event(self, account, jid, event):
- # No such account before ?
- if account not in self._events:
- self._events[account] = {jid: [event]}
- # no such jid before ?
- elif jid not in self._events[account]:
- self._events[account][jid] = [event]
- else:
- self._events[account][jid].append(event)
- event.jid = jid
- event.account = account
- self.fire_event_added(event)
-
- def remove_events(self, account, jid, event=None, types=None):
- """
- If event is not specified, remove all events from this jid, optionally
- only from given type return True if no such event found
- """
- if types is None:
- types = []
- if account not in self._events:
- return True
- if jid not in self._events[account]:
- return True
- if event: # remove only one event
- if event in self._events[account][jid]:
- if len(self._events[account][jid]) == 1:
- del self._events[account][jid]
- else:
- self._events[account][jid].remove(event)
- self.fire_event_removed([event])
- return
- else:
- return True
- if types:
- new_list = [] # list of events to keep
- removed_list = [] # list of removed events
- for ev in self._events[account][jid]:
- if ev.type_ not in types:
- new_list.append(ev)
- else:
- removed_list.append(ev)
- if len(new_list) == len(self._events[account][jid]):
- return True
- if new_list:
- self._events[account][jid] = new_list
- else:
- del self._events[account][jid]
- self.fire_event_removed(removed_list)
- return
- # no event nor type given, remove them all
- self.fire_event_removed(self._events[account][jid])
- del self._events[account][jid]
-
- def change_jid(self, account, old_jid, new_jid):
- if account not in self._events:
- return
- if old_jid not in self._events[account]:
- return
- if new_jid in self._events[account]:
- self._events[account][new_jid] += self._events[account][old_jid]
- else:
- self._events[account][new_jid] = self._events[account][old_jid]
- del self._events[account][old_jid]
-
- def get_nb_events(self, types=None, account=None):
- if types is None:
- types = []
- return self._get_nb_events(types = types, account = account)
-
- def get_events(self, account, jid=None, types=None):
- """
- Return all events from the given account of the form {jid1: [], jid2:
- []}. If jid is given, returns all events from the given jid in a list: []
- optionally only from given type
- """
- if types is None:
- types = []
- if account not in self._events:
- return []
- if not jid:
- events_list = {} # list of events
- for jid_ in self._events[account]:
- events = []
- for ev in self._events[account][jid_]:
- if not types or ev.type_ in types:
- events.append(ev)
- if events:
- events_list[jid_] = events
- return events_list
- if jid not in self._events[account]:
- return []
- events_list = [] # list of events
- for ev in self._events[account][jid]:
- if not types or ev.type_ in types:
- events_list.append(ev)
- return events_list
-
- def get_first_event(self, account=None, jid=None, type_=None):
- """
- Return the first event of type type_ if given
- """
- if not account:
- return self._get_first_event_with_attribute(self._events)
- events_list = self.get_events(account, jid, type_)
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_event = None
- for event in events_list:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_event = event
- return first_event
-
- def _get_nb_events(self, account=None, jid=None, attribute=None, types=None):
- """
- Return the number of pending events
- """
- if types is None:
- types = []
- nb = 0
- if account:
- accounts = [account]
- else:
- accounts = self._events.keys()
- for acct in accounts:
- if acct not in self._events:
- continue
- if jid:
- jids = [jid]
- else:
- jids = self._events[acct].keys()
- for j in jids:
- if j not in self._events[acct]:
- continue
- for event in self._events[acct][j]:
- if types and event.type_ not in types:
- continue
- if not attribute or \
- attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- nb += 1
- return nb
-
- def _get_some_events(self, attribute):
- """
- Attribute in systray, roster
- """
- events = {}
- for account in self._events:
- events[account] = {}
- for jid in self._events[account]:
- events[account][jid] = []
- for event in self._events[account][jid]:
- if attribute == 'systray' and event.show_in_systray or \
- attribute == 'roster' and event.show_in_roster:
- events[account][jid].append(event)
- if not events[account][jid]:
- del events[account][jid]
- if not events[account]:
- del events[account]
- return events
-
- def _get_first_event_with_attribute(self, events):
- """
- Get the first event
-
- events is in the form {account1: {jid1: [ev1, ev2], },. }
- """
- # be sure it's bigger than latest event
- first_event_time = time.time() + 1
- first_account = None
- first_jid = None
- first_event = None
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- if event.time_ < first_event_time:
- first_event_time = event.time_
- first_account = account
- first_jid = jid
- first_event = event
- return first_account, first_jid, first_event
-
- def get_nb_systray_events(self, types=None):
- """
- Return the number of events displayed in roster
- """
- if types is None:
- types = []
- return self._get_nb_events(attribute='systray', types=types)
-
- def get_systray_events(self):
- """
- Return all events that must be displayed in systray:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('systray')
-
- def get_first_systray_event(self):
- events = self.get_systray_events()
- return self._get_first_event_with_attribute(events)
-
- def get_nb_roster_events(self, account=None, jid=None, types=None):
- """
- Return the number of events displayed in roster
- """
- if types is None:
- types = []
- return self._get_nb_events(attribute='roster', account=account,
- jid=jid, types=types)
-
- def get_roster_events(self):
- """
- Return all events that must be displayed in roster:
- {account1: {jid1: [ev1, ev2], },. }
- """
- return self._get_some_events('roster')
diff --git a/src/common/exceptions.py b/src/common/exceptions.py
deleted file mode 100644
index 79c418b56..000000000
--- a/src/common/exceptions.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/exceptions.py
-##
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Brendan Taylor <whateley AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-class PysqliteOperationalError(Exception):
- """
- Sqlite2 raised pysqlite2.dbapi2.OperationalError
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class DatabaseMalformed(Exception):
- """
- The databas can't be read
- """
-
- def __init__(self, path=''):
- Exception.__init__(self)
- self.path = path
-
- def __str__(self):
- return _('The database file (%s) cannot be read. '
- 'Try to repair it (see '
- 'https://dev.gajim.org/gajim/gajim/wikis/help/DatabaseBackup)'
- ' or remove it (all history will be lost).') % self.path
-
-class ServiceNotAvailable(Exception):
- """
- This exception is raised when we cannot use Gajim remotely'
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('Service not available: Gajim is not running, or remote_control is False')
-
-class DbusNotSupported(Exception):
- """
- D-Bus is not installed or python bindings are missing
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('D-Bus is not present on this machine or python module is missing')
-
-class SessionBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('Session bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
-
-class SystemBusNotPresent(Exception):
- """
- This exception indicates that there is no session daemon
- """
-
- def __init__(self):
- Exception.__init__(self)
-
- def __str__(self):
- return _('System bus is not available.\nTry reading %(url)s') % \
- {'url': 'http://trac.gajim.org/wiki/GajimDBus'}
-
-class NegotiationError(Exception):
- """
- A session negotiation failed
- """
- pass
-
-class DecryptionError(Exception):
- """
- A message couldn't be decrypted into usable XML
- """
- pass
-
-class Cancelled(Exception):
- """
- The user cancelled an operation
- """
- pass
-
-class LatexError(Exception):
- """
- LaTeX processing failed for some reason
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class GajimGeneralException(Exception):
- """
- This exception is our general exception
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
-
-class PluginsystemError(Exception):
- """
- Error in the pluginsystem
- """
-
- def __init__(self, text=''):
- Exception.__init__(self)
- self.text = text
-
- def __str__(self):
- return self.text
diff --git a/src/common/file_props.py b/src/common/file_props.py
deleted file mode 100644
index a5d7a5fb6..000000000
--- a/src/common/file_props.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-This module is in charge of taking care of all the infomation related to
-individual files. Files are identified by the account name and its sid.
-
-
->>> print FilesProp.getFileProp('jabberid', '10')
-None
->>> fp = FilesProp()
-Traceback (most recent call last):
- ...
-Exception: this class should not be instatiated
->>> print FilesProp.getAllFileProp()
-[]
->>> fp = FilesProp.getNewFileProp('jabberid', '10')
->>> fp2 = FilesProp.getFileProp('jabberid', '10')
->>> fp == fp2
-True
-"""
-
-class FilesProp:
- _files_props = {}
-
- def __init__(self):
- raise Exception('this class should not be instatiated')
-
- @classmethod
- def getNewFileProp(cls, account, sid):
- fp = FileProp(account, sid)
- cls.setFileProp(fp, account, sid)
- return fp
-
- @classmethod
- def getFileProp(cls, account, sid):
- if (account, sid) in cls._files_props.keys():
- return cls._files_props[account, sid]
-
- @classmethod
- def getFilePropByAccount(cls, account):
- # Returns a list of file_props in one account
- file_props = []
- for account, sid in cls._files_props:
- if account == account:
- file_props.append(cls._files_props[account, sid])
- return file_props
-
- @classmethod
- def getFilePropByType(cls, type_, sid):
- # This method should be deleted. Getting fileprop by type and sid is not
- # unique enough. More than one fileprop might have the same type and sid
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.type_ == type_ and fp.sid == sid:
- return fp
-
- @classmethod
- def getFilePropBySid(cls, sid):
- # This method should be deleted. It is kept to make things compatible
- # This method should be replaced and instead get the file_props by
- # account and sid
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.sid == sid:
- return fp
-
- @classmethod
- def getFilePropByTransportSid(cls, account, sid):
- files_prop = cls.getAllFileProp()
- for fp in files_prop:
- if fp.account == account and fp.transport_sid == sid:
- return fp
-
- @classmethod
- def getAllFileProp(cls):
- return list(cls._files_props.values())
-
- @classmethod
- def setFileProp(cls, fp, account, sid):
- cls._files_props[account, sid] = fp
-
- @classmethod
- def deleteFileProp(cls, file_prop):
- files_props = cls._files_props
- a = s = None
- for account, sid in files_props:
- fp = files_props[account, sid]
- if fp is file_prop:
- a = account
- s = sid
- if a != None and s != None:
- del files_props[a, s]
-
-
-class FileProp(object):
-
- def __init__(self, account, sid):
- # Do not instatiate this class directly. Call FilesProp.getNeFileProp
- # instead
- self.streamhosts = []
- self.transfered_size = []
- self.started = False
- self.completed = False
- self.paused = False
- self.stalled = False
- self.connected = False
- self.stopped = False
- self.is_a_proxy = False
- self.proxyhost = None
- self.proxy_sender = None
- self.proxy_receiver = None
- self.streamhost_used = None
- # method callback called in case of transfer failure
- self.failure_cb = None
- # method callback called when disconnecting
- self.disconnect_cb = None
- self.continue_cb = None
- self.sha_str = None
- # transfer type: 's' for sending and 'r' for receiving
- self.type_ = None
- self.error = None
- # Elapsed time of the file transfer
- self.elapsed_time = 0
- self.last_time = None
- self.received_len = None
- # full file path
- self.file_name = None
- self.name = None
- self.date = None
- self.desc = None
- self.offset = None
- self.sender = None
- self.receiver = None
- self.tt_account = None
- self.size = None
- self._sid = sid
- self.transport_sid = None
- self.account = account
- self.mime_type = None
- self.algo = None
- self.direction = None
- self.syn_id = None
- self.seq = None
- self.hash_ = None
- self.fd = None
- self.startexmpp = None
- # Type of the session, if it is 'jingle' or 'si'
- self.session_type = None
- self.request_id = None
- self.proxyhosts = None
- self.dstaddr = None
-
- def getsid(self):
- # Getter of the property sid
- return self._sid
-
- def setsid(self, value):
- # The sid value will change
- # we need to change the in _files_props key as well
- del FilesProp._files_props[self.account, self._sid]
- self._sid = value
- FilesProp._files_props[self.account, self._sid] = self
-
- sid = property(getsid, setsid)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/src/common/fuzzyclock.py b/src/common/fuzzyclock.py
deleted file mode 100755
index 79fded35f..000000000
--- a/src/common/fuzzyclock.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/fuzzyclock.py
-##
-## Copyright (C) 2006 Christoph Neuroth <delmonico AT gmx.net>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net>
-##
-## 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/>.
-##
-
-"""
-Python class to show a "fuzzy clock".
-Homepage of the original: http://home.gna.org/fuzzyclock/
-Project Page of the original: http://gna.org/projects/fuzzyclock
-
-The class is based on a port from PHP code by
-Henrique Recidive <henrique at recidive.com> which was
-in turn based on the Fuzzy Clock Applet of Frerich Raabe (KDE).
-So most of the credit goes to this guys, thanks :-)
-"""
-
-
-class FuzzyClock:
- HOUR_NAMES = [ _('twelve'), _('one'), _('two'), _('three'), _('four'),
- _('five'), _('six'), _('seven'), _('eight'), _('nine'), _('ten'),
- _('eleven') ]
-
- #Strings to use for the output. %(0)s will be replaced with the preceding hour
- #(e.g. "x PAST %(0)s"), %(1)s with the coming hour (e.g. "x TO %(0)s"). '''
- FUZZY_TIME = [ _("%(0)s o'clock"), _('five past %(0)s'), _('ten past %(0)s'),
- _('quarter past %(0)s'), _('twenty past %(0)s'), _('twenty five past %(0)s'),
- _('half past %(0)s'), _('twenty five to %(1)s'), _('twenty to %(1)s'),
- _('quarter to %(1)s'), _('ten to %(1)s'), _('five to %(1)s'), _("%(1)s o'clock") ]
-
- FUZZY_DAYTIME = [ _('Night'), _('Early morning'), _('Morning'),
- _('Almost noon'), _('Noon'), _('Afternoon'), _('Evening'),
- _('Late evening'), _('Night') ]
-
- FUZZY_WEEK = [ _('Start of week'), _('Middle of week'), _('Middle of week'),
- _('Middle of week'), _('End of week'), _('Weekend!'), _('Weekend!') ]
-
- def fuzzy_time(self, fuzzyness, now):
- if fuzzyness == 1 or fuzzyness == 2:
- if fuzzyness == 1:
- sector = int(round(now.tm_min / 5.0))
- else:
- sector = int(round(now.tm_min / 15.0)) * 3
-
- return self.FUZZY_TIME[sector] % {
- '0': self.HOUR_NAMES[now.tm_hour % 12],
- '1': self.HOUR_NAMES[(now.tm_hour + 1) % 12]}
-
- elif fuzzyness == 3:
- return self.FUZZY_DAYTIME[int(round(now.tm_hour / 3.0))]
-
- else:
- return self.FUZZY_WEEK[now.tm_wday]
diff --git a/src/common/gajim.py b/src/common/gajim.py
deleted file mode 100644
index ef6b816a2..000000000
--- a/src/common/gajim.py
+++ /dev/null
@@ -1,486 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/gajim.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import logging
-import locale
-import gi
-import uuid
-from distutils.version import LooseVersion as V
-
-from common import config
-import nbxmpp
-from common import ged as ged_module
-
-interface = None # The actual interface (the gtk one for the moment)
-thread_interface = None # Interface to run a thread and then a callback
-config = config.Config()
-version = config.get('version')
-connections = {} # 'account name': 'account (connection.Connection) instance'
-ipython_window = None
-app = None # Gtk.Application
-
-ged = ged_module.GlobalEventsDispatcher() # Global Events Dispatcher
-nec = None # Network Events Controller
-plugin_manager = None # Plugins Manager
-
-log = logging.getLogger('gajim')
-
-logger = None
-
-from common import configpaths
-gajimpaths = configpaths.gajimpaths
-
-VCARD_PATH = gajimpaths['VCARD']
-AVATAR_PATH = gajimpaths['AVATAR']
-MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
-MY_ICONSETS_PATH = gajimpaths['MY_ICONSETS']
-MY_MOOD_ICONSETS_PATH = gajimpaths['MY_MOOD_ICONSETS']
-MY_ACTIVITY_ICONSETS_PATH = gajimpaths['MY_ACTIVITY_ICONSETS']
-MY_CACERTS = gajimpaths['MY_CACERTS']
-MY_PEER_CERTS_PATH = gajimpaths['MY_PEER_CERTS']
-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']
-MY_CERT_DIR = gajimpaths['MY_CERT']
-
-try:
- LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
-except (ValueError, locale.Error):
- # unknown locale, use en is better than fail
- LANG = None
-if LANG is None:
- LANG = 'en'
-else:
- LANG = LANG[:2] # en, fr, el etc..
-
-os_info = None # used to cache os information
-
-from common.contacts import LegacyContactsAPI
-from common.events import Events
-
-gmail_domains = ['gmail.com', 'googlemail.com']
-
-transport_type = {} # list the type of transport
-
-last_message_time = {} # list of time of the latest incomming message
- # {acct1: {jid1: time1, jid2: time2}, }
-encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
-
-contacts = LegacyContactsAPI()
-gc_connected = {} # tell if we are connected to the room or not
- # {acct: {room_jid: True}}
-gc_passwords = {} # list of the pass required to enter a room
- # {room_jid: password}
-automatic_rooms = {} # list of rooms that must be automaticaly configured
- # and for which we have a list of invities
- #{account: {room_jid: {'invities': []}}}
-new_room_nick = None # if it's != None, use this nick instead of asking for
- # a new nickname when there is a conflict.
-
-groups = {} # list of groups
-newly_added = {} # list of contacts that has just signed in
-to_be_removed = {} # list of contacts that has just signed out
-
-events = Events()
-
-notification = None
-
-nicks = {} # list of our nick names in each account
-# should we block 'contact signed in' notifications for this account?
-# this is only for the first 30 seconds after we change our show
-# to something else than offline
-# can also contain account/transport_jid to block notifications for contacts
-# from this transport
-block_signed_in_notifications = {}
-con_types = {} # type of each connection (ssl, tls, tcp, ...)
-
-sleeper_state = {} # whether we pass auto away / xa or not
-#'off': don't use sleeper for this account
-#'online': online and use sleeper
-#'autoaway': autoaway and use sleeper
-#'autoxa': autoxa and use sleeper
-status_before_autoaway = {}
-
-# jid of transport contacts for which we need to ask avatar when transport will
-# be online
-transport_avatar = {} # {transport_jid: [jid_list]}
-
-# Is Gnome configured to activate on single click ?
-single_click = False
-SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'error']
-
-# zeroconf account name
-ZEROCONF_ACC_NAME = 'Local'
-
-# These will be set in gajim.gui_interface.
-idlequeue = None
-socks5queue = None
-
-HAVE_ZEROCONF = True
-try:
- __import__('avahi')
-except ImportError:
- try:
- __import__('pybonjour')
- except Exception: # Linux raises ImportError, Windows raises WindowsError
- HAVE_ZEROCONF = False
-
-HAVE_PYCRYPTO = True
-try:
- __import__('Crypto')
-except ImportError:
- HAVE_PYCRYPTO = False
-
-HAVE_GPG = True
-GPG_BINARY = 'gpg'
-try:
- import gnupg
- '''
- We need https://pypi.python.org/pypi/python-gnupg
- but https://pypi.python.org/pypi/gnupg shares the same package name.
- It cannot be used as a drop-in replacement.
- We test with a version check if python-gnupg is installed as it is
- on a much lower version number than gnupg
- Also we need at least python-gnupg 0.3.8
- '''
- v_gnupg = gnupg.__version__
- if V(v_gnupg) < V('0.3.8') or V(v_gnupg) > V('1.0.0'):
- log.info('Gajim needs python-gnupg >= 0.3.8')
- HAVE_GPG = False
-except ImportError:
- HAVE_GPG = False
-else:
- import os
- import subprocess
- def test_gpg(binary='gpg'):
- if os.name == 'nt':
- gpg_cmd = binary + ' -h >nul 2>&1'
- else:
- gpg_cmd = binary + ' -h >/dev/null 2>&1'
- if subprocess.call(gpg_cmd, shell=True):
- return False
- return True
- if test_gpg(binary='gpg2'):
- GPG_BINARY = 'gpg2'
- if not test_gpg(binary='gpg'):
- HAVE_GPG = False
-
-HAVE_PYOPENSSL = True
-try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- ver = OpenSSL.__version__
- ver_l = [int(i) for i in ver.split('.')]
- if ver_l < [0, 12]:
- raise ImportError
-except Exception:
- HAVE_PYOPENSSL = False
-
-HAVE_FARSTREAM = True
-try:
- if os.name == 'nt':
- os.environ['FS_PLUGIN_PATH'] = 'gtk\\lib\\farstream-0.1'
- os.environ['GST_PLUGIN_PATH'] = 'gtk\\lib\\gstreamer-0.10'
- gi.require_version('Farstream', '0.2')
- from gi.repository import Farstream
- gi.require_version('Gst', '1.0')
- from gi.repository import Gst
- from gi.repository import GLib
- try:
- Gst.init(None)
- conference = Gst.ElementFactory.make('fsrtpconference', None)
- session = conference.new_session(Farstream.MediaType.AUDIO)
- del session
- del conference
- except GLib.GError:
- HAVE_FARSTREAM = False
-
-except (ImportError, ValueError):
- HAVE_FARSTREAM = False
-
-HAVE_UPNP_IGD = True
-try:
- gi.require_version('GUPnPIgd', '1.0')
- from gi.repository import GUPnPIgd
- gupnp_igd = GUPnPIgd.SimpleIgd()
-except ValueError:
- HAVE_UPNP_IGD = False
-
-HAVE_PYCURL = True
-try:
- __import__('pycurl')
-except ImportError:
- HAVE_PYCURL = False
-
-
-gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
-gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
- nbxmpp.NS_MUC, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
- nbxmpp.NS_MUC_CONFIG, nbxmpp.NS_COMMANDS, nbxmpp.NS_DISCO_INFO, 'ipv6',
- 'jabber:iq:gateway', nbxmpp.NS_LAST, nbxmpp.NS_PRIVACY, nbxmpp.NS_PRIVATE,
- nbxmpp.NS_REGISTER, nbxmpp.NS_VERSION, nbxmpp.NS_DATA, nbxmpp.NS_ENCRYPTED,
- 'msglog', 'sslc2s', 'stringprep', nbxmpp.NS_PING, nbxmpp.NS_TIME_REVISED,
- nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK,
- nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
- nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
- nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT]
-
-# Optional features gajim supports per account
-gajim_optional_features = {}
-
-# Capabilities hash per account
-caps_hash = {}
-
-def get_an_id():
- return str(uuid.uuid4())
-
-def get_nick_from_jid(jid):
- pos = jid.find('@')
- return jid[:pos]
-
-def get_server_from_jid(jid):
- pos = jid.find('@') + 1 # after @
- return jid[pos:]
-
-def get_name_and_server_from_jid(jid):
- name = get_nick_from_jid(jid)
- server = get_server_from_jid(jid)
- return name, server
-
-def get_room_and_nick_from_fjid(jid):
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.no/nick/nick-continued
- # return ('gaim@conference.jabber.no', 'nick/nick-continued')
- l = jid.split('/', 1)
- if len(l) == 1: # No nick
- l.append('')
- return l
-
-def get_real_jid_from_fjid(account, fjid):
- """
- Return real jid or returns None, if we don't know the real jid
- """
- room_jid, nick = get_room_and_nick_from_fjid(fjid)
- if not nick: # It's not a fake_jid, it is a real jid
- return fjid # we return the real jid
- real_jid = fjid
- if interface.msg_win_mgr.get_gc_control(room_jid, account):
- # It's a pm, so if we have real jid it's in contact.jid
- gc_contact = contacts.get_gc_contact(account, room_jid, nick)
- if not gc_contact:
- return
- # gc_contact.jid is None when it's not a real jid (we don't know real jid)
- real_jid = gc_contact.jid
- return real_jid
-
-def get_room_from_fjid(jid):
- return get_room_and_nick_from_fjid(jid)[0]
-
-def get_contact_name_from_jid(account, jid):
- c = contacts.get_first_contact_from_jid(account, jid)
- return c.name
-
-def get_jid_without_resource(jid):
- return jid.split('/')[0]
-
-def construct_fjid(room_jid, nick):
- # fake jid is the jid for a contact in a room
- # gaim@conference.jabber.org/nick
- return room_jid + '/' + nick
-
-def get_resource_from_jid(jid):
- jids = jid.split('/', 1)
- if len(jids) > 1:
- return jids[1] # abc@doremi.org/res/res-continued
- else:
- return ''
-
-def get_number_of_accounts():
- """
- Return the number of ALL accounts
- """
- return len(connections.keys())
-
-def get_number_of_connected_accounts(accounts_list = None):
- """
- Returns the number of CONNECTED accounts. Uou can optionally pass an
- accounts_list and if you do those will be checked, else all will be checked
- """
- connected_accounts = 0
- if accounts_list is None:
- accounts = connections.keys()
- else:
- accounts = accounts_list
- for account in accounts:
- if account_is_connected(account):
- connected_accounts = connected_accounts + 1
- return connected_accounts
-
-def account_is_connected(account):
- if account not in connections:
- return False
- if connections[account].connected > 1: # 0 is offline, 1 is connecting
- return True
- else:
- return False
-
-def account_is_disconnected(account):
- return not account_is_connected(account)
-
-def zeroconf_is_connected():
- return account_is_connected(ZEROCONF_ACC_NAME) and \
- config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf')
-
-def get_number_of_securely_connected_accounts():
- """
- Return the number of the accounts that are SSL/TLS connected
- """
- num_of_secured = 0
- for account in connections.keys():
- if account_is_securely_connected(account):
- num_of_secured += 1
- return num_of_secured
-
-def account_is_securely_connected(account):
- if account_is_connected(account) and \
- account in con_types and con_types[account] in ('tls', 'ssl'):
- return True
- else:
- return False
-
-def get_transport_name_from_jid(jid, use_config_setting = True):
- """
- Returns 'aim', 'gg', 'irc' etc
-
- If JID is not from transport returns None.
- """
- #FIXME: jid can be None! one TB I saw had this problem:
- # in the code block # it is a groupchat presence in handle_event_notify
- # jid was None. Yann why?
- if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
- return
-
- host = get_server_from_jid(jid)
- if host in transport_type:
- return transport_type[host]
-
- # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
- host_splitted = host.split('.')
- if len(host_splitted) != 0:
- # now we support both 'icq.' and 'icq' but not icqsucks.org
- host = host_splitted[0]
-
- if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo',
- 'mrim', 'facebook'):
- return host
- elif host == 'gg':
- return 'gadu-gadu'
- elif host == 'jit':
- return 'icq'
- elif host == 'facebook':
- return 'facebook'
- else:
- return None
-
-def jid_is_transport(jid):
- # if not '@' or '@' starts the jid then it is transport
- if jid.find('@') <= 0:
- return True
- return False
-
-def get_jid_from_account(account_name, full=False):
- """
- Return the jid we use in the given account
- """
- name = config.get_per('accounts', account_name, 'name')
- hostname = config.get_per('accounts', account_name, 'hostname')
- jid = name + '@' + hostname
- if full:
- resource = connections[account_name].server_resource
- jid += '/' + resource
- return jid
-
-def get_our_jids():
- """
- Returns a list of the jids we use in our accounts
- """
- our_jids = list()
- for account in contacts.get_accounts():
- our_jids.append(get_jid_from_account(account))
- return our_jids
-
-def get_hostname_from_account(account_name, use_srv = False):
- """
- Returns hostname (if custom hostname is used, that is returned)
- """
- if use_srv and connections[account_name].connected_hostname:
- return connections[account_name].connected_hostname
- if config.get_per('accounts', account_name, 'use_custom_host'):
- return config.get_per('accounts', account_name, 'custom_host')
- return config.get_per('accounts', account_name, 'hostname')
-
-def get_notification_image_prefix(jid):
- """
- Returns the prefix for the notification images
- """
- transport_name = get_transport_name_from_jid(jid)
- if transport_name in ('aim', 'icq', 'msn', 'yahoo', 'facebook'):
- prefix = transport_name
- else:
- prefix = 'jabber'
- return prefix
-
-def get_name_from_jid(account, jid):
- """
- Return from JID's shown name and if no contact returns jids
- """
- contact = contacts.get_first_contact_from_jid(account, jid)
- if contact:
- actor = contact.get_shown_name()
- else:
- actor = jid
- return actor
-
-def get_priority(account, show):
- """
- Return the priority an account must have
- """
- if not show:
- show = 'online'
-
- if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \
- config.get_per('accounts', account, 'adjust_priority_with_status'):
- return config.get_per('accounts', account, 'autopriority_' + show)
- return config.get_per('accounts', account, 'priority')
diff --git a/src/common/ged.py b/src/common/ged.py
deleted file mode 100644
index 1f387f016..000000000
--- a/src/common/ged.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-Global Events Dispatcher module.
-
-:author: Mateusz Biliński <mateusz@bilinski.it>
-:since: 8th August 2008
-:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
-:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
-:license: GPL
-'''
-
-import traceback
-
-from nbxmpp import NodeProcessed
-import logging
-log = logging.getLogger('gajim.c.ged')
-
-PRECORE = 10
-CORE = 20
-POSTCORE = 30
-PREGUI = 40
-PREGUI1 = 50
-GUI1 = 60
-POSTGUI1 = 70
-PREGUI2 = 80
-GUI2 = 90
-POSTGUI2 = 100
-POSTGUI = 110
-
-OUT_PREGUI = 10
-OUT_PREGUI1 = 20
-OUT_GUI1 = 30
-OUT_POSTGUI1 = 40
-OUT_PREGUI2 = 50
-OUT_GUI2 = 60
-OUT_POSTGUI2 = 70
-OUT_POSTGUI = 80
-OUT_PRECORE = 90
-OUT_CORE = 100
-OUT_POSTCORE = 110
-
-class GlobalEventsDispatcher(object):
-
- def __init__(self):
- self.handlers = {}
-
- def register_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- handlers_list = self.handlers[event_name]
- i = 0
- for i, h in enumerate(handlers_list):
- if priority < h[0]:
- break
- else:
- # no event with smaller prio found, put it at the end
- i += 1
-
- handlers_list.insert(i, (priority, handler))
- else:
- self.handlers[event_name] = [(priority, handler)]
-
- def remove_event_handler(self, event_name, priority, handler):
- if event_name in self.handlers:
- try:
- self.handlers[event_name].remove((priority, handler))
- except ValueError as error:
- log.warning('''Function (%s) with priority "%s" never registered
- as handler of event "%s". Couldn\'t remove. Error: %s'''
- %(handler, priority, event_name, error))
-
- def raise_event(self, event_name, *args, **kwargs):
- log.debug('%s Args: %s'%(event_name, str(args)))
- if event_name in self.handlers:
- node_processed = False
- for priority, handler in self.handlers[event_name]:
- try:
- if handler(*args, **kwargs):
- return True
- except NodeProcessed:
- node_processed = True
- except Exception:
- log.error('Error while running an even handler: %s' % \
- handler)
- traceback.print_exc()
- if node_processed:
- raise NodeProcessed
diff --git a/src/common/gpg.py b/src/common/gpg.py
deleted file mode 100644
index dc7f695cb..000000000
--- a/src/common/gpg.py
+++ /dev/null
@@ -1,150 +0,0 @@
-## src/common/gpg.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import logging
-from common import gajim
-from common.gajim import HAVE_GPG, GPG_BINARY
-
-if HAVE_GPG:
- import gnupg
- gnupg.logger = logging.getLogger('gajim.c.gnupg')
-
- class GnuPG(gnupg.GPG):
- def __init__(self):
- use_agent = gajim.config.get('use_gpg_agent')
- gnupg.GPG.__init__(self, gpgbinary=GPG_BINARY, use_agent=use_agent)
- encoding = gajim.config.get('pgp_encoding')
- if encoding:
- self.encoding = encoding
- self.decode_errors = 'replace'
- self.passphrase = None
- self.always_trust = [] # list of keyID to always trust
-
- def encrypt(self, str_, recipients, always_trust=False):
- trust = always_trust
- if not trust:
- # check if we trust all keys
- trust = True
- for key in recipients:
- if key not in self.always_trust:
- trust = False
- if not trust:
- # check that we'll be able to encrypt
- result = super(GnuPG, self).list_keys(keys=recipients)
- for key in result:
- if key['trust'] not in ('f', 'u'):
- if key['keyid'][-8:] not in self.always_trust:
- return '', 'NOT_TRUSTED ' + key['keyid'][-8:]
- else:
- trust = True
- result = super(GnuPG, self).encrypt(str_.encode('utf8'), recipients,
- always_trust=trust, passphrase=self.passphrase)
-
- if result.ok:
- error = ''
- else:
- error = result.status
-
- return self._stripHeaderFooter(str(result)), error
-
- def decrypt(self, str_, keyID):
- data = self._addHeaderFooter(str_, 'MESSAGE')
- result = super(GnuPG, self).decrypt(data.encode('utf8'),
- passphrase=self.passphrase)
-
- return str(result)
-
- def sign(self, str_, keyID):
- result = super(GnuPG, self).sign(str_.encode('utf8'), keyid=keyID, detach=True,
- passphrase=self.passphrase)
-
- if result.fingerprint:
- return self._stripHeaderFooter(str(result))
- if hasattr(result, 'status') and result.status == 'key expired':
- return 'KEYEXPIRED'
- return 'BAD_PASSPHRASE'
-
- def verify(self, str_, sign):
- if str_ is None:
- return ''
- # Hash algorithm is not transfered in the signed presence stanza so try
- # all algorithms. Text name for hash algorithms from RFC 4880 - section 9.4
- hash_algorithms = ['SHA512', 'SHA384', 'SHA256', 'SHA224', 'SHA1', 'RIPEMD160']
- for algo in hash_algorithms:
- data = os.linesep.join(
- ['-----BEGIN PGP SIGNED MESSAGE-----',
- 'Hash: ' + algo,
- '',
- str_,
- self._addHeaderFooter(sign, 'SIGNATURE')]
- )
- result = super(GnuPG, self).verify(data.encode('utf8'))
- if result.valid:
- return result.key_id
-
- return ''
-
- def get_key(self, keyID):
- return super(GnuPG, self).list_keys(keys=[keyID])
-
- def get_keys(self, secret=False):
- keys = {}
- result = super(GnuPG, self).list_keys(secret=secret)
- for key in result:
- # Take first not empty uid
- keys[key['keyid'][8:]] = [uid for uid in key['uids'] if uid][0]
- return keys
-
- def get_secret_keys(self):
- return self.get_keys(True)
-
- def _stripHeaderFooter(self, data):
- """
- Remove header and footer from data
- """
- if not data: return ''
- lines = data.splitlines()
- while lines[0] != '':
- lines.remove(lines[0])
- while lines[0] == '':
- lines.remove(lines[0])
- i = 0
- for line in lines:
- if line:
- if line[0] == '-': break
- i = i+1
- line = '\n'.join(lines[0:i])
- return line
-
- def _addHeaderFooter(self, data, type_):
- """
- Add header and footer from data
- """
- out = "-----BEGIN PGP %s-----" % type_ + os.linesep
- out = out + "Version: PGP" + os.linesep
- out = out + os.linesep
- out = out + data + os.linesep
- out = out + "-----END PGP %s-----" % type_ + os.linesep
- return out
diff --git a/src/common/helpers.py b/src/common/helpers.py
deleted file mode 100644
index 17a1fb9d0..000000000
--- a/src/common/helpers.py
+++ /dev/null
@@ -1,1531 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/helpers.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import sys
-import re
-import os
-import subprocess
-import urllib
-import webbrowser
-import errno
-import select
-import base64
-import hashlib
-import shlex
-from common import caps_cache
-import socket
-import time
-import datetime
-
-from encodings.punycode import punycode_encode
-from string import Template
-
-from common.i18n import Q_
-from common.i18n import ngettext
-
-if os.name == 'nt':
- try:
- HAS_WINSOUND = True
- import winsound # windows-only built-in module for playing wav
- except ImportError:
- HAS_WINSOUND = False
- print('Gajim is not able to playback sound because'
- 'pywin32 is missing', file=sys.stderr)
-
-try:
- import wave # posix-only fallback wav playback
- import ossaudiodev as oss
-except Exception:
- pass
-
-import logging
-log = logging.getLogger('gajim.c.helpers')
-
-special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
-
-class InvalidFormat(Exception):
- pass
-
-def decompose_jid(jidstring):
- user = None
- server = None
- resource = None
-
- # Search for delimiters
- user_sep = jidstring.find('@')
- res_sep = jidstring.find('/')
-
- if user_sep == -1:
- if res_sep == -1:
- # host
- server = jidstring
- else:
- # host/resource
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:]
- else:
- if res_sep == -1:
- # user@host
- user = jidstring[0:user_sep]
- server = jidstring[user_sep + 1:]
- else:
- if user_sep < res_sep:
- # user@host/resource
- user = jidstring[0:user_sep]
- server = jidstring[user_sep + 1:user_sep + (res_sep - user_sep)]
- resource = jidstring[res_sep + 1:]
- else:
- # server/resource (with an @ in resource)
- server = jidstring[0:res_sep]
- resource = jidstring[res_sep + 1:]
- return user, server, resource
-
-def parse_jid(jidstring):
- """
- Perform stringprep on all JID fragments from a string and return the full
- jid
- """
- # This function comes from http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
-
- return prep(*decompose_jid(jidstring))
-
-def idn_to_ascii(host):
- """
- Convert IDN (Internationalized Domain Names) to ACE (ASCII-compatible
- encoding)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- if label:
- converted_labels.append(idna.ToASCII(label).decode('utf-8'))
- else:
- converted_labels.append('')
- return ".".join(converted_labels)
-
-def ascii_to_idn(host):
- """
- Convert ACE (ASCII-compatible encoding) to IDN (Internationalized Domain
- Names)
- """
- from encodings import idna
- labels = idna.dots.split(host)
- converted_labels = []
- for label in labels:
- converted_labels.append(idna.ToUnicode(label))
- return ".".join(converted_labels)
-
-def puny_encode_url(url):
- _url = url
- if '//' not in _url:
- _url = '//' + _url
- try:
- o = urllib.parse.urlparse(_url)
- p_loc = idn_to_ascii(o.netloc)
- except Exception:
- log.debug('urlparse failed: %s', url)
- return False
- return url.replace(o.netloc, p_loc)
-
-def parse_resource(resource):
- """
- Perform stringprep on resource and return it
- """
- if resource:
- try:
- from nbxmpp.stringprepare import resourceprep
- return resourceprep.prepare(resource)
- except UnicodeError:
- raise InvalidFormat('Invalid character in resource.')
-
-def prep(user, server, resource):
- """
- Perform stringprep on all JID fragments and return the full jid
- """
- # This function comes from
- #http://svn.twistedmatrix.com/cvs/trunk/twisted/words/protocols/jabber/jid.py
-
- ip_address = False
-
- try:
- socket.inet_aton(server)
- ip_address = True
- except socket.error:
- pass
-
- if not ip_address and hasattr(socket, 'inet_pton'):
- try:
- socket.inet_pton(socket.AF_INET6, server.strip('[]'))
- server = '[%s]' % server.strip('[]')
- ip_address = True
- except (socket.error, ValueError):
- pass
-
- if not ip_address:
- if server is not None:
- if len(server) < 1 or len(server) > 1023:
- raise InvalidFormat(_('Server must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import nameprep
- server = nameprep.prepare(server)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in hostname.'))
- else:
- raise InvalidFormat(_('Server address required.'))
-
- if user is not None:
- if len(user) < 1 or len(user) > 1023:
- raise InvalidFormat(_('Username must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import nodeprep
- user = nodeprep.prepare(user)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in username.'))
- else:
- user = None
-
- if resource is not None:
- if len(resource) < 1 or len(resource) > 1023:
- raise InvalidFormat(_('Resource must be between 1 and 1023 chars'))
- try:
- from nbxmpp.stringprepare import resourceprep
- resource = resourceprep.prepare(resource)
- except UnicodeError:
- raise InvalidFormat(_('Invalid character in resource.'))
- else:
- resource = None
-
- if user:
- if resource:
- return '%s@%s/%s' % (user, server, resource)
- else:
- return '%s@%s' % (user, server)
- else:
- if resource:
- return '%s/%s' % (server, resource)
- else:
- return server
-
-def windowsify(s):
- if os.name == 'nt':
- return s.capitalize()
- return s
-
-def temp_failure_retry(func, *args, **kwargs):
- while True:
- try:
- return func(*args, **kwargs)
- except (os.error, IOError, select.error) as ex:
- if ex.errno == errno.EINTR:
- continue
- else:
- raise
-
-def get_uf_show(show, use_mnemonic = False):
- """
- Return a userfriendly string for dnd/xa/chat and make all strings
- translatable
-
- If use_mnemonic is True, it adds _ so GUI should call with True for
- accessibility issues
- """
- if show == 'dnd':
- if use_mnemonic:
- uf_show = _('_Busy')
- else:
- uf_show = _('Busy')
- elif show == 'xa':
- if use_mnemonic:
- uf_show = _('_Not Available')
- else:
- uf_show = _('Not Available')
- elif show == 'chat':
- if use_mnemonic:
- uf_show = _('_Free for Chat')
- else:
- uf_show = _('Free for Chat')
- elif show == 'online':
- if use_mnemonic:
- uf_show = Q_('?user status:_Available')
- else:
- uf_show = Q_('?user status:Available')
- elif show == 'connecting':
- uf_show = _('Connecting')
- elif show == 'away':
- if use_mnemonic:
- uf_show = _('A_way')
- else:
- uf_show = _('Away')
- elif show == 'offline':
- if use_mnemonic:
- uf_show = _('_Offline')
- else:
- uf_show = _('Offline')
- elif show == 'invisible':
- if use_mnemonic:
- uf_show = _('_Invisible')
- else:
- uf_show = _('Invisible')
- elif show == 'not in roster':
- uf_show = _('Not in Roster')
- elif show == 'requested':
- uf_show = Q_('?contact has status:Unknown')
- else:
- uf_show = Q_('?contact has status:Has errors')
- return uf_show
-
-def get_uf_sub(sub):
- if sub == 'none':
- uf_sub = Q_('?Subscription we already have:None')
- elif sub == 'to':
- uf_sub = _('To')
- elif sub == 'from':
- uf_sub = _('From')
- elif sub == 'both':
- uf_sub = _('Both')
- else:
- uf_sub = sub
-
- return uf_sub
-
-def get_uf_ask(ask):
- if ask is None:
- uf_ask = Q_('?Ask (for Subscription):None')
- elif ask == 'subscribe':
- uf_ask = _('Subscribe')
- else:
- uf_ask = ask
-
- return uf_ask
-
-def get_uf_role(role, plural = False):
- ''' plural determines if you get Moderators or Moderator'''
- if role == 'none':
- role_name = Q_('?Group Chat Contact Role:None')
- elif role == 'moderator':
- if plural:
- role_name = _('Moderators')
- else:
- role_name = _('Moderator')
- elif role == 'participant':
- if plural:
- role_name = _('Participants')
- else:
- role_name = _('Participant')
- elif role == 'visitor':
- if plural:
- role_name = _('Visitors')
- else:
- role_name = _('Visitor')
- return role_name
-
-def get_uf_affiliation(affiliation):
- '''Get a nice and translated affilition for muc'''
- if affiliation == 'none':
- affiliation_name = Q_('?Group Chat Contact Affiliation:None')
- elif affiliation == 'owner':
- affiliation_name = _('Owner')
- elif affiliation == 'admin':
- affiliation_name = _('Administrator')
- elif affiliation == 'member':
- affiliation_name = _('Member')
- else: # Argl ! An unknown affiliation !
- affiliation_name = affiliation.capitalize()
- return affiliation_name
-
-def get_sorted_keys(adict):
- keys = sorted(adict.keys())
- return keys
-
-def to_one_line(msg):
- msg = msg.replace('\\', '\\\\')
- msg = msg.replace('\n', '\\n')
- # s1 = 'test\ntest\\ntest'
- # s11 = s1.replace('\\', '\\\\')
- # s12 = s11.replace('\n', '\\n')
- # s12
- # 'test\\ntest\\\\ntest'
- return msg
-
-def from_one_line(msg):
- # (?<!\\) is a lookbehind assertion which asks anything but '\'
- # to match the regexp that follows it
-
- # So here match '\\n' but not if you have a '\' before that
- expr = re.compile(r'(?<!\\)\\n')
- msg = expr.sub('\n', msg)
- msg = msg.replace('\\\\', '\\')
- # s12 = 'test\\ntest\\\\ntest'
- # s13 = re.sub('\n', s12)
- # s14 s13.replace('\\\\', '\\')
- # s14
- # 'test\ntest\\ntest'
- return msg
-
-def get_uf_chatstate(chatstate):
- """
- Remove chatstate jargon and returns user friendly messages
- """
- if chatstate == 'active':
- return _('is paying attention to the conversation')
- elif chatstate == 'inactive':
- return _('is doing something else')
- elif chatstate == 'composing':
- return _('is composing a message…')
- elif chatstate == 'paused':
- #paused means he or she was composing but has stopped for a while
- return _('paused composing a message')
- elif chatstate == 'gone':
- return _('has closed the chat window or tab')
- return ''
-
-def is_in_path(command, return_abs_path=False):
- """
- Return True if 'command' is found in one of the directories in the user's
- path. If 'return_abs_path' is True, return the absolute path of the first
- found command instead. Return False otherwise and on errors
- """
- for directory in os.getenv('PATH').split(os.pathsep):
- try:
- if command in os.listdir(directory):
- if return_abs_path:
- return os.path.join(directory, command)
- else:
- return True
- except OSError:
- # If the user has non directories in his path
- pass
- return False
-
-def exec_command(command, use_shell=False, posix=True):
- """
- execute a command. if use_shell is True, we run the command as is it was
- typed in a console. So it may be dangerous if you are not sure about what
- is executed.
- """
- if use_shell:
- subprocess.Popen('%s &' % command, shell=True).wait()
- else:
- args = shlex.split(command, posix=posix)
- p = subprocess.Popen(args)
- gajim.thread_interface(p.wait)
-
-def build_command(executable, parameter):
- # we add to the parameter (can hold path with spaces)
- # "" so we have good parsing from shell
- parameter = parameter.replace('"', '\\"') # but first escape "
- command = '%s "%s"' % (executable, parameter)
- return command
-
-def get_file_path_from_dnd_dropped_uri(uri):
- path = urllib.parse.unquote(uri.decode('utf-8')) # escape special chars
- path = path.strip('\r\n\x00') # remove \r\n and NULL
- # get the path to file
- if re.match('^file:///[a-zA-Z]:/', path): # windows
- path = path[8:] # 8 is len('file:///')
- elif path.startswith('file://'): # nautilus, rox
- path = path[7:] # 7 is len('file://')
- elif path.startswith('file:'): # xffm
- path = path[5:] # 5 is len('file:')
- return path
-
-def from_xs_boolean_to_python_boolean(value):
- # this is xs:boolean so 'true', 'false', '1', '0'
- # convert those to True/False (python booleans)
- if value in ('1', 'true'):
- val = True
- else: # '0', 'false' or anything else
- val = False
-
- return val
-
-def get_xmpp_show(show):
- if show in ('online', 'offline'):
- return None
- return show
-
-def get_output_of_command(command):
- try:
- child_stdin, child_stdout = os.popen2(command)
- except ValueError:
- return None
-
- output = child_stdout.readlines()
- child_stdout.close()
- child_stdin.close()
-
- return output
-
-def sanitize_filename(filename):
- """
- Make sure the filename we will write does contain only acceptable and latin
- characters, and is not too long (in that case hash it)
- """
- # 48 is the limit
- if len(filename) > 48:
- hash = hashlib.md5(filename.encode('utf-8'))
- filename = base64.b64encode(hash.digest()).decode('utf-8')
-
- # make it latin chars only
- filename = punycode_encode(filename).decode('utf-8')
- filename = filename.replace('/', '_')
- if os.name == 'nt':
- filename = filename.replace('?', '_').replace(':', '_')\
- .replace('\\', '_').replace('"', "'").replace('|', '_')\
- .replace('*', '_').replace('<', '_').replace('>', '_')
-
- return filename
-
-def reduce_chars_newlines(text, max_chars = 0, max_lines = 0):
- """
- Cut the chars after 'max_chars' on each line and show only the first
- 'max_lines'
-
- If any of the params is not present (None or 0) the action on it is not
- performed
- """
- def _cut_if_long(string):
- if len(string) > max_chars:
- string = string[:max_chars - 3] + '…'
- return string
-
- if max_lines == 0:
- lines = text.split('\n')
- else:
- lines = text.split('\n', max_lines)[:max_lines]
- if max_chars > 0:
- if lines:
- lines = [_cut_if_long(e) for e in lines]
- if lines:
- reduced_text = '\n'.join(lines)
- if reduced_text != text:
- reduced_text += '…'
- else:
- reduced_text = ''
- return reduced_text
-
-def get_account_status(account):
- status = reduce_chars_newlines(account['status_line'], 100, 1)
- return status
-
-def get_avatar_path(prefix):
- """
- Return the filename of the avatar, distinguishes between user- and contact-
- provided one. Return None if no avatar was found at all. prefix is the path
- to the requested avatar just before the ".png" or ".jpeg"
- """
- # First, scan for a local, user-set avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '_local.' + type_
- if os.path.exists(file_):
- return file_
- # If none available, scan for a contact-provided avatar
- for type_ in ('jpeg', 'png'):
- file_ = prefix + '.' + type_
- if os.path.exists(file_):
- return file_
- return None
-
-def datetime_tuple(timestamp):
- """
- Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S
-
- Because of various datetime formats are used the following exceptions
- are handled:
- - Optional milliseconds appened to the string are removed
- - Optional Z (that means UTC) appened to the string are removed
- - XEP-082 datetime strings have all '-' cahrs removed to meet
- the above format.
- """
- date, tim = timestamp.split('T', 1)
- date = date.replace('-', '')
- tim = tim.replace('z', '')
- tim = tim.replace('Z', '')
- zone = None
- if '+' in tim:
- sign = -1
- tim, zone = tim.split('+', 1)
- if '-' in tim:
- sign = 1
- tim, zone = tim.split('-', 1)
- tim = tim.split('.')[0]
- tim = time.strptime(date + 'T' + tim, '%Y%m%dT%H:%M:%S')
- if zone:
- zone = zone.replace(':', '')
- tim = datetime.datetime.fromtimestamp(time.mktime(tim))
- if len(zone) > 2:
- zone = time.strptime(zone, '%H%M')
- else:
- zone = time.strptime(zone, '%H')
- zone = datetime.timedelta(hours=zone.tm_hour, minutes=zone.tm_min)
- tim += zone * sign
- tim = tim.timetuple()
- return tim
-
-from common import gajim
-if gajim.HAVE_PYCURL:
- import pycurl
- from io import StringIO
-
-def convert_bytes(string):
- suffix = ''
- # IEC standard says KiB = 1024 bytes KB = 1000 bytes
- # but do we use the standard?
- use_kib_mib = gajim.config.get('use_kib_mib')
- align = 1024.
- bytes_ = float(string)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if bytes_ >= align:
- bytes_ = round(bytes_/align, 1)
- if use_kib_mib:
- #GiB means gibibyte
- suffix = _('%s GiB')
- else:
- #GB means gigabyte
- suffix = _('%s GB')
- else:
- if use_kib_mib:
- #MiB means mibibyte
- suffix = _('%s MiB')
- else:
- #MB means megabyte
- suffix = _('%s MB')
- else:
- if use_kib_mib:
- #KiB means kibibyte
- suffix = _('%s KiB')
- else:
- #KB means kilo bytes
- suffix = _('%s KB')
- else:
- #B means bytes
- suffix = _('%s B')
- return suffix % str(bytes_)
-
-def get_contact_dict_for_account(account):
- """
- Create a dict of jid, nick -> contact with all contacts of account.
-
- Can be used for completion lists
- """
- contacts_dict = {}
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- contacts_dict[jid] = contact
- name = contact.name
- if name in contacts_dict:
- contact1 = contacts_dict[name]
- del contacts_dict[name]
- contacts_dict['%s (%s)' % (name, contact1.jid)] = contact1
- contacts_dict['%s (%s)' % (name, jid)] = contact
- elif contact.name:
- if contact.name == gajim.get_nick_from_jid(jid):
- del contacts_dict[jid]
- contacts_dict[name] = contact
- return contacts_dict
-
-def launch_browser_mailer(kind, uri):
- # kind = 'url' or 'mail'
- if kind == 'url' and uri.startswith('file://'):
- launch_file_manager(uri)
- return
- if kind in ('mail', 'sth_at_sth') and not uri.startswith('mailto:'):
- uri = 'mailto:' + uri
-
- if kind == 'url' and uri.startswith('www.'):
- uri = 'http://' + uri
-
- if not gajim.config.get('autodetect_browser_mailer'):
- if kind == 'url':
- command = gajim.config.get('custombrowser')
- elif kind in ('mail', 'sth_at_sth'):
- command = gajim.config.get('custommailapp')
- if command == '': # if no app is configured
- return
-
- command = build_command(command, uri)
- try:
- exec_command(command)
- except Exception:
- pass
-
- else:
- webbrowser.open(uri)
-
-
-def launch_file_manager(path_to_open):
- if not path_to_open.startswith('file://'):
- uri = 'file://' + path_to_open
- if os.name == 'nt':
- try:
- os.startfile(path_to_open) # if pywin32 is installed we open
- except Exception:
- pass
- else:
- if not gajim.config.get('autodetect_browser_mailer'):
- command = gajim.config.get('custom_file_manager')
- if command == '': # if no app is configured
- return
- else:
- command = 'xdg-open'
- command = build_command(command, path_to_open)
- try:
- exec_command(command)
- except Exception:
- pass
-
-def play_sound(event):
- if not gajim.config.get('sounds_on'):
- return
- path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
- play_sound_file(path_to_soundfile)
-
-def check_soundfile_path(file_, dirs=(gajim.gajimpaths.data_root,
-gajim.DATA_DIR)):
- """
- Check if the sound file exists
-
- :param file_: the file to check, absolute or relative to 'dirs' path
- :param dirs: list of knows paths to fallback if the file doesn't exists
- (eg: ~/.gajim/sounds/, DATADIR/sounds...).
- :return the path to file or None if it doesn't exists.
- """
- if not file_:
- return None
- elif os.path.exists(file_):
- return file_
-
- for d in dirs:
- d = os.path.join(d, 'sounds', file_)
- if os.path.exists(d):
- return d
- return None
-
-def strip_soundfile_path(file_, dirs=(gajim.gajimpaths.data_root,
-gajim.DATA_DIR), abs=True):
- """
- Remove knowns paths from a sound file
-
- Filechooser returns absolute path. If path is a known fallback path, we remove it.
- So config have no hardcoded path to DATA_DIR and text in textfield is shorther.
- param: file_: the filename to strip.
- param: dirs: list of knowns paths from which the filename should be stripped.
- param: abs: force absolute path on dirs
- """
- if not file_:
- return None
-
- name = os.path.basename(file_)
- for d in dirs:
- d = os.path.join(d, 'sounds', name)
- if abs:
- d = os.path.abspath(d)
- if file_ == d:
- return name
- return file_
-
-def play_sound_file(path_to_soundfile):
- if path_to_soundfile == 'beep':
- exec_command('beep')
- return
- path_to_soundfile = check_soundfile_path(path_to_soundfile)
- if path_to_soundfile is None:
- return
- elif sys.platform == 'win32' and HAS_WINSOUND:
- try:
- winsound.PlaySound(path_to_soundfile,
- winsound.SND_FILENAME|winsound.SND_ASYNC)
- except Exception:
- log.exception('Sound Playback Error')
- elif sys.platform == 'linux':
- if gajim.config.get('soundplayer') == '':
- def _oss_play():
- sndfile = wave.open(path_to_soundfile, 'rb')
- (nc, sw, fr, nf, comptype, compname) = sndfile.getparams()
- dev = oss.open('/dev/dsp', 'w')
- dev.setparameters(sw * 8, nc, fr)
- dev.write(sndfile.readframes(nf))
- sndfile.close()
- dev.close()
- gajim.thread_interface(_oss_play)
- return
- player = gajim.config.get('soundplayer')
- command = build_command(player, path_to_soundfile)
- exec_command(command)
-
-def get_global_show():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- return gajim.SHOW_LIST[maxi]
-
-def get_global_status():
- maxi = 0
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- connected = gajim.connections[account].connected
- if connected > maxi:
- maxi = connected
- status = gajim.connections[account].status
- return status
-
-
-def statuses_unified():
- """
- Test if all statuses are the same
- """
- reference = None
- for account in gajim.connections:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- if reference is None:
- reference = gajim.connections[account].connected
- elif reference != gajim.connections[account].connected:
- return False
- return True
-
-def get_icon_name_to_show(contact, account = None):
- """
- Get the icon name to show in online, away, requested, etc
- """
- if account and gajim.events.get_nb_roster_events(account, contact.jid):
- return 'event'
- if account and gajim.events.get_nb_roster_events(account,
- contact.get_full_jid()):
- return 'event'
- if account and account in gajim.interface.minimized_controls and \
- contact.jid in gajim.interface.minimized_controls[account] and gajim.interface.\
- minimized_controls[account][contact.jid].get_nb_unread_pm() > 0:
- return 'event'
- if account and contact.jid in gajim.gc_connected[account]:
- if gajim.gc_connected[account][contact.jid]:
- return 'muc_active'
- else:
- return 'muc_inactive'
- if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
- return contact.show
- if contact.sub in ('both', 'to'):
- return contact.show
- if contact.ask == 'subscribe':
- return 'requested'
- transport = gajim.get_transport_name_from_jid(contact.jid)
- if transport:
- return contact.show
- if contact.show in gajim.SHOW_LIST:
- return contact.show
- return 'not in roster'
-
-def get_full_jid_from_iq(iq_obj):
- """
- Return the full jid (with resource) from an iq
- """
- return parse_jid(str(iq_obj.getFrom()))
-
-def get_jid_from_iq(iq_obj):
- """
- Return the jid (without resource) from an iq
- """
- jid = get_full_jid_from_iq(iq_obj)
- return gajim.get_jid_without_resource(jid)
-
-def get_auth_sha(sid, initiator, target):
- """
- Return sha of sid + initiator + target used for proxy auth
- """
- return hashlib.sha1(("%s%s%s" % (sid, initiator, target)).encode('utf-8')).\
- hexdigest()
-
-def remove_invalid_xml_chars(string):
- if string:
- string = re.sub(gajim.interface.invalid_XML_chars_re, '', string)
- return string
-
-distro_info = {
- 'Arch Linux': '/etc/arch-release',
- 'Aurox Linux': '/etc/aurox-release',
- 'Conectiva Linux': '/etc/conectiva-release',
- 'CRUX': '/usr/bin/crux',
- 'Debian GNU/Linux': '/etc/debian_version',
- 'Fedora Linux': '/etc/fedora-release',
- 'Gentoo Linux': '/etc/gentoo-release',
- 'Linux from Scratch': '/etc/lfs-release',
- 'Mandrake Linux': '/etc/mandrake-release',
- 'Slackware Linux': '/etc/slackware-version',
- 'Solaris/Sparc': '/etc/release',
- 'Source Mage': '/etc/sourcemage_version',
- 'SUSE Linux': '/etc/SuSE-release',
- 'Sun JDS': '/etc/sun-release',
- 'PLD Linux': '/etc/pld-release',
- 'Yellow Dog Linux': '/etc/yellowdog-release',
- 'AgiliaLinux': '/etc/agilialinux-version',
- # many distros use the /etc/redhat-release for compatibility
- # so Redhat is the last
- 'Redhat Linux': '/etc/redhat-release'
-}
-
-def get_random_string_16():
- """
- Create random string of length 16
- """
- rng = list(range(65, 90))
- rng.extend(range(48, 57))
- char_sequence = [chr(e) for e in rng]
- from random import sample
- return ''.join(sample(char_sequence, 16))
-
-def get_os_info():
- if gajim.os_info:
- return gajim.os_info
- if os.name == 'nt':
- # platform.release() seems to return the name of the windows
- ver = sys.getwindowsversion()
- ver_format = ver[3], ver[0], ver[1]
- win_version = {
- (1, 4, 0): '95',
- (1, 4, 10): '98',
- (1, 4, 90): 'ME',
- (2, 4, 0): 'NT',
- (2, 5, 0): '2000',
- (2, 5, 1): 'XP',
- (2, 5, 2): '2003',
- (2, 6, 0): 'Vista',
- (2, 6, 1): '7',
- }
- if ver_format in win_version:
- os_info = 'Windows' + ' ' + win_version[ver_format]
- else:
- os_info = 'Windows'
- gajim.os_info = os_info
- return os_info
- elif os.name == 'posix':
- executable = 'lsb_release'
- params = ' --description --codename --release --short'
- full_path_to_executable = is_in_path(executable, return_abs_path = True)
- if full_path_to_executable:
- command = executable + params
- p = subprocess.Popen([command], shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, close_fds=True)
- p.wait()
- output = temp_failure_retry(p.stdout.readline).strip()
- # some distros put n/a in places, so remove those
- output = output.decode('utf-8').replace('n/a', '').replace('N/A', '')
- gajim.os_info = output
- p.stdout.close()
- p.stdin.close()
- return output
-
- # lsb_release executable not available, so parse files
- for distro_name in distro_info:
- path_to_file = distro_info[distro_name]
- if os.path.exists(path_to_file):
- if os.access(path_to_file, os.X_OK):
- # the file is executable (f.e. CRUX)
- # yes, then run it and get the first line of output.
- text = get_output_of_command(path_to_file)[0]
- else:
- fd = open(path_to_file)
- text = fd.readline().strip() # get only first line
- fd.close()
- if path_to_file.endswith('version'):
- # sourcemage_version and slackware-version files
- # have all the info we need (name and version of distro)
- if not os.path.basename(path_to_file).startswith(
- 'sourcemage') or not\
- os.path.basename(path_to_file).startswith('slackware'):
- text = distro_name + ' ' + text
- elif path_to_file.endswith('aurox-release') or \
- path_to_file.endswith('arch-release'):
- # file doesn't have version
- text = distro_name
- elif path_to_file.endswith('lfs-release'):
- # file just has version
- text = distro_name + ' ' + text
- os_info = text.replace('\n', '')
- gajim.os_info = os_info
- return os_info
-
- # our last chance, ask uname and strip it
- uname_output = get_output_of_command('uname -sr')
- if uname_output is not None:
- os_info = uname_output[0] # only first line
- gajim.os_info = os_info
- return os_info
- os_info = 'N/A'
- gajim.os_info = os_info
- return os_info
-
-
-def allow_showing_notification(account, type_='notify_on_new_message',
-is_first_message=True):
- """
- Is it allowed to show nofication?
-
- Check OUR status and if we allow notifications for that status type is the
- option that need to be True e.g.: notify_on_signing is_first_message: set it
- to false when it's not the first message
- """
- if type_ and (not gajim.config.get(type_) or not is_first_message):
- return False
- if gajim.config.get('autopopupaway'): # always show notification
- return True
- if gajim.connections[account].connected in (2, 3): # we're online or chat
- return True
- return False
-
-def allow_popup_window(account):
- """
- Is it allowed to popup windows?
- """
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
- if autopopup and (autopopupaway or \
- gajim.connections[account].connected in (2, 3)): # we're online or chat
- return True
- return False
-
-def allow_sound_notification(account, sound_event):
- if gajim.config.get('sounddnd') or gajim.connections[account].connected != \
- gajim.SHOW_LIST.index('dnd') and gajim.config.get_per('soundevents',
- sound_event, 'enabled'):
- return True
- return False
-
-def get_chat_control(account, contact):
- full_jid_with_resource = contact.jid
- if contact.resource:
- full_jid_with_resource += '/' + contact.resource
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- account, contact.jid)
-
- # Look for a chat control that has the given resource, or default to
- # one without resource
- ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
- account)
-
- if ctrl:
- return ctrl
- elif highest_contact and highest_contact.resource and \
- contact.resource != highest_contact.resource:
- return None
- else:
- # unknown contact or offline message
- return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
-
-def get_notification_icon_tooltip_dict():
- """
- Return a dict of the form {acct: {'show': show, 'message': message,
- 'event_lines': [list of text lines to show in tooltip]}
- """
- # How many events must there be before they're shown summarized, not per-user
- max_ungrouped_events = 10
-
- accounts = get_accounts_info()
-
- # Gather events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- account['event_lines'] = []
- # Gather events per-account
- pending_events = gajim.events.get_events(account = account_name)
- messages, non_messages, total_messages, total_non_messages = {}, {}, 0, 0
- for jid in pending_events:
- for event in pending_events[jid]:
- if event.type_.count('file') > 0:
- # This is a non-messagee event.
- messages[jid] = non_messages.get(jid, 0) + 1
- total_non_messages = total_non_messages + 1
- else:
- # This is a message.
- messages[jid] = messages.get(jid, 0) + 1
- total_messages = total_messages + 1
- # Display unread messages numbers, if any
- if total_messages > 0:
- if total_messages > max_ungrouped_events:
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- total_messages, total_messages, total_messages)
- account['event_lines'].append(text)
- else:
- for jid in messages.keys():
- text = ngettext(
- '%d message pending',
- '%d messages pending',
- messages[jid], messages[jid], messages[jid])
- contact = gajim.contacts.get_first_contact_from_jid(
- account['name'], jid)
- text += ' '
- if jid in gajim.gc_connected[account['name']]:
- text += _('from room %s') % (jid)
- elif contact:
- name = contact.get_shown_name()
- text += _('from user %s') % (name)
- else:
- text += _('from %s') % (jid)
- account['event_lines'].append(text)
-
- # Display unseen events numbers, if any
- if total_non_messages > 0:
- if total_non_messages > max_ungrouped_events:
- text = ngettext(
- '%d event pending',
- '%d events pending',
- total_non_messages, total_non_messages,total_non_messages)
- account['event_lines'].append(text)
- else:
- for jid in non_messages.keys():
- text = ngettext('%d event pending', '%d events pending',
- non_messages[jid], non_messages[jid], non_messages[jid])
- text += ' ' + _('from user %s') % (jid)
- account[account]['event_lines'].append(text)
-
- return accounts
-
-def get_notification_icon_tooltip_text():
- text = None
- # How many events must there be before they're shown summarized, not per-user
- # max_ungrouped_events = 10
- # Character which should be used to indent in the tooltip.
- indent_with = ' '
-
- accounts = get_notification_icon_tooltip_dict()
-
- if len(accounts) == 0:
- # No configured account
- return _('Gajim')
-
- # at least one account present
-
- # Is there more that one account?
- if len(accounts) == 1:
- show_more_accounts = False
- else:
- show_more_accounts = True
-
- # If there is only one account, its status is shown on the first line.
- if show_more_accounts:
- text = _('Gajim')
- else:
- text = _('Gajim - %s') % (get_account_status(accounts[0]))
-
- # Gather and display events. (With accounts, when there are more.)
- for account in accounts:
- account_name = account['name']
- # Set account status, if not set above
- if (show_more_accounts):
- message = '\n' + indent_with + ' %s - %s'
- text += message % (account_name, get_account_status(account))
- # Account list shown, messages need to be indented more
- indent_how = 2
- else:
- # If no account list is shown, messages could have default indenting.
- indent_how = 1
- for line in account['event_lines']:
- text += '\n' + indent_with * indent_how + ' '
- text += line
- return text
-
-def get_accounts_info():
- """
- Helper for notification icon tooltip
- """
- accounts = []
- accounts_list = sorted(gajim.contacts.get_accounts())
- for account in accounts_list:
- status_idx = gajim.connections[account].connected
- # uncomment the following to hide offline accounts
- # if status_idx == 0: continue
- status = gajim.SHOW_LIST[status_idx]
- message = gajim.connections[account].status
- single_line = get_uf_show(status)
- if message is None:
- message = ''
- else:
- message = message.strip()
- if message != '':
- single_line += ': ' + message
- accounts.append({'name': account, 'status_line': single_line,
- 'show': status, 'message': message})
- return accounts
-
-
-def get_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', iconset)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_ICONSETS_PATH, iconset)
-
-def get_mood_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'moods', iconset)):
- return os.path.join(gajim.DATA_DIR, 'moods', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)):
- return os.path.join(gajim.MY_MOOD_ICONSETS_PATH, iconset)
-
-def get_activity_iconset_path(iconset):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'activities', iconset)):
- return os.path.join(gajim.DATA_DIR, 'activities', iconset)
- elif os.path.isdir(os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH,
- iconset)):
- return os.path.join(gajim.MY_ACTIVITY_ICONSETS_PATH, iconset)
-
-def get_transport_path(transport):
- if os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
- transport)):
- return os.path.join(gajim.DATA_DIR, 'iconsets', 'transports', transport)
- elif os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, 'transports',
- transport)):
- return os.path.join(gajim.MY_ICONSETS_PATH, 'transports', transport)
- # No transport folder found, use default jabber one
- return get_iconset_path(gajim.config.get('iconset'))
-
-def prepare_and_validate_gpg_keyID(account, jid, keyID):
- """
- Return an eight char long keyID that can be used with for GPG encryption
- with this contact
-
- If the given keyID is None, return UNKNOWN; if the key does not match the
- assigned key XXXXXXXXMISMATCH is returned. If the key is trusted and not yet
- assigned, assign it.
- """
- if gajim.connections[account].USE_GPG:
- if keyID and len(keyID) == 16:
- keyID = keyID[8:]
-
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
-
- if jid in attached_keys and keyID:
- attachedkeyID = attached_keys[attached_keys.index(jid) + 1]
- if attachedkeyID != keyID:
- # Get signing subkeys for the attached key
- subkeys = []
- for key in gajim.connections[account].gpg.list_keys():
- if key['keyid'][8:] == attachedkeyID:
- subkeys = [subkey[0][8:] for subkey in key['subkeys'] \
- if subkey[1] == 's']
- break
-
- if keyID not in subkeys:
- # Mismatch! Another gpg key was expected
- keyID += 'MISMATCH'
- elif jid in attached_keys:
- # An unsigned presence, just use the assigned key
- keyID = attached_keys[attached_keys.index(jid) + 1]
- elif keyID:
- full_key = gajim.connections[account].ask_gpg_keys(keyID=keyID)
- # Assign the corresponding key, if we have it in our keyring
- if full_key:
- for u in gajim.contacts.get_contacts(account, jid):
- u.keyID = keyID
- keys_str = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys')
- keys_str += jid + ' ' + keyID + ' '
- gajim.config.set_per('accounts', account, 'attached_gpg_keys',
- keys_str)
- elif keyID is None:
- keyID = 'UNKNOWN'
- return keyID
-
-def update_optional_features(account = None):
- import nbxmpp
- if account:
- accounts = [account]
- else:
- accounts = [a for a in gajim.connections]
- for a in accounts:
- gajim.gajim_optional_features[a] = []
- if gajim.config.get_per('accounts', a, 'subscribe_mood'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_MOOD + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_activity'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_ACTIVITY + \
- '+notify')
- if gajim.config.get_per('accounts', a, 'publish_tune'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_TUNE)
- if gajim.config.get_per('accounts', a, 'publish_location'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_LOCATION)
- if gajim.config.get_per('accounts', a, 'subscribe_tune'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_TUNE + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_nick'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_NICK + '+notify')
- if gajim.config.get_per('accounts', a, 'subscribe_location'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_LOCATION + \
- '+notify')
- if gajim.config.get('outgoing_chat_state_notifactions') != 'disabled':
- gajim.gajim_optional_features[a].append(nbxmpp.NS_CHATSTATES)
- if not gajim.config.get('ignore_incoming_xhtml'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_XHTML_IM)
- if gajim.HAVE_PYCRYPTO \
- and gajim.config.get_per('accounts', a, 'enable_esessions'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_ESESSION)
- if gajim.config.get_per('accounts', a, 'answer_receipts'):
- gajim.gajim_optional_features[a].append(nbxmpp.NS_RECEIPTS)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE)
- if gajim.HAVE_FARSTREAM:
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP_AUDIO)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_RTP_VIDEO)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_ICE_UDP)
- gajim.gajim_optional_features[a].append(
- nbxmpp.NS_JINGLE_FILE_TRANSFER_5)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_XTLS)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_BYTESTREAM)
- gajim.gajim_optional_features[a].append(nbxmpp.NS_JINGLE_IBB)
- gajim.caps_hash[a] = caps_cache.compute_caps_hash([gajim.gajim_identity],
- gajim.gajim_common_features + gajim.gajim_optional_features[a])
- # re-send presence with new hash
- connected = gajim.connections[a].connected
- if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible':
- gajim.connections[a].change_status(gajim.SHOW_LIST[connected],
- gajim.connections[a].status)
-
-def jid_is_blocked(account, jid):
- return ((jid in gajim.connections[account].blocked_contacts) or \
- gajim.connections[account].blocked_all)
-
-def group_is_blocked(account, group):
- return ((group in gajim.connections[account].blocked_groups) or \
- gajim.connections[account].blocked_all)
-
-def get_subscription_request_msg(account=None):
- s = gajim.config.get_per('accounts', account, 'subscription_request_msg')
- if s:
- return s
- s = _('I would like to add you to my contact list.')
- if account:
- s = _('Hello, I am $name.') + ' ' + s
- our_jid = gajim.get_jid_from_account(account)
- vcard = gajim.connections[account].get_cached_vcard(our_jid)
- name = ''
- if vcard:
- if 'N' in vcard:
- if 'GIVEN' in vcard['N'] and 'FAMILY' in vcard['N']:
- name = vcard['N']['GIVEN'] + ' ' + vcard['N']['FAMILY']
- if not name and 'FN' in vcard:
- name = vcard['FN']
- nick = gajim.nicks[account]
- if name and nick:
- name += ' (%s)' % nick
- elif nick:
- name = nick
- s = Template(s).safe_substitute({'name': name})
- return s
-
-def replace_dataform_media(form, stanza):
- import nbxmpp
- found = False
- for field in form.getTags('field'):
- for media in field.getTags('media'):
- for uri in media.getTags('uri'):
- uri_data = uri.getData()
- if uri_data.startswith('cid:'):
- uri_data = uri_data[4:]
- for data in stanza.getTags('data', namespace=nbxmpp.NS_BOB):
- if data.getAttr('cid') == uri_data:
- uri.setData(data.getData())
- found = True
- return found
-
-def get_proxy_info(account):
- p = gajim.config.get_per('accounts', account, 'proxy')
- if not p:
- if gajim.config.get_per('accounts', account, 'use_env_http_proxy'):
- try:
- try:
- env_http_proxy = os.environ['HTTP_PROXY']
- except Exception:
- env_http_proxy = os.environ['http_proxy']
- env_http_proxy = env_http_proxy.strip('"')
- # Dispose of the http:// prefix
- env_http_proxy = env_http_proxy.split('://')[-1]
- env_http_proxy = env_http_proxy.split('@')
-
- if len(env_http_proxy) == 2:
- login = env_http_proxy[0].split(':')
- addr = env_http_proxy[1].split(':')
- else:
- login = ['', '']
- addr = env_http_proxy[0].split(':')
-
- proxy = {'host': addr[0], 'type' : 'http', 'user':login[0]}
-
- if len(addr) == 2:
- proxy['port'] = addr[1]
- else:
- proxy['port'] = 3128
-
- if len(login) == 2:
- proxy['pass'] = login[1]
- proxy['useauth'] = True
- else:
- proxy['pass'] = ''
- return proxy
-
- except Exception:
- proxy = None
- p = gajim.config.get('global_proxy')
- if p and p in gajim.config.get_per('proxies'):
- proxy = {}
- proxyptr = gajim.config.get_per('proxies', p)
- if not proxyptr:
- return proxy
- for key in proxyptr.keys():
- proxy[key] = proxyptr[key]
- return proxy
-
-def _get_img_direct(attrs):
- """
- Download an image. This function should be launched in a separated thread.
- """
- mem = b''
- alt = ''
- max_size = 2*1024*1024
- if 'max_size' in attrs:
- max_size = attrs['max_size']
- # Wait maximum 10s for connection
- socket.setdefaulttimeout(10)
- try:
- req = urllib.request.Request(attrs['src'])
- req.add_header('User-Agent', 'Gajim ' + gajim.version)
- f = urllib.request.urlopen(req)
- except Exception as ex:
- log.debug('Error loading image %s ' % attrs['src'] + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- else:
- # Wait 2s between each byte
- try:
- f.fp._sock.fp._sock.settimeout(2)
- except Exception:
- pass
- # On a slow internet connection with ~1000kbps you need ~10 seconds for 1 MB
- deadline = time.time() + (10 * (max_size / 1048576))
- while True:
- if time.time() > deadline:
- log.debug('Timeout loading image %s ' % attrs['src'])
- mem = ''
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- try:
- temp = f.read(100)
- except socket.timeout as ex:
- log.debug('Timeout loading image %s ' % attrs['src'] + str(ex))
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Timeout loading image')
- break
- if temp:
- mem += temp
- else:
- break
- if len(mem) > max_size:
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Image is too big')
- break
- f.close()
- return (mem, alt)
-
-def _get_img_proxy(attrs, proxy):
- """
- Download an image through a proxy. This function should be launched in a
- separated thread.
- """
- if not gajim.HAVE_PYCURL:
- return '', _('PyCURL is not installed')
- mem, alt, max_size = '', '', 2*1024*1024
- if 'max_size' in attrs:
- max_size = attrs['max_size']
- try:
- b = StringIO()
- c = pycurl.Curl()
- c.setopt(pycurl.URL, attrs['src'].encode('utf-8'))
- c.setopt(pycurl.FOLLOWLOCATION, 1)
- # Wait maximum 10s for connection
- c.setopt(pycurl.CONNECTTIMEOUT, 10)
- # On a slow internet connection with ~1000kbps you need ~10 seconds for 1 MB
- c.setopt(pycurl.TIMEOUT, 10 * (max_size / 1048576))
- c.setopt(pycurl.MAXFILESIZE, max_size)
- c.setopt(pycurl.WRITEFUNCTION, b.write)
- c.setopt(pycurl.USERAGENT, 'Gajim ' + gajim.version)
- # set proxy
- c.setopt(pycurl.PROXY, proxy['host'].encode('utf-8'))
- c.setopt(pycurl.PROXYPORT, proxy['port'])
- if proxy['useauth']:
- c.setopt(pycurl.PROXYUSERPWD, proxy['user'].encode('utf-8')\
- + ':' + proxy['pass'].encode('utf-8'))
- c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
- if proxy['type'] == 'http':
- c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
- elif proxy['type'] == 'socks5':
- c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
- x = c.perform()
- c.close()
- t = b.getvalue()
- return (t, attrs.get('alt', ''))
- except pycurl.error as ex:
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- if ex.errno == pycurl.E_FILESIZE_EXCEEDED:
- alt += _('Image is too big')
- elif ex.errno == pycurl.E_OPERATION_TIMEOUTED:
- alt += _('Timeout loading image')
- else:
- alt += _('Error loading image')
- except Exception as ex:
- log.debug('Error loading image %s ' % attrs['src'] + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- return ('', alt)
-
-def download_image(account, attrs):
- proxy = get_proxy_info(account)
- if proxy and proxy['type'] in ('http', 'socks5'):
- return _get_img_proxy(attrs, proxy)
- return _get_img_direct(attrs)
diff --git a/src/common/i18n.py b/src/common/i18n.py
deleted file mode 100644
index 01ba2be89..000000000
--- a/src/common/i18n.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/i18n.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2009 Benjamin Richter <br AT waldteufel-online.net>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import locale
-import gettext
-import os
-from common import defs
-import unicodedata
-
-# May be changed after GTK is imported
-direction_mark = '\u200E'
-
-def paragraph_direction_mark(text):
- """
- Determine paragraph writing direction according to
- http://www.unicode.org/reports/tr9/#The_Paragraph_Level
-
- Returns either Unicode LTR mark or RTL mark.
- """
- for char in text:
- bidi = unicodedata.bidirectional(char)
- if bidi == 'L':
- return '\u200E'
- elif bidi == 'AL' or bidi == 'R':
- return '\u200F'
-
- return '\u200E'
-
-APP = 'gajim'
-DIR = defs.localedir
-
-# set '' so each part of the locale that should be modified is set
-# according to the environment variables
-locale.setlocale(locale.LC_ALL, '')
-
-## For windows: set, if needed, a value in LANG environmental variable ##
-if os.name == 'nt':
- lang = os.getenv('LANG')
- if lang is None:
- default_lang = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
- if default_lang:
- lang = default_lang
-
- if lang:
- os.environ['LANG'] = lang
-
-gettext.install(APP, DIR)
-if gettext._translations:
- _translation = list(gettext._translations.values())[0]
-else:
- _translation = gettext.NullTranslations()
-
-def Q_(text):
- """
- Translate the given text, optionally qualified with a special
- construction, which will help translators to disambiguate between
- same terms, but in different contexts.
-
- When translated text is returned - this rudimentary construction
- will be stripped off, if it's present.
-
- Here is the construction to use:
- Q_("?vcard:Unknown")
-
- Everything between ? and : - is the qualifier to convey the context
- to the translators. Everything after : - is the text itself.
- """
- text = _(text)
- if text.startswith('?'):
- qualifier, text = text.split(':', 1)
- return text
-
-def ngettext(s_sing, s_plural, n, replace_sing = None, replace_plural = None):
- """
- Use as:
- i18n.ngettext('leave room %s', 'leave rooms %s', len(rooms), 'a', 'a, b, c')
-
- In other words this is a hack to ngettext() to support %s %d etc..
- """
- text = _translation.ngettext(s_sing, s_plural, n)
- if n == 1 and replace_sing is not None:
- text = text % replace_sing
- elif n > 1 and replace_plural is not None:
- text = text % replace_plural
- return text
diff --git a/src/common/idle.py b/src/common/idle.py
deleted file mode 100644
index 9e475c47d..000000000
--- a/src/common/idle.py
+++ /dev/null
@@ -1,99 +0,0 @@
-## src/common/idle.py
-##
-## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-
-import ctypes
-import ctypes.util
-
-class XScreenSaverInfo(ctypes.Structure):
- _fields_ = [
- ('window', ctypes.c_ulong),
- ('state', ctypes.c_int),
- ('kind', ctypes.c_int),
- ('til_or_since', ctypes.c_ulong),
- ('idle', ctypes.c_ulong),
- ('eventMask', ctypes.c_ulong)
- ]
-XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
-
-display_p = ctypes.c_void_p
-xid = ctypes.c_ulong
-c_int_p = ctypes.POINTER(ctypes.c_int)
-
-try:
- libX11path = ctypes.util.find_library('X11')
- if libX11path == None:
- raise OSError('libX11 could not be found.')
- libX11 = ctypes.cdll.LoadLibrary(libX11path)
- libX11.XOpenDisplay.restype = display_p
- libX11.XOpenDisplay.argtypes = ctypes.c_char_p,
- libX11.XDefaultRootWindow.restype = xid
- libX11.XDefaultRootWindow.argtypes = display_p,
-
- libXsspath = ctypes.util.find_library('Xss')
- if libXsspath == None:
- raise OSError('libXss could not be found.')
- libXss = ctypes.cdll.LoadLibrary(libXsspath)
- libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
- libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
- libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
-
- dpy_p = libX11.XOpenDisplay(None)
- if dpy_p == None:
- raise OSError('Could not open X Display.')
-
- _event_basep = ctypes.c_int()
- _error_basep = ctypes.c_int()
- if libXss.XScreenSaverQueryExtension(dpy_p, ctypes.byref(_event_basep),
- ctypes.byref(_error_basep)) == 0:
- raise OSError('XScreenSaver Extension not available on display.')
-
- xss_info_p = libXss.XScreenSaverAllocInfo()
- if xss_info_p == None:
- raise OSError('XScreenSaverAllocInfo: Out of Memory.')
-
- rootwindow = libX11.XDefaultRootWindow(dpy_p)
- xss_available = True
-except OSError:
- # Logging?
- xss_available = False
-
-def getIdleSec():
- global xss_available
- """
- Return the idle time in seconds
- """
- if not xss_available:
- return 0
- if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
- return 0
- else:
- return int(xss_info_p.contents.idle) / 1000
-
-def close():
- global xss_available
- if xss_available:
- libX11.XFree(xss_info_p)
- libX11.XCloseDisplay(dpy_p)
- xss_available = False
-
-if __name__ == '__main__':
- import time
- time.sleep(2.1)
- print(getIdleSec())
- close()
- print(getIdleSec())
diff --git a/src/common/jingle.py b/src/common/jingle.py
deleted file mode 100644
index 6903679b9..000000000
--- a/src/common/jingle.py
+++ /dev/null
@@ -1,233 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-"""
-Handles the jingle signalling protocol
-"""
-
-#TODO:
-# * things in XEP 0176, including:
-# - http://xmpp.org/extensions/xep-0176.html#protocol-restarts
-# - http://xmpp.org/extensions/xep-0176.html#fallback
-# * XEP 0177 (raw udp)
-
-# * UI:
-# - make state and codec informations available to the user
-# - video integration
-# * config:
-# - codecs
-
-import logging
-
-import nbxmpp
-from common import helpers
-from common import gajim
-
-from common.jingle_session import JingleSession, JingleStates
-from common.jingle_ft import JingleFileTransfer
-from common.jingle_transport import JingleTransportSocks5, JingleTransportIBB
-if gajim.HAVE_FARSTREAM:
- from common.jingle_rtp import JingleAudio, JingleVideo
-
-logger = logging.getLogger('gajim.c.jingle')
-
-
-class ConnectionJingle(object):
- """
- This object depends on that it is a part of Connection class.
- """
-
- def __init__(self):
- # dictionary: sessionid => JingleSession object
- self._sessions = {}
-
- # dictionary: (jid, iq stanza id) => JingleSession object,
- # one time callbacks
- self.__iq_responses = {}
- self.files = []
-
- def delete_jingle_session(self, sid):
- """
- Remove a jingle session from a jingle stanza dispatcher
- """
- if sid in self._sessions:
- #FIXME: Move this elsewhere?
- for content in list(self._sessions[sid].contents.values()):
- content.destroy()
- self._sessions[sid].callbacks = []
- del self._sessions[sid]
-
- def _JingleCB(self, con, stanza):
- """
- The jingle stanza dispatcher
-
- Route jingle stanza to proper JingleSession object, or create one if it
- is a new session.
-
- TODO: Also check if the stanza isn't an error stanza, if so route it
- adequatelly.
- """
- # get data
- try:
- jid = helpers.get_full_jid_from_iq(stanza)
- except helpers.InvalidFormat:
- logger.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
- return
- id_ = stanza.getID()
- if (jid, id_) in self.__iq_responses.keys():
- self.__iq_responses[(jid, id_)].on_stanza(stanza)
- del self.__iq_responses[(jid, id_)]
- raise nbxmpp.NodeProcessed
- jingle = stanza.getTag('jingle')
- # a jingle element is not necessary in iq-result stanza
- # don't check for that
- if jingle:
- sid = jingle.getAttr('sid')
- else:
- sid = None
- for sesn in self._sessions.values():
- if id_ in sesn.iq_ids:
- sesn.on_stanza(stanza)
- return
- # do we need to create a new jingle object
- if sid not in self._sessions:
- #TODO: tie-breaking and other things...
- newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
- iq_id=id_, sid=sid)
- self._sessions[sid] = newjingle
- # we already have such session in dispatcher...
- self._sessions[sid].collect_iq_id(id_)
- self._sessions[sid].on_stanza(stanza)
- # Delete invalid/unneeded sessions
- if sid in self._sessions and \
- self._sessions[sid].state == JingleStates.ENDED:
- self.delete_jingle_session(sid)
- raise nbxmpp.NodeProcessed
-
- def start_audio(self, jid):
- if self.get_jingle_session(jid, media='audio'):
- return self.get_jingle_session(jid, media='audio').sid
- jingle = self.get_jingle_session(jid, media='video')
- if jingle:
- jingle.add_content('voice', JingleAudio(jingle))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self._sessions[jingle.sid] = jingle
- jingle.add_content('voice', JingleAudio(jingle))
- jingle.start_session()
- return jingle.sid
-
- def start_video(self, jid, in_xid, out_xid):
- if self.get_jingle_session(jid, media='video'):
- return self.get_jingle_session(jid, media='video').sid
- jingle = self.get_jingle_session(jid, media='audio')
- if jingle:
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
- out_xid=out_xid))
- else:
- jingle = JingleSession(self, weinitiate=True, jid=jid)
- self._sessions[jingle.sid] = jingle
- jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
- out_xid=out_xid))
- jingle.start_session()
- return jingle.sid
-
- def start_file_transfer(self, jid, file_props, request=False):
- logger.info("start file transfer with file: %s", file_props)
- contact = gajim.contacts.get_contact_with_highest_priority(self.name,
- gajim.get_jid_without_resource(jid))
- if gajim.contacts.is_gc_contact(self.name, jid):
- gcc = jid.split('/')
- if len(gcc) == 2:
- contact = gajim.contacts.get_gc_contact(self.name, gcc[0], gcc[1])
- if contact is None:
- return
- use_security = contact.supports(nbxmpp.NS_JINGLE_XTLS)
- jingle = JingleSession(self, weinitiate=True, jid=jid, werequest=request)
- # this is a file transfer
- jingle.session_type_ft = True
- self._sessions[jingle.sid] = jingle
- file_props.sid = jingle.sid
- if contact.supports(nbxmpp.NS_JINGLE_BYTESTREAM):
- transport = JingleTransportSocks5()
- elif contact.supports(nbxmpp.NS_JINGLE_IBB):
- transport = JingleTransportIBB()
- c = JingleFileTransfer(jingle, transport=transport,
- file_props=file_props,
- use_security=use_security)
- file_props.algo = self.__hash_support(contact)
- jingle.add_content('file' + helpers.get_random_string_16(), c)
- jingle.start_session()
- return c.transport.sid
-
- def __hash_support(self, contact):
- if contact.supports(nbxmpp.NS_HASHES_2):
- if contact.supports(nbxmpp.NS_HASHES_BLAKE2B_512):
- return 'blake2b-512'
- elif contact.supports(nbxmpp.NS_HASHES_BLAKE2B_256):
- return 'blake2b-256'
- elif contact.supports(nbxmpp.NS_HASHES_SHA3_512):
- return 'sha3-512'
- elif contact.supports(nbxmpp.NS_HASHES_SHA3_256):
- return 'sha3-256'
- elif contact.supports(nbxmpp.NS_HASHES_SHA512):
- return 'sha-512'
- elif contact.supports(nbxmpp.NS_HASHES_SHA256):
- return 'sha-256'
- return None
-
- def iter_jingle_sessions(self, jid, sid=None, media=None):
- if sid:
- return (session for session in self._sessions.values() if \
- session.sid == sid)
- sessions = (session for session in self._sessions.values() if \
- session.peerjid == jid)
- if media:
- if media not in ('audio', 'video', 'file'):
- return tuple()
- else:
- return (session for session in sessions if \
- session.get_content(media))
- else:
- return sessions
-
- def set_file_info(self, file_):
- # Saves information about the files we have transfered in case they need
- # to be requested again.
- self.files.append(file_)
-
- def get_file_info(self, peerjid, hash_=None, name=None, account=None):
- if hash_:
- for f in self.files: # DEBUG
- #if f['hash'] == '1294809248109223':
- if f['hash'] == hash_ and f['peerjid'] == peerjid:
- return f
- elif name:
- for f in self.files:
- if f['name'] == name and f['peerjid'] == peerjid:
- return f
-
- def get_jingle_session(self, jid, sid=None, media=None):
- if sid:
- if sid in self._sessions:
- return self._sessions[sid]
- else:
- return None
- elif media:
- if media not in ('audio', 'video', 'file'):
- return None
- for session in self._sessions.values():
- if session.peerjid == jid and session.get_content(media):
- return session
- return None
diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py
deleted file mode 100644
index 4108f8ebc..000000000
--- a/src/common/jingle_content.py
+++ /dev/null
@@ -1,240 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle contents (XEP 0166)
-"""
-
-import os
-from common import gajim
-import nbxmpp
-from .jingle_xtls import SELF_SIGNED_CERTIFICATE
-from .jingle_xtls import load_cert_file
-
-contents = {}
-
-def get_jingle_content(node):
- namespace = node.getNamespace()
- if namespace in contents:
- return contents[namespace](node)
-
-
-class JingleContentSetupException(Exception):
- """
- Exception that should be raised when a content fails to setup.
- """
-
-
-class JingleContent:
- """
- An abstraction of content in Jingle sessions
- """
-
- def __init__(self, session, transport):
- self.session = session
- self.transport = transport
- # will be filled by JingleSession.add_content()
- # don't uncomment these lines, we will catch more buggy code then
- # (a JingleContent not added to session shouldn't send anything)
- self.creator = None
- self.name = None
- self.accepted = False
- self.sent = False
- self.negotiated = False
-
- self.media = None
-
- self.senders = 'both' #FIXME
- self.allow_sending = True # Used for stream direction, attribute 'senders'
-
- # These were found by the Politie
- self.file_props = None
- self.use_security = None
-
- self.callbacks = {
- # these are called when *we* get stanzas
- 'content-accept': [self.__on_transport_info,
- self.__on_content_accept],
- 'content-add': [self.__on_transport_info],
- 'content-modify': [],
- 'content-reject': [],
- 'content-remove': [],
- 'description-info': [],
- 'security-info': [],
- 'session-accept': [self.__on_transport_info,
- self.__on_content_accept],
- 'session-info': [],
- 'session-initiate': [self.__on_transport_info],
- 'session-terminate': [],
- 'transport-info': [self.__on_transport_info],
- 'transport-replace': [self.__on_transport_replace],
- 'transport-accept': [],
- 'transport-reject': [],
- 'iq-result': [],
- 'iq-error': [],
- # these are called when *we* sent these stanzas
- 'content-accept-sent': [self.__fill_jingle_stanza,
- self.__on_content_accept],
- 'content-add-sent': [self.__fill_jingle_stanza],
- 'session-initiate-sent': [self.__fill_jingle_stanza],
- 'session-accept-sent': [self.__fill_jingle_stanza,
- self.__on_content_accept],
- 'session-terminate-sent': [],
- }
-
- def is_ready(self):
- return self.accepted and not self.sent
-
- def __on_content_accept(self, stanza, content, error, action):
- self.on_negotiated()
-
- def on_negotiated(self):
- if self.accepted:
- self.negotiated = True
- self.session.content_negotiated(self.media)
-
- def add_remote_candidates(self, candidates):
- """
- Add a list of candidates to the list of remote candidates
- """
- self.transport.remote_candidates = candidates
-
- def on_stanza(self, stanza, content, error, action):
- """
- Called when something related to our content was sent by peer
- """
- if action in self.callbacks:
- for callback in self.callbacks[action]:
- callback(stanza, content, error, action)
-
- def __on_transport_replace(self, stanza, content, error, action):
- content.addChild(node=self.transport.make_transport())
-
- def __on_transport_info(self, stanza, content, error, action):
- """
- Got a new transport candidate
- """
- candidates = self.transport.parse_transport_stanza(
- content.getTag('transport'))
- if candidates:
- self.add_remote_candidates(candidates)
-
- def __content(self, payload=None):
- """
- Build a XML content-wrapper for our data
- """
- if payload is None:
- payload = []
- return nbxmpp.Node('content',
- attrs={'name': self.name, 'creator': self.creator},
- payload=payload)
-
- def send_candidate(self, candidate):
- """
- Send a transport candidate for a previously defined transport.
- """
- content = self.__content()
- content.addChild(node=self.transport.make_transport([candidate]))
- self.session.send_transport_info(content)
-
- def send_error_candidate(self):
- """
- Sends a candidate-error when we can't connect to a candidate.
- """
- content = self.__content()
- tp = self.transport.make_transport(add_candidates=False)
- tp.addChild(name='candidate-error')
- content.addChild(node=tp)
- self.session.send_transport_info(content)
-
-
- def send_description_info(self):
- content = self.__content()
- self._fill_content(content)
- self.session.send_description_info(content)
-
- def __fill_jingle_stanza(self, stanza, content, error, action):
- """
- Add our things to session-initiate stanza
- """
- self._fill_content(content)
- self.sent = True
- content.addChild(node=self.transport.make_transport())
-
- def _fill_content(self, content):
- description_node = nbxmpp.simplexml.Node(
- tag=nbxmpp.NS_JINGLE_FILE_TRANSFER_5 + ' description')
- file_tag = description_node.setTag('file')
- if self.file_props.name:
- node = nbxmpp.simplexml.Node(tag='name')
- node.addData(self.file_props.name)
- file_tag.addChild(node=node)
- if self.file_props.date:
- node = nbxmpp.simplexml.Node(tag='date')
- node.addData(self.file_props.date)
- file_tag.addChild(node=node)
- if self.file_props.size:
- node = nbxmpp.simplexml.Node(tag='size')
- node.addData(self.file_props.size)
- file_tag.addChild(node=node)
- if self.file_props.type_ == 'r':
- if self.file_props.hash_:
- file_tag.addChild('hash', attrs={'algo': self.file_props.algo},
- namespace=nbxmpp.NS_HASHES_2,
- payload=self.file_props.hash_)
- else:
- # if the file is less than 10 mb, then it is small
- # lets calculate it right away
- if self.file_props.size < 10000000 and not self.file_props.hash_:
- hash_data = self._compute_hash()
- if hash_data:
- file_tag.addChild(node=hash_data)
- pjid = gajim.get_jid_without_resource(self.session.peerjid)
- file_info = {'name' : self.file_props.name,
- 'file-name' : self.file_props.file_name,
- 'hash' : self.file_props.hash_,
- 'size' : self.file_props.size,
- 'date' : self.file_props.date,
- 'peerjid' : pjid
- }
- self.session.connection.set_file_info(file_info)
- desc = file_tag.setTag('desc')
- if self.file_props.desc:
- desc.setData(self.file_props.desc)
- if self.use_security:
- security = nbxmpp.simplexml.Node(
- tag=nbxmpp.NS_JINGLE_XTLS + ' security')
- certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)\
- + '.cert'
- cert = load_cert_file(certpath)
- if cert:
- try:
- digest_algo = (cert.get_signature_algorithm()
- .decode('utf-8').split('With')[0])
- except AttributeError:
- # Old py-OpenSSL is missing get_signature_algorithm
- digest_algo = "sha256"
- security.addChild('fingerprint').addData(cert.digest(
- digest_algo).decode('utf-8'))
- for m in ('x509', ): # supported authentication methods
- method = nbxmpp.simplexml.Node(tag='method')
- method.setAttr('name', m)
- security.addChild(node=method)
- content.addChild(node=security)
- content.addChild(node=description_node)
-
- def destroy(self):
- self.callbacks = None
- del self.session.contents[(self.creator, self.name)]
diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py
deleted file mode 100644
index 3f7eae168..000000000
--- a/src/common/jingle_ft.py
+++ /dev/null
@@ -1,413 +0,0 @@
-# -*- coding:utf-8 -*-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-"""
-Handles Jingle File Transfer (XEP 0234)
-"""
-
-import hashlib
-import logging
-import os
-import threading
-from enum import IntEnum, unique
-import nbxmpp
-from common import gajim
-from common import configpaths
-from common import jingle_xtls
-from common.jingle_content import contents, JingleContent
-from common.jingle_transport import JingleTransportSocks5, TransportType
-from common import helpers
-from common.connection_handlers_events import FileRequestReceivedEvent
-from common.jingle_ftstates import (
- StateInitialized, StateCandSent, StateCandReceived, StateTransfering,
- StateCandSentAndRecv, StateTransportReplace)
-
-log = logging.getLogger('gajim.c.jingle_ft')
-
-
-@unique
-class State(IntEnum):
- NOT_STARTED = 0
- INITIALIZED = 1
- # We send the candidates and we are waiting for a reply
- CAND_SENT = 2
- # We received the candidates and we are waiting to reply
- CAND_RECEIVED = 3
- # We have sent and received the candidates
- # This also includes any candidate-error received or sent
- CAND_SENT_AND_RECEIVED = 4
- TRANSPORT_REPLACE = 5
- # We are transfering the file
- TRANSFERING = 6
-
-
-class JingleFileTransfer(JingleContent):
-
- def __init__(self, session, transport=None, file_props=None,
- use_security=False):
- JingleContent.__init__(self, session, transport)
- log.info("transport value: %s", transport)
- # events we might be interested in
- self.callbacks['session-initiate'] += [self.__on_session_initiate]
- self.callbacks['session-initiate-sent'] += [
- self.__on_session_initiate_sent]
- self.callbacks['content-add'] += [self.__on_session_initiate]
- self.callbacks['session-accept'] += [self.__on_session_accept]
- self.callbacks['session-terminate'] += [self.__on_session_terminate]
- self.callbacks['session-info'] += [self.__on_session_info]
- self.callbacks['transport-accept'] += [self.__on_transport_accept]
- self.callbacks['transport-replace'] += [self.__on_transport_replace]
- self.callbacks['session-accept-sent'] += [self.__transport_setup]
- # fallback transport method
- self.callbacks['transport-reject'] += [self.__on_transport_reject]
- self.callbacks['transport-info'] += [self.__on_transport_info]
- self.callbacks['iq-result'] += [self.__on_iq_result]
- self.use_security = use_security
- self.x509_fingerprint = None
- self.file_props = file_props
- self.weinitiate = self.session.weinitiate
- self.werequest = self.session.werequest
- if self.file_props is not None:
- if self.session.werequest:
- self.file_props.sender = self.session.peerjid
- self.file_props.receiver = self.session.ourjid
- else:
- self.file_props.sender = self.session.ourjid
- self.file_props.receiver = self.session.peerjid
- self.file_props.session_type = 'jingle'
- self.file_props.sid = session.sid
- self.file_props.transfered_size = []
- self.file_props.transport_sid = self.transport.sid
- log.info("FT request: %s", file_props)
- if transport is None:
- self.transport = JingleTransportSocks5()
- self.transport.set_connection(session.connection)
- self.transport.set_file_props(self.file_props)
- self.transport.set_our_jid(session.ourjid)
- log.info('ourjid: %s', session.ourjid)
- self.session = session
- self.media = 'file'
- self.nominated_cand = {}
- if gajim.contacts.is_gc_contact(session.connection.name,
- session.peerjid):
- roomjid = session.peerjid.split('/')[0]
- dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid,
- session.ourjid, roomjid))
- .encode('utf-8')).hexdigest()
- self.file_props.dstaddr = dstaddr
- self.state = State.NOT_STARTED
- self.states = {
- State.INITIALIZED : StateInitialized(self),
- State.CAND_SENT : StateCandSent(self),
- State.CAND_RECEIVED : StateCandReceived(self),
- State.TRANSFERING : StateTransfering(self),
- State.TRANSPORT_REPLACE : StateTransportReplace(self),
- State.CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
- }
-
- if jingle_xtls.PYOPENSSL_PRESENT:
- cert_name = os.path.join(configpaths.gajimpaths['MY_CERT'],
- jingle_xtls.SELF_SIGNED_CERTIFICATE)
- if not (os.path.exists(cert_name + '.cert')
- and os.path.exists(cert_name + '.pkey')):
- jingle_xtls.make_certs(cert_name, 'gajim')
-
- def __state_changed(self, nextstate, args=None):
- # Executes the next state action and sets the next state
- current_state = self.state
- st = self.states[nextstate]
- st.action(args)
- # state can have been changed during the action. Don't go back.
- if self.state == current_state:
- self.state = nextstate
-
- def __on_session_initiate(self, stanza, content, error, action):
- log.debug("Jingle FT request received")
- gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
- conn=self.session.connection,
- stanza=stanza,
- jingle_content=content,
- FT_content=self))
- if self.session.request:
- # accept the request
- self.session.approve_content(self.media, self.name)
- self.session.accept_session()
-
- def __on_session_initiate_sent(self, stanza, content, error, action):
- pass
-
- def __send_hash(self):
- # Send hash in a session info
- checksum = nbxmpp.Node(tag='checksum',
- payload=[nbxmpp.Node(tag='file',
- payload=[self._compute_hash()])])
- checksum.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER_5)
- self.session.__session_info(checksum)
- pjid = gajim.get_jid_without_resource(self.session.peerjid)
- file_info = {'name' : self.file_props.name,
- 'file-name' : self.file_props.file_name,
- 'hash' : self.file_props.hash_,
- 'size' : self.file_props.size,
- 'date' : self.file_props.date,
- 'peerjid' : pjid
- }
- self.session.connection.set_file_info(file_info)
-
- def _compute_hash(self):
- # Caculates the hash and returns a xep-300 hash stanza
- if self.file_props.algo is None:
- return
- try:
- file_ = open(self.file_props.file_name, 'rb')
- except IOError:
- # can't open file
- return
- h = nbxmpp.Hashes2()
- hash_ = h.calculateHash(self.file_props.algo, file_)
- file_.close()
- # DEBUG
- #hash_ = '1294809248109223'
- if not hash_:
- # Hash alogrithm not supported
- return
- self.file_props.hash_ = hash_
- h.addHash(hash_, self.file_props.algo)
- return h
-
- def on_cert_received(self):
- self.session.approve_session()
- self.session.approve_content('file', name=self.name)
-
- def __on_session_accept(self, stanza, content, error, action):
- log.info("__on_session_accept")
- con = self.session.connection
- security = content.getTag('security')
- if not security: # responder can not verify our fingerprint
- self.use_security = False
- else:
- fingerprint = security.getTag('fingerprint')
- if fingerprint:
- fingerprint = fingerprint.getData()
- self.x509_fingerprint = fingerprint
- if not jingle_xtls.check_cert(gajim.get_jid_without_resource(
- self.session.responder), fingerprint):
- id_ = jingle_xtls.send_cert_request(con,
- self.session.responder)
- jingle_xtls.key_exchange_pend(id_,
- self.continue_session_accept,
- [stanza])
- raise nbxmpp.NodeProcessed
- self.continue_session_accept(stanza)
-
- def continue_session_accept(self, stanza):
- con = self.session.connection
- if self.state == State.TRANSPORT_REPLACE:
- # If we are requesting we don't have the file
- if self.session.werequest:
- raise nbxmpp.NodeProcessed
- # We send the file
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- self.file_props.streamhosts = self.transport.remote_candidates
- # Calculate file hash in a new thread
- # if we haven't sent the hash already.
- if self.file_props.hash_ is None and self.file_props.algo and \
- not self.werequest:
- self.hash_thread = threading.Thread(target=self.__send_hash)
- self.hash_thread.start()
- for host in self.file_props.streamhosts:
- host['initiator'] = self.session.initiator
- host['target'] = self.session.responder
- host['sid'] = self.file_props.sid
- fingerprint = None
- if self.use_security:
- fingerprint = 'client'
- if self.transport.type_ == TransportType.SOCKS5:
- gajim.socks5queue.connect_to_hosts(self.session.connection.name,
- self.file_props.sid,
- self.on_connect,
- self._on_connect_error,
- fingerprint=fingerprint,
- receiving=False)
- raise nbxmpp.NodeProcessed
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
-
- def __on_session_terminate(self, stanza, content, error, action):
- log.info("__on_session_terminate")
-
- def __on_session_info(self, stanza, content, error, action):
- pass
-
- def __on_transport_accept(self, stanza, content, error, action):
- log.info("__on_transport_accept")
-
- def __on_transport_replace(self, stanza, content, error, action):
- log.info("__on_transport_replace")
-
- def __on_transport_reject(self, stanza, content, error, action):
- log.info("__on_transport_reject")
-
- def __on_transport_info(self, stanza, content, error, action):
- log.info("__on_transport_info")
- cand_error = content.getTag('transport').getTag('candidate-error')
- cand_used = content.getTag('transport').getTag('candidate-used')
- if (cand_error or cand_used) and \
- self.state >= State.CAND_SENT_AND_RECEIVED:
- raise nbxmpp.NodeProcessed
- if cand_error:
- if not gajim.socks5queue.listener.connections:
- gajim.socks5queue.listener.disconnect()
- self.nominated_cand['peer-cand'] = False
- if self.state == State.CAND_SENT:
- if not self.nominated_cand['our-cand'] and \
- not self.nominated_cand['peer-cand']:
- if not self.weinitiate:
- return
- self.__state_changed(State.TRANSPORT_REPLACE)
- else:
- response = stanza.buildReply('result')
- response.delChild(response.getQuery())
- self.session.connection.connection.send(response)
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- else:
- args = {'cand_error' : True}
- self.__state_changed(State.CAND_RECEIVED, args)
- return
- if cand_used:
- streamhost_cid = cand_used.getAttr('cid')
- streamhost_used = None
- for cand in self.transport.candidates:
- if cand['candidate_id'] == streamhost_cid:
- streamhost_used = cand
- break
- if streamhost_used is None or streamhost_used['type'] == 'proxy':
- if gajim.socks5queue.listener and \
- not gajim.socks5queue.listener.connections:
- gajim.socks5queue.listener.disconnect()
- if content.getTag('transport').getTag('activated'):
- self.state = State.TRANSFERING
- jid = gajim.get_jid_without_resource(self.session.ourjid)
- gajim.socks5queue.send_file(self.file_props,
- self.session.connection.name, 'client')
- return
- args = {'content': content,
- 'sendCand': False}
- if self.state == State.CAND_SENT:
- self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
- self.__state_changed(State.TRANSFERING)
- raise nbxmpp.NodeProcessed
- else:
- self.__state_changed(State.CAND_RECEIVED, args)
-
- def __on_iq_result(self, stanza, content, error, action):
- log.info("__on_iq_result")
-
- if self.state == State.NOT_STARTED:
- self.__state_changed(State.INITIALIZED)
- elif self.state == State.CAND_SENT_AND_RECEIVED:
- if not self.nominated_cand['our-cand'] and \
- not self.nominated_cand['peer-cand']:
- if not self.weinitiate:
- return
- self.__state_changed(State.TRANSPORT_REPLACE)
- return
- # initiate transfer
- self.__state_changed(State.TRANSFERING)
-
- def __transport_setup(self, stanza=None, content=None, error=None,
- action=None):
- # Sets up a few transport specific things for the file transfer
- if self.transport.type_ == TransportType.IBB:
- # No action required, just set the state to transfering
- self.state = State.TRANSFERING
- else:
- self._listen_host()
-
- def on_connect(self, streamhost):
- """
- send candidate-used stanza
- """
- log.info('send_candidate_used')
- if streamhost is None:
- return
- args = {'streamhost' : streamhost,
- 'sendCand' : True}
- self.nominated_cand['our-cand'] = streamhost
- self.__send_candidate(args)
-
- def _on_connect_error(self, sid):
- log.info('connect error, sid=' + sid)
- args = {'candError' : True,
- 'sendCand' : True}
- self.__send_candidate(args)
-
- def __send_candidate(self, args):
- if self.state == State.CAND_RECEIVED:
- self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
- else:
- self.__state_changed(State.CAND_SENT, args)
-
- def _store_socks5_sid(self, sid, hash_id):
- # callback from socsk5queue.start_listener
- self.file_props.hash_ = hash_id
-
- def _listen_host(self):
- receiver = self.file_props.receiver
- sender = self.file_props.sender
- sha_str = helpers.get_auth_sha(self.file_props.sid, sender,
- receiver)
- self.file_props.sha_str = sha_str
- port = gajim.config.get('file_transfers_port')
- fingerprint = None
- if self.use_security:
- fingerprint = 'server'
- listener = gajim.socks5queue.start_listener(port, sha_str,
- self._store_socks5_sid,
- self.file_props,
- fingerprint=fingerprint,
- typ='sender' if self.weinitiate else 'receiver')
- if not listener:
- # send error message, notify the user
- return
-
- def is_our_candidate_used(self):
- '''
- If this method returns true then the candidate we nominated will be
- used, if false, the candidate nominated by peer will be used
- '''
-
- if not self.nominated_cand['peer-cand']:
- return True
- if not self.nominated_cand['our-cand']:
- return False
- peer_pr = int(self.nominated_cand['peer-cand']['priority'])
- our_pr = int(self.nominated_cand['our-cand']['priority'])
- if peer_pr != our_pr:
- return our_pr > peer_pr
- return self.weinitiate
-
- def start_ibb_transfer(self):
- if self.file_props.type_ == 's':
- self.__state_changed(State.TRANSFERING)
-
-
-def get_content(desc):
- return JingleFileTransfer
-
-contents[nbxmpp.NS_JINGLE_FILE_TRANSFER_5] = get_content
diff --git a/src/common/jingle_ftstates.py b/src/common/jingle_ftstates.py
deleted file mode 100644
index 5c305f78a..000000000
--- a/src/common/jingle_ftstates.py
+++ /dev/null
@@ -1,229 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-
-import nbxmpp
-from common import gajim
-from common.jingle_transport import TransportType
-from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
-
-import logging
-log = logging.getLogger('gajim.c.jingle_ftstates')
-
-
-class JingleFileTransferStates:
- '''
- This class implements the state machine design pattern
- '''
-
- def __init__(self, jingleft):
- self.jft = jingleft
-
- def action(self, args=None):
- '''
- This method MUST be overriden by a subclass
- '''
- raise NotImplementedError('This is an abstract method!')
-
-
-class StateInitialized(JingleFileTransferStates):
- '''
- This state initializes the file transfer
- '''
-
- def action(self, args=None):
- if self.jft.weinitiate:
- # update connection's fileprops
- self.jft._listen_host()
- # Listen on configured port for file transfer
- else:
- fingerprint = None
- if self.jft.use_security:
- fingerprint = 'client'
- # Connect to the candidate host, on success call on_connect method
- gajim.socks5queue.connect_to_hosts(self.jft.session.connection.name,
- self.jft.file_props.sid,
- self.jft.on_connect,
- self.jft._on_connect_error,
- fingerprint=fingerprint)
-
-
-class StateCandSent(JingleFileTransferStates):
- '''
- This state sends our nominated candidate
- '''
-
- def _send_candidate(self, args):
- if 'candError' in args:
- self.jft.nominated_cand['our-cand'] = False
- self.jft.send_error_candidate()
- return
- # Send candidate used
- streamhost = args['streamhost']
- self.jft.nominated_cand['our-cand'] = streamhost
- content = nbxmpp.Node('content')
- content.setAttr('creator', 'initiator')
- content.setAttr('name', self.jft.name)
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', self.jft.transport.sid)
- candidateused = nbxmpp.Node('candidate-used')
- candidateused.setAttr('cid', streamhost['cid'])
- transport.addChild(node=candidateused)
- content.addChild(node=transport)
- self.jft.session.send_transport_info(content)
-
- def action(self, args=None):
- self._send_candidate(args)
-
-
-class StateCandReceived(JingleFileTransferStates):
- '''
- This state happens when we receive a candidate.
- It takes the arguments: canError if we receive a candidate-error
- '''
-
- def _recv_candidate(self, args):
- if 'candError' in args:
- return
- content = args['content']
- streamhost_cid = content.getTag('transport').getTag('candidate-used').\
- getAttr('cid')
- streamhost_used = None
- for cand in self.jft.transport.candidates:
- if cand['candidate_id'] == streamhost_cid:
- streamhost_used = cand
- break
- if streamhost_used is None:
- log.info("unknow streamhost")
- return
- # We save the candidate nominated by peer
- self.jft.nominated_cand['peer-cand'] = streamhost_used
-
- def action(self, args=None):
- self._recv_candidate(args)
-
-
-class StateCandSentAndRecv(StateCandSent, StateCandReceived):
- '''
- This state happens when we have received and sent the candidates.
- It takes the boolean argument: sendCand in order to decide whether
- we should execute the action of when we receive or send a candidate.
- '''
-
- def action(self, args=None):
- if args['sendCand']:
- self._send_candidate(args)
- else:
- self._recv_candidate(args)
-
-
-class StateTransportReplace(JingleFileTransferStates):
- '''
- This state initiates transport replace
- '''
-
- def action(self, args=None):
- self.jft.session.transport_replace()
-
-
-class StateTransfering(JingleFileTransferStates):
- '''
- This state will start the transfer depeding on the type of transport
- we have.
- '''
-
- def _start_ibb_transfer(self, con):
- self.jft.file_props.transport_sid = self.jft.transport.sid
- fp = open(self.jft.file_props.file_name, 'rb')
- con.OpenStream(self.jft.file_props.sid, self.jft.session.peerjid, fp,
- blocksize=4096)
-
- def _start_sock5_transfer(self):
- # It tells wether we start the transfer as client or server
- mode = None
- if self.jft.is_our_candidate_used():
- mode = 'client'
- streamhost_used = self.jft.nominated_cand['our-cand']
- gajim.socks5queue.remove_server(self.jft.file_props.sid)
- else:
- mode = 'server'
- streamhost_used = self.jft.nominated_cand['peer-cand']
- gajim.socks5queue.remove_client(self.jft.file_props.sid)
-# our_cand = self.jft.nominated_cand['our-cand']
-# gajim.socks5queue.remove_receiver(our_cand['idx'])
- if streamhost_used['type'] == 'proxy':
- self.jft.file_props.is_a_proxy = True
- if self.jft.file_props.type_ == 's' and self.jft.weinitiate:
- self.jft.file_props.proxy_sender = streamhost_used['initiator']
- self.jft.file_props.proxy_receiver = streamhost_used['target']
- else:
- self.jft.file_props.proxy_sender = streamhost_used['target']
- self.jft.file_props.proxy_receiver = streamhost_used[
- 'initiator']
- if self.jft.file_props.type_ == 's':
- s = gajim.socks5queue.senders
- for sender in s:
- if s[sender].host == streamhost_used['host'] and \
- s[sender].connected:
- return
- elif self.jft.file_props.type_ == 'r':
- r = gajim.socks5queue.readers
- for reader in r:
- if r[reader].host == streamhost_used['host'] and \
- r[reader].connected:
- return
- else:
- raise TypeError
- self.jft.file_props.streamhost_used = True
- streamhost_used['sid'] = self.jft.file_props.sid
- self.jft.file_props.streamhosts = []
- self.jft.file_props.streamhosts.append(streamhost_used)
- self.jft.file_props.proxyhosts = []
- self.jft.file_props.proxyhosts.append(streamhost_used)
- if self.jft.file_props.type_ == 's':
- gajim.socks5queue.idx += 1
- idx = gajim.socks5queue.idx
- sockobj = Socks5SenderClient(gajim.idlequeue, idx,
- gajim.socks5queue, _sock=None,
- host=str(streamhost_used['host']),
- port=int(streamhost_used['port']),
- fingerprint=None, connected=False,
- file_props=self.jft.file_props)
- else:
- sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used,
- sid=self.jft.file_props.sid,
- file_props=self.jft.file_props,
- fingerprint=None)
- sockobj.proxy = True
- sockobj.streamhost = streamhost_used
- gajim.socks5queue.add_sockobj(self.jft.session.connection.name,
- sockobj)
- streamhost_used['idx'] = sockobj.queue_idx
- # If we offered the nominated candidate used, we activate
- # the proxy
- if not self.jft.is_our_candidate_used():
- gajim.socks5queue.on_success[self.jft.file_props.sid] = \
- self.jft.transport._on_proxy_auth_ok
- # TODO: add on failure
- else:
- jid = gajim.get_jid_without_resource(self.jft.session.ourjid)
- gajim.socks5queue.send_file(self.jft.file_props,
- self.jft.session.connection.name, mode)
-
- def action(self, args=None):
- if self.jft.transport.type_ == TransportType.IBB:
- self._start_ibb_transfer(self.jft.session.connection)
- elif self.jft.transport.type_ == TransportType.SOCKS5:
- self._start_sock5_transfer()
diff --git a/src/common/jingle_rtp.py b/src/common/jingle_rtp.py
deleted file mode 100644
index 12fc9a7e8..000000000
--- a/src/common/jingle_rtp.py
+++ /dev/null
@@ -1,477 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle RTP sessions (XEP 0167)
-"""
-
-import logging
-import socket
-import nbxmpp
-import gi
-from gi.repository import Farstream
-
-gi.require_version('Gst', '1.0')
-from gi.repository import Gst
-from gi.repository import GLib
-
-from common import gajim
-
-from common.jingle_transport import JingleTransportICEUDP
-from common.jingle_content import contents, JingleContent, JingleContentSetupException
-from common.connection_handlers_events import InformationEvent
-from common.jingle_session import FailedApplication
-
-from collections import deque
-
-log = logging.getLogger('gajim.c.jingle_rtp')
-
-
-class JingleRTPContent(JingleContent):
- def __init__(self, session, media, transport=None):
- if transport is None:
- transport = JingleTransportICEUDP(None)
- JingleContent.__init__(self, session, transport)
- self.media = media
- self._dtmf_running = False
- self.farstream_media = {
- 'audio': Farstream.MediaType.AUDIO,
- 'video': Farstream.MediaType.VIDEO}[media]
-
- self.pipeline = None
- self.src_bin = None
- self.stream_failed_once = False
-
- self.candidates_ready = False # True when local candidates are prepared
-
- # TODO
- self.conference = None
- self.funnel = None
- self.p2psession = None
- self.p2pstream = None
-
- self.callbacks['session-initiate'] += [self.__on_remote_codecs]
- self.callbacks['content-add'] += [self.__on_remote_codecs]
- self.callbacks['description-info'] += [self.__on_remote_codecs]
- self.callbacks['content-accept'] += [self.__on_remote_codecs]
- self.callbacks['session-accept'] += [self.__on_remote_codecs]
- self.callbacks['session-terminate'] += [self.__stop]
- self.callbacks['session-terminate-sent'] += [self.__stop]
-
- def setup_stream(self, on_src_pad_added):
- # pipeline and bus
- self.pipeline = Gst.Pipeline()
- bus = self.pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message', self._on_gst_message)
-
- # conference
- self.conference = Gst.ElementFactory.make('fsrtpconference', None)
- self.pipeline.add(self.conference)
- self.funnel = None
-
- self.p2psession = self.conference.new_session(self.farstream_media)
-
- participant = self.conference.new_participant()
- # FIXME: Consider a workaround, here...
- # pidgin and telepathy-gabble don't follow the XEP, and it won't work
- # due to bad controlling-mode
-
- params = {'controlling-mode': self.session.weinitiate, 'debug': False}
- if gajim.config.get('use_stun_server'):
- stun_server = gajim.config.get('stun_server')
- if not stun_server and self.session.connection._stun_servers:
- stun_server = self.session.connection._stun_servers[0]['host']
- if stun_server:
- try:
- ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
- socket.SOCK_STREAM)[0][4][0]
- except socket.gaierror as e:
- log.warning('Lookup of stun ip failed: %s', str(e))
- else:
- params['stun-ip'] = ip
-
- self.p2pstream = self.p2psession.new_stream(participant,
- Farstream.StreamDirection.BOTH)
- self.p2pstream.connect('src-pad-added', on_src_pad_added)
- self.p2pstream.set_transmitter_ht('nice', params)
-
- def is_ready(self):
- return JingleContent.is_ready(self) and self.candidates_ready
-
- def make_bin_from_config(self, config_key, pipeline, text):
- pipeline = pipeline % gajim.config.get(config_key)
- try:
- gst_bin = Gst.parse_bin_from_description(pipeline, True)
- return gst_bin
- except GLib.GError as e:
- gajim.nec.push_incoming_event(
- InformationEvent(
- None, conn=self.session.connection, level='error',
- pri_txt=_('%s configuration error') % text.capitalize(),
- sec_txt=_('Couldn’t setup %s. Check your configuration.\n\n'
- 'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e))))
- raise JingleContentSetupException
-
- def add_remote_candidates(self, candidates):
- JingleContent.add_remote_candidates(self, candidates)
- # FIXME: connectivity should not be etablished yet
- # Instead, it should be etablished after session-accept!
- if self.sent:
- self.p2pstream.add_remote_candidates(candidates)
-
- def batch_dtmf(self, events):
- """
- Send several DTMF tones
- """
- if self._dtmf_running:
- raise Exception("There is a DTMF batch already running")
- events = deque(events)
- self._dtmf_running = True
- self._start_dtmf(events.popleft())
- GLib.timeout_add(500, self._next_dtmf, events)
-
- def _next_dtmf(self, events):
- self._stop_dtmf()
- if events:
- self._start_dtmf(events.popleft())
- GLib.timeout_add(500, self._next_dtmf, events)
- else:
- self._dtmf_running = False
-
- def _start_dtmf(self, event):
- if event in ('*', '#'):
- event = {'*': Farstream.DTMFEvent.STAR,
- '#': Farstream.DTMFEvent.POUND}[event]
- else:
- event = int(event)
- self.p2psession.start_telephony_event(event, 2)
-
- def _stop_dtmf(self):
- self.p2psession.stop_telephony_event()
-
- def _fill_content(self, content):
- content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
- attrs={'media': self.media},
- payload=list(self.iter_codecs()))
-
- def _setup_funnel(self):
- self.funnel = Gst.ElementFactory.make('funnel', None)
- self.pipeline.add(self.funnel)
- self.funnel.link(self.sink)
- self.sink.set_state(Gst.State.PLAYING)
- self.funnel.set_state(Gst.State.PLAYING)
-
- def _on_src_pad_added(self, stream, pad, codec):
- if not self.funnel:
- self._setup_funnel()
- pad.link(self.funnel.get_request_pad('sink_%u'))
-
- def _on_gst_message(self, bus, message):
- if message.type == Gst.MessageType.ELEMENT:
- name = message.get_structure().get_name()
- log.debug('gst element message: %s: %s', name, message)
- if name == 'farstream-new-active-candidate-pair':
- pass
- elif name == 'farstream-recv-codecs-changed':
- pass
- elif name == 'farstream-codecs-changed':
- if self.sent and self.p2psession.props.codecs_without_config:
- self.send_description_info()
- if self.transport.remote_candidates:
- # those lines MUST be done after we get info on our
- # codecs
- self.p2pstream.add_remote_candidates(
- self.transport.remote_candidates)
- self.transport.remote_candidates = []
- self.p2pstream.set_property('direction',
- Farstream.StreamDirection.BOTH)
-
- elif name == 'farstream-local-candidates-prepared':
- self.candidates_ready = True
- if self.is_ready():
- self.session.on_session_state_changed(self)
- elif name == 'farstream-new-local-candidate':
- candidate = self.p2pstream.parse_new_local_candidate(message)[1]
- self.transport.candidates.append(candidate)
- if self.sent:
- # FIXME: Is this case even possible?
- self.send_candidate(candidate)
- elif name == 'farstream-component-state-changed':
- state = message.get_structure().get_value('state')
- if state == Farstream.StreamState.FAILED:
- reason = nbxmpp.Node('reason')
- reason.setTag('failed-transport')
- self.session.remove_content(self.creator, self.name, reason)
- elif name == 'farstream-error':
- log.error('Farstream error #%d!\nMessage: %s',
- message.get_structure().get_value('error-no'),
- message.get_structure().get_value('error-msg'))
- elif message.type == Gst.MessageType.ERROR:
- # TODO: Fix it to fallback to videotestsrc anytime an error occur,
- # or raise an error, Jingle way
- # or maybe one-sided stream?
- if not self.stream_failed_once:
- gajim.nec.push_incoming_event(
- InformationEvent(
- None, conn=self.session.connection, level='error',
- pri_txt=_('GStreamer error'),
- sec_txt=_('Error: %s\nDebug: %s' %
- (message.get_structure().get_value('gerror'),
- message.get_structure().get_value('debug')))))
-
- sink_pad = self.p2psession.get_property('sink-pad')
-
- # Remove old source
- self.src_bin.get_static_pad('src').unlink(sink_pad)
- self.src_bin.set_state(Gst.State.NULL)
- self.pipeline.remove(self.src_bin)
-
- if not self.stream_failed_once:
- # Add fallback source
- self.src_bin = self.get_fallback_src()
- self.pipeline.add(self.src_bin)
- self.src_bin.link(sink_pad)
- self.stream_failed_once = True
- else:
- reason = nbxmpp.Node('reason')
- reason.setTag('failed-application')
- self.session.remove_content(self.creator, self.name, reason)
-
- # Start playing again
- self.pipeline.set_state(Gst.State.PLAYING)
-
- @staticmethod
- def get_fallback_src():
- return Gst.ElementFactory.make('fakesrc', None)
-
- def on_negotiated(self):
- if self.accepted:
- if self.p2psession.get_property('codecs'):
- # those lines MUST be done after we get info on our codecs
- if self.transport.remote_candidates:
- self.p2pstream.add_remote_candidates(
- self.transport.remote_candidates)
- self.transport.remote_candidates = []
- # TODO: Farstream.StreamDirection.BOTH only if senders='both'
-# self.p2pstream.set_property('direction',
-# Farstream.StreamDirection.BOTH)
- JingleContent.on_negotiated(self)
-
- def __on_remote_codecs(self, stanza, content, error, action):
- """
- Get peer codecs from what we get from peer
- """
-
- codecs = []
- for codec in content.getTag('description').iterTags('payload-type'):
- if not codec['id'] or not codec['name'] or not codec['clockrate']:
- # ignore invalid payload-types
- continue
- c = Farstream.Codec.new(int(codec['id']), codec['name'],
- self.farstream_media, int(codec['clockrate']))
- if 'channels' in codec:
- c.channels = int(codec['channels'])
- else:
- c.channels = 1
- for p in codec.iterTags('parameter'):
- c.add_optional_parameter(p['name'], str(p['value']))
- codecs.append(c)
-
- if codecs:
- try:
- self.p2pstream.set_remote_codecs(codecs)
- except GLib.Error:
- raise FailedApplication
-
- def iter_codecs(self):
- codecs = self.p2psession.props.codecs_without_config
- for codec in codecs:
- attrs = {
- 'name': codec.encoding_name,
- 'id': codec.id,
- 'channels': codec.channels
- }
- if codec.clock_rate:
- attrs['clockrate'] = codec.clock_rate
- if codec.optional_params:
- payload = [nbxmpp.Node('parameter',
- {'name': p.name, 'value': p.value})
- for p in codec.optional_params]
- else:
- payload = []
- yield nbxmpp.Node('payload-type', attrs, payload)
-
- def __stop(self, *things):
- self.pipeline.set_state(Gst.State.NULL)
-
- def __del__(self):
- self.__stop()
-
- def destroy(self):
- JingleContent.destroy(self)
- self.p2pstream.disconnect_by_func(self._on_src_pad_added)
- self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
-
-
-class JingleAudio(JingleRTPContent):
- """
- Jingle VoIP sessions consist of audio content transported over an ICE UDP
- protocol
- """
-
- def __init__(self, session, transport=None):
- JingleRTPContent.__init__(self, session, 'audio', transport)
- self.setup_stream()
-
- def set_mic_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.mic_volume.set_property('volume', vol)
-
- def set_out_volume(self, vol):
- """
- vol must be between 0 ans 1
- """
- self.out_volume.set_property('volume', vol)
-
- def setup_stream(self):
- JingleRTPContent.setup_stream(self, self._on_src_pad_added)
-
- # Configure SPEEX
- # Workaround for psi (not needed since rev
- # 147aedcea39b43402fe64c533d1866a25449888a):
- # place 16kHz before 8kHz, as buggy psi versions will take in
- # account only the first codec
-
- codecs = [
- Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
- Farstream.MediaType.AUDIO, 16000),
- Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
- Farstream.MediaType.AUDIO, 8000)]
- self.p2psession.set_codec_preferences(codecs)
-
- # the local parts
- # TODO: Add queues?
- self.src_bin = self.make_bin_from_config('audio_input_device',
- '%s ! audioconvert',
- _("audio input"))
-
- self.sink = self.make_bin_from_config('audio_output_device',
- 'audioconvert ! volume name=gajim_out_vol ! %s',
- _("audio output"))
-
- self.mic_volume = self.src_bin.get_by_name('gajim_vol')
- self.out_volume = self.sink.get_by_name('gajim_out_vol')
-
- # link gst elements
- self.pipeline.add(self.sink)
- self.pipeline.add(self.src_bin)
-
- self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
-
- # The following is needed for farstream to process ICE requests:
- self.pipeline.set_state(Gst.State.PLAYING)
-
-
-class JingleVideo(JingleRTPContent):
- def __init__(self, session, transport=None, in_xid=0, out_xid=0):
- JingleRTPContent.__init__(self, session, 'video', transport)
- self.in_xid = in_xid
- self.out_xid = out_xid
- self.out_xid_set = False
- self.setup_stream()
-
- def setup_stream(self):
- # TODO: Everything is not working properly:
- # sometimes, one window won't show up,
- # sometimes it'll freeze...
- JingleRTPContent.setup_stream(self, self._on_src_pad_added)
- bus = self.pipeline.get_bus()
- bus.enable_sync_message_emission()
- bus.connect('sync-message::element', self._on_sync_message)
-
- # the local parts
- if gajim.config.get('video_framerate'):
- framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
- gajim.config.get('video_framerate')
- else:
- framerate = ''
- try:
- w, h = gajim.config.get('video_size').split('x')
- except:
- w = h = None
- if w and h:
- video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
- else:
- video_size = ''
- if gajim.config.get('video_see_self'):
- tee = '! tee name=t ! queue ! videoscale ! ' + \
- 'video/x-raw,width=160,height=120 ! videoconvert ! ' + \
- '%s t. ! queue ' % gajim.config.get(
- 'video_output_device')
- else:
- tee = ''
-
- self.src_bin = self.make_bin_from_config('video_input_device',
- '%%s %s! %svideoscale ! %svideoconvert' %
- (tee, framerate, video_size),
- _("video input"))
-
- self.pipeline.add(self.src_bin)
- self.pipeline.set_state(Gst.State.PLAYING)
-
- self.sink = self.make_bin_from_config('video_output_device',
- 'videoscale ! videoconvert ! %s',
- _("video output"))
-
- self.pipeline.add(self.sink)
-
- self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
- 'sink-pad'))
-
- # The following is needed for farstream to process ICE requests:
- self.pipeline.set_state(Gst.State.PLAYING)
-
- def _on_sync_message(self, bus, message):
- if message.get_structure() is None:
- return False
- if message.get_structure().get_name() == 'prepare-window-handle':
- message.src.set_property('force-aspect-ratio', True)
- imagesink = message.src
- if gajim.config.get('video_see_self') and not self.out_xid_set:
- imagesink.set_window_handle(self.out_xid)
- self.out_xid_set = True
- else:
- imagesink.set_window_handle(self.in_xid)
-
- def get_fallback_src(self):
- # TODO: Use avatar?
- pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert'
- return Gst.parse_bin_from_description(pipeline, True)
-
- def destroy(self):
- JingleRTPContent.destroy(self)
- self.pipeline.get_bus().disconnect_by_func(self._on_sync_message)
-
-def get_content(desc):
- if desc['media'] == 'audio':
- return JingleAudio
- elif desc['media'] == 'video':
- return JingleVideo
-
-contents[nbxmpp.NS_JINGLE_RTP] = get_content
diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py
deleted file mode 100644
index 371baf56c..000000000
--- a/src/common/jingle_session.py
+++ /dev/null
@@ -1,833 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle sessions (XEP 0166)
-"""
-
-#TODO:
-# * 'senders' attribute of 'content' element
-# * security preconditions
-# * actions:
-# - content-modify
-# - session-info
-# - security-info
-# - transport-accept, transport-reject
-# - Tie-breaking
-# * timeout
-
-import logging
-from enum import Enum, unique
-import nbxmpp
-from common import gajim
-from common.jingle_transport import get_jingle_transport, JingleTransportIBB
-from common.jingle_content import get_jingle_content, JingleContentSetupException, JingleContent
-from common.jingle_ft import State
-from common.connection_handlers_events import (
- FilesProp, JingleRequestReceivedEvent, JingleDisconnectedReceivedEvent,
- JingleTransferCancelledEvent, JingleConnectedReceivedEvent,
- JingleErrorReceivedEvent)
-
-log = logging.getLogger("gajim.c.jingle_session")
-
-# FIXME: Move it to JingleSession.States?
-@unique
-class JingleStates(Enum):
- """
- States in which jingle session may exist
- """
- ENDED = 0
- PENDING = 1
- ACTIVE = 2
-
-class OutOfOrder(Exception):
- """
- Exception that should be raised when an action is received when in the wrong
- state
- """
-
-class TieBreak(Exception):
- """
- Exception that should be raised in case of a tie, when we overrule the other
- action
- """
-
-class FailedApplication(Exception):
- """
- Exception that should be raised in case responder supports none of the
- payload-types offered by the initiator
- """
-
-class JingleSession:
- """
- This represents one jingle session, that is, one or more content types
- negotiated between an initiator and a responder.
- """
-
- def __init__(self, con, weinitiate, jid, iq_id=None, sid=None,
- werequest=False):
- """
- con -- connection object,
- weinitiate -- boolean, are we the initiator?
- jid - jid of the other entity
- """
- self.contents = {} # negotiated contents
- self.connection = con # connection to use
- # our full jid
- self.ourjid = gajim.get_jid_from_account(self.connection.name)
- if con.server_resource:
- self.ourjid = self.ourjid + '/' + con.server_resource
- self.peerjid = jid # jid we connect to
- # jid we use as the initiator
- self.initiator = self.ourjid if weinitiate else self.peerjid
- # jid we use as the responder
- self.responder = self.peerjid if weinitiate else self.ourjid
- # are we an initiator?
- self.weinitiate = weinitiate
- # Are we requesting or offering a file?
- self.werequest = werequest
- self.request = False
- # what state is session in? (one from JingleStates)
- self.state = JingleStates.ENDED
- if not sid:
- sid = con.connection.getAnID()
- self.sid = sid # sessionid
- # iq stanza id, used to determine which sessions to summon callback
- # later on when iq-result stanza arrives
- if iq_id is not None:
- self.iq_ids = [iq_id]
- else:
- self.iq_ids = []
- self.accepted = True # is this session accepted by user
- # Tells whether this session is a file transfer or not
- self.session_type_ft = False
- # callbacks to call on proper contents
- # use .prepend() to add new callbacks, especially when you're going
- # to send error instead of ack
- self.callbacks = {
- 'content-accept': [self.__ack, self.__on_content_accept,
- self.__broadcast],
- 'content-add': [self.__ack,
- self.__on_content_add, self.__broadcast
- ], #TODO
- 'content-modify': [self.__ack], #TODO
- 'content-reject': [self.__ack, self.__on_content_remove],
- 'content-remove': [self.__ack, self.__on_content_remove],
- 'description-info': [self.__ack, self.__broadcast], #TODO
- 'security-info': [self.__ack], #TODO
- 'session-accept': [self.__ack, self.__on_session_accept,
- self.__on_content_accept,
- self.__broadcast],
- 'session-info': [self.__ack, self.__broadcast,
- self.__on_session_info],
- 'session-initiate': [self.__ack, self.__on_session_initiate,
- self.__broadcast],
- 'session-terminate': [self.__ack, self.__on_session_terminate,
- self.__broadcast_all],
- 'transport-info': [self.__ack, self.__broadcast],
- 'transport-replace': [self.__ack, self.__broadcast,
- self.__on_transport_replace], #TODO
- 'transport-accept': [self.__ack], #TODO
- 'transport-reject': [self.__ack], #TODO
- 'iq-result': [self.__broadcast],
- 'iq-error': [self.__on_error],
- }
-
- def collect_iq_id(self, iq_id):
- if iq_id is not None:
- self.iq_ids.append(iq_id)
-
- def approve_session(self):
- """
- Called when user accepts session in UI (when we aren't the initiator)
- """
- self.accept_session()
-
- def decline_session(self):
- """
- Called when user declines session in UI (when we aren't the initiator)
- """
- reason = nbxmpp.Node('reason')
- reason.addChild('decline')
- self._session_terminate(reason)
-
- def cancel_session(self):
- """
- Called when user declines session in UI (when we aren't the initiator)
- """
- reason = nbxmpp.Node('reason')
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- def approve_content(self, media, name=None):
- content = self.get_content(media, name)
- if content:
- content.accepted = True
- self.on_session_state_changed(content)
-
- def reject_content(self, media, name=None):
- content = self.get_content(media, name)
- if content:
- if self.state == JingleStates.ACTIVE:
- self.__content_reject(content)
- content.destroy()
- self.on_session_state_changed()
-
- def end_session(self):
- """
- Called when user stops or cancel session in UI
- """
- reason = nbxmpp.Node('reason')
- if self.state == JingleStates.ACTIVE:
- reason.addChild('success')
- else:
- reason.addChild('cancel')
- self._session_terminate(reason)
-
- def get_content(self, media=None, name=None):
- if media is None:
- return
- for content in self.contents.values():
- if content.media == media:
- if name is None or content.name == name:
- return content
-
- def add_content(self, name, content, creator='we'):
- """
- Add new content to session. If the session is active, this will send
- proper stanza to update session
-
- Creator must be one of ('we', 'peer', 'initiator', 'responder')
- """
- assert creator in ('we', 'peer', 'initiator', 'responder')
- if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
- not self.weinitiate):
- creator = 'initiator'
- elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
- not self.weinitiate):
- creator = 'responder'
- content.creator = creator
- content.name = name
- self.contents[(creator, name)] = content
- if (creator == 'initiator') == self.weinitiate:
- # The content is from us, accept it
- content.accepted = True
-
- def remove_content(self, creator, name, reason=None):
- """
- Remove the content `name` created by `creator`
- by sending content-remove, or by sending session-terminate if
- there is no content left.
- """
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- self.__content_remove(content, reason)
- self.contents[(creator, name)].destroy()
- if not self.contents:
- self.end_session()
-
- def modify_content(self, creator, name, transport=None):
- '''
- Currently used for transport replacement
- '''
- content = self.contents[(creator, name)]
- transport.set_sid(content.transport.sid)
- transport.set_file_props(content.transport.file_props)
- content.transport = transport
- # The content will have to be resend now that it is modified
- content.sent = False
- content.accepted = True
-
- def on_session_state_changed(self, content=None):
- if self.state == JingleStates.ENDED:
- # Session not yet started, only one action possible: session-initiate
- if self.is_ready() and self.weinitiate:
- self.__session_initiate()
- elif self.state == JingleStates.PENDING:
- # We can either send a session-accept or a content-add
- if self.is_ready() and not self.weinitiate:
- self.__session_accept()
- elif content and (content.creator == 'initiator') == self.weinitiate:
- self.__content_add(content)
- elif content and self.weinitiate:
- self.__content_accept(content)
- elif self.state == JingleStates.ACTIVE:
- # We can either send a content-add or a content-accept. However, if
- # we are sending a file we can only use session_initiate.
- if not content:
- return
- we_created_content = (content.creator == 'initiator') \
- == self.weinitiate
- if we_created_content and content.media == 'file':
- self.__session_initiate()
- if we_created_content:
- # We initiated this content. It's a pending content-add.
- self.__content_add(content)
- else:
- # The other side created this content, we accept it.
- self.__content_accept(content)
-
- def is_ready(self):
- """
- Return True when all codecs and candidates are ready (for all contents)
- """
- return (any((content.is_ready() for content in self.contents.values()))
- and self.accepted)
-
- def accept_session(self):
- """
- Mark the session as accepted
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def start_session(self):
- """
- Mark the session as ready to be started
- """
- self.accepted = True
- self.on_session_state_changed()
-
- def send_session_info(self):
- pass
-
- def send_content_accept(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-accept')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def send_transport_info(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('transport-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
-
- def send_description_info(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('description-info')
- jingle.addChild(node=content)
- self.connection.connection.send(stanza)
-
- def on_stanza(self, stanza):
- """
- A callback for ConnectionJingle. It gets stanza, then tries to send it to
- all internally registered callbacks. First one to raise
- nbxmpp.NodeProcessed breaks function
- """
- jingle = stanza.getTag('jingle')
- error = stanza.getTag('error')
- if error:
- # it's an iq-error stanza
- action = 'iq-error'
- elif jingle:
- # it's a jingle action
- action = jingle.getAttr('action')
- if action not in self.callbacks:
- self.__send_error(stanza, 'bad-request')
- return
- # FIXME: If we aren't initiated and it's not a session-initiate...
- if action not in ['session-initiate', 'session-terminate'] \
- and self.state == JingleStates.ENDED:
- self.__send_error(stanza, 'item-not-found', 'unknown-session')
- return
- else:
- # it's an iq-result (ack) stanza
- action = 'iq-result'
- callables = self.callbacks[action]
- try:
- for call in callables:
- call(stanza=stanza, jingle=jingle, error=error, action=action)
- except nbxmpp.NodeProcessed:
- pass
- except TieBreak:
- self.__send_error(stanza, 'conflict', 'tiebreak')
- except OutOfOrder:
- # FIXME
- self.__send_error(stanza, 'unexpected-request', 'out-of-order')
- except FailedApplication:
- reason = nbxmpp.Node('reason')
- reason.addChild('failed-application')
- self._session_terminate(reason)
-
- def __ack(self, stanza, jingle, error, action):
- """
- Default callback for action stanzas -- simple ack and stop processing
- """
- response = stanza.buildReply('result')
- response.delChild(response.getQuery())
- self.connection.connection.send(response)
-
- def __on_error(self, stanza, jingle, error, action):
- # FIXME
- text = error.getTagData('text')
- error_name = None
- for child in error.getChildren():
- if child.getNamespace() == nbxmpp.NS_JINGLE_ERRORS:
- error_name = child.getName()
- break
- elif child.getNamespace() == nbxmpp.NS_STANZAS:
- error_name = child.getName()
- self.__dispatch_error(error_name, text, error.getAttr('type'))
-
- def transport_replace(self):
- transport = JingleTransportIBB()
- # For debug only, delete this and replace for a function
- # that will identify contents by its sid
- for creator, name in self.contents:
- self.modify_content(creator, name, transport)
- cont = self.contents[(creator, name)]
- cont.transport = transport
- stanza, jingle = self.__make_jingle('transport-replace')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'transport-replace')
- self.connection.connection.send(stanza)
- self.state = JingleStates.PENDING
-
- def __on_transport_replace(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- transport_ns = content.getTag('transport').getNamespace()
- if transport_ns == nbxmpp.NS_JINGLE_ICE_UDP:
- # FIXME: We don't manage anything else than ICE-UDP now...
- # What was the previous transport?!?
- # Anyway, content's transport is not modifiable yet
- pass
- elif transport_ns == nbxmpp.NS_JINGLE_IBB:
- transport = JingleTransportIBB()
- self.modify_content(creator, name, transport)
- self.state = JingleStates.PENDING
- self.contents[(creator, name)].state = State.TRANSPORT_REPLACE
- self.__ack(stanza, jingle, error, action)
- self.__session_accept()
- self.contents[(creator, name)].start_IBB_transfer()
- else:
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise nbxmpp.NodeProcessed
- else:
- # FIXME: This ressource is unknown to us, what should we do?
- # For now, reject the transport
- stanza, jingle = self.__make_jingle('transport-reject')
- content = jingle.setTag('content', attrs={'creator': creator,
- 'name': name})
- content.setTag('transport', namespace=transport_ns)
- self.connection.connection.send(stanza)
- raise nbxmpp.NodeProcessed
-
- def __on_session_info(self, stanza, jingle, error, action):
- # TODO: active, (un)hold, (un)mute
- payload = jingle.getPayload()
- if payload[0].getName() == 'ringing':
- # ignore ringing
- raise nbxmpp.NodeProcessed
- if self.state != JingleStates.ACTIVE:
- raise OutOfOrder
- for child in payload:
- if child.getName() == 'checksum':
- hash_ = child.getTag('file').getTag(name='hash',
- namespace=nbxmpp.NS_HASHES_2)
- algo = hash_.getAttr('algo')
- if algo in nbxmpp.Hashes2.supported:
- file_props = FilesProp.getFileProp(self.connection.name,
- self.sid)
- file_props.algo = algo
- file_props.hash_ = hash_.getData()
- raise nbxmpp.NodeProcessed
- self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info',
- type_='modify')
- raise nbxmpp.NodeProcessed
-
- def __on_content_remove(self, stanza, jingle, error, action):
- for content in jingle.iterTags('content'):
- creator = content['creator']
- name = content['name']
- if (creator, name) in self.contents:
- content = self.contents[(creator, name)]
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(
- None, conn=self.connection, jingle_session=self,
- media=content.media, reason='removed'))
- content.destroy()
- if not self.contents:
- reason = nbxmpp.Node('reason')
- reason.setTag('success')
- self._session_terminate(reason)
-
- def __on_session_accept(self, stanza, jingle, error, action):
- # FIXME
- if self.state != JingleStates.PENDING:
- raise OutOfOrder
- self.state = JingleStates.ACTIVE
-
- @staticmethod
- def __on_content_accept(stanza, jingle, error, action):
- """
- Called when we get content-accept stanza or equivalent one (like
- session-accept)
- """
- # check which contents are accepted
- for content in jingle.iterTags('content'):
- creator = content['creator']
- # TODO
- name = content['name']
-
- def __on_content_add(self, stanza, jingle, error, action):
- if self.state == JingleStates.ENDED:
- raise OutOfOrder
- parse_result = self.__parse_contents(jingle)
- contents = parse_result[0]
- rejected_contents = parse_result[1]
- for name, creator in rejected_contents:
- # TODO
- content = JingleContent()
- self.add_content(name, content, creator)
- self.__content_reject(content)
- self.contents[(content.creator, content.name)].destroy()
- gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- contents=contents))
-
- def __on_session_initiate(self, stanza, jingle, error, action):
- """
- We got a jingle session request from other entity, therefore we are the
- receiver... Unpack the data, inform the user
- """
- if self.state != JingleStates.ENDED:
- raise OutOfOrder
- self.initiator = jingle['initiator']
- self.responder = self.ourjid
- self.peerjid = self.initiator
- self.accepted = False # user did not accept this session yet
- # TODO: If the initiator is unknown to the receiver (e.g., via presence
- # subscription) and the receiver has a policy of not communicating via
- # Jingle with unknown entities, it SHOULD return a <service-unavailable/>
- # error.
- # Lets check what kind of jingle session does the peer want
- contents, contents_rejected, reason_txt = self.__parse_contents(jingle)
- # If we are not receivin a file
- # Check if there's already a session with this user:
- if contents[0][0] != 'file':
- for session in self.connection.iter_jingle_sessions(self.peerjid):
- if session is not self:
- reason = nbxmpp.Node('reason')
- alternative_session = reason.setTag('alternative-session')
- alternative_session.setTagData('sid', session.sid)
- self.__ack(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise nbxmpp.NodeProcessed
- else:
- # Stop if we don't have the requested file or the peer is not
- # allowed to request the file
- request = \
- jingle.getTag('content').getTag('description').getTag('request')
- if request:
- self.request = True
- hash_tag = request.getTag('file').getTag('hash')
- hash_data = hash_tag.getData() if hash_tag else None
- n = request.getTag('file').getTag('name')
- n = n.getData() if n else None
- pjid = gajim.get_jid_without_resource(self.peerjid)
- file_info = self.connection.get_file_info(pjid, hash_data, n,
- self.connection.name)
- if not file_info:
- log.warning('The peer %s is requesting a ' \
- 'file that we dont have or ' \
- 'it is not allowed to request', pjid)
- self.decline_session()
- raise nbxmpp.NodeProcessed
- # If there's no content we understand...
- if not contents:
- # TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
- reason = nbxmpp.Node('reason')
- reason.setTag(reason_txt)
- self.__ack(stanza, jingle, error, action)
- self._session_terminate(reason)
- raise nbxmpp.NodeProcessed
- self.state = JingleStates.PENDING
- # Send event about starting a session
- gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- contents=contents))
-
- def __broadcast(self, stanza, jingle, error, action):
- """
- Broadcast the stanza contents to proper content handlers
- """
- #if jingle is None: # it is a iq-result stanza
- # for cn in self.contents.values():
- # cn.on_stanza(stanza, None, error, action)
- # return
- # special case: iq-result stanza does not come with a jingle element
- if action == 'iq-result':
- for cn in self.contents.values():
- cn.on_stanza(stanza, None, error, action)
- return
- for content in jingle.iterTags('content'):
- name = content['name']
- creator = content['creator']
- if (creator, name) not in self.contents:
- text = 'Content %s (created by %s) does not exist' % (name, creator)
- self.__send_error(stanza, 'bad-request', text=text, type_='_modify')
- raise nbxmpp.NodeProcessed
- else:
- cn = self.contents[(creator, name)]
- cn.on_stanza(stanza, content, error, action)
-
- def __on_session_terminate(self, stanza, jingle, error, action):
- self.connection.delete_jingle_session(self.sid)
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- # TODO
- text = reason
- if reason == 'cancel' and self.session_type_ft:
- gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=None,
- reason=text))
-
- def __broadcast_all(self, stanza, jingle, error, action):
- """
- Broadcast the stanza to all content handlers
- """
- for content in self.contents.values():
- content.on_stanza(stanza, None, error, action)
-
- def __parse_contents(self, jingle):
- # TODO: Needs some reworking
- contents = []
- contents_rejected = []
- reasons = set()
- for element in jingle.iterTags('content'):
- transport = get_jingle_transport(element.getTag('transport'))
- if transport:
- transport.ourjid = self.ourjid
- content_type = get_jingle_content(element.getTag('description'))
- if content_type:
- try:
- if transport:
- content = content_type(self, transport)
- self.add_content(element['name'],
- content, 'peer')
- contents.append((content.media,))
- else:
- reasons.add('unsupported-transports')
- contents_rejected.append((element['name'], 'peer'))
- except JingleContentSetupException:
- reasons.add('failed-application')
- else:
- contents_rejected.append((element['name'], 'peer'))
- reasons.add('unsupported-applications')
- failure_reason = None
- # Store the first reason of failure
- for reason in ('failed-application', 'unsupported-transports',
- 'unsupported-applications'):
- if reason in reasons:
- failure_reason = reason
- break
- return (contents, contents_rejected, failure_reason)
-
- def __dispatch_error(self, error=None, text=None, type_=None):
- if text:
- text = '%s (%s)' % (error, text)
- if type_ != 'modify':
- gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- reason=text or error))
-
- @staticmethod
- def __reason_from_stanza(stanza):
- # TODO: Move to GUI?
- reason = 'success'
- reasons = [
- 'success', 'busy', 'cancel', 'connectivity-error', 'decline',
- 'expired', 'failed-application', 'failed-transport',
- 'general-error', 'gone', 'incompatible-parameters', 'media-error',
- 'security-error', 'timeout', 'unsupported-applications',
- 'unsupported-transports'
- ]
- tag = stanza.getTag('reason')
- text = ''
- if tag:
- text = tag.getTagData('text')
- for r in reasons:
- if tag.getTag(r):
- reason = r
- break
- return (reason, text)
-
- def __make_jingle(self, action, reason=None):
- stanza = nbxmpp.Iq(typ='set', to=nbxmpp.JID(self.peerjid),
- frm=self.ourjid)
- attrs = {
- 'action': action,
- 'sid': self.sid,
- 'initiator' : self.initiator
- }
- jingle = stanza.addChild('jingle', attrs=attrs,
- namespace=nbxmpp.NS_JINGLE)
- if reason is not None:
- jingle.addChild(node=reason)
- return stanza, jingle
-
- def __send_error(self, stanza, error, jingle_error=None, text=None, type_=None):
- err_stanza = nbxmpp.Error(stanza, '%s %s' % (nbxmpp.NS_STANZAS, error))
- err = err_stanza.getTag('error')
- if type_:
- err.setAttr('type', type_)
- if jingle_error:
- err.setTag(jingle_error, namespace=nbxmpp.NS_JINGLE_ERRORS)
- if text:
- err.setTagData('text', text)
- self.connection.connection.send(err_stanza)
- self.__dispatch_error(jingle_error or error, text, type_)
-
- @staticmethod
- def __append_content(jingle, content):
- """
- Append <content/> element to <jingle/> element, with (full=True) or
- without (full=False) <content/> children
- """
- jingle.addChild('content',
- attrs={'name': content.name,
- 'creator': content.creator})
-
- def __append_contents(self, jingle):
- """
- Append all <content/> elements to <jingle/>
- """
- # TODO: integrate with __appendContent?
- # TODO: parameters 'name', 'content'?
- for content in self.contents.values():
- if content.is_ready():
- self.__append_content(jingle, content)
-
- def __session_initiate(self):
- assert self.state == JingleStates.ENDED
- stanza, jingle = self.__make_jingle('session-initiate')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
- self.state = JingleStates.PENDING
-
- def __session_accept(self):
- assert self.state == JingleStates.PENDING
- stanza, jingle = self.__make_jingle('session-accept')
- self.__append_contents(jingle)
- self.__broadcast(stanza, jingle, None, 'session-accept-sent')
- self.connection.connection.send(stanza)
- self.collect_iq_id(stanza.getID())
- self.state = JingleStates.ACTIVE
-
- def __session_info(self, payload=None):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('session-info')
- if payload:
- jingle.addChild(node=payload)
- self.connection.connection.send(stanza)
-
- def _JingleFileTransfer__session_info(self, payload):
- # For some strange reason when I call
- # self.session.__session_info(payload) from the jingleFileTransfer object
- # within a thread, this method gets called instead. Even though, it
- # isn't being called explicitly.
- self.__session_info(payload)
-
- def _session_terminate(self, reason=None):
- stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
- self.__broadcast_all(stanza, jingle, None, 'session-terminate-sent')
- if self.connection.connection and self.connection.connected >= 2:
- self.connection.connection.send(stanza)
- # TODO: Move to GUI?
- reason, text = self.__reason_from_stanza(jingle)
- if reason not in ('success', 'cancel', 'decline'):
- self.__dispatch_error(reason, text)
- if text:
- text = '%s (%s)' % (reason, text)
- else:
- text = reason
- self.connection.delete_jingle_session(self.sid)
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=None,
- reason=text))
-
- def __content_add(self, content):
- # TODO: test
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-add')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-add-sent')
- id_ = self.connection.connection.send(stanza)
- self.collect_iq_id(id_)
-
- def __content_accept(self, content):
- # TODO: test
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-accept')
- self.__append_content(jingle, content)
- self.__broadcast(stanza, jingle, None, 'content-accept-sent')
- id_ = self.connection.connection.send(stanza)
- self.collect_iq_id(id_)
-
- def __content_reject(self, content):
- assert self.state != JingleStates.ENDED
- stanza, jingle = self.__make_jingle('content-reject')
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=content.media,
- reason='rejected'))
-
- def __content_modify(self):
- assert self.state != JingleStates.ENDED
-
- def __content_remove(self, content, reason=None):
- assert self.state != JingleStates.ENDED
- if self.connection.connection and self.connection.connected > 1:
- stanza, jingle = self.__make_jingle('content-remove', reason=reason)
- self.__append_content(jingle, content)
- self.connection.connection.send(stanza)
- # TODO: this will fail if content is not an RTP content
- gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=content.media,
- reason='removed'))
-
- def content_negotiated(self, media):
- gajim.nec.push_incoming_event(JingleConnectedReceivedEvent(None,
- conn=self.connection,
- jingle_session=self,
- media=media))
diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py
deleted file mode 100644
index 0632dff69..000000000
--- a/src/common/jingle_transport.py
+++ /dev/null
@@ -1,449 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## 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/>.
-
-"""
-Handles Jingle Transports (currently only ICE-UDP)
-"""
-
-import logging
-import socket
-from enum import IntEnum, unique
-import nbxmpp
-from common import gajim
-
-log = logging.getLogger('gajim.c.jingle_transport')
-
-
-transports = {}
-
-def get_jingle_transport(node):
- namespace = node.getNamespace()
- if namespace in transports:
- return transports[namespace](node)
-
-
-@unique
-class TransportType(IntEnum):
- """
- Possible types of a JingleTransport
- """
- ICEUDP = 1
- SOCKS5 = 2
- IBB = 3
-
-
-class JingleTransport:
- """
- An abstraction of a transport in Jingle sessions
- """
-
- __slots__ = ['type_', 'candidates', 'remote_candidates', 'connection',
- 'file_props', 'ourjid', 'sid']
-
- def __init__(self, type_):
- self.type_ = type_
- self.candidates = []
- self.remote_candidates = []
-
- self.connection = None
- self.file_props = None
- self.ourjid = None
- self.sid = None
-
- def _iter_candidates(self):
- for candidate in self.candidates:
- yield self.make_candidate(candidate)
-
- def make_candidate(self, candidate):
- """
- Build a candidate stanza for the given candidate
- """
- pass
-
- def make_transport(self, candidates=None):
- """
- Build a transport stanza with the given candidates (or self.candidates if
- candidates is None)
- """
- if not candidates:
- candidates = list(self._iter_candidates())
- else:
- candidates = (self.make_candidate(candidate) for candidate in candidates)
- transport = nbxmpp.Node('transport', payload=candidates)
- return transport
-
- def parse_transport_stanza(self, transport):
- """
- Return the list of transport candidates from a transport stanza
- """
- return []
-
- def set_connection(self, conn):
- self.connection = conn
- if not self.sid:
- self.sid = self.connection.connection.getAnID()
-
- def set_file_props(self, file_props):
- self.file_props = file_props
-
- def set_our_jid(self, jid):
- self.ourjid = jid
-
- def set_sid(self, sid):
- self.sid = sid
-
-class JingleTransportSocks5(JingleTransport):
- """
- Socks5 transport in jingle scenario
- Note: Don't forget to call set_file_props after initialization
- """
- def __init__(self, node=None):
- JingleTransport.__init__(self, TransportType.SOCKS5)
- self.connection = None
- self.remote_candidates = []
- self.sid = None
- if node and node.getAttr('sid'):
- self.sid = node.getAttr('sid')
-
-
- def make_candidate(self, candidate):
- log.info('candidate dict, %s', candidate)
- attrs = {
- 'cid': candidate['candidate_id'],
- 'host': candidate['host'],
- 'jid': candidate['jid'],
- 'port': candidate['port'],
- 'priority': candidate['priority'],
- 'type': candidate['type']
- }
-
- return nbxmpp.Node('candidate', attrs=attrs)
-
- def make_transport(self, candidates=None, add_candidates=True):
- if add_candidates:
- self._add_local_ips_as_candidates()
- self._add_additional_candidates()
- self._add_proxy_candidates()
- transport = JingleTransport.make_transport(self, candidates)
- else:
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', self.sid)
- if self.file_props.dstaddr:
- transport.setAttr('dstaddr', self.file_props.dstaddr)
- return transport
-
- def parse_transport_stanza(self, transport):
- candidates = []
- for candidate in transport.iterTags('candidate'):
- typ = 'direct' # default value
- if candidate.has_attr('type'):
- typ = candidate['type']
- cand = {
- 'state': 0,
- 'target': self.ourjid,
- 'host': candidate['host'],
- 'port': int(candidate['port']),
- 'cid': candidate['cid'],
- 'type': typ,
- 'priority': candidate['priority']
- }
- candidates.append(cand)
-
- # we need this when we construct file_props on session-initiation
- if candidates:
- self.remote_candidates = candidates
- return candidates
-
-
- def _add_candidates(self, candidates):
- for cand in candidates:
- in_remote = False
- for cand2 in self.remote_candidates:
- if cand['host'] == cand2['host'] and \
- cand['port'] == cand2['port']:
- in_remote = True
- break
- if not in_remote:
- self.candidates.append(cand)
-
- def _add_local_ips_as_candidates(self):
- if not gajim.config.get_per('accounts', self.connection.name,
- 'ft_send_local_ips'):
- return
- if not self.connection:
- return
- port = int(gajim.config.get('file_transfers_port'))
- #type preference of connection type. XEP-0260 section 2.2
- type_preference = 126
- priority = (2**16) * type_preference
-
- hosts = set()
- local_ip_cand = []
-
- candidate = {
- 'host': self.connection.peerhost[0],
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority
- }
- hosts.add(self.connection.peerhost[0])
- local_ip_cand.append(candidate)
-
- try:
- for addrinfo in socket.getaddrinfo(socket.gethostname(), None):
- addr = addrinfo[4][0]
- if not addr in hosts and not addr.startswith('127.') and \
- addr != '::1':
- candidate = {
- 'host': addr,
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- hosts.add(addr)
- local_ip_cand.append(candidate)
- except socket.gaierror:
- pass # ignore address-related errors for getaddrinfo
-
- self._add_candidates(local_ip_cand)
-
- def _add_additional_candidates(self):
- if not self.connection:
- return
- type_preference = 126
- priority = (2**16) * type_preference
- additional_ip_cand = []
- port = int(gajim.config.get('file_transfers_port'))
- ft_add_hosts = gajim.config.get('ft_add_hosts_to_send')
-
- if ft_add_hosts:
- hosts = [e.strip() for e in ft_add_hosts.split(',')]
- for host in hosts:
- candidate = {
- 'host': host,
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': port,
- 'type': 'direct',
- 'jid': self.ourjid,
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- additional_ip_cand.append(candidate)
-
- self._add_candidates(additional_ip_cand)
-
- def _add_proxy_candidates(self):
- if not self.connection:
- return
- type_preference = 10
- priority = (2**16) * type_preference
- proxy_cand = []
- socks5conn = self.connection
- proxyhosts = socks5conn._get_file_transfer_proxies_from_config(self.file_props)
-
- if proxyhosts:
- self.file_props.proxyhosts = proxyhosts
-
- for proxyhost in proxyhosts:
- candidate = {
- 'host': proxyhost['host'],
- 'candidate_id': self.connection.connection.getAnID(),
- 'port': int(proxyhost['port']),
- 'type': 'proxy',
- 'jid': proxyhost['jid'],
- 'priority': priority,
- 'initiator': self.file_props.sender,
- 'target': self.file_props.receiver
- }
- proxy_cand.append(candidate)
-
- self._add_candidates(proxy_cand)
-
- def get_content(self):
- sesn = self.connection.get_jingle_session(self.ourjid,
- self.file_props.sid)
- for content in sesn.contents.values():
- if content.transport == self:
- return content
-
- def _on_proxy_auth_ok(self, proxy):
- log.info('proxy auth ok for ' + str(proxy))
- # send activate request to proxy, send activated confirmation to peer
- if not self.connection:
- return
- sesn = self.connection.get_jingle_session(self.ourjid,
- self.file_props.sid)
- if sesn is None:
- return
-
- iq = nbxmpp.Iq(to=proxy['jid'], frm=self.ourjid, typ='set')
- auth_id = "au_" + proxy['sid']
- iq.setID(auth_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', proxy['sid'])
- activate = query.setTag('activate')
- activate.setData(sesn.peerjid)
- iq.setID(auth_id)
- self.connection.connection.send(iq)
-
-
- content = nbxmpp.Node('content')
- content.setAttr('creator', 'initiator')
- content_object = self.get_content()
- content.setAttr('name', content_object.name)
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
- transport.setAttr('sid', proxy['sid'])
- activated = nbxmpp.Node('activated')
- cid = None
-
- if 'cid' in proxy:
- cid = proxy['cid']
- else:
- for host in self.candidates:
- if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \
- and host['port'] == proxy['port']:
- cid = host['candidate_id']
- break
- if cid is None:
- raise Exception('cid is missing')
- activated.setAttr('cid', cid)
- transport.addChild(node=activated)
- content.addChild(node=transport)
- sesn.send_transport_info(content)
-
-
-class JingleTransportIBB(JingleTransport):
-
- def __init__(self, node=None, block_sz=None):
-
- JingleTransport.__init__(self, TransportType.IBB)
-
- if block_sz:
- self.block_sz = block_sz
- else:
- self.block_sz = '4096'
-
- self.connection = None
- self.sid = None
- if node and node.getAttr('sid'):
- self.sid = node.getAttr('sid')
-
-
- def make_transport(self):
-
- transport = nbxmpp.Node('transport')
- transport.setNamespace(nbxmpp.NS_JINGLE_IBB)
- transport.setAttr('block-size', self.block_sz)
- transport.setAttr('sid', self.sid)
- return transport
-
-try:
- from gi.repository import Farstream
-except ImportError:
- pass
-
-class JingleTransportICEUDP(JingleTransport):
- def __init__(self, node):
- JingleTransport.__init__(self, TransportType.ICEUDP)
-
- def make_candidate(self, candidate):
- types = {
- Farstream.CandidateType.HOST: 'host',
- Farstream.CandidateType.SRFLX: 'srflx',
- Farstream.CandidateType.PRFLX: 'prflx',
- Farstream.CandidateType.RELAY: 'relay',
- Farstream.CandidateType.MULTICAST: 'multicast'
- }
- attrs = {
- 'component': candidate.component_id,
- 'foundation': '1', # hack
- 'generation': '0',
- 'ip': candidate.ip,
- 'network': '0',
- 'port': candidate.port,
- 'priority': int(candidate.priority), # hack
- 'id': gajim.get_an_id()
- }
- if candidate.type in types:
- attrs['type'] = types[candidate.type]
- if candidate.proto == Farstream.NetworkProtocol.UDP:
- attrs['protocol'] = 'udp'
- else:
- # we actually don't handle properly different tcp options in jingle
- attrs['protocol'] = 'tcp'
- return nbxmpp.Node('candidate', attrs=attrs)
-
- def make_transport(self, candidates=None):
- transport = JingleTransport.make_transport(self, candidates)
- transport.setNamespace(nbxmpp.NS_JINGLE_ICE_UDP)
- if self.candidates and self.candidates[0].username and \
- self.candidates[0].password:
- transport.setAttr('ufrag', self.candidates[0].username)
- transport.setAttr('pwd', self.candidates[0].password)
- return transport
-
- def parse_transport_stanza(self, transport):
- candidates = []
- for candidate in transport.iterTags('candidate'):
- foundation = str(candidate['foundation'])
- component_id = int(candidate['component'])
- ip = str(candidate['ip'])
- port = int(candidate['port'])
- base_ip = None
- base_port = 0
- if candidate['protocol'] == 'udp':
- proto = Farstream.NetworkProtocol.UDP
- else:
- # we actually don't handle properly different tcp options in
- # jingle
- proto = Farstream.NetworkProtocol.TCP
- priority = int(candidate['priority'])
- types = {
- 'host': Farstream.CandidateType.HOST,
- 'srflx': Farstream.CandidateType.SRFLX,
- 'prflx': Farstream.CandidateType.PRFLX,
- 'relay': Farstream.CandidateType.RELAY,
- 'multicast': Farstream.CandidateType.MULTICAST
- }
- if 'type' in candidate and candidate['type'] in types:
- type_ = types[candidate['type']]
- else:
- log.warning('Unknown type %s', candidate['type'])
- type_ = Farstream.CandidateType.HOST
- username = str(transport['ufrag'])
- password = str(transport['pwd'])
- ttl = 0
-
- cand = Farstream.Candidate.new_full(foundation, component_id, ip,
- port, base_ip, base_port,
- proto, priority, type_,
- username, password, ttl)
-
- candidates.append(cand)
- self.remote_candidates.extend(candidates)
- return candidates
-
-transports[nbxmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP
-transports[nbxmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5
-transports[nbxmpp.NS_JINGLE_IBB] = JingleTransportIBB
diff --git a/src/common/jingle_xtls.py b/src/common/jingle_xtls.py
deleted file mode 100644
index 10a60d815..000000000
--- a/src/common/jingle_xtls.py
+++ /dev/null
@@ -1,299 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/jingle_xtls.py
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import logging
-import os
-
-import nbxmpp
-from common import gajim
-
-log = logging.getLogger('gajim.c.jingle_xtls')
-
-
-PYOPENSSL_PRESENT = False
-
-# key-exchange id -> [callback, args], accept that session once key-exchange completes
-pending_contents = {}
-
-def key_exchange_pend(id_, cb, args):
- # args is a list
- pending_contents[id_] = [cb, args]
-
-def approve_pending_content(id_):
- cb = pending_contents[id_][0]
- args = pending_contents[id_][1]
- cb(*args)
-
-try:
- import OpenSSL.SSL
- PYOPENSSL_PRESENT = True
-except ImportError:
- log.info("PyOpenSSL not available")
-
-if PYOPENSSL_PRESENT:
- from OpenSSL import SSL, crypto
- TYPE_RSA = crypto.TYPE_RSA
- TYPE_DSA = crypto.TYPE_DSA
-
-SELF_SIGNED_CERTIFICATE = 'localcert'
-DH_PARAMS = 'dh_params.pem'
-DEFAULT_DH_PARAMS = 'dh4096.pem'
-
-def default_callback(connection, certificate, error_num, depth, return_code):
- log.info("certificate: %s", certificate)
- return return_code
-
-def load_cert_file(cert_path, cert_store=None):
- """
- This is almost identical to the one in nbxmpp.tls_nb
- """
- if not os.path.isfile(cert_path):
- return None
- try:
- f = open(cert_path)
- except IOError as e:
- log.warning('Unable to open certificate file %s: %s', cert_path,
- str(e))
- return None
- lines = f.readlines()
- i = 0
- begin = -1
- for line in lines:
- if 'BEGIN CERTIFICATE' in line:
- begin = i
- elif 'END CERTIFICATE' in line and begin > -1:
- cert = ''.join(lines[begin:i+2])
- try:
- x509cert = OpenSSL.crypto.load_certificate(
- OpenSSL.crypto.FILETYPE_PEM, cert)
- if cert_store:
- cert_store.add_cert(x509cert)
- f.close()
- return x509cert
- except OpenSSL.crypto.Error as exception_obj:
- log.warning('Unable to load a certificate from file %s: %s',
- cert_path, exception_obj.args[0][0][2])
- except:
- log.warning('Unknown error while loading certificate from file '
- '%s', cert_path)
- begin = -1
- i += 1
- f.close()
-
-def get_context(fingerprint, verify_cb=None, remote_jid=None):
- """
- constructs and returns the context objects
- """
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- flags = (SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL.OP_SINGLE_DH_USE \
- | SSL.OP_NO_TICKET)
- ctx.set_options(flags)
- ctx.set_cipher_list('HIGH:!aNULL:!3DES')
-
- if fingerprint == 'server': # for testing purposes only
- ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
- verify_cb or default_callback)
- elif fingerprint == 'client':
- ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback)
-
- cert_name = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE)
- ctx.use_privatekey_file((cert_name + '.pkey').encode('utf-8'))
- ctx.use_certificate_file((cert_name + '.cert').encode('utf-8'))
-
- # Try to load Diffie-Hellman parameters.
- # First try user DH parameters, if this fails load the default DH parameters
- dh_params_name = os.path.join(gajim.MY_CERT_DIR, DH_PARAMS)
- try:
- with open(dh_params_name, "r") as dh_params_file:
- ctx.load_tmp_dh(dh_params_name.encode('utf-8'))
- except FileNotFoundError as err:
- default_dh_params_name = os.path.join(gajim.DATA_DIR,
- 'other', DEFAULT_DH_PARAMS)
- try:
- with open(default_dh_params_name, "r") as default_dh_params_file:
- ctx.load_tmp_dh(default_dh_params_name.encode('utf-8'))
- except FileNotFoundError as err:
- log.error('Unable to load default DH parameter file: %s, %s',
- default_dh_params_name, err)
- raise
-
- if remote_jid:
- store = ctx.get_cert_store()
- path = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH),
- remote_jid) + '.cert'
- if os.path.exists(path):
- load_cert_file(path, cert_store=store)
- log.debug('certificate file %s loaded fingerprint %s',
- path, fingerprint)
- return ctx
-
-def read_cert(certpath):
- certificate = ''
- with open(certpath, 'r') as certfile:
- for line in certfile.readlines():
- if not line.startswith('-'):
- certificate += line
- return certificate
-
-def send_cert(con, jid_from, sid):
- certpath = os.path.join(gajim.MY_CERT_DIR, SELF_SIGNED_CERTIFICATE) + \
- '.cert'
- certificate = read_cert(certpath)
- iq = nbxmpp.Iq('result', to=jid_from)
- iq.setAttr('id', sid)
-
- pubkey = iq.setTag('pubkeys')
- pubkey.setNamespace(nbxmpp.NS_PUBKEY_PUBKEY)
-
- keyinfo = pubkey.setTag('keyinfo')
- name = keyinfo.setTag('name')
- name.setData('CertificateHash')
- cert = keyinfo.setTag('x509cert')
- cert.setData(certificate)
-
- con.send(iq)
-
-def handle_new_cert(con, obj, jid_from):
- jid = gajim.get_jid_without_resource(jid_from)
- certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid)
- certpath += '.cert'
-
- id_ = obj.getAttr('id')
-
- x509cert = obj.getTag('pubkeys').getTag('keyinfo').getTag('x509cert')
-
- cert = x509cert.getData()
-
- f = open(certpath, 'w')
- f.write('-----BEGIN CERTIFICATE-----\n')
- f.write(cert)
- f.write('-----END CERTIFICATE-----\n')
- f.close()
-
- approve_pending_content(id_)
-
-def check_cert(jid, fingerprint):
- certpath = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH), jid)
- certpath += '.cert'
- if os.path.exists(certpath):
- cert = load_cert_file(certpath)
- if cert:
- try:
- digest_algo = cert.get_signature_algorithm().decode('utf-8').\
- split('With')[0]
- except AttributeError as e:
- # Old py-OpenSSL is missing get_signature_algorithm
- digest_algo = "sha256"
- if cert.digest(digest_algo) == fingerprint:
- return True
- return False
-
-def send_cert_request(con, to_jid):
- iq = nbxmpp.Iq('get', to=to_jid)
- id_ = con.connection.getAnID()
- iq.setAttr('id', id_)
- pubkey = iq.setTag('pubkeys')
- pubkey.setNamespace(nbxmpp.NS_PUBKEY_PUBKEY)
- con.connection.send(iq)
- return str(id_)
-
-# the following code is partly due to pyopenssl examples
-
-def createKeyPair(type_, bits):
- """
- Create a public/private key pair.
-
- Arguments: type_ - Key type, must be one of TYPE_RSA and TYPE_DSA
- bits - Number of bits to use in the key
- Returns: The public/private key pair in a PKey object
- """
- pkey = crypto.PKey()
- pkey.generate_key(type_, bits)
- return pkey
-
-def createCertRequest(pkey, digest="sha256", **name):
- """
- Create a certificate request.
-
- Arguments: pkey - The key to associate with the request
- digest - Digestion method to use for signing, default is sha256
- **name - The name of the subject of the request, possible
- arguments are:
- C - Country name
- ST - State or province name
- L - Locality name
- O - Organization name
- OU - Organizational unit name
- CN - Common name
- emailAddress - E-mail address
- Returns: The certificate request in an X509Req object
- """
- req = crypto.X509Req()
- subj = req.get_subject()
-
- for (key, value) in name.items():
- setattr(subj, key, value)
-
- req.set_pubkey(pkey)
- req.sign(pkey, digest)
- return req
-
-def createCertificate(req, issuerCert, issuerKey, serial, notBefore, notAfter, digest="sha256"):
- """
- Generate a certificate given a certificate request.
-
- Arguments: req - Certificate reqeust to use
- issuerCert - The certificate of the issuer
- issuerKey - The private key of the issuer
- serial - Serial number for the certificate
- notBefore - Timestamp (relative to now) when the certificate
- starts being valid
- notAfter - Timestamp (relative to now) when the certificate
- stops being valid
- digest - Digest method to use for signing, default is sha256
- Returns: The signed certificate in an X509 object
- """
- cert = crypto.X509()
- cert.set_serial_number(serial)
- cert.gmtime_adj_notBefore(notBefore)
- cert.gmtime_adj_notAfter(notAfter)
- cert.set_issuer(issuerCert.get_subject())
- cert.set_subject(req.get_subject())
- cert.set_pubkey(req.get_pubkey())
- cert.sign(issuerKey, digest)
- return cert
-
-def make_certs(filepath, CN):
- """
- make self signed certificates
- filepath : absolute path of certificate file, will be appended the '.pkey'
- and '.cert' extensions
- CN : common name
- """
- key = createKeyPair(TYPE_RSA, 4096)
- req = createCertRequest(key, CN=CN)
- cert = createCertificate(req, req, key, 0, 0, 60*60*24*365*5) # five years
- with open(filepath + '.pkey', 'wb') as f:
- os.chmod(filepath + '.pkey', 0o600)
- f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
- with open(filepath + '.cert', 'wb') as f:
- f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
-
-
-if __name__ == '__main__':
- make_certs('./selfcert', 'gajim')
diff --git a/src/common/location_listener.py b/src/common/location_listener.py
deleted file mode 100644
index 6542244f4..000000000
--- a/src/common/location_listener.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/common/location_listener.py
-##
-## Copyright (C) 2009-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from datetime import datetime
-
-from common import gajim
-from common import dbus_support
-if dbus_support.supported:
- import dbus
-
-class LocationListener:
- _instance = None
- @classmethod
- def get(cls):
- if cls._instance is None:
- cls._instance = cls()
- return cls._instance
-
- def __init__(self):
- self._data = {}
-
- def get_data(self):
- bus = dbus.SessionBus()
- try:
- # Initializes Geoclue.
- obj = bus.get_object('org.freedesktop.Geoclue.Master',
- '/org/freedesktop/Geoclue/Master')
- # get MasterClient path
- path = obj.Create()
- # get MasterClient
- cli = bus.get_object('org.freedesktop.Geoclue.Master', path)
- cli.SetRequirements(1, 0, True, 1023)
-
- self._get_address(cli)
- self._get_position(cli)
- except:
- self._on_geoclue_position_changed()
- return
-
-
- def _get_address(self, cli):
- bus = dbus.SessionBus()
- cli.AddressStart()
- # Check that there is a provider
- name, description, service, path = cli.GetAddressProvider()
- if path:
- provider = bus.get_object(service, path)
- timestamp, address, accuracy = provider.GetAddress()
- self._on_geoclue_address_changed(timestamp, address, accuracy)
-
- def _get_position(self, cli):
- bus = dbus.SessionBus()
- cli.PositionStart()
- # Check that there is a provider
- name, description, service, path = cli.GetPositionProvider()
- if path:
- provider = bus.get_object(service, path)
- fields, timestamp, lat, lon, alt, accuracy = provider.GetPosition()
- self._on_geoclue_position_changed(fields, timestamp, lat, lon, alt,
- accuracy)
-
- def start(self):
- self.location_info = {}
- self.get_data()
- bus = dbus.SessionBus()
- # Geoclue
- bus.add_signal_receiver(self._on_geoclue_address_changed,
- 'AddressChanged', 'org.freedesktop.Geoclue.Address')
- bus.add_signal_receiver(self._on_geoclue_position_changed,
- 'PositionChanged', 'org.freedesktop.Geoclue.Position')
-
- def shut_down(self):
- pass
-
- def _on_geoclue_address_changed(self, timestamp=None, address=None,
- accuracy=None):
- # update data with info we just received
- if address is None:
- address = {}
- for field in ['country', 'countrycode', 'locality', 'postalcode',
- 'region', 'street']:
- self._data[field] = address.get(field, None)
- if timestamp:
- self._data['timestamp'] = self._timestamp_to_utc(timestamp)
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
-
- def _on_geoclue_position_changed(self, fields=None, timestamp=None, lat=None,
- lon=None, alt=None, accuracy=None):
- if fields is None:
- fields = []
- # update data with info we just received
- _dict = {'lat': lat, 'lon': lon, 'alt': alt}
- for field in _dict:
- if _dict[field] is not None:
- self._data[field] = _dict[field]
- if timestamp:
- self._data['timestamp'] = self._timestamp_to_utc(timestamp)
- if accuracy:
- # in PEP it's horizontal accuracy
- self._data['accuracy'] = accuracy[1]
- self._send_location()
-
- def _send_location(self):
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.account_is_connected(acct):
- continue
- if not gajim.config.get_per('accounts', acct, 'publish_location'):
- continue
- if self.location_info == self._data:
- continue
- if 'timestamp' in self.location_info and 'timestamp' in self._data:
- last_data = self.location_info.copy()
- del last_data['timestamp']
- new_data = self._data.copy()
- del new_data['timestamp']
- if last_data == new_data:
- continue
- gajim.connections[acct].send_location(self._data)
- self.location_info = self._data.copy()
-
- def _timestamp_to_utc(self, timestamp):
- time = datetime.utcfromtimestamp(timestamp)
- return time.strftime('%Y-%m-%dT%H:%MZ')
-
-def enable():
- listener = LocationListener.get()
- listener.start()
-
-def disable():
- listener = LocationListener.get()
- listener.shut_down()
diff --git a/src/common/logger.py b/src/common/logger.py
deleted file mode 100644
index b9221e8ba..000000000
--- a/src/common/logger.py
+++ /dev/null
@@ -1,1193 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/logger.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-"""
-This module allows to access the on-disk database of logs
-"""
-
-import os
-import sys
-import time
-import datetime
-import json
-from collections import namedtuple
-from gzip import GzipFile
-from io import BytesIO
-from gi.repository import GLib
-from enum import IntEnum, unique
-
-from common import exceptions
-from common import gajim
-from common import ged
-
-import sqlite3 as sqlite
-
-LOG_DB_PATH = gajim.gajimpaths['LOG_DB']
-LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
-CACHE_DB_PATH = gajim.gajimpaths['CACHE_DB']
-
-import logging
-log = logging.getLogger('gajim.c.logger')
-
-@unique
-class JIDConstant(IntEnum):
- NORMAL_TYPE = 0
- ROOM_TYPE = 1
-
-@unique
-class KindConstant(IntEnum):
- STATUS = 0
- GCSTATUS = 1
- GC_MSG = 2
- SINGLE_MSG_RECV = 3
- CHAT_MSG_RECV = 4
- SINGLE_MSG_SENT = 5
- CHAT_MSG_SENT = 6
- ERROR = 7
-
-@unique
-class ShowConstant(IntEnum):
- ONLINE = 0
- CHAT = 1
- AWAY = 2
- XA = 3
- DND = 4
- OFFLINE = 5
-
-@unique
-class TypeConstant(IntEnum):
- AIM = 0
- GG = 1
- HTTP_WS = 2
- ICQ = 3
- MSN = 4
- QQ = 5
- SMS = 6
- SMTP = 7
- TLEN = 8
- YAHOO = 9
- NEWMAIL = 10
- RSS = 11
- WEATHER = 12
- MRIM = 13
- NO_TRANSPORT = 14
-
-@unique
-class SubscriptionConstant(IntEnum):
- NONE = 0
- TO = 1
- FROM = 2
- BOTH = 3
-
-class Logger:
- def __init__(self):
- self.jids_already_in = [] # holds jids that we already have in DB
- self.con = None
- self.commit_timout_id = None
-
- if not os.path.exists(LOG_DB_PATH):
- # this can happen only the first time (the time we create the db)
- # db is not created here but in src/common/checks_paths.py
- return
- self.init_vars()
- if not os.path.exists(CACHE_DB_PATH):
- # this can happen cache database is not present when gajim is launched
- # db will be created in src/common/checks_paths.py
- return
- self.attach_cache_database()
- gajim.ged.register_event_handler('gc-message-received',
- ged.POSTCORE, self._nec_gc_message_received)
-
- def dispatch(self, event, error):
- gajim.ged.raise_event(event, None, str(error))
-
- def close_db(self):
- if self.con:
- self.con.close()
- self.con = None
- self.cur = None
-
- def open_db(self):
- self.close_db()
-
- # FIXME: sqlite3_open wants UTF8 strings. So a path with
- # non-ascii chars doesn't work. See #2812 and
- # http://lists.initd.org/pipermail/pysqlite/2005-August/000134.html
- back = os.getcwd()
- os.chdir(LOG_DB_FOLDER)
-
- # if locked, wait up to 20 sec to unlock
- # before raise (hopefully should be enough)
-
- self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
- isolation_level='IMMEDIATE')
- os.chdir(back)
- self.cur = self.con.cursor()
- self.set_synchronous(False)
-
- def attach_cache_database(self):
- try:
- self.cur.execute("ATTACH DATABASE '%s' AS cache" % \
- CACHE_DB_PATH.replace("'", "''"))
- except sqlite.Error as e:
- log.debug("Failed to attach cache database: %s" % str(e))
-
- def set_synchronous(self, sync):
- try:
- if sync:
- self.cur.execute("PRAGMA synchronous = NORMAL")
- else:
- self.cur.execute("PRAGMA synchronous = OFF")
- except sqlite.Error as e:
- log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
-
- def init_vars(self):
- self.open_db()
- self.get_jids_already_in_db()
-
- def _really_commit(self):
- try:
- self.con.commit()
- except sqlite.OperationalError as e:
- print(str(e), file=sys.stderr)
- self.commit_timout_id = None
- return False
-
- def _timeout_commit(self):
- if self.commit_timout_id:
- return
- self.commit_timout_id = GLib.timeout_add(500, self._really_commit)
-
- def simple_commit(self, sql_to_commit):
- """
- Helper to commit
- """
- self.cur.execute(sql_to_commit)
- self._timeout_commit()
-
- def get_jids_already_in_db(self):
- try:
- self.cur.execute('SELECT jid FROM jids')
- # list of tuples: [('aaa@bbb',), ('cc@dd',)]
- rows = self.cur.fetchall()
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed(LOG_DB_PATH)
- self.jids_already_in = []
- for row in rows:
- # row[0] is first item of row (the only result here, the jid)
- if row[0] == '':
- # malformed jid, ignore line
- pass
- else:
- self.jids_already_in.append(row[0])
-
- def get_jids_in_db(self):
- return self.jids_already_in
-
- def jid_is_from_pm(self, jid):
- """
- If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
- is not a normal guy and nkour is not his resource? we ask if gajim@conf
- is already in jids (with type room jid) this fails if user disables
- logging for room and only enables for pm (so higly unlikely) and if we
- fail we do not go chaos (user will see the first pm as if it was message
- in room's public chat) and after that all okay
- """
- if jid.find('/') > -1:
- possible_room_jid = jid.split('/', 1)[0]
- return self.jid_is_room_jid(possible_room_jid)
- else:
- # it's not a full jid, so it's not a pm one
- return False
-
- def jid_is_room_jid(self, jid):
- """
- Return True if it's a room jid, False if it's not, None if we don't know
- """
- self.cur.execute('SELECT type FROM jids WHERE jid=?', (jid,))
- row = self.cur.fetchone()
- if row is None:
- return None
- else:
- if row[0] == JIDConstant.ROOM_TYPE:
- return True
- return False
-
- def get_jid_id(self, jid, typestr=None):
- """
- jids table has jid and jid_id logs table has log_id, jid_id,
- contact_name, time, kind, show, message so to ask logs we need jid_id
- that matches our jid in jids table this method wants jid and returns the
- jid_id for later sql-ing on logs typestr can be 'ROOM' or anything else
- depending on the type of JID and is only needed to be specified when the
- JID is new in DB
- """
- if jid.find('/') != -1: # if it has a /
- jid_is_from_pm = self.jid_is_from_pm(jid)
- if not jid_is_from_pm: # it's normal jid with resource
- jid = jid.split('/', 1)[0] # remove the resource
- if jid in self.jids_already_in: # we already have jids in DB
- self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
- row = self.cur.fetchone()
- if row:
- return row[0]
- # oh! a new jid :), we add it now
- if typestr == 'ROOM':
- typ = JIDConstant.ROOM_TYPE
- else:
- typ = JIDConstant.NORMAL_TYPE
- try:
- self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)', (jid,
- typ))
- self.con.commit()
- except sqlite.IntegrityError:
- # Jid already in DB, maybe added by another instance. re-read DB
- self.get_jids_already_in_db()
- return self.get_jid_id(jid, typestr)
- except sqlite.OperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- jid_id = self.cur.lastrowid
- self.jids_already_in.append(jid)
- return jid_id
-
- def convert_human_values_to_db_api_values(self, kind, show):
- """
- Convert from string style to constant ints for db
- """
- if kind == 'status':
- kind_col = KindConstant.STATUS
- elif kind == 'gcstatus':
- kind_col = KindConstant.GCSTATUS
- elif kind == 'gc_msg':
- kind_col = KindConstant.GC_MSG
- elif kind == 'single_msg_recv':
- kind_col = KindConstant.SINGLE_MSG_RECV
- elif kind == 'single_msg_sent':
- kind_col = KindConstant.SINGLE_MSG_SENT
- elif kind == 'chat_msg_recv':
- kind_col = KindConstant.CHAT_MSG_RECV
- elif kind == 'chat_msg_sent':
- kind_col = KindConstant.CHAT_MSG_SENT
- elif kind == 'error':
- kind_col = KindConstant.ERROR
-
- if show == 'online':
- show_col = ShowConstant.ONLINE
- elif show == 'chat':
- show_col = ShowConstant.CHAT
- elif show == 'away':
- show_col = ShowConstant.AWAY
- elif show == 'xa':
- show_col = ShowConstant.XA
- elif show == 'dnd':
- show_col = ShowConstant.DND
- elif show == 'offline':
- show_col = ShowConstant.OFFLINE
- elif show is None:
- show_col = None
- else: # invisible in GC when someone goes invisible
- # it's a RFC violation .... but we should not crash
- show_col = 'UNKNOWN'
-
- return kind_col, show_col
-
- def convert_human_transport_type_to_db_api_values(self, type_):
- """
- Convert from string style to constant ints for db
- """
- if type_ == 'aim':
- return TypeConstant.AIM
- if type_ == 'gadu-gadu':
- return TypeConstant.GG
- if type_ == 'http-ws':
- return TypeConstant.HTTP_WS
- if type_ == 'icq':
- return TypeConstant.ICQ
- if type_ == 'msn':
- return TypeConstant.MSN
- if type_ == 'qq':
- return TypeConstant.QQ
- if type_ == 'sms':
- return TypeConstant.SMS
- if type_ == 'smtp':
- return TypeConstant.SMTP
- if type_ in ('tlen', 'x-tlen'):
- return TypeConstant.TLEN
- if type_ == 'yahoo':
- return TypeConstant.YAHOO
- if type_ == 'newmail':
- return TypeConstant.NEWMAIL
- if type_ == 'rss':
- return TypeConstant.RSS
- if type_ == 'weather':
- return TypeConstant.WEATHER
- if type_ == 'mrim':
- return TypeConstant.MRIM
- if type_ == 'jabber':
- return TypeConstant.NO_TRANSPORT
- return None
-
- def convert_api_values_to_human_transport_type(self, type_id):
- """
- Convert from constant ints for db to string style
- """
- if type_id == TypeConstant.AIM:
- return 'aim'
- if type_id == TypeConstant.GG:
- return 'gadu-gadu'
- if type_id == TypeConstant.HTTP_WS:
- return 'http-ws'
- if type_id == TypeConstant.ICQ:
- return 'icq'
- if type_id == TypeConstant.MSN:
- return 'msn'
- if type_id == TypeConstant.QQ:
- return 'qq'
- if type_id == TypeConstant.SMS:
- return 'sms'
- if type_id == TypeConstant.SMTP:
- return 'smtp'
- if type_id == TypeConstant.TLEN:
- return 'tlen'
- if type_id == TypeConstant.YAHOO:
- return 'yahoo'
- if type_id == TypeConstant.NEWMAIL:
- return 'newmail'
- if type_id == TypeConstant.RSS:
- return 'rss'
- if type_id == TypeConstant.WEATHER:
- return 'weather'
- if type_id == TypeConstant.MRIM:
- return 'mrim'
- if type_id == TypeConstant.NO_TRANSPORT:
- return 'jabber'
-
- def convert_human_subscription_values_to_db_api_values(self, sub):
- """
- Convert from string style to constant ints for db
- """
- if sub == 'none':
- return SubscriptionConstant.NONE
- if sub == 'to':
- return SubscriptionConstant.TO
- if sub == 'from':
- return SubscriptionConstant.FROM
- if sub == 'both':
- return SubscriptionConstant.BOTH
-
- def convert_db_api_values_to_human_subscription_values(self, sub):
- """
- Convert from constant ints for db to string style
- """
- if sub == SubscriptionConstant.NONE:
- return 'none'
- if sub == SubscriptionConstant.TO:
- return 'to'
- if sub == SubscriptionConstant.FROM:
- return 'from'
- if sub == SubscriptionConstant.BOTH:
- return 'both'
-
- def commit_to_db(self, values, write_unread=False):
- sql = '''INSERT INTO logs (jid_id, contact_name, time, kind, show,
- message, subject, additional_data) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'''
- try:
- self.cur.execute(sql, values)
- except sqlite.OperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- except sqlite.DatabaseError:
- raise exceptions.DatabaseMalformed(LOG_DB_PATH)
- message_id = None
- if write_unread:
- try:
- self.con.commit()
- message_id = self.cur.lastrowid
- except sqlite.OperationalError as e:
- print(str(e), file=sys.stderr)
- else:
- self._timeout_commit()
- if message_id:
- self.insert_unread_events(message_id, values[0])
- return message_id
-
- def insert_unread_events(self, message_id, jid_id):
- """
- Add unread message with id: message_id
- """
- sql = 'INSERT INTO unread_messages VALUES (%d, %d, 0)' % (message_id,
- jid_id)
- self.simple_commit(sql)
-
- def set_read_messages(self, message_ids):
- """
- Mark all messages with ids in message_ids as read
- """
- ids = ','.join([str(i) for i in message_ids])
- sql = 'DELETE FROM unread_messages WHERE message_id IN (%s)' % ids
- self.simple_commit(sql)
-
- def set_shown_unread_msgs(self, msg_log_id):
- """
- Mark unread message as shown un GUI
- """
- sql = 'UPDATE unread_messages SET shown = 1 where message_id = %s' % \
- msg_log_id
- self.simple_commit(sql)
-
- def reset_shown_unread_messages(self):
- """
- Set shown field to False in unread_messages table
- """
- sql = 'UPDATE unread_messages SET shown = 0'
- self.simple_commit(sql)
-
- def get_unread_msgs(self):
- """
- Get all unread messages
- """
- all_messages = []
- try:
- self.cur.execute(
- 'SELECT message_id, shown from unread_messages')
- unread_results = self.cur.fetchall()
- except Exception:
- unread_results = []
- for message in unread_results:
- msg_log_id = message[0]
- shown = message[1]
- # here we get infos for that message, and related jid from jids table
- # do NOT change order of SELECTed things, unless you change function(s)
- # that called this function
- self.cur.execute('''
- SELECT logs.log_line_id, logs.message, logs.time, logs.subject,
- jids.jid, logs.additional_data
- FROM logs, jids
- WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
- ''' % msg_log_id
- )
- results = self.cur.fetchall()
- if len(results) == 0:
- # Log line is no more in logs table. remove it from unread_messages
- self.set_read_messages([msg_log_id])
- continue
- results[0] = list(results[0])
- results[0][5] = json.loads(results[0][5])
- results[0].append(shown)
- all_messages.append(results[0])
- return all_messages
-
- def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
- additional_data=None, mam_query=False):
- """
- Write a row (status, gcstatus, message etc) to logs database
-
- kind can be status, gcstatus, gc_msg, (we only recv for those 3),
- single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent we cannot
- know if it is pm or normal chat message, we try to guess see
- jid_is_from_pm()
-
- We analyze jid and store it as follows:
- jids.jid text column will hold JID if TC-related, room_jid if GC-related,
- ROOM_JID/nick if pm-related.
- """
-
- if additional_data is None:
- additional_data = {}
- if self.jids_already_in == []: # only happens if we just created the db
- self.open_db()
-
- contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
- # message holds the message unless kind is status or gcstatus,
- # then it holds status message
- message_col = message
- subject_col = subject
- additional_data_col = json.dumps(additional_data)
- if tim:
- time_col = float(tim)
- else:
- time_col = float(time.time())
-
- kind_col, show_col = self.convert_human_values_to_db_api_values(kind,
- show)
-
- write_unread = False
- try:
- # now we may have need to do extra care for some values in columns
- if kind == 'status': # we store (not None) time, jid, show, msg
- # status for roster items
- jid_id = self.get_jid_id(jid)
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = ShowConstant.ONLINE
-
- elif kind == 'gcstatus':
- # status in ROOM (for pm status see status)
- if show is None: # show is None (xmpp), but we say that 'online'
- show_col = ShowConstant.ONLINE
- jid, nick = jid.split('/', 1)
-
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
- contact_name_col = nick
-
- elif kind == 'gc_msg':
- if jid.find('/') != -1: # if it has a /
- jid, nick = jid.split('/', 1)
- else:
- # it's server message f.e. error message
- # when user tries to ban someone but he's not allowed to
- nick = None
-
- # re-get jid_id for the new jid
- jid_id = self.get_jid_id(jid, 'ROOM')
-
- contact_name_col = nick
- else:
- jid_id = self.get_jid_id(jid)
- if kind == 'chat_msg_recv':
- if not self.jid_is_from_pm(jid) and not mam_query:
- # Save in unread table only if it's not a pm
- write_unread = True
-
- if show_col == 'UNKNOWN': # unknown show, do not log
- return
-
- values = (jid_id, contact_name_col, time_col, kind_col, show_col,
- message_col, subject_col, additional_data_col)
- return self.commit_to_db(values, write_unread)
-
- except (exceptions.DatabaseMalformed,
- exceptions.PysqliteOperationalError) as error:
- self.dispatch('DB_ERROR', error)
-
- def get_last_conversation_lines(self, jid, restore_how_many_rows,
- pending_how_many, timeout, account):
- """
- Accept how many rows to restore and when to time them out (in minutes)
- (mark them as too old) and number of messages that are in queue and are
- already logged but pending to be viewed, returns a list of tuples
- containg time, kind, message, subject list with empty tuple if nothing
- found to meet our demands
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- now = int(float(time.time()))
- timed_out = now - (timeout * 60) # before that they are too old
- # so if we ask last 5 lines and we have 2 pending we get
- # 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
- try:
- self.cur.execute('''
- SELECT time, kind, message, subject, additional_data FROM logs
- WHERE (%s) AND kind IN (%d, %d, %d, %d, %d) AND time > %d
- ORDER BY time DESC LIMIT %d OFFSET %d
- ''' % (where_sql, KindConstant.SINGLE_MSG_RECV,
- KindConstant.CHAT_MSG_RECV, KindConstant.SINGLE_MSG_SENT,
- KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timed_out,
- restore_how_many_rows, pending_how_many), jid_tuple)
-
- results = self.cur.fetchall()
- messages = []
- for entry in results:
- additional_data = json.loads(entry[4])
- parsed_entry = entry[:4] + (additional_data, ) + entry[5:]
- messages.append(parsed_entry)
- except sqlite.DatabaseError:
- self.dispatch('DB_ERROR',
- exceptions.DatabaseMalformed(LOG_DB_PATH))
- return []
-
- messages.reverse()
- return messages
-
- def get_unix_time_from_date(self, year, month, day):
- # year (fe 2005), month (fe 11), day (fe 25)
- # returns time in seconds for the second that starts that date since epoch
- # gimme unixtime from year month day:
- d = datetime.date(year, month, day)
- local_time = d.timetuple() # time tuple (compat with time.localtime())
- # we have time since epoch baby :)
- start_of_day = int(time.mktime(local_time))
- return start_of_day
-
- Message = namedtuple('Message',
- ['contact_name', 'time', 'kind', 'show', 'message', 'subject',
- 'additional_data', 'log_line_id'])
-
- def get_conversation_for_date(self, jid, year, month, day, account):
- """
- Load the complete conversation with a given jid on a specific date
-
- The conversation contains all messages that were exchanged between
- `account` and `jid` on the day specified by `year`, `month` and `day`,
- where `month` and `day` are 1-based.
-
- The conversation will be returned as a list of single messages of type
- `Logger.Message`. Messages in the list are sorted chronologically. An
- empty list will be returned if there are no messages in the log database
- for the requested combination of `jid` and `account` on the given date.
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
-
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
-
- results = [self.Message(*row) for row in self.cur.fetchall()]
- for message in results:
- message._replace(additional_data=json.loads(message.additional_data))
-
- return results
-
- def search_log(self, jid, query, account, year=None, month=None, day=None):
- """
- Search the conversation log for messages containing the `query` string.
-
- The search can either span the complete log for the given `account` and
- `jid` or be restriced to a single day by specifying `year`, `month` and
- `day`, where `month` and `day` are 1-based.
-
- All messages matching the specified criteria will be returned in a list
- containing tuples of type `Logger.Message`. If no messages match the
- criteria, an empty list will be returned.
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
-
- where_sql, jid_tuple = self._build_contact_where(account, jid)
- like_sql = '%' + query.replace("'", "''") + '%'
- if year and month and day:
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s) AND message LIKE '%s'
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, like_sql, start_of_day, last_second_of_day),
- jid_tuple)
- else:
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject,
- additional_data, log_line_id
- FROM logs
- WHERE (%s) AND message LIKE '%s'
- ORDER BY time
- ''' % (where_sql, like_sql), jid_tuple)
-
- results = [self.Message(*row) for row in self.cur.fetchall()]
- for message in results:
- message._replace(additional_data=json.loads(message.additional_data))
-
- return results
-
- def get_days_with_logs(self, jid, year, month, max_day, account):
- """
- Return the list of days that have logs (not status messages)
- """
- try:
- self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return []
- days_with_logs = []
- where_sql, jid_tuple = self._build_contact_where(account, jid)
-
- # First select all date of month whith logs we want
- start_of_month = self.get_unix_time_from_date(year, month, 1)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_month = start_of_month + (seconds_in_a_day * max_day) - 1
-
- # Select times and 'floor' them to time 0:00
- # (by dividing, they are integers)
- # and take only one of the same values (distinct)
- # Now we have timestamps of time 0:00 of every day with logs
- self.cur.execute('''
- SELECT DISTINCT time/(86400)*86400 FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- AND kind NOT IN (%d, %d)
- ORDER BY time
- ''' % (where_sql, start_of_month, last_second_of_month,
- KindConstant.STATUS, KindConstant.GCSTATUS), jid_tuple)
- result = self.cur.fetchall()
-
- # convert timestamps to day of month
- for line in result:
- days_with_logs[0:0]=[time.gmtime(line[0])[2]]
-
- return days_with_logs
-
- def get_last_date_that_has_logs(self, jid, account=None, is_room=False):
- """
- Return last time (in seconds since EPOCH) for which we had logs
- (excluding statuses)
- """
- where_sql = ''
- if not is_room:
- where_sql, jid_tuple = self._build_contact_where(account, jid)
- else:
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = ?'
- jid_tuple = (jid_id,)
- self.cur.execute('''
- SELECT MAX(time) FROM logs
- WHERE (%s)
- AND kind NOT IN (%d, %d)
- ''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS),
- jid_tuple)
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def get_room_last_message_time(self, jid):
- """
- Return FASTLY last time (in seconds since EPOCH) for which we had logs
- for that room from rooms_last_message_time table
- """
- try:
- jid_id = self.get_jid_id(jid, 'ROOM')
- except exceptions.PysqliteOperationalError:
- # Error trying to create a new jid_id. This means there is no log
- return None
- where_sql = 'jid_id = %s' % jid_id
- self.cur.execute('''
- SELECT time FROM rooms_last_message_time
- WHERE (%s)
- ''' % (where_sql))
-
- results = self.cur.fetchone()
- if results is not None:
- result = results[0]
- else:
- result = None
- return result
-
- def set_room_last_message_time(self, jid, time):
- """
- Set last time (in seconds since EPOCH) for which we had logs for that
- room in rooms_last_message_time table
- """
- jid_id = self.get_jid_id(jid, 'ROOM')
- # jid_id is unique in this table, create or update :
- sql = 'REPLACE INTO rooms_last_message_time VALUES (%d, %d)' % \
- (jid_id, time)
- self.simple_commit(sql)
-
- def _build_contact_where(self, account, jid):
- """
- Build the where clause for a jid, including metacontacts jid(s) if any
- """
- where_sql = ''
- jid_tuple = ()
- # will return empty list if jid is not associated with
- # any metacontacts
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- for user in family:
- try:
- jid_id = self.get_jid_id(user['jid'])
- except exceptions.PysqliteOperationalError:
- continue
- where_sql += 'jid_id = ?'
- jid_tuple += (jid_id,)
- if user != family[-1]:
- where_sql += ' OR '
- else: # if jid was not associated with metacontacts
- jid_id = self.get_jid_id(jid)
- where_sql = 'jid_id = ?'
- jid_tuple += (jid_id,)
- return where_sql, jid_tuple
-
- def save_transport_type(self, jid, type_):
- """
- Save the type of the transport in DB
- """
- type_id = self.convert_human_transport_type_to_db_api_values(type_)
- if not type_id:
- # unknown type
- return
- self.cur.execute(
- 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
- results = self.cur.fetchall()
- if results:
- result = results[0][0]
- if result == type_id:
- return
- sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
- (type_id, jid)
- self.simple_commit(sql)
- return
- sql = 'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id)
- self.simple_commit(sql)
-
- def get_transports_type(self):
- """
- Return all the type of the transports in DB
- """
- self.cur.execute(
- 'SELECT * from transports_cache')
- results = self.cur.fetchall()
- if not results:
- return {}
- answer = {}
- for result in results:
- answer[result[0]] = self.convert_api_values_to_human_transport_type(
- result[1])
- return answer
-
- # A longer note here:
- # The database contains a blob field. Pysqlite seems to need special care for
- # such fields.
- # When storing, we need to convert string into buffer object (1).
- # When retrieving, we need to convert it back to a string to decompress it.
- # (2)
- # GzipFile needs a file-like object, StringIO emulates file for plain strings
- def iter_caps_data(self):
- """
- Iterate over caps cache data stored in the database
-
- The iterator values are pairs of (node, ver, ext, identities, features):
- identities == {'category':'foo', 'type':'bar', 'name':'boo'},
- features being a list of feature namespaces.
- """
- # get data from table
- # the data field contains binary object (gzipped data), this is a hack
- # to get that data without trying to convert it to unicode
- try:
- self.cur.execute('SELECT hash_method, hash, data FROM caps_cache;')
- except sqlite.OperationalError:
- # might happen when there's no caps_cache table yet
- # -- there's no data to read anyway then
- return
-
- # list of corrupted entries that will be removed
- to_be_removed = []
- for hash_method, hash_, data in self.cur:
- # for each row: unpack the data field
- # (format: (category, type, name, category, type, name, ...
- # ..., 'FEAT', feature1, feature2, ...).join(' '))
- # NOTE: if there's a need to do more gzip, put that to a function
- try:
- data = GzipFile(fileobj=BytesIO(data)).read().decode('utf-8').split('\0')
- except IOError:
- # This data is corrupted. It probably contains non-ascii chars
- to_be_removed.append((hash_method, hash_))
- continue
- i = 0
- identities = list()
- features = list()
- while i < (len(data) - 3) and data[i] != 'FEAT':
- category = data[i]
- type_ = data[i + 1]
- lang = data[i + 2]
- name = data[i + 3]
- identities.append({'category': category, 'type': type_,
- 'xml:lang': lang, 'name': name})
- i += 4
- i+=1
- while i < len(data):
- features.append(data[i])
- i += 1
-
- # yield the row
- yield hash_method, hash_, identities, features
- for hash_method, hash_ in to_be_removed:
- sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
- hash = "%s"''' % (hash_method, hash_)
- self.simple_commit(sql)
-
- def add_caps_entry(self, hash_method, hash_, identities, features):
- data = []
- for identity in identities:
- # there is no FEAT category
- if identity['category'] == 'FEAT':
- return
- data.extend((identity.get('category'), identity.get('type', ''),
- identity.get('xml:lang', ''), identity.get('name', '')))
- data.append('FEAT')
- data.extend(features)
- data = '\0'.join(data)
- # if there's a need to do more gzip, put that to a function
- string = BytesIO()
- gzip = GzipFile(fileobj=string, mode='w')
- gzip.write(data.encode('utf-8'))
- gzip.close()
- data = string.getvalue()
- self.cur.execute('''
- INSERT INTO caps_cache ( hash_method, hash, data, last_seen )
- VALUES (?, ?, ?, ?);
- ''', (hash_method, hash_, memoryview(data), int(time.time())))
- # (1) -- note above
- self._timeout_commit()
-
- def update_caps_time(self, method, hash_):
- sql = '''UPDATE caps_cache SET last_seen = %d
- WHERE hash_method = "%s" and hash = "%s"''' % \
- (int(time.time()), method, hash_)
- self.simple_commit(sql)
-
- def clean_caps_table(self):
- """
- Remove caps which was not seen for 3 months
- """
- sql = '''DELETE FROM caps_cache WHERE last_seen < %d''' % \
- int(time.time() - 3*30*24*3600)
- self.simple_commit(sql)
-
- def replace_roster(self, account_name, roster_version, roster):
- """
- Replace current roster in DB by a new one
-
- accout_name is the name of the account to change.
- roster_version is the version of the new roster.
- roster is the new version.
- """
- # First we must reset roster_version value to ensure that the server
- # sends back all the roster at the next connexion if the replacement
- # didn't work properly.
- gajim.config.set_per('accounts', account_name, 'roster_version', '')
-
- account_jid = gajim.get_jid_from_account(account_name)
- account_jid_id = self.get_jid_id(account_jid)
-
- # Delete old roster
- self.remove_roster(account_jid)
-
- # Fill roster tables with the new roster
- for jid in roster:
- self.add_or_update_contact(account_jid, jid, roster[jid]['name'],
- roster[jid]['subscription'], roster[jid]['ask'],
- roster[jid]['groups'], commit=False)
- self._timeout_commit()
-
- # At this point, we are sure the replacement works properly so we can
- # set the new roster_version value.
- gajim.config.set_per('accounts', account_name, 'roster_version',
- roster_version)
-
- def del_contact(self, account_jid, jid):
- """
- Remove jid from account_jid roster
- """
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self.cur.execute(
- 'DELETE FROM roster_entry WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- self._timeout_commit()
-
- def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups,
- commit=True):
- """
- Add or update a contact from account_jid roster
- """
- if sub == 'remove':
- self.del_contact(account_jid, jid)
- return
-
- try:
- account_jid_id = self.get_jid_id(account_jid)
- jid_id = self.get_jid_id(jid)
- except exceptions.PysqliteOperationalError as e:
- raise exceptions.PysqliteOperationalError(str(e))
-
- # Update groups information
- # First we delete all previous groups information
- self.cur.execute(
- 'DELETE FROM roster_group WHERE account_jid_id=? AND jid_id=?',
- (account_jid_id, jid_id))
- # Then we add all new groups information
- for group in groups:
- self.cur.execute('INSERT INTO roster_group VALUES(?, ?, ?)',
- (account_jid_id, jid_id, group))
-
- if name is None:
- name = ''
-
- self.cur.execute('REPLACE INTO roster_entry VALUES(?, ?, ?, ?, ?)',
- (account_jid_id, jid_id, name,
- self.convert_human_subscription_values_to_db_api_values(sub),
- bool(ask)))
- if commit:
- self._timeout_commit()
-
- def get_roster(self, account_jid):
- """
- Return the accound_jid roster in NonBlockingRoster format
- """
- data = {}
- account_jid_id = self.get_jid_id(account_jid)
-
- # First we fill data with roster_entry informations
- self.cur.execute('''
- SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
- FROM roster_entry re, jids j
- WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
- for jid, jid_id, name, subscription, ask in self.cur:
- jid = jid
- name = name
- data[jid] = {}
- if name:
- data[jid]['name'] = name
- else:
- data[jid]['name'] = None
- data[jid]['subscription'] = \
- self.convert_db_api_values_to_human_subscription_values(
- subscription)
- data[jid]['groups'] = []
- data[jid]['resources'] = {}
- if ask:
- data[jid]['ask'] = 'subscribe'
- else:
- data[jid]['ask'] = None
- data[jid]['id'] = jid_id
-
- # Then we add group for roster entries
- for jid in data:
- self.cur.execute('''
- SELECT group_name FROM roster_group
- WHERE account_jid_id=? AND jid_id=?''',
- (account_jid_id, data[jid]['id']))
- for (group_name,) in self.cur:
- group_name = group_name
- data[jid]['groups'].append(group_name)
- del data[jid]['id']
-
- return data
-
- def remove_roster(self, account_jid):
- """
- Remove all entry from account_jid roster
- """
- account_jid_id = self.get_jid_id(account_jid)
-
- self.cur.execute('DELETE FROM roster_entry WHERE account_jid_id=?',
- (account_jid_id,))
- self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?',
- (account_jid_id,))
- self._timeout_commit()
-
- def save_if_not_exists(self, with_, direction, tim, msg='', nick=None, additional_data=None):
- if additional_data is None:
- additional_data = {}
- if tim:
- time_col = float(tim)
- else:
- time_col = float(time.time())
- if not msg:
- return
- if self.jid_is_from_pm(with_) or nick:
- # It's a groupchat message
- if nick:
- # It's a message from a groupchat occupent
- type_ = 'gc_msg'
- with_ = with_ + '/' + nick
- else:
- # It's a server message message, we don't log them
- return
- else:
- if direction == 'from':
- type_ = 'chat_msg_recv'
- elif direction == 'to':
- type_ = 'chat_msg_sent'
- jid_id = self.get_jid_id(with_)
- where_sql = 'jid_id = %s AND message=?' % jid_id
- if type_ == 'gc_msg':
- # We cannot differentiate gc message and pm messages, so look in
- # both logs
- with_2 = gajim.get_jid_without_resource(with_)
- if with_ != with_2:
- jid_id2 = self.get_jid_id(with_2)
- where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id,
- jid_id2)
- start_time = time_col - 300 # 5 minutes arrount given time
- end_time = time_col + 300 # 5 minutes arrount given time
- self.cur.execute('''
- SELECT log_line_id FROM logs
- WHERE (%s)
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, start_time, end_time), (msg,))
- results = self.cur.fetchall()
- if results:
- log.debug('Log already in DB, ignoring it')
- return
- log.debug('New log received from server archives, storing it')
- self.write(type_, with_, message=msg, tim=tim,
- additional_data=additional_data, mam_query=True)
-
- def _nec_gc_message_received(self, obj):
- tim_f = float(obj.timestamp)
- tim_int = int(tim_f)
- if gajim.config.should_log(obj.conn.name, obj.jid) and not \
- tim_int < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
- obj.nick:
- # if not obj.nick, it means message comes from room itself
- # usually it hold description and can be send at each connection
- # so don't store it in logs
- self.write('gc_msg', obj.fjid, obj.msgtxt, tim=obj.timestamp, additional_data=obj.additional_data)
- # store in memory time of last message logged.
- # this will also be saved in rooms_last_message_time table
- # when we quit this muc
- obj.conn.last_history_time[obj.jid] = tim_f
diff --git a/src/common/logging_helpers.py b/src/common/logging_helpers.py
deleted file mode 100644
index 30aeddddc..000000000
--- a/src/common/logging_helpers.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/logging_helpers.py
-##
-## Copyright (C) 2009 Bruno Tarquini <btarquini AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import logging
-import os
-import sys
-
-def parseLogLevel(arg):
- """
- Eiter numeric value or level name from logging module
- """
- if arg.isdigit():
- return int(arg)
- elif arg.isupper() and hasattr(logging, arg):
- return getattr(logging, arg)
- else:
- print(_('%s is not a valid loglevel') % repr(arg))
- return 0
-
-def parseLogTarget(arg):
- """
- [gajim.]c.x.y -> gajim.c.x.y
- .other_logger -> other_logger
- <None> -> gajim
- """
- arg = arg.lower()
- if not arg:
- return 'gajim'
- elif arg.startswith('.'):
- return arg[1:]
- elif arg.startswith('gajim'):
- return arg
- else:
- return 'gajim.' + arg
-
-def parseAndSetLogLevels(arg):
- """
- [=]LOGLEVEL -> gajim=LOGLEVEL
- gajim=LOGLEVEL -> gajim=LOGLEVEL
- .other=10 -> other=10
- .=10 -> <nothing>
- c.x.y=c.z=20 -> gajim.c.x.y=20
- gajim.c.z=20
- gajim=10,c.x=20 -> gajim=10
- gajim.c.x=20
- """
- for directive in arg.split(','):
- directive = directive.strip()
- if not directive:
- continue
- if '=' not in directive:
- directive = '=' + directive
- targets, level = directive.rsplit('=', 1)
- level = parseLogLevel(level.strip())
- for target in targets.split('='):
- target = parseLogTarget(target.strip())
- if target:
- logging.getLogger(target).setLevel(level)
- print("Logger %s level set to %d" % (target, level))
-
-
-class colors:
- NONE = chr(27) + "[0m"
- BLACk = chr(27) + "[30m"
- RED = chr(27) + "[31m"
- GREEN = chr(27) + "[32m"
- BROWN = chr(27) + "[33m"
- BLUE = chr(27) + "[34m"
- MAGENTA = chr(27) + "[35m"
- CYAN = chr(27) + "[36m"
- LIGHT_GRAY = chr(27) + "[37m"
- DARK_GRAY = chr(27) + "[30;1m"
- BRIGHT_RED = chr(27) + "[31;1m"
- BRIGHT_GREEN = chr(27) + "[32;1m"
- YELLOW = chr(27) + "[33;1m"
- BRIGHT_BLUE = chr(27) + "[34;1m"
- PURPLE = chr(27) + "[35;1m"
- BRIGHT_CYAN = chr(27) + "[36;1m"
- WHITE = chr(27) + "[37;1m"
-
-def colorize(text, color):
- return color + text + colors.NONE
-
-class FancyFormatter(logging.Formatter):
- """
- An eye-candy formatter with colors
- """
- colors_mapping = {
- 'DEBUG': colors.BLUE,
- 'INFO': colors.GREEN,
- 'WARNING': colors.BROWN,
- 'ERROR': colors.RED,
- 'CRITICAL': colors.BRIGHT_RED,
- }
-
- def __init__(self, fmt, datefmt=None, use_color=False):
- logging.Formatter.__init__(self, fmt, datefmt)
- self.use_color = use_color
-
- def formatTime(self, record, datefmt=None):
- f = logging.Formatter.formatTime(self, record, datefmt)
- if self.use_color:
- f = colorize(f, colors.DARK_GRAY)
- return f
-
- def format(self, record):
- level = record.levelname
- record.levelname = '(%s)' % level[0]
-
- if self.use_color:
- c = FancyFormatter.colors_mapping.get(level, '')
- record.levelname = colorize(record.levelname, c)
- record.name = colorize(record.name, colors.CYAN)
- else:
- record.name += ':'
-
- return logging.Formatter.format(self, record)
-
-
-def init():
- """
- Iinitialize the logging system
- """
- use_color = False
- if os.name != 'nt':
- use_color = sys.stderr.isatty()
-
- consoleloghandler = logging.StreamHandler()
- consoleloghandler.setFormatter(
- FancyFormatter(
- '%(asctime)s %(levelname)s %(name)s %(message)s',
- '%x %H:%M:%S',
- use_color
- )
- )
-
- # fake the root logger so we have 'gajim' root name instead of 'root'
- root_log = logging.getLogger('gajim')
- root_log.setLevel(logging.WARNING)
- root_log.addHandler(consoleloghandler)
- root_log.propagate = False
-
- # handle nbxmpp logs too
- root_log = logging.getLogger('nbxmpp')
- root_log.setLevel(logging.WARNING)
- root_log.addHandler(consoleloghandler)
- root_log.propagate = False
-
-def set_loglevels(loglevels_string):
- parseAndSetLogLevels(loglevels_string)
-
-def set_verbose():
- parseAndSetLogLevels('gajim=DEBUG')
- parseAndSetLogLevels('.nbxmpp=INFO')
-
-def set_quiet():
- parseAndSetLogLevels('gajim=CRITICAL')
- parseAndSetLogLevels('.nbxmpp=CRITICAL')
-
-
-# tests
-if __name__ == '__main__':
- init()
-
- set_loglevels('gajim.c=DEBUG,INFO')
-
- log = logging.getLogger('gajim')
- log.debug('debug')
- log.info('info')
- log.warning('warn')
- log.error('error')
- log.critical('critical')
-
- log = logging.getLogger('gajim.c.x.dispatcher')
- log.debug('debug')
- log.info('info')
- log.warning('warn')
- log.error('error')
- log.critical('critical')
diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py
deleted file mode 100644
index 45c619207..000000000
--- a/src/common/message_archiving.py
+++ /dev/null
@@ -1,459 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/message_archiving.py
-##
-## Copyright (C) 2009 Anaël Verrier <elghinn AT free.fr>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import nbxmpp
-from common import gajim
-from common import ged
-from common import helpers
-from common.connection_handlers_events import ArchivingReceivedEvent
-
-from calendar import timegm
-from time import localtime
-
-import logging
-log = logging.getLogger('gajim.c.message_archiving')
-
-ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived'
-ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived'
-ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived'
-MAM_RESULTS_ARRIVED = 'mam_results_arrived'
-
-class ConnectionArchive:
- def __init__(self):
- pass
-
-
-class ConnectionArchive313(ConnectionArchive):
- def __init__(self):
- ConnectionArchive.__init__(self)
- self.archiving_313_supported = False
- self.mam_awaiting_disco_result = {}
- self.iq_answer = []
- gajim.ged.register_event_handler('archiving-finished-legacy', ged.CORE,
- self._nec_result_finished)
- gajim.ged.register_event_handler('archiving-finished', ged.CORE,
- self._nec_result_finished)
- gajim.ged.register_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error)
- gajim.ged.register_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info)
- gajim.ged.register_event_handler('mam-decrypted-message-received',
- ged.CORE, self._nec_mam_decrypted_message_received)
- gajim.ged.register_event_handler(
- 'archiving-313-preferences-changed-received', ged.CORE,
- self._nec_archiving_313_preferences_changed_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('archiving-finished-legacy', ged.CORE,
- self._nec_result_finished)
- gajim.ged.remove_event_handler('archiving-finished', ged.CORE,
- self._nec_result_finished)
- gajim.ged.remove_event_handler('agent-info-error-received', ged.CORE,
- self._nec_agent_info_error)
- gajim.ged.remove_event_handler('agent-info-received', ged.CORE,
- self._nec_agent_info)
- gajim.ged.remove_event_handler('mam-decrypted-message-received',
- ged.CORE, self._nec_mam_decrypted_message_received)
- gajim.ged.remove_event_handler(
- 'archiving-313-preferences-changed-received', ged.CORE,
- self._nec_archiving_313_preferences_changed_received)
-
- def _nec_archiving_313_preferences_changed_received(self, obj):
- if obj.id in self.iq_answer:
- obj.answer = True
-
- def _nec_agent_info_error(self, obj):
- if obj.jid in self.mam_awaiting_disco_result:
- log.warn('Unable to discover %s, ignoring those logs', obj.jid)
- del self.mam_awaiting_disco_result[obj.jid]
-
- def _nec_agent_info(self, obj):
- if obj.jid in self.mam_awaiting_disco_result:
- for identity in obj.identities:
- if identity['category'] == 'conference':
- # it's a groupchat
- for with_, direction, tim, msg_txt, res in \
- self.mam_awaiting_disco_result[obj.jid]:
- gajim.logger.get_jid_id(with_, 'ROOM')
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=msg_txt, nick=res)
- del self.mam_awaiting_disco_result[obj.jid]
- return
- # it's not a groupchat
- for with_, direction, tim, msg_txt, res in \
- self.mam_awaiting_disco_result[obj.jid]:
- gajim.logger.get_jid_id(with_)
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=msg_txt)
- del self.mam_awaiting_disco_result[obj.jid]
-
- def _nec_result_finished(self, obj):
- if obj.conn.name != self.name:
- return
-
- if obj.queryid not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[obj.queryid][0] == MAM_RESULTS_ARRIVED:
- set_ = obj.fin.getTag('set', namespace=nbxmpp.NS_RSM)
- if set_:
- last = set_.getTagData('last')
- if last:
- gajim.config.set_per('accounts', self.name, 'last_mam_id', last)
- complete = obj.fin.getAttr('complete')
- if complete != 'true':
- self.request_archive(after=last)
- del self.awaiting_answers[obj.queryid]
-
- def _nec_mam_decrypted_message_received(self, obj):
- if obj.conn.name != self.name:
- return
- gajim.logger.save_if_not_exists(obj.with_, obj.direction, obj.tim,
- msg=obj.msgtxt, nick=obj.nick, additional_data=obj.additional_data)
-
- def request_archive(self, start=None, end=None, with_=None, after=None,
- max=30):
- iq_ = nbxmpp.Iq('set')
- query = iq_.addChild('query', namespace=self.archiving_namespace)
- x = query.addChild(node=nbxmpp.DataForm(typ='submit'))
- x.addChild(node=nbxmpp.DataField(typ='hidden', name='FORM_TYPE', value=self.archiving_namespace))
- if start:
- x.addChild(node=nbxmpp.DataField(typ='text-single', name='start', value=start))
- if end:
- x.addChild(node=nbxmpp.DataField(typ='text-single', name='end', value=end))
- if with_:
- x.addChild(node=nbxmpp.DataField(typ='jid-single', name='with', value=with_))
- set_ = query.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- queryid_ = self.connection.getAnID()
- query.setAttr('queryid', queryid_)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[queryid_] = (MAM_RESULTS_ARRIVED, )
- self.connection.send(iq_)
-
- def request_archive_preferences(self):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='get')
- id_ = self.connection.getAnID()
- iq.setID(id_)
- iq.addChild(name='prefs', namespace=self.archiving_namespace)
- self.connection.send(iq)
-
- def set_archive_preferences(self, items, default):
- if not gajim.account_is_connected(self.name):
- return
- iq = nbxmpp.Iq(typ='set')
- id_ = self.connection.getAnID()
- self.iq_answer.append(id_)
- iq.setID(id_)
- prefs = iq.addChild(name='prefs', namespace=self.archiving_namespace, attrs={'default': default})
- always = prefs.addChild(name='always')
- never = prefs.addChild(name='never')
- for item in items:
- jid, preference = item
- if preference == 'always':
- always.addChild(name='jid').setData(jid)
- else:
- never.addChild(name='jid').setData(jid)
- self.connection.send(iq)
-
-class ConnectionArchive136(ConnectionArchive):
- def __init__(self):
- ConnectionArchive.__init__(self)
- self.archiving_136_supported = False
- self.archive_auto_supported = False
- self.archive_manage_supported = False
- self.archive_manual_supported = False
- self.archive_pref_supported = False
- self.auto = None
- self.method_auto = None
- self.method_local = None
- self.method_manual = None
- self.default = None
- self.items = {}
- gajim.ged.register_event_handler(
- 'archiving-preferences-changed-received', ged.CORE,
- self._nec_archiving_changed_received)
- gajim.ged.register_event_handler('raw-iq-received', ged.CORE,
- self._nec_raw_iq_136_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler(
- 'archiving-preferences-changed-received', ged.CORE,
- self._nec_archiving_changed_received)
- gajim.ged.remove_event_handler('raw-iq-received', ged.CORE,
- self._nec_raw_iq_136_received)
-
- def _nec_raw_iq_136_received(self, obj):
- if obj.conn.name != self.name:
- return
-
- id_ = obj.stanza.getID()
- if id_ not in self.awaiting_answers:
- return
-
- if self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED:
- del self.awaiting_answers[id_]
- # TODO
- print('ARCHIVING_COLLECTIONS_ARRIVED')
-
- elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED:
- def save_if_not_exists(with_, nick, direction, tim, payload):
- assert len(payload) == 1, 'got several archiving messages in' +\
- ' the same time %s' % ''.join(payload)
- if payload[0].getName() == 'body':
- gajim.logger.save_if_not_exists(with_, direction, tim,
- msg=payload[0].getData(), nick=nick)
- elif payload[0].getName() == 'message':
- print('Not implemented')
- chat = obj.stanza.getTag('chat')
- if chat:
- with_ = chat.getAttr('with')
- start_ = chat.getAttr('start')
- tim = helpers.datetime_tuple(start_)
- tim = timegm(tim)
- nb = 0
- for element in chat.getChildren():
- try:
- secs = int(element.getAttr('secs'))
- except TypeError:
- secs = 0
- if secs:
- tim += secs
- nick = element.getAttr('name')
- if element.getName() == 'from':
- save_if_not_exists(with_, nick, 'from', localtime(tim),
- element.getPayload())
- nb += 1
- if element.getName() == 'to':
- save_if_not_exists(with_, nick, 'to', localtime(tim),
- element.getPayload())
- nb += 1
- set_ = chat.getTag('set')
- first = set_.getTag('first')
- if first:
- try:
- index = int(first.getAttr('index'))
- except TypeError:
- index = 0
- try:
- count = int(set_.getTagData('count'))
- except TypeError:
- count = 0
- if count > index + nb:
- # Request the next page
- after = element.getTagData('last')
- self.request_collection_page(with_, start_, after=after)
- del self.awaiting_answers[id_]
-
- elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED:
- modified = obj.stanza.getTag('modified')
- if modified:
- for element in modified.getChildren():
- if element.getName() == 'changed':
- with_ = element.getAttr('with')
- start_ = element.getAttr('start')
- self.request_collection_page(with_, start_)
- #elif element.getName() == 'removed':
- # do nothing
- del self.awaiting_answers[id_]
-
- def request_message_archiving_preferences(self):
- iq_ = nbxmpp.Iq('get')
- iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- self.connection.send(iq_)
-
- def set_pref(self, name, **data):
- '''
- data contains names and values of pref name attributes.
- '''
- iq_ = nbxmpp.Iq('set')
- pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- tag = pref.setTag(name)
- for key, value in data.items():
- if value is not None:
- tag.setAttr(key, value)
- self.connection.send(iq_)
-
- def set_auto(self, save):
- self.set_pref('auto', save=save)
-
- def set_method(self, type, use):
- self.set_pref('method', type=type, use=use)
-
- def set_default(self, otr, save, expire=None):
- self.set_pref('default', otr=otr, save=save, expire=expire)
-
- def append_or_update_item(self, jid, otr, save, expire):
- self.set_pref('item', jid=jid, otr=otr, save=save)
-
- def remove_item(self, jid):
- iq_ = nbxmpp.Iq('set')
- itemremove = iq_.setTag('itemremove', namespace=nbxmpp.NS_ARCHIVE)
- item = itemremove.setTag('item')
- item.setAttr('jid', jid)
- self.connection.send(iq_)
-
- def stop_archiving_session(self, thread_id):
- iq_ = nbxmpp.Iq('set')
- pref = iq_.setTag('pref', namespace=nbxmpp.NS_ARCHIVE)
- session = pref.setTag('session', attrs={'thread': thread_id,
- 'save': 'false', 'otr': 'concede'})
- self.connection.send(iq_)
-
- def get_item_pref(self, jid):
- jid = nbxmpp.JID(jid)
- if str(jid) in self.items:
- return self.items[jid]
-
- if jid.getStripped() in self.items:
- return self.items[jid.getStripped()]
-
- if jid.getDomain() in self.items:
- return self.items[jid.getDomain()]
-
- return self.default
-
- def logging_preference(self, jid, initiator_options=None):
- otr = self.get_item_pref(jid)
- if not otr:
- return
- otr = otr['otr']
- if initiator_options:
- if ((initiator_options == ['mustnot'] and otr == 'forbid') or
- (initiator_options == ['may'] and otr == 'require')):
- return None
-
- if (initiator_options == ['mustnot'] or
- (initiator_options[0] == 'mustnot' and
- otr not in ('opppose', 'forbid')) or
- (initiator_options == ['may', 'mustnot'] and
- otr in ('require', 'prefer'))):
- return 'mustnot'
-
- return 'may'
-
- if otr == 'require':
- return ['mustnot']
-
- if otr in ('prefer', 'approve'):
- return ['mustnot', 'may']
-
- if otr in ('concede', 'oppose'):
- return ['may', 'mustnot']
-
- # otr == 'forbid'
- return ['may']
-
- def _ArchiveCB(self, con, iq_obj):
- log.debug('_ArchiveCB %s' % iq_obj.getType())
- gajim.nec.push_incoming_event(ArchivingReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_archiving_changed_received(self, obj):
- if obj.conn.name != self.name:
- return
- for key in ('auto', 'default'):
- if key not in obj.conf:
- self.archiving_136_supported = False
- self.archive_auto_supported = False
- self.archive_manage_supported = False
- self.archive_manual_supported = False
- self.archive_pref_supported = False
- return True
- for key in ('auto', 'method_auto', 'method_local', 'method_manual',
- 'default'):
- if key in obj.conf:
- self.__dict__[key] = obj.conf[key]
-
- for jid, pref in obj.new_items.items():
- self.items[jid] = pref
-
- for jid in obj.removed_items:
- del self.items[jid]
-
- def request_collections_list_page(self, with_='', start=None, end=None,
- after=None, max=30, exact_match=False):
- iq_ = nbxmpp.Iq('get')
- list_ = iq_.setTag('list', namespace=nbxmpp.NS_ARCHIVE)
- if with_:
- list_.setAttr('with', with_)
- if exact_match:
- list_.setAttr('exactmatch', 'true')
- if start:
- list_.setAttr('start', start)
- if end:
- list_.setAttr('end', end)
- set_ = list_.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, )
- self.connection.send(iq_)
-
- def request_collection_page(self, with_, start, end=None, after=None,
- max=30, exact_match=False):
- iq_ = nbxmpp.Iq('get')
- retrieve = iq_.setTag('retrieve', namespace=nbxmpp.NS_ARCHIVE,
- attrs={'with': with_, 'start': start})
- if exact_match:
- retrieve.setAttr('exactmatch', 'true')
- set_ = retrieve.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- if after:
- set_.setTagData('after', after)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, )
- self.connection.send(iq_)
-
- def remove_collection(self, with_='', start=None, end=None,
- exact_match=False, open=False):
- iq_ = nbxmpp.Iq('set')
- remove = iq_.setTag('remove', namespace=nbxmpp.NS_ARCHIVE)
- if with_:
- remove.setAttr('with', with_)
- if exact_match:
- remove.setAttr('exactmatch', 'true')
- if start:
- remove.setAttr('start', start)
- if end:
- remove.setAttr('end', end)
- if open:
- remove.setAttr('open', 'true')
- self.connection.send(iq_)
-
- def request_modifications_page(self, start, max=30):
- iq_ = nbxmpp.Iq('get')
- moified = iq_.setTag('modified', namespace=nbxmpp.NS_ARCHIVE,
- attrs={'start': start})
- set_ = moified.setTag('set', namespace=nbxmpp.NS_RSM)
- set_.setTagData('max', max)
- id_ = self.connection.getAnID()
- iq_.setID(id_)
- self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, )
- self.connection.send(iq_)
diff --git a/src/common/multimedia_helpers.py b/src/common/multimedia_helpers.py
deleted file mode 100644
index 19e21ed72..000000000
--- a/src/common/multimedia_helpers.py
+++ /dev/null
@@ -1,111 +0,0 @@
-##
-## Copyright (C) 2009 Thibaut GIRKA <thib AT sitedethib.com>
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-
-import gi
-gi.require_version('Gst', '1.0')
-from gi.repository import Gst
-
-
-class DeviceManager(object):
- def __init__(self):
- self.devices = {}
-
- def detect(self):
- self.devices = {}
-
- def get_devices(self):
- if not self.devices:
- self.detect()
- return self.devices
-
- def detect_element(self, name, text, pipe='%s'):
- if Gst.ElementFactory.find(name):
- element = Gst.ElementFactory.make(name, '%spresencetest' % name)
- if hasattr(element.props, 'device'):
- element.set_state(Gst.State.READY)
- devices = element.get_properties('device')
- if devices:
- self.devices[text % _('Default device')] = pipe % name
- for device in devices:
- element.set_state(Gst.State.NULL)
- element.set_property('device', device)
- element.set_state(Gst.State.READY)
- device_name = element.get_property('device-name')
- self.devices[text % device_name] = pipe % \
- '%s device=%s' % (name, device)
- element.set_state(Gst.State.NULL)
- else:
- self.devices[text] = pipe % name
- else:
- print('element \'%s\' not found' % name)
-
-
-class AudioInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('audiotestsrc', _('Audio test'),
- '%s is-live=true name=gajim_vol')
- # Auto src
- self.detect_element('autoaudiosrc', _('Autodetect'),
- '%s ! volume name=gajim_vol')
- # Alsa src
- self.detect_element('alsasrc', _('ALSA: %s'),
- '%s ! volume name=gajim_vol')
- # Pulseaudio src
- self.detect_element('pulsesrc', _('Pulse: %s'),
- '%s ! volume name=gajim_vol')
-
-
-class AudioOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake sink
- self.detect_element('fakesink', _('Fake audio output'))
- # Auto sink
- self.detect_element('autoaudiosink', _('Autodetect'))
- # Alsa sink
- self.detect_element('alsasink', _('ALSA: %s'), '%s sync=false')
- # Pulseaudio sink
- self.detect_element('pulsesink', _('Pulse: %s'), '%s sync=true')
-
-
-class VideoInputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Test src
- self.detect_element('videotestsrc', _('Video test'),
- '%s is-live=true ! video/x-raw,framerate=10/1')
- # Auto src
- self.detect_element('autovideosrc', _('Autodetect'))
- # V4L2 src
- self.detect_element('v4l2src', _('V4L2: %s'))
- # Funny things, just to test...
- # self.devices['GOOM'] = 'audiotestsrc ! goom'
- self.detect_element('ximagesrc', _('Screen'), '%s ! ffmpegcolorspace')
-
-
-class VideoOutputManager(DeviceManager):
- def detect(self):
- self.devices = {}
- # Fake video output
- self.detect_element('fakesink', _('Fake video output'))
- # Auto sink
- self.detect_element('xvimagesink',
- _('X Window System (X11/XShm/Xv): %s'))
- # ximagesink
- self.detect_element('ximagesink', _('X Window System (without Xv)'))
- self.detect_element('autovideosink', _('Autodetect'))
-
diff --git a/src/common/nec.py b/src/common/nec.py
deleted file mode 100644
index b699085c1..000000000
--- a/src/common/nec.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-Network Events Controller.
-
-:author: Mateusz Biliński <mateusz@bilinski.it>
-:since: 10th August 2008
-:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
-:copyright: Copyright (2011) Yann Leboulanger <asterix@lagaule.org>
-:license: GPL
-'''
-
-#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`
- '''
- self.outgoing_events_generators = {}
- '''
- Keys: names of events
- Values: list of class objects that are subclasses
- of `NetworkOutgoingEvent`
- '''
-
- 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):
- for base_event_name in event_class.base_network_events:
- event_list = self.outgoing_events_generators.setdefault(
- base_event_name, [])
- if not event_class in event_list:
- event_list.append(event_class)
-
- def unregister_outgoing_event(self, event_class):
- for base_event_name in event_class.base_network_events:
- if base_event_name in self.outgoing_events_generators:
- self.outgoing_events_generators[base_event_name].remove(
- event_class)
-
- def push_incoming_event(self, event_object):
- if event_object.generate():
- if not gajim.ged.raise_event(event_object.name, event_object):
- self._generate_events_based_on_incoming_event(event_object)
-
- def push_outgoing_event(self, event_object):
- if event_object.generate():
- if not gajim.ged.raise_event(event_object.name, event_object):
- self._generate_events_based_on_outgoing_event(event_object)
-
- 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 not gajim.ged.raise_event(new_event_object.name,
- new_event_object):
- self._generate_events_based_on_incoming_event(
- new_event_object)
-
- def _generate_events_based_on_outgoing_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.outgoing_events_generators:
- for new_event_class in self.outgoing_events_generators[
- base_event_name]:
- new_event_object = new_event_class(None,
- base_event=event_object)
- if new_event_object.generate():
- if not gajim.ged.raise_event(new_event_object.name,
- new_event_object):
- self._generate_events_based_on_outgoing_event(
- new_event_object)
-
-class NetworkEvent(object):
- name = ''
-
- def __init__(self, new_name, **kwargs):
- if new_name:
- self.name = new_name
-
- self.init()
-
- self._set_kwargs_as_attributes(**kwargs)
-
- 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.items():
- setattr(self, k, v)
-
-
-class NetworkIncomingEvent(NetworkEvent):
- base_network_events = []
- '''
- Names of base network events that new event is going to be generated on.
- '''
-
-
-class NetworkOutgoingEvent(NetworkEvent):
- base_network_events = []
- '''
- Names of base network events that new event is going to be generated on.
- '''
diff --git a/src/common/optparser.py b/src/common/optparser.py
deleted file mode 100644
index bdd550cfa..000000000
--- a/src/common/optparser.py
+++ /dev/null
@@ -1,1001 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/optparser.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Brendan Taylor <whateley AT gmail.com>
-## Tomasz Melcer <liori AT exroot.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import sys
-import re
-from time import time
-from common import gajim
-from common import helpers
-from common import caps_cache
-
-import sqlite3 as sqlite
-from common import logger
-
-import logging
-log = logging.getLogger('gajim.c.optparser')
-
-class OptionsParser:
- def __init__(self, filename):
- self.__filename = os.path.realpath(filename)
- self.old_values = {} # values that are saved in the file and maybe
- # no longer valid
-
- def read(self):
- try:
- fd = open(self.__filename)
- except Exception:
- if os.path.exists(self.__filename):
- #we talk about a file
- print(_('Error: cannot open %s for reading') % self.__filename,
- file=sys.stderr)
- return False
-
- new_version = gajim.config.get('version')
- new_version = new_version.split('-', 1)[0]
- seen = set()
- regex = re.compile(r"(?P<optname>[^.=]+)(?:(?:\.(?P<key>.+))?\.(?P<subname>[^.=]+))?\s=\s(?P<value>.*)")
-
- for line in fd:
- match = regex.match(line)
- if match is None:
- log.warn('Invalid configuration line, ignoring it: %s', line)
- continue
- optname, key, subname, value = match.groups()
- if key is None:
- self.old_values[optname] = value
- gajim.config.set(optname, value)
- else:
- if (optname, key) not in seen:
- if optname in self.old_values:
- self.old_values[optname][key] = {}
- else:
- self.old_values[optname] = {key: {}}
- gajim.config.add_per(optname, key)
- seen.add((optname, key))
- self.old_values[optname][key][subname] = value
- gajim.config.set_per(optname, key, subname, value)
-
- old_version = gajim.config.get('version')
- old_version = old_version.split('-', 1)[0]
-
- self.update_config(old_version, new_version)
- self.old_values = {} # clean mem
-
- fd.close()
- return True
-
- def write_line(self, fd, opt, parents, value):
- if value is None:
- return
- # convert to utf8 before writing to file if needed
- value = str(value)
- s = ''
- if parents:
- if len(parents) == 1:
- return
- for p in parents:
- s += p + '.'
- s += opt
- fd.write(s + ' = ' + value + '\n')
-
- def write(self):
- (base_dir, filename) = os.path.split(self.__filename)
- self.__tempfile = os.path.join(base_dir, '.' + filename)
- try:
- f = os.fdopen(os.open(self.__tempfile,
- os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0o600), 'w')
- except IOError as e:
- return str(e)
- try:
- gajim.config.foreach(self.write_line, f)
- except IOError as e:
- return str(e)
- f.flush()
- os.fsync(f.fileno())
- f.close()
- if os.path.exists(self.__filename):
- if os.name == 'nt':
- # win32 needs this
- try:
- os.remove(self.__filename)
- except Exception:
- pass
- try:
- os.rename(self.__tempfile, self.__filename)
- except IOError as e:
- return str(e)
-
- def update_config(self, old_version, new_version):
- old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y)
- old = []
- while len(old_version_list):
- old.append(int(old_version_list.pop(0)))
- new_version_list = new_version.split('.')
- new = []
- while len(new_version_list):
- new.append(int(new_version_list.pop(0)))
-
- if old < [0, 9] and new >= [0, 9]:
- self.update_config_x_to_09()
- if old < [0, 10] and new >= [0, 10]:
- self.update_config_09_to_010()
- if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
- self.update_config_to_01011()
- if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
- self.update_config_to_01012()
- if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
- self.update_config_to_01013()
- if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]:
- self.update_config_to_01014()
- if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]:
- self.update_config_to_01015()
- if old < [0, 10, 1, 6] and new >= [0, 10, 1, 6]:
- self.update_config_to_01016()
- if old < [0, 10, 1, 7] and new >= [0, 10, 1, 7]:
- self.update_config_to_01017()
- if old < [0, 10, 1, 8] and new >= [0, 10, 1, 8]:
- self.update_config_to_01018()
- if old < [0, 11, 0, 1] and new >= [0, 11, 0, 1]:
- self.update_config_to_01101()
- if old < [0, 11, 0, 2] and new >= [0, 11, 0, 2]:
- self.update_config_to_01102()
- if old < [0, 11, 1, 1] and new >= [0, 11, 1, 1]:
- self.update_config_to_01111()
- if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]:
- self.update_config_to_01112()
- if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]:
- self.update_config_to_01113()
- if old < [0, 11, 1, 4] and new >= [0, 11, 1, 4]:
- self.update_config_to_01114()
- if old < [0, 11, 1, 5] and new >= [0, 11, 1, 5]:
- self.update_config_to_01115()
- if old < [0, 11, 2, 1] and new >= [0, 11, 2, 1]:
- self.update_config_to_01121()
- if old < [0, 11, 4, 1] and new >= [0, 11, 4, 1]:
- self.update_config_to_01141()
- if old < [0, 11, 4, 2] and new >= [0, 11, 4, 2]:
- self.update_config_to_01142()
- if old < [0, 11, 4, 3] and new >= [0, 11, 4, 3]:
- self.update_config_to_01143()
- if old < [0, 11, 4, 4] and new >= [0, 11, 4, 4]:
- self.update_config_to_01144()
- if old < [0, 12, 0, 1] and new >= [0, 12, 0, 1]:
- self.update_config_to_01201()
- if old < [0, 12, 1, 1] and new >= [0, 12, 1, 1]:
- self.update_config_to_01211()
- if old < [0, 12, 1, 2] and new >= [0, 12, 1, 2]:
- self.update_config_to_01212()
- if old < [0, 12, 1, 3] and new >= [0, 12, 1, 3]:
- self.update_config_to_01213()
- if old < [0, 12, 1, 4] and new >= [0, 12, 1, 4]:
- self.update_config_to_01214()
- if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]:
- self.update_config_to_01215()
- if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]:
- self.update_config_to_01231()
- if old < [0, 12, 5, 1] and new >= [0, 12, 5, 1]:
- self.update_config_from_0125()
- self.update_config_to_01251()
- if old < [0, 12, 5, 2] and new >= [0, 12, 5, 2]:
- self.update_config_to_01252()
- if old < [0, 12, 5, 3] and new >= [0, 12, 5, 3]:
- self.update_config_to_01253()
- if old < [0, 12, 5, 4] and new >= [0, 12, 5, 4]:
- self.update_config_to_01254()
- if old < [0, 12, 5, 5] and new >= [0, 12, 5, 5]:
- self.update_config_to_01255()
- if old < [0, 12, 5, 6] and new >= [0, 12, 5, 6]:
- self.update_config_to_01256()
- if old < [0, 12, 5, 7] and new >= [0, 12, 5, 7]:
- self.update_config_to_01257()
- if old < [0, 12, 5, 8] and new >= [0, 12, 5, 8]:
- self.update_config_to_01258()
- if old < [0, 13, 10, 0] and new >= [0, 13, 10, 0]:
- self.update_config_to_013100()
- if old < [0, 13, 10, 1] and new >= [0, 13, 10, 1]:
- self.update_config_to_013101()
- if old < [0, 13, 90, 1] and new >= [0, 13, 90, 1]:
- self.update_config_to_013901()
- if old < [0, 14, 0, 1] and new >= [0, 14, 0, 1]:
- self.update_config_to_01401()
- if old < [0, 14, 90, 0] and new >= [0, 14, 90, 0]:
- self.update_config_to_014900()
- if old < [0, 16, 0, 1] and new >= [0, 16, 0, 1]:
- self.update_config_to_01601()
- if old < [0, 16, 4, 1] and new >= [0, 16, 4, 1]:
- self.update_config_to_01641()
- if old < [0, 16, 10, 1] and new >= [0, 16, 10, 1]:
- self.update_config_to_016101()
- if old < [0, 16, 10, 2] and new >= [0, 16, 10, 2]:
- self.update_config_to_016102()
- if old < [0, 16, 10, 3] and new >= [0, 16, 10, 3]:
- self.update_config_to_016103()
-
- gajim.logger.init_vars()
- gajim.logger.attach_cache_database()
- gajim.config.set('version', new_version)
-
- caps_cache.capscache.initialize_from_db()
-
- @staticmethod
- def assert_unread_msgs_table_exists():
- """
- Create table unread_messages if there is no such table
- """
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE unread_messages (
- message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
- jid_id INTEGER
- );
- '''
- )
- con.commit()
- gajim.logger.init_vars()
- except sqlite.OperationalError:
- pass
- con.close()
-
- @staticmethod
- def update_ft_proxies(to_remove=None, to_add=None):
- if to_remove is None:
- to_remove = []
- if to_add is None:
- to_add = []
- for account in gajim.config.get_per('accounts'):
- proxies_str = gajim.config.get_per('accounts', account,
- 'file_transfer_proxies')
- proxies = [p.strip() for p in proxies_str.split(',')]
- for wrong_proxy in to_remove:
- if wrong_proxy in proxies:
- proxies.remove(wrong_proxy)
- for new_proxy in to_add:
- if new_proxy not in proxies:
- proxies.append(new_proxy)
- proxies_str = ', '.join(proxies)
- gajim.config.set_per('accounts', account, 'file_transfer_proxies',
- proxies_str)
-
- def update_config_x_to_09(self):
- # Var name that changed:
- # avatar_width /height -> chat_avatar_width / height
- if 'avatar_width' in self.old_values:
- gajim.config.set('chat_avatar_width', self.old_values['avatar_width'])
- if 'avatar_height' in self.old_values:
- gajim.config.set('chat_avatar_height', self.old_values['avatar_height'])
- if 'use_dbus' in self.old_values:
- gajim.config.set('remote_control', self.old_values['use_dbus'])
- # always_compact_view -> always_compact_view_chat / _gc
- if 'always_compact_view' in self.old_values:
- gajim.config.set('always_compact_view_chat',
- self.old_values['always_compact_view'])
- gajim.config.set('always_compact_view_gc',
- self.old_values['always_compact_view'])
- # new theme: grocery, plain
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- for theme_name in (_('grocery'), _('default')):
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- # Remove cyan theme if it's not the current theme
- if 'cyan' in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', 'cyan')
- if _('cyan') in gajim.config.get_per('themes'):
- gajim.config.del_per('themes', _('cyan'))
- # If we removed our roster_theme, choose the default green one or another
- # one if doesn't exists in config
- if gajim.config.get('roster_theme') not in gajim.config.get_per('themes'):
- theme = _('green')
- if theme not in gajim.config.get_per('themes'):
- theme = gajim.config.get_per('themes')[0]
- gajim.config.set('roster_theme', theme)
- # new proxies in accounts.name.file_transfer_proxies
- self.update_ft_proxies(to_add=['proxy.netlab.cz'])
-
- gajim.config.set('version', '0.9')
-
- def update_config_09_to_010(self):
- if 'usetabbedchat' in self.old_values and not \
- self.old_values['usetabbedchat']:
- gajim.config.set('one_message_window', 'never')
- if 'autodetect_browser_mailer' in self.old_values and \
- self.old_values['autodetect_browser_mailer'] is True:
- gajim.config.set('autodetect_browser_mailer', False)
- if 'useemoticons' in self.old_values and \
- not self.old_values['useemoticons']:
- gajim.config.set('emoticons_theme', '')
- if 'always_compact_view_chat' in self.old_values and \
- self.old_values['always_compact_view_chat'] != 'False':
- gajim.config.set('always_hide_chat_buttons', True)
- if 'always_compact_view_gc' in self.old_values and \
- self.old_values['always_compact_view_gc'] != 'False':
- gajim.config.set('always_hide_groupchat_buttons', True)
-
- self.update_ft_proxies(to_remove=['proxy65.jabber.autocom.pl',
- 'proxy65.jabber.ccc.de'], to_add=['transfer.jabber.freenet.de'])
- # create unread_messages table if needed
- self.assert_unread_msgs_table_exists()
-
- gajim.config.set('version', '0.10')
-
- def update_config_to_01011(self):
- if 'print_status_in_muc' in self.old_values and \
- self.old_values['print_status_in_muc'] in (True, False):
- gajim.config.set('print_status_in_muc', 'in_and_out')
- gajim.config.set('version', '0.10.1.1')
-
- def update_config_to_01012(self):
- # See [6456]
- if 'emoticons_theme' in self.old_values and \
- self.old_values['emoticons_theme'] == 'Disabled':
- gajim.config.set('emoticons_theme', '')
- gajim.config.set('version', '0.10.1.2')
-
- def update_config_to_01013(self):
- """
- Create table transports_cache if there is no such table
- """
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE transports_cache (
- transport TEXT UNIQUE,
- type INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.3')
-
- def update_config_to_01014(self):
- """
- Apply indeces to the logs database
- """
- print(_('migrating logs database to indices'))
- # FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- # apply indeces
- try:
- cur.executescript(
- '''
- CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
- CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
- '''
- )
-
- con.commit()
- except Exception:
- pass
- con.close()
- gajim.config.set('version', '0.10.1.4')
-
- def update_config_to_01015(self):
- """
- Clean show values in logs database
- """
- #FIXME see #2812
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \
- logger.constants.__dict__.keys() if i.startswith('SHOW_'))
- for show in status:
- cur.execute('update logs set show = ? where show = ?;', (status[show],
- show))
- cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);')
- con.commit()
- cur.close() # remove this in 2007 [pysqlite old versions need this]
- con.close()
- gajim.config.set('version', '0.10.1.5')
-
- def update_config_to_01016(self):
- """
- #2494 : Now we play gc_received_message sound even if
- notify_on_all_muc_messages is false. Keep precedent behaviour
- """
- if 'notify_on_all_muc_messages' in self.old_values and \
- self.old_values['notify_on_all_muc_messages'] == 'False' and \
- gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
- gajim.config.set_per('soundevents',\
- 'muc_message_received', 'enabled', False)
- gajim.config.set('version', '0.10.1.6')
-
- def update_config_to_01017(self):
- """
- trayicon_notification_on_new_messages -> trayicon_notification_on_events
- """
- if 'trayicon_notification_on_new_messages' in self.old_values:
- gajim.config.set('trayicon_notification_on_events',
- self.old_values['trayicon_notification_on_new_messages'])
- gajim.config.set('version', '0.10.1.7')
-
- def update_config_to_01018(self):
- """
- chat_state_notifications -> outgoing_chat_state_notifications
- """
- if 'chat_state_notifications' in self.old_values:
- gajim.config.set('outgoing_chat_state_notifications',
- self.old_values['chat_state_notifications'])
- gajim.config.set('version', '0.10.1.8')
-
- def update_config_to_01101(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'before_time' in self.old_values:
- gajim.config.set('time_stamp', '%s%%X%s ' % (
- self.old_values['before_time'], self.old_values['after_time']))
- gajim.config.set('version', '0.11.0.1')
-
- def update_config_to_01102(self):
- """
- Fill time_stamp from before_time and after_time
- """
- if 'ft_override_host_to_send' in self.old_values:
- gajim.config.set('ft_add_hosts_to_send',
- self.old_values['ft_override_host_to_send'])
- gajim.config.set('version', '0.11.0.2')
-
- def update_config_to_01111(self):
- """
- Always_hide_chatbuttons -> compact_view
- """
- if 'always_hide_groupchat_buttons' in self.old_values and \
- 'always_hide_chat_buttons' in self.old_values:
- gajim.config.set('compact_view', self.old_values['always_hide_groupchat_buttons'] and \
- self.old_values['always_hide_chat_buttons'])
- gajim.config.set('version', '0.11.1.1')
-
- def update_config_to_01112(self):
- """
- GTK+ theme is renamed to default
- """
- if 'roster_theme' in self.old_values and \
- self.old_values['roster_theme'] == 'gtk+':
- gajim.config.set('roster_theme', _('default'))
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01113(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.3')
-
- def update_config_to_01114(self):
- # add default theme if it doesn't exist
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor', 'groupfont',
- 'groupfontattrs', 'contacttextcolor', 'contactbgcolor', 'contactfont',
- 'contactfontattrs', 'bannertextcolor', 'bannerbgcolor', 'bannerfont',
- 'bannerfontattrs']
- theme_name = _('default')
- if theme_name not in gajim.config.get_per('themes'):
- gajim.config.add_per('themes', theme_name)
- if gajim.config.get_per('themes', 'gtk+'):
- # copy from old gtk+ theme
- for o in d:
- val = gajim.config.get_per('themes', 'gtk+', o)
- gajim.config.set_per('themes', theme_name, o, val)
- gajim.config.del_per('themes', 'gtk+')
- else:
- # copy from default theme
- theme = gajim.config.themes_default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o, theme[d.index(o)])
- gajim.config.set('version', '0.11.1.4')
-
- def update_config_to_01115(self):
- # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DELETE FROM caps_cache;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.1.5')
-
- def update_config_to_01121(self):
- # remove old unencrypted secrets file
- from common.configpaths import gajimpaths
-
- new_file = gajimpaths['SECRETS_FILE']
-
- old_file = os.path.dirname(new_file) + '/secrets'
-
- if os.path.exists(old_file):
- os.remove(old_file)
-
- gajim.config.set('version', '0.11.2.1')
-
- def update_config_to_01141(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS caps_cache (
- node TEXT,
- ver TEXT,
- ext TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.1')
-
- def update_config_to_01142(self):
- """
- next_message_received sound event is splittedin 2 events
- """
- gajim.config.add_per('soundevents', 'next_message_received_focused')
- gajim.config.add_per('soundevents', 'next_message_received_unfocused')
- if gajim.config.get_per('soundevents', 'next_message_received'):
- enabled = gajim.config.get_per('soundevents', 'next_message_received',
- 'enabled')
- path = gajim.config.get_per('soundevents', 'next_message_received',
- 'path')
- gajim.config.del_per('soundevents', 'next_message_received')
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'enabled', enabled)
- gajim.config.set_per('soundevents', 'next_message_received_focused',
- 'path', path)
- gajim.config.set('version', '0.11.1.2')
-
- def update_config_to_01143(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS rooms_last_message_time(
- jid_id INTEGER PRIMARY KEY UNIQUE,
- time INTEGER
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.3')
-
- def update_config_to_01144(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript('DROP TABLE caps_cache;')
- con.commit()
- except sqlite.OperationalError:
- pass
- try:
- cur.executescript(
- '''
- CREATE TABLE caps_cache (
- hash_method TEXT,
- hash TEXT,
- data BLOB
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.11.4.4')
-
- def update_config_to_01201(self):
- if 'uri_schemes' in self.old_values:
- new_values = self.old_values['uri_schemes'].replace(' mailto', '').\
- replace(' xmpp', '')
- gajim.config.set('uri_schemes', new_values)
- gajim.config.set('version', '0.12.0.1')
-
- def update_config_to_01211(self):
- if 'trayicon' in self.old_values:
- if self.old_values['trayicon'] == 'False':
- gajim.config.set('trayicon', 'never')
- else:
- gajim.config.set('trayicon', 'always')
- gajim.config.set('version', '0.12.1.1')
-
- def update_config_to_01212(self):
- for opt in ('ignore_unknown_contacts', 'send_os_info',
- 'log_encrypted_sessions'):
- if opt in self.old_values:
- val = self.old_values[opt]
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, opt, val)
- gajim.config.set('version', '0.12.1.2')
-
- def update_config_to_01213(self):
- msgs = gajim.config.statusmsg_default
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name in msgs:
- gajim.config.set_per('statusmsg', msg_name, 'activity',
- msgs[msg_name][1])
- gajim.config.set_per('statusmsg', msg_name, 'subactivity',
- msgs[msg_name][2])
- gajim.config.set_per('statusmsg', msg_name, 'activity_text',
- msgs[msg_name][3])
- gajim.config.set_per('statusmsg', msg_name, 'mood',
- msgs[msg_name][4])
- gajim.config.set_per('statusmsg', msg_name, 'mood_text',
- msgs[msg_name][5])
- gajim.config.set('version', '0.12.1.3')
-
- def update_config_to_01214(self):
- for status in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible',
- 'offline']:
- if 'last_status_msg_' + status in self.old_values:
- gajim.config.add_per('statusmsg', '_last_' + status)
- gajim.config.set_per('statusmsg', '_last_' + status, 'message',
- self.old_values['last_status_msg_' + status])
- gajim.config.set('version', '0.12.1.4')
-
- def update_config_to_01215(self):
- """
- Remove hardcoded ../data/sounds from config
- """
- dirs = ['../data', gajim.gajimpaths.data_root, gajim.DATA_DIR]
- if os.name != 'nt':
- dirs.append(os.path.expanduser('~/.gajim'))
- for evt in gajim.config.get_per('soundevents'):
- path = gajim.config.get_per('soundevents', evt, 'path')
- # absolute and relative passes are necessary
- path = helpers.strip_soundfile_path(path, dirs, abs=False)
- path = helpers.strip_soundfile_path(path, dirs, abs=True)
- gajim.config.set_per('soundevents', evt, 'path', path)
- gajim.config.set('version', '0.12.1.5')
-
- def update_config_to_01231(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- CREATE TABLE IF NOT EXISTS roster_entry(
- account_jid_id INTEGER,
- jid_id INTEGER,
- name TEXT,
- subscription INTEGER,
- ask BOOLEAN,
- PRIMARY KEY (account_jid_id, jid_id)
- );
-
- CREATE TABLE IF NOT EXISTS roster_group(
- account_jid_id INTEGER,
- jid_id INTEGER,
- group_name TEXT,
- PRIMARY KEY (account_jid_id, jid_id, group_name)
- );
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.3.1')
-
- def update_config_from_0125(self):
- # All those functions need to be called for 0.12.5 to 0.13 transition
- self.update_config_to_01211()
- self.update_config_to_01213()
- self.update_config_to_01214()
- self.update_config_to_01215()
- self.update_config_to_01231()
-
- def update_config_to_01251(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE unread_messages
- ADD shown BOOLEAN default 0;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.12.5.1')
-
- def update_config_to_01252(self):
- if 'alwaysauth' in self.old_values:
- val = self.old_values['alwaysauth']
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, 'autoauth', val)
- gajim.config.set('version', '0.12.5.2')
-
- def update_config_to_01253(self):
- if 'enable_zeroconf' in self.old_values:
- val = self.old_values['enable_zeroconf']
- for account in gajim.config.get_per('accounts'):
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- gajim.config.set_per('accounts', account, 'active', val)
- else:
- gajim.config.set_per('accounts', account, 'active', True)
- gajim.config.set('version', '0.12.5.3')
-
- def update_config_to_01254(self):
- vals = {'inmsgcolor': ['#a34526', '#a40000'],
- 'outmsgcolor': ['#164e6f', '#3465a4'],
- 'restored_messages_color': ['grey', '#555753'],
- 'statusmsgcolor': ['#1eaa1e', '#73d216'],
- 'urlmsgcolor': ['#0000ff', '#204a87'],
- 'gc_nicknames_colors': ['#a34526:#c000ff:#0012ff:#388a99:#045723:#7c7c7c:#ff8a00:#94452d:#244b5a:#32645a', '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.4')
-
- def update_config_to_01255(self):
- vals = {'statusmsgcolor': ['#73d216', '#4e9a06'],
- 'outmsgtxtcolor': ['#a2a2a2', '#555753']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.5')
-
- def update_config_to_01256(self):
- vals = {'gc_nicknames_colors': ['#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', '#f57900:#ce5c00:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000']}
- for c in vals:
- if c not in self.old_values:
- continue
- val = self.old_values[c]
- if val == vals[c][0]:
- # We didn't change default value, so update it with new default
- gajim.config.set(c, vals[c][1])
- gajim.config.set('version', '0.12.5.6')
-
- def update_config_to_01257(self):
- if 'iconset' in self.old_values:
- if self.old_values['iconset'] in ('nuvola', 'crystal', 'gossip',
- 'simplebulb', 'stellar'):
- gajim.config.set('iconset', gajim.config.DEFAULT_ICONSET)
- gajim.config.set('version', '0.12.5.7')
-
- def update_config_to_01258(self):
- self.update_ft_proxies(to_remove=['proxy65.talkonaut.com',
- 'proxy.jabber.org', 'proxy.netlab.cz', 'transfer.jabber.freenet.de',
- 'proxy.jabber.cd.chalmers.se'], to_add=['proxy.eu.jabber.org',
- 'proxy.jabber.ru', 'proxy.jabbim.cz'])
- gajim.config.set('version', '0.12.5.8')
-
- def update_config_to_013100(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE caps_cache
- ADD last_seen INTEGER default %d;
- ''' % int(time())
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.0')
-
- def update_config_to_013101(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- DROP INDEX IF EXISTS idx_logs_jid_id_kind;
-
- CREATE INDEX IF NOT EXISTS
- idx_logs_jid_id_time ON logs (jid_id, time DESC);
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.13.10.1')
-
- def update_config_to_013901(self):
- schemes = 'aaa:// aaas:// acap:// cap:// cid: crid:// data: dav: dict:// dns: fax: file:/ ftp:// geo: go: gopher:// h323: http:// https:// iax: icap:// im: imap:// info: ipp:// iris: iris.beep: iris.xpc: iris.xpcs: iris.lwz: ldap:// mid: modem: msrp:// msrps:// mtqp:// mupdate:// news: nfs:// nntp:// opaquelocktoken: pop:// pres: prospero:// rtsp:// service: shttp:// sip: sips: sms: snmp:// soap.beep:// soap.beeps:// tag: tel: telnet:// tftp:// thismessage:/ tip:// tv: urn:// vemmi:// xmlrpc.beep:// xmlrpc.beeps:// z39.50r:// z39.50s:// about: apt: cvs:// daap:// ed2k:// feed: fish:// git:// iax2: irc:// ircs:// ldaps:// magnet: mms:// rsync:// ssh:// svn:// sftp:// smb:// webcal://'
- gajim.config.set('uri_schemes', schemes)
- gajim.config.set('version', '0.13.90.1')
-
- def update_config_to_01401(self):
- if 'autodetect_browser_mailer' not in self.old_values or 'openwith' \
- not in self.old_values or \
- (self.old_values['autodetect_browser_mailer'] == False and \
- self.old_values['openwith'] != 'custom'):
- gajim.config.set('autodetect_browser_mailer', True)
- gajim.config.set('openwith', gajim.config.DEFAULT_OPENWITH)
- gajim.config.set('version', '0.14.0.1')
-
- def update_config_to_014900(self):
- if 'use_stun_server' in self.old_values and self.old_values[
- 'use_stun_server'] and not self.old_values['stun_server']:
- gajim.config.set('use_stun_server', False)
- if os.name == 'nt':
- gajim.config.set('autodetect_browser_mailer', True)
-
- def update_config_to_01601(self):
- if 'last_mam_id' in self.old_values:
- last_mam_id = self.old_values['last_mam_id']
- for account in gajim.config.get_per('accounts'):
- gajim.config.set_per('accounts', account, 'last_mam_id',
- last_mam_id)
- gajim.config.set('version', '0.16.0.1')
-
- def update_config_to_01641(self):
- for account in self.old_values['accounts'].keys():
- connection_types = self.old_values['accounts'][account][
- 'connection_types'].split()
- if 'plain' in connection_types and len(connection_types) > 1:
- connection_types.remove('plain')
- gajim.config.set_per('accounts', account, 'connection_types',
- ' '.join(connection_types))
- gajim.config.set('version', '0.16.4.1')
-
- def update_config_to_016101(self):
- if 'video_input_device' in self.old_values:
- if self.old_values['video_input_device'] == 'autovideosrc ! videoscale ! ffmpegcolorspace':
- gajim.config.set('video_input_device', 'autovideosrc')
- if self.old_values['video_input_device'] == 'videotestsrc is-live=true ! video/x-raw-yuv,framerate=10/1':
- gajim.config.set('video_input_device', 'videotestsrc is-live=true ! video/x-raw,framerate=10/1')
- gajim.config.set('version', '0.16.10.1')
-
- def update_config_to_016102(self):
- for account in self.old_values['accounts'].keys():
- gajim.config.del_per('accounts', account, 'minimized_gc')
-
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE logs ADD COLUMN 'additional_data' TEXT DEFAULT '{}';
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
-
- gajim.config.set('version', '0.16.10.2')
-
- def update_config_to_016103(self):
- back = os.getcwd()
- os.chdir(logger.LOG_DB_FOLDER)
- con = sqlite.connect(logger.LOG_DB_FILE)
- os.chdir(back)
- cur = con.cursor()
- try:
- cur.executescript(
- '''
- ALTER TABLE logs ADD COLUMN 'stanza_id' TEXT;
- ALTER TABLE logs ADD COLUMN 'mam_id' TEXT;
- ALTER TABLE logs ADD COLUMN 'encryption' TEXT;
- ALTER TABLE logs ADD COLUMN 'encryption_state' TEXT;
- ALTER TABLE logs ADD COLUMN 'marker' INTEGER;
- '''
- )
- con.commit()
- except sqlite.OperationalError:
- pass
- con.close()
- gajim.config.set('version', '0.16.10.3')
diff --git a/src/common/passwords.py b/src/common/passwords.py
deleted file mode 100644
index 72789b302..000000000
--- a/src/common/passwords.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/passwords.py
-##
-## Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (c) 2009 Thorsten Glaser <t.glaser AT tarent.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import logging
-import gi
-from common import gajim
-
-__all__ = ['get_password', 'save_password']
-
-log = logging.getLogger('gajim.password')
-
-if os.name == 'nt':
- try:
- import keyring
- except ImportError:
- log.debug('python-keyring missing, falling back to plaintext storage')
-
-
-Secret = None
-
-class PasswordStorage(object):
- def get_password(self, account_name):
- raise NotImplementedError
- def save_password(self, account_name, password):
- raise NotImplementedError
-
-
-class SimplePasswordStorage(PasswordStorage):
- def get_password(self, account_name):
- passwd = gajim.config.get_per('accounts', account_name, 'password')
- if passwd and (passwd.startswith('libsecret:') or passwd.startswith('winvault:')):
- # this is not a real password, it’s stored through libsecret.
- return None
- else:
- return passwd
-
- def save_password(self, account_name, password):
- gajim.config.set_per('accounts', account_name, 'password', password)
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
-
-
-class SecretPasswordStorage(PasswordStorage):
- def __init__(self):
- self.GAJIM_SCHEMA = Secret.Schema.new("org.gnome.keyring.NetworkPassword",
- Secret.SchemaFlags.NONE,
- {
- 'user': Secret.SchemaAttributeType.STRING,
- 'server': Secret.SchemaAttributeType.STRING,
- 'protocol': Secret.SchemaAttributeType.STRING,
- }
- )
-
- def get_password(self, account_name):
- conf = gajim.config.get_per('accounts', account_name, 'password')
- if conf is None:
- return None
- if not conf.startswith('libsecret:'):
- password = conf
- ## migrate the password over to keyring
- try:
- self.save_password(account_name, password, update=False)
- except Exception:
- ## no keyring daemon: in the future, stop using it
- set_storage(SimplePasswordStorage())
- return password
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- password = Secret.password_lookup_sync(self.GAJIM_SCHEMA, {'user': user,
- 'server': server, 'protocol': 'xmpp'}, None)
- return password
-
- def save_password(self, account_name, password, update=True):
- server = gajim.config.get_per('accounts', account_name, 'hostname')
- user = gajim.config.get_per('accounts', account_name, 'name')
- display_name = _('XMPP account %s@%s') % (user, server)
- if password is None:
- password = str()
- attributes = {'user': user, 'server': server, 'protocol': 'xmpp'}
- Secret.password_store_sync(self.GAJIM_SCHEMA, attributes,
- Secret.COLLECTION_DEFAULT, display_name, password, None)
- gajim.config.set_per('accounts', account_name, 'password',
- 'libsecret:')
- if account_name in gajim.connections:
- gajim.connections[account_name].password = password
-
-
-class SecretWindowsPasswordStorage(PasswordStorage):
- """ Windows Keyring """
-
- def __init__(self):
- self.win_keyring = keyring.get_keyring()
-
- def save_password(self, account_name, password):
- try:
- self.win_keyring.set_password('gajim', account_name, password)
- gajim.config.set_per(
- 'accounts', account_name, 'password', 'winvault:')
- except:
- log.exception('error:')
- set_storage(SimplePasswordStorage())
- storage.save_password(account_name, password)
-
- def get_password(self, account_name):
- log.debug('getting password')
- conf = gajim.config.get_per('accounts', account_name, 'password')
- if conf is None:
- return None
- if not conf.startswith('winvault:'):
- password = conf
- # migrate the password over to keyring
- self.save_password(account_name, password)
- return password
- return self.win_keyring.get_password('gajim', account_name)
-
-
-storage = None
-def get_storage():
- global storage
- if storage is None: # None is only in first time get_storage is called
- global Secret
- if gajim.config.get('use_keyring'):
- try:
- gi.require_version('Secret', '1')
- gir = __import__('gi.repository', globals(), locals(),
- ['Secret'], 0)
- Secret = gir.Secret
- except (ValueError, AttributeError):
- pass
- try:
- if os.name != 'nt':
- storage = SecretPasswordStorage()
- else:
- storage = SecretWindowsPasswordStorage()
- except Exception:
- storage = SimplePasswordStorage()
- else:
- storage = SimplePasswordStorage()
- return storage
-
-def set_storage(storage_):
- global storage
- storage = storage_
-
-def get_password(account_name):
- return get_storage().get_password(account_name)
-
-def save_password(account_name, password):
- if account_name in gajim.connections:
- gajim.connections[account_name].set_password(password)
- return get_storage().save_password(account_name, password)
diff --git a/src/common/pep.py b/src/common/pep.py
deleted file mode 100644
index 4a96a085a..000000000
--- a/src/common/pep.py
+++ /dev/null
@@ -1,473 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/pep.py
-##
-## Copyright (C) 2007 Piotr Gaczkowski <doomhammerng AT gmail.com>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Jonathan Schleifer <js-common.gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-MOODS = {
- 'afraid': _('Afraid'),
- 'amazed': _('Amazed'),
- 'amorous': _('Amorous'),
- 'angry': _('Angry'),
- 'annoyed': _('Annoyed'),
- 'anxious': _('Anxious'),
- 'aroused': _('Aroused'),
- 'ashamed': _('Ashamed'),
- 'bored': _('Bored'),
- 'brave': _('Brave'),
- 'calm': _('Calm'),
- 'cautious': _('Cautious'),
- 'cold': _('Cold'),
- 'confident': _('Confident'),
- 'confused': _('Confused'),
- 'contemplative': _('Contemplative'),
- 'contented': _('Contented'),
- 'cranky': _('Cranky'),
- 'crazy': _('Crazy'),
- 'creative': _('Creative'),
- 'curious': _('Curious'),
- 'dejected': _('Dejected'),
- 'depressed': _('Depressed'),
- 'disappointed': _('Disappointed'),
- 'disgusted': _('Disgusted'),
- 'dismayed': _('Dismayed'),
- 'distracted': _('Distracted'),
- 'embarrassed': _('Embarrassed'),
- 'envious': _('Envious'),
- 'excited': _('Excited'),
- 'flirtatious': _('Flirtatious'),
- 'frustrated': _('Frustrated'),
- 'grateful': _('Grateful'),
- 'grieving': _('Grieving'),
- 'grumpy': _('Grumpy'),
- 'guilty': _('Guilty'),
- 'happy': _('Happy'),
- 'hopeful': _('Hopeful'),
- 'hot': _('Hot'),
- 'humbled': _('Humbled'),
- 'humiliated': _('Humiliated'),
- 'hungry': _('Hungry'),
- 'hurt': _('Hurt'),
- 'impressed': _('Impressed'),
- 'in_awe': _('In Awe'),
- 'in_love': _('In Love'),
- 'indignant': _('Indignant'),
- 'interested': _('Interested'),
- 'intoxicated': _('Intoxicated'),
- 'invincible': _('Invincible'),
- 'jealous': _('Jealous'),
- 'lonely': _('Lonely'),
- 'lost': _('Lost'),
- 'lucky': _('Lucky'),
- 'mean': _('Mean'),
- 'moody': _('Moody'),
- 'nervous': _('Nervous'),
- 'neutral': _('Neutral'),
- 'offended': _('Offended'),
- 'outraged': _('Outraged'),
- 'playful': _('Playful'),
- 'proud': _('Proud'),
- 'relaxed': _('Relaxed'),
- 'relieved': _('Relieved'),
- 'remorseful': _('Remorseful'),
- 'restless': _('Restless'),
- 'sad': _('Sad'),
- 'sarcastic': _('Sarcastic'),
- 'satisfied': _('Satisfied'),
- 'serious': _('Serious'),
- 'shocked': _('Shocked'),
- 'shy': _('Shy'),
- 'sick': _('Sick'),
- 'sleepy': _('Sleepy'),
- 'spontaneous': _('Spontaneous'),
- 'stressed': _('Stressed'),
- 'strong': _('Strong'),
- 'surprised': _('Surprised'),
- 'thankful': _('Thankful'),
- 'thirsty': _('Thirsty'),
- 'tired': _('Tired'),
- 'undefined': _('Undefined'),
- 'weak': _('Weak'),
- 'worried': _('Worried')}
-
-ACTIVITIES = {
- 'doing_chores': {'category': _('Doing Chores'),
- 'buying_groceries': _('Buying Groceries'),
- 'cleaning': _('Cleaning'),
- 'cooking': _('Cooking'),
- 'doing_maintenance': _('Doing Maintenance'),
- 'doing_the_dishes': _('Doing the Dishes'),
- 'doing_the_laundry': _('Doing the Laundry'),
- 'gardening': _('Gardening'),
- 'running_an_errand': _('Running an Errand'),
- 'walking_the_dog': _('Walking the Dog')},
- 'drinking': {'category': _('Drinking'),
- 'having_a_beer': _('Having a Beer'),
- 'having_coffee': _('Having Coffee'),
- 'having_tea': _('Having Tea')},
- 'eating': {'category': _('Eating'),
- 'having_a_snack': _('Having a Snack'),
- 'having_breakfast': _('Having Breakfast'),
- 'having_dinner': _('Having Dinner'),
- 'having_lunch': _('Having Lunch')},
- 'exercising': {'category': _('Exercising'),
- 'cycling': _('Cycling'),
- 'dancing': _('Dancing'),
- 'hiking': _('Hiking'),
- 'jogging': _('Jogging'),
- 'playing_sports': _('Playing Sports'),
- 'running': _('Running'),
- 'skiing': _('Skiing'),
- 'swimming': _('Swimming'),
- 'working_out': _('Working out')},
- 'grooming': {'category': _('Grooming'),
- 'at_the_spa': _('At the Spa'),
- 'brushing_teeth': _('Brushing Teeth'),
- 'getting_a_haircut': _('Getting a Haircut'),
- 'shaving': _('Shaving'),
- 'taking_a_bath': _('Taking a Bath'),
- 'taking_a_shower': _('Taking a Shower')},
- 'having_appointment': {'category': _('Having an Appointment')},
- 'inactive': {'category': _('Inactive'),
- 'day_off': _('Day Off'),
- 'hanging_out': _('Hanging out'),
- 'hiding': _('Hiding'),
- 'on_vacation': _('On Vacation'),
- 'praying': _('Praying'),
- 'scheduled_holiday': _('Scheduled Holiday'),
- 'sleeping': _('Sleeping'),
- 'thinking': _('Thinking')},
- 'relaxing': {'category': _('Relaxing'),
- 'fishing': _('Fishing'),
- 'gaming': _('Gaming'),
- 'going_out': _('Going out'),
- 'partying': _('Partying'),
- 'reading': _('Reading'),
- 'rehearsing': _('Rehearsing'),
- 'shopping': _('Shopping'),
- 'smoking': _('Smoking'),
- 'socializing': _('Socializing'),
- 'sunbathing': _('Sunbathing'),
- 'watching_tv': _('Watching TV'),
- 'watching_a_movie': _('Watching a Movie')},
- 'talking': {'category': _('Talking'),
- 'in_real_life': _('In Real Life'),
- 'on_the_phone': _('On the Phone'),
- 'on_video_phone': _('On Video Phone')},
- 'traveling': {'category': _('Traveling'),
- 'commuting': _('Commuting'),
- 'cycling': _('Cycling'),
- 'driving': _('Driving'),
- 'in_a_car': _('In a Car'),
- 'on_a_bus': _('On a Bus'),
- 'on_a_plane': _('On a Plane'),
- 'on_a_train': _('On a Train'),
- 'on_a_trip': _('On a Trip'),
- 'walking': _('Walking')},
- 'working': {'category': _('Working'),
- 'coding': _('Coding'),
- 'in_a_meeting': _('In a Meeting'),
- 'studying': _('Studying'),
- 'writing': _('Writing')}}
-
-TUNE_DATA = ['artist', 'title', 'source', 'track', 'length']
-
-LOCATION_DATA = {
- 'accuracy': _('accuracy'),
- 'alt': _('alt'),
- 'area': _('area'),
- 'bearing': _('bearing'),
- 'building': _('building'),
- 'country': _('country'),
- 'countrycode': _('countrycode'),
- 'datum': _('datum'),
- 'description': _('description'),
- 'error': _('error'),
- 'floor': _('floor'),
- 'lat': _('lat'),
- 'locality': _('locality'),
- 'lon': _('lon'),
- 'postalcode': _('postalcode'),
- 'region': _('region'),
- 'room': _('room'),
- 'speed': _('speed'),
- 'street': _('street'),
- 'text': _('text'),
- 'timestamp': _('timestamp'),
- 'uri': _('uri')}
-
-from gi.repository import GLib
-
-import logging
-log = logging.getLogger('gajim.c.pep')
-
-import nbxmpp
-from common import gajim
-
-
-class AbstractPEP(object):
-
- type_ = ''
- namespace = ''
-
- @classmethod
- def get_tag_as_PEP(cls, jid, account, event_tag):
- items = event_tag.getTag('items', {'node': cls.namespace})
- if items:
- log.debug("Received PEP 'user %s' from %s" % (cls.type_, jid))
- return cls(jid, account, items)
- else:
- return None
-
- def __init__(self, jid, account, items):
- self._pep_specific_data, self._retracted = self._extract_info(items)
-
- self._update_contacts(jid, account)
- if jid == gajim.get_jid_from_account(account):
- self._update_account(account)
-
- def _extract_info(self, items):
- '''To be implemented by subclasses'''
- raise NotImplementedError
-
- def _update_contacts(self, jid, account):
- for contact in gajim.contacts.get_contacts(account, jid):
- if self._retracted:
- if self.type_ in contact.pep:
- del contact.pep[self.type_]
- else:
- contact.pep[self.type_] = self
-
- def _update_account(self, account):
- acc = gajim.connections[account]
- if self._retracted:
- if self.type_ in acc.pep:
- del acc.pep[self.type_]
- else:
- acc.pep[self.type_] = self
-
- def asMarkupText(self):
- '''SHOULD be implemented by subclasses'''
- return ''
-
-
-class UserMoodPEP(AbstractPEP):
- '''XEP-0107: User Mood'''
-
- type_ = 'mood'
- namespace = nbxmpp.NS_MOOD
-
- def _extract_info(self, items):
- mood_dict = {}
-
- for item in items.getTags('item'):
- mood_tag = item.getTag('mood')
- if mood_tag:
- for child in mood_tag.getChildren():
- name = child.getName().strip()
- if name == 'text':
- mood_dict['text'] = child.getData()
- else:
- mood_dict['mood'] = name
-
- retracted = items.getTag('retract') or not 'mood' in mood_dict
- return (mood_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- untranslated_mood = self._pep_specific_data['mood']
- mood = self._translate_mood(untranslated_mood)
- markuptext = '<b>%s</b>' % GLib.markup_escape_text(mood)
- if 'text' in self._pep_specific_data:
- text = self._pep_specific_data['text']
- markuptext += ' (%s)' % GLib.markup_escape_text(text)
- return markuptext
-
- def _translate_mood(self, mood):
- if mood in MOODS:
- return MOODS[mood]
- else:
- return mood
-
-
-class UserTunePEP(AbstractPEP):
- '''XEP-0118: User Tune'''
-
- type_ = 'tune'
- namespace = nbxmpp.NS_TUNE
-
- def _extract_info(self, items):
- tune_dict = {}
-
- for item in items.getTags('item'):
- tune_tag = item.getTag('tune')
- if tune_tag:
- for child in tune_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in TUNE_DATA:
- tune_dict[name] = data
-
- retracted = items.getTag('retract') or not ('artist' in tune_dict or
- 'title' in tune_dict)
- return (tune_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- tune = self._pep_specific_data
-
- artist = tune.get('artist', _('Unknown Artist'))
- artist = GLib.markup_escape_text(artist)
-
- title = tune.get('title', _('Unknown Title'))
- title = GLib.markup_escape_text(title)
-
- source = tune.get('source', _('Unknown Source'))
- source = GLib.markup_escape_text(source)
-
- tune_string = _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
- 'from <i>%(source)s</i>') % {'title': title,
- 'artist': artist, 'source': source}
- return tune_string
-
-
-class UserActivityPEP(AbstractPEP):
- '''XEP-0108: User Activity'''
-
- type_ = 'activity'
- namespace = nbxmpp.NS_ACTIVITY
-
- def _extract_info(self, items):
- activity_dict = {}
-
- for item in items.getTags('item'):
- activity_tag = item.getTag('activity')
- if activity_tag:
- for child in activity_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if name == 'text':
- activity_dict['text'] = data
- else:
- activity_dict['activity'] = name
- for subactivity in child.getChildren():
- subactivity_name = subactivity.getName().strip()
- activity_dict['subactivity'] = subactivity_name
-
- retracted = items.getTag('retract') or not 'activity' in activity_dict
- return (activity_dict, retracted)
-
- def asMarkupText(self):
- assert not self._retracted
- pep = self._pep_specific_data
- activity = pep['activity']
- subactivity = pep['subactivity'] if 'subactivity' in pep else None
- text = pep['text'] if 'text' in pep else None
-
- if activity in ACTIVITIES:
- # Translate standard activities
- if subactivity in ACTIVITIES[activity]:
- subactivity = ACTIVITIES[activity][subactivity]
- activity = ACTIVITIES[activity]['category']
-
- markuptext = '<b>' + GLib.markup_escape_text(activity)
- if subactivity:
- markuptext += ': ' + GLib.markup_escape_text(subactivity)
- markuptext += '</b>'
- if text:
- markuptext += ' (%s)' % GLib.markup_escape_text(text)
- return markuptext
-
-
-class UserNicknamePEP(AbstractPEP):
- '''XEP-0172: User Nickname'''
-
- type_ = 'nickname'
- namespace = nbxmpp.NS_NICK
-
- def _extract_info(self, items):
- nick = ''
- for item in items.getTags('item'):
- child = item.getTag('nick')
- if child:
- nick = child.getData()
- break
-
- retracted = items.getTag('retract') or not nick
- return (nick, retracted)
-
- def _update_contacts(self, jid, account):
- nick = '' if self._retracted else self._pep_specific_data
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.contact_name = nick
-
- def _update_account(self, account):
- if self._retracted:
- gajim.nicks[account] = gajim.config.get_per('accounts', account, 'name')
- else:
- gajim.nicks[account] = self._pep_specific_data
-
-
-class UserLocationPEP(AbstractPEP):
- '''XEP-0080: User Location'''
-
- type_ = 'location'
- namespace = nbxmpp.NS_LOCATION
-
- def _extract_info(self, items):
- location_dict = {}
-
- for item in items.getTags('item'):
- location_tag = item.getTag('geoloc')
- if location_tag:
- for child in location_tag.getChildren():
- name = child.getName().strip()
- data = child.getData().strip()
- if child.getName() in LOCATION_DATA:
- location_dict[name] = data
-
- retracted = items.getTag('retract') or not location_dict
- return (location_dict, retracted)
-
- def _update_account(self, account):
- AbstractPEP._update_account(self, account)
- con = gajim.connections[account].location_info = \
- self._pep_specific_data
-
- def asMarkupText(self):
- assert not self._retracted
- location = self._pep_specific_data
- location_string = ''
-
- for entry in location.keys():
- text = location[entry]
- text = GLib.markup_escape_text(text)
- # Translate standart location tag
- tag = LOCATION_DATA.get(entry, entry)
- location_string += '\n<b>%(tag)s</b>: %(text)s' % \
- {'tag': tag.capitalize(), 'text': text}
-
- return location_string.strip()
-
-
-SUPPORTED_PERSONAL_USER_EVENTS = [UserMoodPEP, UserTunePEP, UserActivityPEP,
- UserNicknamePEP, UserLocationPEP]
diff --git a/src/common/protocol/__init__.py b/src/common/protocol/__init__.py
deleted file mode 100644
index 2d167f344..000000000
--- a/src/common/protocol/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""
-Implementations of specific XMPP protocols and XEPs
-"""
diff --git a/src/common/protocol/bytestream.py b/src/common/protocol/bytestream.py
deleted file mode 100644
index 809a682f3..000000000
--- a/src/common/protocol/bytestream.py
+++ /dev/null
@@ -1,1023 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/connection_handlers.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2007 Tomasz Melcer <liori AT exroot.org>
-## Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import socket
-import base64
-from gi.repository import GLib
-import time
-
-import nbxmpp
-from common import gajim
-from common import helpers
-from common import ged
-from common import jingle_xtls
-from common.file_props import FilesProp
-from common.socks5 import Socks5SenderClient
-
-import logging
-log = logging.getLogger('gajim.c.p.bytestream')
-
-def is_transfer_paused(file_props):
- if file_props.stopped:
- return False
- if file_props.completed:
- return False
- if file_props.disconnect_cb:
- return False
- return file_props.paused
-
-def is_transfer_active(file_props):
- if file_props.stopped:
- return False
- if file_props.completed:
- return False
- if not file_props.started:
- return False
- if file_props.paused:
- return True
- return not file_props.paused
-
-def is_transfer_stopped(file_props):
- if not file_props:
- return True
- if file_props.error:
- return True
- if file_props.completed:
- return True
- if not file_props.stopped:
- return False
- return True
-
-
-class ConnectionBytestream:
-
- def __init__(self):
- gajim.ged.register_event_handler('file-request-received', ged.GUI1,
- self._nec_file_request_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('file-request-received', ged.GUI1,
- self._nec_file_request_received)
-
- def _ft_get_our_jid(self):
- our_jid = gajim.get_jid_from_account(self.name)
- resource = self.server_resource
- return our_jid + '/' + resource
-
- def _ft_get_receiver_jid(self, file_props):
- return file_props.receiver.jid + '/' + file_props.receiver.resource
-
- def _ft_get_from(self, iq_obj):
- return helpers.get_full_jid_from_iq(iq_obj)
-
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return helpers.parse_jid(streamhost.getAttr('jid'))
-
- def send_file_request(self, file_props):
- """
- Send iq for new FT request
- """
- if not self.connection or self.connected < 2:
- return
- file_props.sender = self._ft_get_our_jid()
- fjid = self._ft_get_receiver_jid(file_props)
- iq = nbxmpp.Iq(to=fjid, typ='set')
- iq.setID(file_props.sid)
- si = iq.setTag('si', namespace=nbxmpp.NS_SI)
- si.setAttr('profile', nbxmpp.NS_FILE)
- si.setAttr('id', file_props.sid)
- file_tag = si.setTag('file', namespace=nbxmpp.NS_FILE)
- file_tag.setAttr('name', file_props.name)
- file_tag.setAttr('size', file_props.size)
- desc = file_tag.setTag('desc')
- if file_props.desc:
- desc.setData(file_props.desc)
- file_tag.setTag('range')
- feature = si.setTag('feature', namespace=nbxmpp.NS_FEATURE)
- _feature = nbxmpp.DataForm(typ='form')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.setAttr('type', 'list-single')
- field.addOption(nbxmpp.NS_BYTESTREAM)
- field.addOption(nbxmpp.NS_IBB)
- self.connection.send(iq)
-
- def send_file_approval(self, file_props):
- """
- Send iq, confirming that we want to download the file
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
-
- # file transfer initiated by a jingle session
- log.info("send_file_approval: jingle session accept")
- if file_props.session_type == 'jingle':
- session = self.get_jingle_session(file_props.sender,
- file_props.sid)
- if not session:
- return
- content = None
- for c in session.contents.values():
- if c.transport.sid == file_props.transport_sid:
- content = c
- break
- if not content:
- return
- if not session.accepted:
- content = session.get_content('file', content.name)
- if content.use_security:
- fingerprint = content.x509_fingerprint
- if not jingle_xtls.check_cert(
- gajim.get_jid_without_resource(file_props.sender),
- fingerprint):
- id_ = jingle_xtls.send_cert_request(self,
- file_props.sender)
- jingle_xtls.key_exchange_pend(id_,
- content.on_cert_received, [])
- return
- session.approve_session()
-
- session.approve_content('file', content.name)
- return
-
- iq = nbxmpp.Iq(to=file_props.sender, typ='result')
- iq.setAttr('id', file_props.request_id)
- si = iq.setTag('si', namespace=nbxmpp.NS_SI)
- if file_props.offset:
- file_tag = si.setTag('file', namespace=nbxmpp.NS_FILE)
- range_tag = file_tag.setTag('range')
- range_tag.setAttr('offset', file_props.offset)
- feature = si.setTag('feature', namespace=nbxmpp.NS_FEATURE)
- _feature = nbxmpp.DataForm(typ='submit')
- feature.addChild(node=_feature)
- field = _feature.setField('stream-method')
- field.delAttr('type')
- if nbxmpp.NS_BYTESTREAM in file_props.stream_methods:
- field.setValue(nbxmpp.NS_BYTESTREAM)
- else:
- file_props.transport_sid = file_props.sid
- field.setValue(nbxmpp.NS_IBB)
- self.connection.send(iq)
-
- def send_file_rejection(self, file_props, code='403', typ=None):
- """
- Inform sender that we refuse to download the file
-
- typ is used when code = '400', in this case typ can be 'strean' for
- invalid stream or 'profile' for invalid profile
- """
- # user response to ConfirmationDialog may come after we've disconneted
- if not self.connection or self.connected < 2:
- return
- if file_props.session_type == 'jingle':
- if file_props.sid in self._sessions:
- jingle = self._sessions[file_props.sid]
- jingle.cancel_session()
- return
- iq = nbxmpp.Iq(to=file_props.sender, typ='error')
- iq.setAttr('id', file_props.request_id)
- if code == '400' and typ in ('stream', 'profile'):
- name = 'bad-request'
- text = ''
- else:
- name = 'forbidden'
- text = 'Offer Declined'
- err = nbxmpp.ErrorNode(code=code, typ='cancel', name=name, text=text)
- if code == '400' and typ in ('stream', 'profile'):
- if typ == 'stream':
- err.setTag('no-valid-streams', namespace=nbxmpp.NS_SI)
- else:
- err.setTag('bad-profile', namespace=nbxmpp.NS_SI)
- iq.addChild(node=err)
- self.connection.send(iq)
-
- def _siResultCB(self, con, iq_obj):
- file_props = FilesProp.getFileProp(self.name, iq_obj.getAttr('id'))
- if not file_props:
- return
- if file_props.request_id:
- # we have already sent streamhosts info
- return
- file_props.receiver = self._ft_get_from(iq_obj)
- si = iq_obj.getTag('si')
- file_tag = si.getTag('file')
- range_tag = None
- if file_tag:
- range_tag = file_tag.getTag('range')
- if range_tag:
- offset = range_tag.getAttr('offset')
- if offset:
- file_props.offset = int(offset)
- length = range_tag.getAttr('length')
- if length:
- file_props.length = int(length)
- feature = si.setTag('feature')
- if feature.getNamespace() != nbxmpp.NS_FEATURE:
- return
- form_tag = feature.getTag('x')
- form = nbxmpp.DataForm(node=form_tag)
- field = form.getField('stream-method')
- if field.getValue() == nbxmpp.NS_BYTESTREAM:
- self._send_socks5_info(file_props)
- raise nbxmpp.NodeProcessed
- if field.getValue() == nbxmpp.NS_IBB:
- sid = file_props.sid
- file_props.transport_sid = sid
- fp = open(file_props.file_name, 'rb')
- self.OpenStream(sid, file_props.receiver, fp)
- raise nbxmpp.NodeProcessed
-
- def _siSetCB(self, con, iq_obj):
- from common.connection_handlers_events import FileRequestReceivedEvent
- gajim.nec.push_incoming_event(FileRequestReceivedEvent(None, conn=self,
- stanza=iq_obj))
- raise nbxmpp.NodeProcessed
-
- def _nec_file_request_received(self, obj):
- pass
-
- def _siErrorCB(self, con, iq_obj):
- si = iq_obj.getTag('si')
- profile = si.getAttr('profile')
- if profile != nbxmpp.NS_FILE:
- return
- file_props = FilesProp.getFileProp(self.name, iq_obj.getAttr('id'))
- if not file_props:
- return
- jid = self._ft_get_from(iq_obj)
- file_props.error = -3
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=jid, file_props=file_props, error_msg=''))
- raise nbxmpp.NodeProcessed
-
-class ConnectionSocks5Bytestream(ConnectionBytestream):
-
- def send_success_connect_reply(self, streamhost):
- """
- Send reply to the initiator of FT that we made a connection
- """
- if not self.connection or self.connected < 2:
- return
- if streamhost is None:
- return None
- iq = nbxmpp.Iq(to=streamhost['initiator'], typ='result',
- frm=streamhost['target'])
- iq.setAttr('id', streamhost['id'])
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- stream_tag = query.setTag('streamhost-used')
- stream_tag.setAttr('jid', streamhost['jid'])
- self.connection.send(iq)
-
- def stop_all_active_file_transfers(self, contact):
- """
- Stop all active transfer to or from the given contact
- """
- for file_props in FilesProp.getAllFileProp():
- if is_transfer_stopped(file_props):
- continue
- receiver_jid = file_props.receiver
- if contact.get_full_jid() == receiver_jid:
- file_props.error = -5
- self.remove_transfer(file_props)
- from common.connection_handlers_events import \
- FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
- conn=self, jid=contact.jid, file_props=file_props,
- error_msg=''))
- sender_jid = file_props.sender
- if contact.get_full_jid() == sender_jid:
- file_props.error = -3
- self.remove_transfer(file_props)
-
- def remove_all_transfers(self):
- """
- Stop and remove all active connections from the socks5 pool
- """
- for file_props in FilesProp.getAllFileProp():
- self.remove_transfer(file_props, remove_from_list=False)
-
- def remove_transfer(self, file_props, remove_from_list=True):
- if file_props is None:
- return
- self.disconnect_transfer(file_props)
- sid = file_props.sid
-
- def disconnect_transfer(self, file_props):
- if file_props is None:
- return
- if file_props.hash_:
- gajim.socks5queue.remove_sender(file_props.hash_)
-
- if file_props.streamhosts:
- for host in file_props.streamhosts:
- if 'idx' in host and host['idx'] > 0:
- gajim.socks5queue.remove_receiver(host['idx'])
- gajim.socks5queue.remove_sender(host['idx'])
-
- def _send_socks5_info(self, file_props):
- """
- Send iq for the present streamhosts and proxies
- """
- if not self.connection or self.connected < 2:
- return
- receiver = file_props.receiver
- sender = file_props.sender
-
- sha_str = helpers.get_auth_sha(file_props.sid, sender, receiver)
- file_props.sha_str = sha_str
-
- port = gajim.config.get('file_transfers_port')
- listener = gajim.socks5queue.start_listener(port, sha_str,
- self._result_socks5_sid, file_props)
- if not listener:
- file_props.error = -5
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=receiver, file_props=file_props, error_msg=''))
- self._connect_error(file_props.sid, error='not-acceptable',
- error_type='modify')
- else:
- iq = nbxmpp.Iq(to=receiver, typ='set')
- file_props.request_id = 'id_' + file_props.sid
- iq.setID(file_props.request_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', file_props.sid)
-
- self._add_addiditional_streamhosts_to_query(query, file_props)
- self._add_local_ips_as_streamhosts_to_query(query, file_props)
- self._add_proxy_streamhosts_to_query(query, file_props)
- self._add_upnp_igd_as_streamhost_to_query(query, file_props, iq)
- # Upnp-igd is ascynchronous, so it will send the iq itself
-
- def _add_streamhosts_to_query(self, query, sender, port, hosts):
- for host in hosts:
- streamhost = nbxmpp.Node(tag='streamhost')
- query.addChild(node=streamhost)
- streamhost.setAttr('port', str(port))
- streamhost.setAttr('host', host)
- streamhost.setAttr('jid', sender)
-
- def _add_local_ips_as_streamhosts_to_query(self, query, file_props):
- if not gajim.config.get_per('accounts', self.name, 'ft_send_local_ips'):
- return
- try:
- my_ips = [self.peerhost[0]] # The ip we're connected to server with
- # all IPs from local DNS
- for addr in socket.getaddrinfo(socket.gethostname(), None):
- if not addr[4][0] in my_ips and not addr[4][0].startswith('127') and not addr[4][0] == '::1':
- my_ips.append(addr[4][0])
-
- sender = file_props.sender
- port = gajim.config.get('file_transfers_port')
- self._add_streamhosts_to_query(query, sender, port, my_ips)
- except socket.gaierror:
- from common.connection_handlers_events import InformationEvent
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Wrong host'),
- sec_txt=_('Invalid local address? :-O')))
-
- def _add_addiditional_streamhosts_to_query(self, query, file_props):
- sender = file_props.sender
- port = gajim.config.get('file_transfers_port')
- ft_add_hosts_to_send = gajim.config.get('ft_add_hosts_to_send')
- additional_hosts = []
- if ft_add_hosts_to_send:
- additional_hosts = [e.strip() for e in ft_add_hosts_to_send.split(',')]
- else:
- additional_hosts = []
- self._add_streamhosts_to_query(query, sender, port, additional_hosts)
-
- def _add_upnp_igd_as_streamhost_to_query(self, query, file_props, iq):
- if not gajim.HAVE_UPNP_IGD:
- self.connection.send(iq)
- return
-
- my_ip = self.peerhost[0]
-
- # check if we are connected with an IPv4 address
- try:
- socket.inet_aton(my_ip)
- except socket.error as e:
- self.connection.send(iq)
- return
-
- def ip_is_local(ip):
- if '.' not in ip:
- # it's an IPv6
- return True
- ip_s = ip.split('.')
- ip_l = int(ip_s[0])<<24 | int(ip_s[1])<<16 | int(ip_s[2])<<8 | \
- int(ip_s[3])
- # 10/8
- if ip_l & (255<<24) == 10<<24:
- return True
- # 172.16/12
- if ip_l & (255<<24 | 240<<16) == (172<<24 | 16<<16):
- return True
- # 192.168
- if ip_l & (255<<24 | 255<<16) == (192<<24 | 168<<16):
- return True
- return False
-
- if not ip_is_local(my_ip):
- self.connection.send(iq)
- return
-
- self.no_gupnp_reply_id = 0
-
- def cleanup_gupnp():
- if self.no_gupnp_reply_id:
- GLib.source_remove(self.no_gupnp_reply_id)
- self.no_gupnp_reply_id = 0
- gajim.gupnp_igd.disconnect(self.ok_id)
- gajim.gupnp_igd.disconnect(self.fail_id)
-
- def ok(s, proto, ext_ip, re, ext_port, local_ip, local_port, desc):
- log.debug('Got GUPnP-IGD answer: external: %s:%s, internal: %s:%s',
- ext_ip, ext_port, local_ip, local_port)
- if local_port != gajim.config.get('file_transfers_port'):
- sender = file_props.sender
- receiver = file_props.receiver
- sha_str = helpers.get_auth_sha(file_props.sid, sender,
- receiver)
- listener = gajim.socks5queue.start_listener(local_port, sha_str,
- self._result_socks5_sid, file_props.sid)
- if listener:
- self._add_streamhosts_to_query(query, sender, ext_port,
- [ext_ip])
- else:
- self._add_streamhosts_to_query(query, file_props.sender,
- ext_port, [ext_ip])
- self.connection.send(iq)
- cleanup_gupnp()
-
- def fail(s, error, proto, ext_ip, local_ip, local_port, desc):
- log.debug('Got GUPnP-IGD error')
- self.connection.send(iq)
- cleanup_gupnp()
-
- def no_upnp_reply():
- log.debug('Got not GUPnP-IGD answer')
- # stop trying to use it
- gajim.HAVE_UPNP_IGD = False
- self.no_gupnp_reply_id = 0
- self.connection.send(iq)
- cleanup_gupnp()
- return False
-
-
- self.ok_id = gajim.gupnp_igd.connect('mapped-external-port', ok)
- self.fail_id = gajim.gupnp_igd.connect('error-mapping-port', fail)
-
- port = gajim.config.get('file_transfers_port')
- self.no_gupnp_reply_id = GLib.timeout_add_seconds(10, no_upnp_reply)
- gajim.gupnp_igd.add_port('TCP', 0, my_ip, port, 3600,
- 'Gajim file transfer')
-
- def _add_proxy_streamhosts_to_query(self, query, file_props):
- proxyhosts = self._get_file_transfer_proxies_from_config(file_props)
- if proxyhosts:
- file_props.proxy_receiver = file_props.receiver
- file_props.proxy_sender = file_props.sender
- file_props.proxyhosts = proxyhosts
-
- for proxyhost in proxyhosts:
- self._add_streamhosts_to_query(query, proxyhost['jid'],
- proxyhost['port'], [proxyhost['host']])
-
- def _get_file_transfer_proxies_from_config(self, file_props):
- configured_proxies = gajim.config.get_per('accounts', self.name,
- 'file_transfer_proxies')
- shall_use_proxies = gajim.config.get_per('accounts', self.name,
- 'use_ft_proxies')
- if shall_use_proxies:
- proxyhost_dicts = []
- proxies = []
- if configured_proxies:
- proxies = [item.strip() for item in configured_proxies.split(',')]
- default_proxy = gajim.proxy65_manager.get_default_for_name(self.name)
- if default_proxy:
- # add/move default proxy at top of the others
- if default_proxy in proxies:
- proxies.remove(default_proxy)
- proxies.insert(0, default_proxy)
-
- for proxy in proxies:
- (host, _port, jid) = gajim.proxy65_manager.get_proxy(proxy, self.name)
- if not host:
- continue
- host_dict = {
- 'state': 0,
- 'target': file_props.receiver,
- 'id': file_props.sid,
- 'sid': file_props.sid,
- 'initiator': proxy,
- 'host': host,
- 'port': str(_port),
- 'jid': jid
- }
- proxyhost_dicts.append(host_dict)
- return proxyhost_dicts
- else:
- return []
-
- def _result_socks5_sid(self, sid, hash_id):
- """
- Store the result of SHA message from auth
- """
- file_props = FilesProp.getFilePropBySid(sid)
- file_props.hash_ = hash_id
- return
-
- def _connect_error(self, sid, error, error_type, msg=None):
- """
- Called when there is an error establishing BS connection, or when
- connection is rejected
- """
- if not self.connection or self.connected < 2:
- return
- file_props = FilesProp.getFileProp(self.name, sid)
- if file_props is None:
- log.error('can not send iq error on failed transfer')
- return
- if file_props.type_ == 's':
- to = file_props.receiver
- else:
- to = file_props.sender
- iq = nbxmpp.Iq(to=to, typ='error')
- iq.setAttr('id', file_props.request_id)
- err = iq.setTag('error')
- err.setAttr('type', error_type)
- err.setTag(error, namespace=nbxmpp.NS_STANZAS)
- self.connection.send(iq)
- if msg:
- self.disconnect_transfer(file_props)
- file_props.error = -3
- from common.connection_handlers_events import \
- FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
- conn=self, jid=to, file_props=file_props, error_msg=msg))
-
- def _proxy_auth_ok(self, proxy):
- """
- Called after authentication to proxy server
- """
- if not self.connection or self.connected < 2:
- return
- file_props = FilesProp.getFileProp(self.name, proxy['sid'])
- iq = nbxmpp.Iq(to=proxy['initiator'], typ='set')
- auth_id = "au_" + proxy['sid']
- iq.setID(auth_id)
- query = iq.setTag('query', namespace=nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', proxy['sid'])
- activate = query.setTag('activate')
- activate.setData(file_props.proxy_receiver)
- iq.setID(auth_id)
- self.connection.send(iq)
-
- # register xmpppy handlers for bytestream and FT stanzas
- def _bytestreamErrorCB(self, con, iq_obj):
- id_ = iq_obj.getAttr('id')
- frm = helpers.get_full_jid_from_iq(iq_obj)
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.error_cb(frm, query)
- jid = helpers.get_jid_from_iq(iq_obj)
- id_ = id_[3:]
- file_props = FilesProp.getFilePropBySid(id_)
- if not file_props:
- return
- file_props.error = -4
- from common.connection_handlers_events import FileRequestErrorEvent
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None, conn=self,
- jid=jid, file_props=file_props, error_msg=''))
- raise nbxmpp.NodeProcessed
-
- def _bytestreamSetCB(self, con, iq_obj):
- target = iq_obj.getAttr('to')
- id_ = iq_obj.getAttr('id')
- query = iq_obj.getTag('query')
- sid = query.getAttr('sid')
- file_props = FilesProp.getFileProp(self.name, sid)
- streamhosts = []
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host_dict = {
- 'state': 0,
- 'target': target,
- 'id': id_,
- 'sid': sid,
- 'initiator': self._ft_get_from(iq_obj)
- }
- for attr in item.getAttrs():
- host_dict[attr] = item.getAttr(attr)
- if 'host' not in host_dict:
- continue
- if 'jid' not in host_dict:
- continue
- if 'port' not in host_dict:
- continue
- streamhosts.append(host_dict)
- file_props = FilesProp.getFilePropBySid(sid)
- if file_props is not None:
- if file_props.type_ == 's': # FIXME: remove fast xmlns
- # only psi do this
- if file_props.streamhosts:
- file_props.streamhosts.extend(streamhosts)
- else:
- file_props.streamhosts = streamhosts
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, None)
- raise nbxmpp.NodeProcessed
- else:
- log.warning('Gajim got streamhosts for unknown transfer. Ignoring it.')
- raise nbxmpp.NodeProcessed
-
- file_props.streamhosts = streamhosts
- def _connection_error(sid):
- self._connect_error(sid, 'item-not-found', 'cancel',
- msg='Could not connect to given hosts')
- if file_props.type_ == 'r':
- gajim.socks5queue.connect_to_hosts(self.name, sid,
- self.send_success_connect_reply, _connection_error)
- raise nbxmpp.NodeProcessed
-
- def _ResultCB(self, con, iq_obj):
- # if we want to respect xep-0065 we have to check for proxy
- # activation result in any result iq
- real_id = iq_obj.getAttr('id')
- if not real_id.startswith('au_'):
- return
- frm = self._ft_get_from(iq_obj)
- id_ = real_id[3:]
- file_props = FilesProp.getFilePropBySid(id_)
- if file_props.streamhost_used:
- for host in file_props.proxyhosts:
- if host['initiator'] == frm and 'idx' in host:
- gajim.socks5queue.activate_proxy(host['idx'])
- raise nbxmpp.NodeProcessed
-
- def _bytestreamResultCB(self, con, iq_obj):
- frm = self._ft_get_from(iq_obj)
- real_id = iq_obj.getAttr('id')
- query = iq_obj.getTag('query')
- gajim.proxy65_manager.resolve_result(frm, query)
-
- try:
- streamhost = query.getTag('streamhost-used')
- except Exception: # this bytestream result is not what we need
- pass
- id_ = real_id[3:]
- file_props = FilesProp.getFileProp(self.name, id_)
- if file_props is None:
- raise nbxmpp.NodeProcessed
- if streamhost is None:
- # proxy approves the activate query
- if real_id.startswith('au_'):
- if file_props.streamhost_used is False:
- raise nbxmpp.NodeProcessed
- if not file_props.proxyhosts:
- raise nbxmpp.NodeProcessed
- for host in file_props.proxyhosts:
- if host['initiator'] == frm and \
- query.getAttr('sid') == file_props.sid:
- gajim.socks5queue.activate_proxy(host['idx'])
- break
- raise nbxmpp.NodeProcessed
- jid = self._ft_get_streamhost_jid_attr(streamhost)
- if file_props.streamhost_used is True:
- raise nbxmpp.NodeProcessed
-
- if real_id.startswith('au_'):
- if file_props.stopped:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name)
- raise nbxmpp.NodeProcessed
-
- proxy = None
- if file_props.proxyhosts:
- for proxyhost in file_props.proxyhosts:
- if proxyhost['jid'] == jid:
- proxy = proxyhost
-
- if file_props.stopped:
- self.remove_transfer(file_props)
- raise nbxmpp.NodeProcessed
- if proxy is not None:
- file_props.streamhost_used = True
- file_props.streamhosts.append(proxy)
- file_props.is_a_proxy = True
- idx = gajim.socks5queue.idx
- sender = Socks5SenderClient(gajim.idlequeue, idx,
- gajim.socks5queue, _sock=None, host=str(proxy['host']),
- port=int(proxy['port']), fingerprint=None,
- connected=False, file_props=file_props)
- sender.streamhost = proxy
- gajim.socks5queue.add_sockobj(self.name, sender)
- proxy['idx'] = sender.queue_idx
- gajim.socks5queue.on_success[file_props.sid] = self._proxy_auth_ok
- raise nbxmpp.NodeProcessed
-
- else:
- if file_props.stopped:
- self.remove_transfer(file_props)
- else:
- gajim.socks5queue.send_file(file_props, self.name, 'server')
-
- raise nbxmpp.NodeProcessed
-
-
-class ConnectionIBBytestream(ConnectionBytestream):
-
- def __init__(self):
- ConnectionBytestream.__init__(self)
- self._streams = {}
-
- def IBBIqHandler(self, conn, stanza):
- """
- Handles streams state change. Used internally.
- """
- typ = stanza.getType()
- log.debug('IBBIqHandler called typ->%s' % typ)
- if typ == 'set' and stanza.getTag('open'):
- self.StreamOpenHandler(conn, stanza)
- elif typ == 'set' and stanza.getTag('close'):
- self.StreamCloseHandler(conn, stanza)
- elif typ == 'set' and stanza.getTag('data'):
- sid = stanza.getTagAttr('data', 'sid')
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if not file_props:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_ITEM_NOT_FOUND))
- elif file_props.connected and self.IBBMessageHandler(conn,
- stanza):
- reply = stanza.buildReply('result')
- reply.delChild('data')
- conn.send(reply)
- elif not file_props.connected:
- log.debug('Received IQ for closed filetransfer, IQ dropped')
- elif typ == 'error':
- gajim.socks5queue.error_cb()
- else:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_BAD_REQUEST))
- raise nbxmpp.NodeProcessed
-
- def StreamOpenHandler(self, conn, stanza):
- """
- Handles opening of new incoming stream. Used internally.
- """
- err = None
- sid = stanza.getTagAttr('open', 'sid')
- blocksize = stanza.getTagAttr('open', 'block-size')
- log.debug('StreamOpenHandler called sid->%s blocksize->%s' % (sid,
- blocksize))
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- try:
- blocksize = int(blocksize)
- except:
- err = nbxmpp.ERR_BAD_REQUEST
- if not sid or not blocksize:
- err = nbxmpp.ERR_BAD_REQUEST
- elif not file_props:
- err = nbxmpp.ERR_UNEXPECTED_REQUEST
- if err:
- rep = nbxmpp.Error(stanza, err)
- else:
- log.debug("Opening stream: id %s, block-size %s" % (sid, blocksize))
- rep = nbxmpp.Protocol('iq', stanza.getFrom(), 'result',
- stanza.getTo(), {'id': stanza.getID()})
- file_props.block_size = blocksize
- file_props.direction = '<'
- file_props.seq = 0
- file_props.received_len = 0
- file_props.last_time = time.time()
- file_props.error = 0
- file_props.paused = False
- file_props.connected = True
- file_props.completed = False
- file_props.disconnect_cb = None
- file_props.continue_cb = None
- file_props.syn_id = stanza.getID()
- file_props.fp = open(file_props.file_name, 'wb')
- conn.send(rep)
-
- def CloseIBBStream(self, file_props):
- file_props.connected = False
- file_props.fp.close()
- file_props.stopped = True
- to = file_props.receiver
- if file_props.direction == '<':
- to = file_props.sender
- self.connection.send(
- nbxmpp.Protocol('iq', to, 'set',
- payload=[nbxmpp.Node(nbxmpp.NS_IBB + ' close',
- {'sid':file_props.transport_sid})]))
- if file_props.completed:
- gajim.socks5queue.complete_transfer_cb(self.name, file_props)
- elif file_props.session_type == 'jingle':
- peerjid = \
- file_props.receiver if file_props.type_ == 's' else file_props.sender
- session = self.get_jingle_session(peerjid, file_props.sid, 'file')
- # According to the xep, the initiator also cancels the jingle session
- # if there are no more files to send using IBB
- if session.weinitiate:
- session.cancel_session()
-
- def OpenStream(self, sid, to, fp, blocksize=4096):
- """
- Start new stream. You should provide stream id 'sid', the endpoind jid
- 'to', the file object containing info for send 'fp'. Also the desired
- blocksize can be specified.
- Take into account that recommended stanza size is 4k and IBB uses
- base64 encoding that increases size of data by 1/3.
- """
- file_props = FilesProp.getFilePropBySid(sid)
- file_props.direction = '>'
- file_props.block_size = blocksize
- file_props.fp = fp
- file_props.seq = 0
- file_props.error = 0
- file_props.paused = False
- file_props.received_len = 0
- file_props.last_time = time.time()
- file_props.connected = True
- file_props.completed = False
- file_props.disconnect_cb = None
- file_props.continue_cb = None
- syn = nbxmpp.Protocol('iq', to, 'set', payload=[nbxmpp.Node(
- nbxmpp.NS_IBB + ' open', {'sid': file_props.transport_sid,
- 'block-size': blocksize, 'stanza': 'iq'})])
- self.connection.send(syn)
- file_props.syn_id = syn.getID()
- return file_props
-
- def SendHandler(self, file_props):
- """
- Send next portion of data if it is time to do it. Used internally.
- """
- log.debug('SendHandler called')
- if file_props.completed:
- self.CloseIBBStream(file_props)
- if file_props.paused:
- return
- if not file_props.connected:
- #TODO: Reply with out of order error
- return
- chunk = file_props.fp.read(file_props.block_size)
- if chunk:
- datanode = nbxmpp.Node(nbxmpp.NS_IBB + ' data', {
- 'sid': file_props.transport_sid,
- 'seq': file_props.seq},
- base64.b64encode(chunk).decode('ascii'))
- file_props.seq += 1
- file_props.started = True
- if file_props.seq == 65536:
- file_props.seq = 0
- file_props.syn_id = self.connection.send(
- nbxmpp.Protocol(name='iq', to=file_props.receiver,
- typ='set', payload=[datanode]))
- current_time = time.time()
- file_props.elapsed_time += current_time - file_props.last_time
- file_props.last_time = current_time
- file_props.received_len += len(chunk)
- if file_props.size == file_props.received_len:
- file_props.completed = True
- gajim.socks5queue.progress_transfer_cb(self.name,
- file_props)
- else:
- log.debug('Nothing to read, but file not completed')
-
- def IBBMessageHandler(self, conn, stanza):
- """
- Receive next portion of incoming datastream and store it write
- it to temporary file. Used internally.
- """
- sid = stanza.getTagAttr('data', 'sid')
- seq = stanza.getTagAttr('data', 'seq')
- data = stanza.getTagData('data')
- log.debug('ReceiveHandler called sid->%s seq->%s' % (sid, seq))
- try:
- seq = int(seq)
- data = base64.b64decode(data.encode('utf-8'))
- except Exception:
- seq = ''
- data = b''
- err = None
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if file_props is None:
- err = nbxmpp.ERR_ITEM_NOT_FOUND
- else:
- if not data:
- err = nbxmpp.ERR_BAD_REQUEST
- elif seq != file_props.seq:
- err = nbxmpp.ERR_UNEXPECTED_REQUEST
- else:
- log.debug('Successfull receive sid->%s %s+%s bytes' % (sid,
- file_props.fp.tell(), len(data)))
- file_props.seq += 1
- file_props.started = True
- file_props.fp.write(data)
- current_time = time.time()
- file_props.elapsed_time += current_time - file_props.last_time
- file_props.last_time = current_time
- file_props.received_len += len(data)
- gajim.socks5queue.progress_transfer_cb(self.name, file_props)
- if file_props.received_len >= file_props.size:
- file_props.completed = True
- if err:
- log.debug('Error on receive: %s' % err)
- conn.send(nbxmpp.Error(nbxmpp.Iq(to=stanza.getFrom(),
- frm=stanza.getTo(),
- payload=[nbxmpp.Node(nbxmpp.NS_IBB + ' close')]), err, reply=0))
- else:
- return True
-
- def StreamCloseHandler(self, conn, stanza):
- """
- Handle stream closure due to all data transmitted.
- Raise xmpppy event specifying successfull data receive.
- """
- sid = stanza.getTagAttr('close', 'sid')
- log.debug('StreamCloseHandler called sid->%s' % sid)
- # look in sending files
- file_props = FilesProp.getFilePropByTransportSid(self.name, sid)
- if file_props:
- reply = stanza.buildReply('result')
- reply.delChild('close')
- conn.send(reply)
- # look in receiving files
- file_props.fp.close()
- file_props.completed = file_props.received_len >= file_props.size
- if not file_props.completed:
- file_props.error = -1
- gajim.socks5queue.complete_transfer_cb(self.name, file_props)
- else:
- conn.send(nbxmpp.Error(stanza, nbxmpp.ERR_ITEM_NOT_FOUND))
-
-
- def IBBAllIqHandler(self, conn, stanza):
- """
- Handle remote side reply about if it agree or not to receive our
- datastream.
- Used internally. Raises xmpppy event specfiying if the data transfer
- is agreed upon.
- """
- syn_id = stanza.getID()
- log.debug('IBBAllIqHandler called syn_id->%s' % syn_id)
- for file_props in FilesProp.getAllFileProp():
- if not file_props.direction or not file_props.connected:
- # It's socks5 bytestream
- # Or we closed the IBB stream
- continue
- if file_props.syn_id == syn_id:
- if stanza.getType() == 'error':
- if file_props.direction[0] == '<':
- conn.Event('IBB', 'ERROR ON RECEIVE', file_props)
- else:
- conn.Event('IBB', 'ERROR ON SEND', file_props)
- elif stanza.getType() == 'result':
- self.SendHandler(file_props)
- break
-
-
-class ConnectionSocks5BytestreamZeroconf(ConnectionSocks5Bytestream):
-
- def _ft_get_from(self, iq_obj):
- return iq_obj.getFrom()
-
- def _ft_get_our_jid(self):
- return gajim.get_jid_from_account(self.name)
-
- def _ft_get_receiver_jid(self, file_props):
- return file_props.receiver.jid
-
- def _ft_get_streamhost_jid_attr(self, streamhost):
- return streamhost.getAttr('jid')
diff --git a/src/common/protocol/caps.py b/src/common/protocol/caps.py
deleted file mode 100644
index 8753449a2..000000000
--- a/src/common/protocol/caps.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/protocol/caps.py
-##
-## Copyright (C) 2009 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-"""
-Module containing the network portion of XEP-115 (Entity Capabilities)
-"""
-
-import logging
-log = logging.getLogger('gajim.c.p.caps')
-
-from common import gajim
-from common import ged
-from common.connection_handlers_events import CapsPresenceReceivedEvent, \
- CapsDiscoReceivedEvent, CapsReceivedEvent
-
-
-class ConnectionCaps(object):
-
- def __init__(self, account, capscache, client_caps_factory):
- self._account = account
- self._capscache = capscache
- self._create_suitable_client_caps = client_caps_factory
- gajim.nec.register_incoming_event(CapsPresenceReceivedEvent)
- gajim.nec.register_incoming_event(CapsReceivedEvent)
- gajim.ged.register_event_handler('caps-presence-received', ged.GUI1,
- self._nec_caps_presence_received)
- gajim.ged.register_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received_caps)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('caps-presence-received', ged.GUI1,
- self._nec_caps_presence_received)
- gajim.ged.remove_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received_caps)
-
- def caps_change_account_name(self, new_name):
- self._account = new_name
-
- def _nec_caps_presence_received(self, obj):
- if obj.conn.name != self._account:
- return
- obj.client_caps = self._create_suitable_client_caps(obj.node,
- obj.caps_hash, obj.hash_method, obj.fjid)
- if obj.show == 'offline' and obj.client_caps._hash_method == 'no':
- self._capscache.forget_caps(obj.client_caps)
- obj.client_caps = self._create_suitable_client_caps(obj.node,
- obj.caps_hash, obj.hash_method)
- else:
- self._capscache.query_client_of_jid_if_unknown(self, obj.fjid,
- obj.client_caps)
- self._update_client_caps_of_contact(obj)
-
- def _update_client_caps_of_contact(self, obj):
- contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
- if contact:
- contact.client_caps = obj.client_caps
- else:
- log.info('Received Caps from unknown contact %s' % obj.fjid)
-
- def _get_contact_or_gc_contact_for_jid(self, jid):
- contact = gajim.contacts.get_contact_from_full_jid(self._account, jid)
- if contact is None:
- room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
- contact = gajim.contacts.get_gc_contact(self._account, room_jid, nick)
- return contact
-
- def _nec_agent_info_received_caps(self, obj):
- """
- callback to update our caps cache with queried information after
- we have retrieved an unknown caps hash and issued a disco
- """
- if obj.conn.name != self._account:
- return
- contact = self._get_contact_or_gc_contact_for_jid(obj.fjid)
- if not contact:
- log.info('Received Disco from unknown contact %s' % obj.fjid)
- return
-
- lookup = contact.client_caps.get_cache_lookup_strategy()
- cache_item = lookup(self._capscache)
-
- if cache_item.is_valid():
- # we already know that the hash is fine and have already cached
- # the identities and features
- return
- else:
- validate = contact.client_caps.get_hash_validation_strategy()
- hash_is_valid = validate(obj.identities, obj.features, obj.data)
-
- if hash_is_valid:
- cache_item.set_and_store(obj.identities, obj.features)
- else:
- node = caps_hash = hash_method = None
- contact.client_caps = self._create_suitable_client_caps(
- obj.node, caps_hash, hash_method)
- log.info('Computed and retrieved caps hash differ.' +
- 'Ignoring caps of contact %s' % contact.get_full_jid())
-
- gajim.nec.push_incoming_event(CapsDiscoReceivedEvent(None,
- conn=self, fjid=obj.fjid, jid=obj.jid, resource=obj.resource,
- client_caps=contact.client_caps))
diff --git a/src/common/proxy65_manager.py b/src/common/proxy65_manager.py
deleted file mode 100644
index 0ebab1356..000000000
--- a/src/common/proxy65_manager.py
+++ /dev/null
@@ -1,487 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/proxy65_manager.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import socket
-import struct
-import errno
-import logging
-log = logging.getLogger('gajim.c.proxy65_manager')
-
-import nbxmpp
-from common import gajim
-from common import helpers
-from common.socks5 import Socks5
-from nbxmpp.idlequeue import IdleObject
-from common.file_props import FilesProp
-
-S_INITIAL = 0
-S_STARTED = 1
-S_RESOLVED = 2
-S_ACTIVATED = 3
-S_FINISHED = 4
-
-CONNECT_TIMEOUT = 20
-
-class Proxy65Manager:
- """
- Keep records for file transfer proxies. Each time account establishes a
- connection to its server call proxy65manger.resolve(proxy) for every proxy
- that is convigured within the account. The class takes care to resolve and
- test each proxy only once
- """
-
- def __init__(self, idlequeue):
- # dict {proxy: proxy properties}
- self.idlequeue = idlequeue
- self.proxies = {}
- # dict {account: proxy} default proxy for account
- self.default_proxies = {}
-
- def resolve(self, proxy, connection, sender_jid, default=None,
- testit=True):
- """
- Start
- if testit=False, Gajim won't try to resolve it
- """
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- else:
- # proxy is being ressolved for the first time
- resolver = ProxyResolver(proxy, sender_jid, testit)
- self.proxies[proxy] = resolver
- resolver.add_connection(connection)
- if default:
- # add this proxy as default for account
- self.default_proxies[default] = proxy
-
- def disconnect(self, connection):
- for resolver in self.proxies.values():
- resolver.disconnect(connection)
-
- def resolve_result(self, proxy, query):
- if proxy not in self.proxies:
- return
- jid = None
- for item in query.getChildren():
- if item.getName() == 'streamhost':
- host = item.getAttr('host')
- jid = item.getAttr('jid')
- port = item.getAttr('port')
- try:
- port = int(port)
- except (ValueError, TypeError) as e:
- port = 1080
- if not host or not jid:
- self.proxies[proxy]._on_connect_failure()
- self.proxies[proxy].resolve_result(host, port, jid)
- # we can have only one streamhost
- raise nbxmpp.NodeProcessed
-
- def error_cb(self, proxy, query):
- sid = query.getAttr('sid')
- for resolver in self.proxies.values():
- if resolver.sid == sid:
- resolver.keep_conf()
- break
-
- def get_default_for_name(self, account):
- if account in self.default_proxies:
- return self.default_proxies[account]
-
- def get_proxy(self, proxy, account):
- if proxy in self.proxies:
- resolver = self.proxies[proxy]
- if resolver.state == S_FINISHED:
- return (resolver.host, resolver.port, resolver.jid)
- return (None, 0, None)
-
-class ProxyResolver:
- def resolve_result(self, host, port, jid):
- """
- Test if host has a real proxy65 listening on port
- """
- self.host = str(host)
- self.port = int(port)
- self.jid = str(jid)
- if not self.testit:
- self.state = S_FINISHED
- return
- self.state = S_INITIAL
- log.info('start resolving %s:%s' % (self.host, self.port))
- self.receiver_tester = ReceiverTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_receiver_success,
- self._on_connect_failure)
- self.receiver_tester.connect()
-
- def _on_receiver_success(self):
- log.debug('Receiver successfully connected %s:%s' % (self.host,
- self.port))
- self.host_tester = HostTester(self.host, self.port, self.jid,
- self.sid, self.sender_jid, self._on_connect_success,
- self._on_connect_failure)
- self.host_tester.connect()
-
- def _on_connect_success(self):
- log.debug('Host successfully connected %s:%s' % (self.host, self.port))
- iq = nbxmpp.Protocol(name='iq', to=self.jid, typ='set')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_BYTESTREAM)
- query.setAttr('sid', self.sid)
-
- activate = query.setTag('activate')
- activate.setData('test@gajim.org/test2')
-
- if self.active_connection:
- log.debug('Activating bytestream on %s:%s' % (self.host, self.port))
- self.active_connection.SendAndCallForResponse(iq,
- self._result_received)
- self.state = S_ACTIVATED
- else:
- self.state = S_INITIAL
-
- def _result_received(self, data):
- self.disconnect(self.active_connection)
- if data.getType() == 'result':
- self.keep_conf()
- else:
- self._on_connect_failure()
-
- def keep_conf(self):
- log.debug('Bytestream activated %s:%s' % (self.host, self.port))
- self.state = S_FINISHED
-
- def _on_connect_failure(self):
- log.debug('Connection failed with %s:%s' % (self.host, self.port))
- self.state = S_FINISHED
- self.host = None
- self.port = 0
- self.jid = None
-
- def disconnect(self, connection):
- if self.host_tester:
- self.host_tester.disconnect()
- FilesProp.deleteFileProp(self.host_tester.file_props)
- self.host_tester = None
- if self.receiver_tester:
- self.receiver_tester.disconnect()
- FilesProp.deleteFileProp(self.receiver_tester.file_props)
- self.receiver_tester = None
- try:
- self.connections.remove(connection)
- except ValueError:
- pass
- if connection == self.active_connection:
- self.active_connection = None
- if self.state != S_FINISHED:
- self.state = S_INITIAL
- self.try_next_connection()
-
- def try_next_connection(self):
- """
- Try to resolve proxy with the next possible connection
- """
- if self.connections:
- connection = self.connections.pop(0)
- self.start_resolve(connection)
-
- def add_connection(self, connection):
- """
- Add a new connection in case the first fails
- """
- self.connections.append(connection)
- if self.state == S_INITIAL:
- self.start_resolve(connection)
-
- def start_resolve(self, connection):
- """
- Request network address from proxy
- """
- self.state = S_STARTED
- self.active_connection = connection
- iq = nbxmpp.Protocol(name='iq', to=self.proxy, typ='get')
- query = iq.setTag('query')
- query.setNamespace(nbxmpp.NS_BYTESTREAM)
- connection.send(iq)
-
- def __init__(self, proxy, sender_jid, testit):
- """
- if testit is False, don't test it, only get IP/port
- """
- self.proxy = proxy
- self.state = S_INITIAL
- self.active_connection = None
- self.connections = []
- self.host_tester = None
- self.receiver_tester = None
- self.jid = None
- self.host = None
- self.port = None
- self.sid = helpers.get_random_string_16()
- self.sender_jid = sender_jid
- self.testit = testit
-
-class HostTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Calls on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = FilesProp.getNewFileProp(jid, sid)
- self.file_props.is_a_proxy = True
- self.file_props.proxy_sender = sender_jid
- self.file_props.proxy_receiver = 'test@gajim.org/test2'
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 2:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == b'':
- # end connection
- self.pollend()
- return
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- log.debug('Host authenticating to %s:%s' % (self.host, self.port))
- elif self.state == 3:
- log.debug('Host authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.connect((self.host, self.port))
- self._sock.setblocking(False)
- log.debug('Host Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception as ee:
- errnum = ee.errno
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = b''
- self.state = 1 # connected
- log.debug('Host connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
- return
-
-class ReceiverTester(Socks5, IdleObject):
- """
- Fake proxy tester
- """
-
- def __init__(self, host, port, jid, sid, sender_jid, on_success, on_failure):
- """
- Try to establish and auth to proxy at (host, port)
-
- Call on_success, or on_failure according to the result.
- """
- self.host = host
- self.port = port
- self.jid = jid
- self.on_success = on_success
- self.on_failure = on_failure
- self._sock = None
- self.file_props = FilesProp.getNewFileProp(jid, sid)
- self.file_props.is_a_proxy = True
- self.file_props.proxy_sender = sender_jid
- self.file_props.proxy_receiver = 'test@gajim.org/test2'
- Socks5.__init__(self, gajim.idlequeue, host, port, None, None, None)
- self.sid = sid
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.host is None:
- self.on_failure()
- return None
- self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- gajim.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- self.pollend()
-
- def pollend(self):
- self.disconnect()
- self.on_failure()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- data = self._get_auth_buff()
- self.send_raw(data)
- else:
- return
- self.state += 1
- # unplug and plug for reading
- gajim.idlequeue.plug_idle(self, False, True)
- gajim.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state in (2, 3):
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- # begin negotiation. on success 'address' != 0
- buff = self.receive()
- if buff == b'':
- # end connection
- self.pollend()
- return
- if self.state == 2:
- # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.pollend()
- return
- log.debug('Receiver authenticating to %s:%s' % (self.host, self.port))
- data = self._get_request_buff(self._get_sha1_auth())
- self.send_raw(data)
- self.state += 1
- elif self.state == 3:
- # read connect response
- if buff is None or len(buff) < 2:
- return None
- version, reply = struct.unpack('!BB', buff[:2])
- if version != 0x05 or reply != 0x00:
- self.pollend()
- return
- log.debug('Receiver authenticated to %s:%s' % (self.host, self.port))
- self.on_success()
- self.disconnect()
- self.state += 1
- else:
- assert False, 'unexpected state: %d' % self.state
-
- def do_connect(self):
- try:
- self._sock.setblocking(False)
- self._sock.connect((self.host, self.port))
- log.debug('Receiver Connecting to %s:%s' % (self.host, self.port))
- self._send = self._sock.send
- self._recv = self._sock.recv
- except Exception as ee:
- errnum = ee.errno
- # 56 is for freebsd
- if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
- # still trying to connect
- return
- # win32 needs this
- if errnum not in (0, 10056, errno.EISCONN):
- # connection failed
- self.on_failure()
- return
- # socket is already connected
- self._sock.setblocking(False)
- self._send = self._sock.send
- self._recv = self._sock.recv
- self.buff = ''
- self.state = 1 # connected
- log.debug('Receiver connected to %s:%s' % (self.host, self.port))
- self.idlequeue.plug_idle(self, True, False)
diff --git a/src/common/pubsub.py b/src/common/pubsub.py
deleted file mode 100644
index 3a219fce2..000000000
--- a/src/common/pubsub.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/pubsub.py
-##
-## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import nbxmpp
-from common import gajim
-#TODO: Doesn't work
-#from common.connection_handlers import PEP_CONFIG
-PEP_CONFIG = 'pep_config'
-from common import ged
-from common.connection_handlers_events import PubsubReceivedEvent
-from common.connection_handlers_events import PubsubBookmarksReceivedEvent
-import logging
-log = logging.getLogger('gajim.c.pubsub')
-
-class ConnectionPubSub:
- def __init__(self):
- self.__callbacks = {}
- gajim.nec.register_incoming_event(PubsubBookmarksReceivedEvent)
- gajim.ged.register_event_handler('pubsub-bookmarks-received',
- ged.CORE, self._nec_pubsub_bookmarks_received)
-
- def cleanup(self):
- gajim.ged.remove_event_handler('pubsub-bookmarks-received',
- ged.CORE, self._nec_pubsub_bookmarks_received)
-
- def send_pb_subscription_query(self, jid, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('subscriptions')
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_subscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = nbxmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('subscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs):
- if not self.connection or self.connected < 2:
- return
- our_jid = gajim.get_jid_from_account(self.name)
- query = nbxmpp.Iq('set', to=jid)
- pb = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
-
- id_ = self.connection.send(query)
-
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_publish(self, jid, node, item, id_=None, options=None):
- """
- Publish item to a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- p = e.addChild('publish', {'node': node})
- attrs = {}
- if id_:
- attrs = {'id': id_}
- p.addChild('item', attrs, [item])
- if options:
- p = e.addChild('publish-options')
- p.addChild(node=options)
-
- self.connection.send(query)
-
- def send_pb_retrieve(self, jid, node, cb=None, *args, **kwargs):
- """
- Get items from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- r = r.addChild('items', {'node': node})
- id_ = self.connection.send(query)
-
- if cb:
- self.__callbacks[id_] = (cb, args, kwargs)
-
- def send_pb_retract(self, jid, node, id_):
- """
- Delete item from a node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- r = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- r = r.addChild('retract', {'node': node, 'notify': '1'})
- r = r.addChild('item', {'id': id_})
-
- self.connection.send(query)
-
- def send_pb_purge(self, jid, node):
- """
- Purge node: Remove all items
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- d = d.addChild('purge', {'node': node})
-
- self.connection.send(query)
-
- def send_pb_delete(self, jid, node, on_ok=None, on_fail=None):
- """
- Delete node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- d = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- d = d.addChild('delete', {'node': node})
-
- def response(con, resp, jid, node):
- if resp.getType() == 'result' and on_ok:
- on_ok(jid, node)
- elif on_fail:
- msg = resp.getErrorMsg()
- on_fail(jid, node, msg)
-
- self.connection.SendAndCallForResponse(query, response, {'jid': jid,
- 'node': node})
-
- def send_pb_create(self, jid, node, configure=False, configure_form=None):
- """
- Create a new node
- """
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB)
- c = c.addChild('create', {'node': node})
- if configure:
- conf = c.addChild('configure')
- if configure_form is not None:
- conf.addChild(node=configure_form)
-
- self.connection.send(query)
-
- def send_pb_configure(self, jid, node, form):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('set', to=jid)
- c = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- c = c.addChild('configure', {'node': node})
- c.addChild(node=form)
-
- self.connection.send(query)
-
- def _PubSubCB(self, conn, stanza):
- log.debug('_PubsubCB')
- try:
- cb, args, kwargs = self.__callbacks.pop(stanza.getID())
- cb(conn, stanza, *args, **kwargs)
- except Exception:
- pass
- gajim.nec.push_incoming_event(PubsubReceivedEvent(None,
- conn=self, stanza=stanza))
-
- def _nec_pubsub_bookmarks_received(self, obj):
- if obj.conn.name != self.name:
- return
- bm_jids = [b['jid'] for b in self.bookmarks]
- for bm in obj.bookmarks:
- if bm['jid'] not in bm_jids:
- self.bookmarks.append(bm)
- # We got bookmarks from pubsub, now get those from xml to merge them
- self.get_bookmarks(storage_type='xml')
-
- def _PubSubErrorCB(self, conn, stanza):
- log.debug('_PubsubErrorCB')
- pubsub = stanza.getTag('pubsub')
- if not pubsub:
- return
- items = pubsub.getTag('items')
- if not items:
- return
- if items.getAttr('node') == 'storage:bookmarks':
- # Receiving bookmarks from pubsub failed, so take them from xml
- self.get_bookmarks(storage_type='xml')
-
- def request_pb_configuration(self, jid, node):
- if not self.connection or self.connected < 2:
- return
- query = nbxmpp.Iq('get', to=jid)
- e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
- e = e.addChild('configure', {'node': node})
- id_ = self.connection.getAnID()
- query.setID(id_)
- self.awaiting_answers[id_] = (PEP_CONFIG,)
- self.connection.send(query)
diff --git a/src/common/resolver.py b/src/common/resolver.py
deleted file mode 100644
index 736463655..000000000
--- a/src/common/resolver.py
+++ /dev/null
@@ -1,157 +0,0 @@
-## common/resolver.py
-##
-## Copyright (C) 2006 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/>.
-##
-
-import sys
-import logging
-import functools
-log = logging.getLogger('gajim.c.resolver')
-
-if __name__ == '__main__':
- sys.path.append('..')
- from common import i18n
- import common.configpaths
- common.configpaths.gajimpaths.init(None)
-
-from gi.repository import Gio, GLib
-
-
-def get_resolver(idlequeue):
- return GioResolver()
-
-
-class CommonResolver():
- def __init__(self):
- # dict {"host+type" : list of records}
- self.resolved_hosts = {}
- # dict {"host+type" : list of callbacks}
- self.handlers = {}
-
- def resolve(self, host, on_ready, type_='srv'):
- host = host.lower()
- log.debug('resolve %s type=%s' % (host, type_))
- assert(type_ in ['srv', 'txt'])
- if not host:
- # empty host, return empty list of srv records
- on_ready([])
- return
- if host + type_ in self.resolved_hosts:
- # host is already resolved, return cached values
- log.debug('%s already resolved: %s' % (host,
- self.resolved_hosts[host + type_]))
- on_ready(host, self.resolved_hosts[host + type_])
- return
- if host + type_ in self.handlers:
- # host is about to be resolved by another connection,
- # attach our callback
- log.debug('already resolving %s' % host)
- self.handlers[host + type_].append(on_ready)
- else:
- # host has never been resolved, start now
- log.debug('Starting to resolve %s using %s' % (host, self))
- self.handlers[host + type_] = [on_ready]
- self.start_resolve(host, type_)
-
- def _on_ready(self, host, type_, result_list):
- # practically it is impossible to be the opposite, but who knows :)
- host = host.lower()
- log.debug('Resolving result for %s: %s' % (host, result_list))
- if host + type_ not in self.resolved_hosts:
- self.resolved_hosts[host + type_] = result_list
- if host + type_ in self.handlers:
- for callback in self.handlers[host + type_]:
- callback(host, result_list)
- del(self.handlers[host + type_])
-
- def start_resolve(self, host, type_):
- pass
-
-
-class GioResolver(CommonResolver):
- """
- Asynchronous resolver using GIO. process() method has to be
- called in order to proceed the pending requests.
- """
-
- def __init__(self):
- super().__init__()
- self.gio_resolver = Gio.Resolver.get_default()
-
- def start_resolve(self, host, type_):
- if type_ == 'txt':
- # TXT record resolution isn't used anywhere at the moment so
- # implementing it here isn't urgent
- raise NotImplemented("Gio resolver does not currently implement TXT records")
- else:
- callback = functools.partial(self._on_ready_srv, host)
- type_ = Gio.ResolverRecordType.SRV
-
- resq = self.gio_resolver.lookup_records_async(host, type_, None, callback)
-
- def _on_ready_srv(self, host, source_object, result):
- try:
- variant_results = source_object.lookup_records_finish(result)
- except GLib.Error as e:
- if e.domain == 'g-resolver-error-quark':
- result_list = []
- log.warning("Could not resolve host: %s", e.message)
- else:
- raise
- else:
- result_list = [
- {
- 'weight': weight,
- 'prio': prio,
- 'port': port,
- 'host': host,
- }
- for prio, weight, port, host
- in variant_results
- ]
- super()._on_ready(host, 'srv', result_list)
-
-
-# below lines is on how to use API and assist in testing
-if __name__ == '__main__':
- from gi.repository import Gtk
- from nbxmpp import idlequeue
-
- idlequeue = idlequeue.get_idlequeue()
- resolver = get_resolver(idlequeue)
-
- def clicked(widget):
- global resolver
- host = text_view.get_text()
- def on_result(host, result_array):
- print('Result:\n' + repr(result_array))
- resolver.resolve(host, on_result)
- win = Gtk.Window()
- win.set_border_width(6)
- win.connect('remove', Gtk.main_quit)
- text_view = Gtk.Entry()
- text_view.set_text('_xmpp-client._tcp.jabber.org')
- hbox = Gtk.HBox()
- hbox.set_spacing(3)
- but = Gtk.Button(' Lookup SRV ')
- hbox.pack_start(text_view, 5, True, 0)
- hbox.pack_start(but, 0, True, 0)
- but.connect('clicked', clicked)
- win.add(hbox)
- win.show_all()
- GLib.timeout_add(200, idlequeue.process)
- Gtk.main()
diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py
deleted file mode 100644
index 77d73e562..000000000
--- a/src/common/rst_xhtml_generator.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/rst_xhtml_generator.py
-##
-## Copyright (C) 2006 Santiago Gala
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-try:
- from docutils import io
- from docutils.core import Publisher
- from docutils.parsers.rst import roles
- from docutils import nodes, utils
- from docutils.parsers.rst.roles import set_classes
-except ImportError:
- print("Requires docutils 0.4 for set_classes to be available")
- def create_xhtml(text):
- return None
-else:
- def pos_int_validator(text):
- """
- Validates that text can be evaluated as a positive integer
- """
- result = int(text)
- if result < 0:
- raise ValueError("Error: value '%(text)s' "
- "must be a positive integer")
- return result
-
- def generate_uri_role( role_name, aliases, anchor_text, base_url,
- interpret_url, validator):
- """
- Create and register a uri based "interpreted role"
-
- Those are similar to the RFC, and PEP ones, and take
- role_name:
- name that will be registered
- aliases:
- list of alternate names
- anchor_text:
- text that will be used, together with the role
- base_url:
- base url for the link
- interpret_url:
- this, modulo the validated text, will be added to it
- validator:
- should return the validated text, or raise ValueError
- """
- def uri_reference_role(role, rawtext, text, lineno, inliner,
- options=None, content=None):
- if options is None:
- options = {}
- try:
- valid_text = validator(text)
- except ValueError as e:
- msg = inliner.reporter.error( e.message % dict(text=text), line=lineno)
- prb = inliner.problematic(rawtext, rawtext, msg)
- return [prb], [msg]
- ref = base_url + interpret_url % valid_text
- set_classes(options)
- node = nodes.reference(rawtext, anchor_text + utils.unescape(text), refuri=ref,
- **options)
- return [node], []
-
- uri_reference_role.__doc__ = """Role to make handy references to URIs.
-
- Use as :%(role_name)s:`71` (or any of %(aliases)s).
- It will use %(base_url)s+%(interpret_url)s
- validator should throw a ValueError, containing optionally
- a %%(text)s format, if the interpreted text is not valid.
- """ % locals()
- roles.register_canonical_role(role_name, uri_reference_role)
- from docutils.parsers.rst.languages import en
- en.roles[role_name] = role_name
- for alias in aliases:
- en.roles[alias] = role_name
-
- generate_uri_role('xep-reference', ('jep', 'xep'),
- 'XEP #', 'http://www.xmpp.org/extensions/', 'xep-%04d.html',
- pos_int_validator)
- generate_uri_role('gajim-ticket-reference', ('ticket', 'gtrack'),
- 'Gajim Ticket #', 'http://trac.gajim.org/ticket/', '%d',
- pos_int_validator)
-
- class HTMLGenerator:
- """
- Really simple HTMLGenerator starting from publish_parts
-
- It reuses the docutils.core.Publisher class, which means it is *not*
- threadsafe.
- """
- def __init__(self, settings_spec=None, settings_overrides=None,
- config_section='general'):
- if settings_overrides is None:
- settings_overrides = {'report_level': 5, 'halt_level': 5}
- self.pub = Publisher(reader=None, parser=None, writer=None,
- settings=None,
- source_class=io.StringInput,
- destination_class=io.StringOutput)
- self.pub.set_components(reader_name='standalone',
- parser_name='restructuredtext',
- writer_name='html')
- # hack: JEP-0071 does not allow HTML char entities, so we hack our way
- # out of it.
- # &mdash; == u"\u2014"
- # a setting to only emit charater entities in the writer would be nice
- # FIXME: several &nbsp; are emitted, and they are explicitly forbidden
- # in the JEP
- # &nbsp; == u"\u00a0"
- self.pub.writer.translator_class.attribution_formats['dash'] = (
- '\u2014', '')
- self.pub.process_programmatic_settings(settings_spec,
- settings_overrides,
- config_section)
-
-
- def create_xhtml(self, text, destination=None, destination_path=None,
- enable_exit_status=None):
- """
- Create xhtml for a fragment of IM dialog. We can use the source_name
- to store info about the message
- """
- self.pub.set_source(text, None)
- self.pub.set_destination(destination, destination_path)
- output = self.pub.publish(enable_exit_status=enable_exit_status)
- # kludge until we can get docutils to stop generating (rare) &nbsp;
- # entities
- return '\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
- '&nbsp;'))
-
- Generator = HTMLGenerator()
-
- def create_xhtml(text):
- return Generator.create_xhtml(text)
-
-
-if __name__ == '__main__':
- print("test 1\n" + Generator.create_xhtml("""
-test::
-
->>> print 1
-1
-
-*I* like it. It is for :JEP:`71`
-
-this `` should trigger`` should trigger the &nbsp; problem.
-
-"""))
- print("test 2\n" + Generator.create_xhtml("""
-*test1
-
-test2_
-"""))
- print("test 3\n" + Generator.create_xhtml(""":ticket:`316` implements :xep:`71`"""))
diff --git a/src/common/sleepy.py b/src/common/sleepy.py
deleted file mode 100644
index ca1a34d3e..000000000
--- a/src/common/sleepy.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/sleepy.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Mateusz Biliński <mateusz AT 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/>.
-##
-
-from common import gajim
-import os
-
-
-STATE_UNKNOWN = 'OS probably not supported'
-STATE_XA = 'extended away'
-STATE_AWAY = 'away'
-STATE_AWAKE = 'awake'
-
-SUPPORTED = True
-try:
- if os.name == 'nt':
- import ctypes
-
- GetTickCount = ctypes.windll.kernel32.GetTickCount
- GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo
-
- class LASTINPUTINFO(ctypes.Structure):
- _fields_ = [('cbSize', ctypes.c_uint), ('dwTime', ctypes.c_uint)]
-
- lastInputInfo = LASTINPUTINFO()
- lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
-
- # one or more of these may not be supported before XP.
- OpenInputDesktop = ctypes.windll.user32.OpenInputDesktop
- CloseDesktop = ctypes.windll.user32.CloseDesktop
- SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
- else: # unix
- from common import idle
- idle.xss_available
-except Exception:
- gajim.log.debug('Unable to load idle module')
- SUPPORTED = False
-
-class SleepyWindows:
- def __init__(self, away_interval = 60, xa_interval = 120):
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- GetLastInputInfo(ctypes.byref(lastInputInfo))
- idleDelta = float(GetTickCount() - lastInputInfo.dwTime) / 1000
- return idleDelta
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- # screen saver, in windows >= XP
- saver_runing = ctypes.c_int(0)
- # 0x72 is SPI_GETSCREENSAVERRUNNING
- if SystemParametersInfo(0x72, 0, ctypes.byref(saver_runing), 0) and \
- saver_runing.value:
- self.state = STATE_XA
- return True
-
- desk = OpenInputDesktop(0, False, 0)
- if not desk:
- # Screen locked
- self.state = STATE_XA
- return True
- CloseDesktop(desk)
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
-
-class SleepyUnix:
- def __init__(self, away_interval = 60, xa_interval = 120):
- global SUPPORTED
- self.away_interval = away_interval
- self.xa_interval = xa_interval
- self.state = STATE_AWAKE # assume we are awake
-
- def getIdleSec(self):
- return idle.getIdleSec()
-
- def poll(self):
- """
- Check to see if we should change state
- """
- if not SUPPORTED:
- return False
-
- idleTime = self.getIdleSec()
-
- # xa is stronger than away so check for xa first
- if idleTime > self.xa_interval:
- self.state = STATE_XA
- elif idleTime > self.away_interval:
- self.state = STATE_AWAY
- else:
- self.state = STATE_AWAKE
- return True
-
- def getState(self):
- return self.state
-
- def setState(self, val):
- self.state = val
-
-if os.name == 'nt':
- Sleepy = SleepyWindows
-else:
- Sleepy = SleepyUnix
diff --git a/src/common/socks5.py b/src/common/socks5.py
deleted file mode 100644
index 1273e6486..000000000
--- a/src/common/socks5.py
+++ /dev/null
@@ -1,1474 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/socks5.py
-##
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import socket
-import struct
-import hashlib
-import os
-import time
-from errno import EWOULDBLOCK
-from errno import ENOBUFS
-from errno import EINTR
-from errno import EISCONN
-from errno import EINPROGRESS
-from errno import EAFNOSUPPORT
-from nbxmpp.idlequeue import IdleObject
-from common.file_props import FilesProp
-from common import gajim
-from common import jingle_xtls
-if jingle_xtls.PYOPENSSL_PRESENT:
- import OpenSSL
-import logging
-log = logging.getLogger('gajim.c.socks5')
-MAX_BUFF_LEN = 65536
-# after foo seconds without activity label transfer as 'stalled'
-STALLED_TIMEOUT = 10
-# after foo seconds of waiting to connect, disconnect from
-# streamhost and try next one
-CONNECT_TIMEOUT = 30
-# nothing received for the last foo seconds - stop transfer
-# if it is 0, then transfer will wait forever
-READ_TIMEOUT = 180
-# nothing sent for the last foo seconds - stop transfer
-# if it is 0, then transfer will wait forever
-SEND_TIMEOUT = 180
-
-
-class SocksQueue:
- """
- Queue for all file requests objects
- """
-
- def __init__(self, idlequeue, complete_transfer_cb=None,
- progress_transfer_cb=None, error_cb=None):
- self.connected = 0
- self.readers = {}
- self.senders = {}
- self.idx = 1
- self.listener = None
- self.sha_handlers = {}
- # handle all io events in the global idle queue, instead of processing
- # each foo seconds
- self.idlequeue = idlequeue
- self.complete_transfer_cb = complete_transfer_cb
- self.progress_transfer_cb = progress_transfer_cb
- self.error_cb = error_cb
- self.on_success = {} # {id: cb}
- self.on_failure = {} # {id: cb}
-
- def start_listener(self, port, sha_str, sha_handler, file_props,
- fingerprint=None, typ='sender'):
- """
- Start waiting for incomming connections on (host, port) and do a socks5
- authentication using sid for generated SHA
- """
- log.debug('Start listening for socks5 connection')
- sid = file_props.sid
- self.sha_handlers[sha_str] = (sha_handler, sid)
- if self.listener is None or self.listener.connections == []:
- self.listener = Socks5Listener(self.idlequeue, port, file_props,
- fingerprint=fingerprint)
- self.listener.queue = self
- self.listener.bind()
- else:
- # There is already a listener, we update the file's information
- # on the new connection.
- self.listener.file_props = file_props
- self.connected += 1
- return self.listener
-
- def send_success_reply(self, file_props, streamhost):
- if file_props.streamhost_used == True:
- for proxy in file_props.proxyhosts:
- if proxy['host'] == streamhost['host']:
- self.on_success[file_props.sid](proxy)
- return 1
- return 0
- for host in file_props.streamhosts:
- if streamhost['state'] == 1:
- return 0
- streamhost['state'] = 1
- self.on_success[file_props.sid](streamhost)
- return 1
-
- def connect_to_hosts(self, account, sid, on_success=None, on_failure=None,
- fingerprint=None, receiving=True):
- self.on_success[sid] = on_success
- self.on_failure[sid] = on_failure
- file_props = FilesProp.getFileProp(account, sid)
- file_props.failure_cb = on_failure
- streamhosts_to_test = []
- # Remove local IPs to not connect to ourself
- for streamhost in file_props.streamhosts:
- if streamhost['host'] == '127.0.0.1' or streamhost['host'] == '::1':
- continue
- streamhosts_to_test.append(streamhost)
- if not streamhosts_to_test:
- on_failure(file_props.sid)
- # add streamhosts to the queue
- for streamhost in streamhosts_to_test:
- if 'type' in streamhost and streamhost['type'] == 'proxy':
- fp = None
- else:
- fp = fingerprint
- if receiving:
- log.debug('Trying to connect as receiver to cid ' + streamhost['cid'])
- file_props.type_ = 'r'
- socks5obj = Socks5ReceiverClient(self.idlequeue, streamhost,
- sid, file_props, fingerprint=fp)
- self.add_sockobj(account, socks5obj)
- else:
- log.debug('Trying to connect as sender to cid' + streamhost['cid'])
- if file_props.sha_str:
- idx = file_props.sha_str
- else:
- idx = self.idx
- self.idx = self.idx + 1
- file_props.type_ = 's'
- if 'type' in streamhost and streamhost['type'] == 'proxy':
- file_props.is_a_proxy = True
- file_props.proxy_sender = streamhost['target']
- file_props.proxy_receiver = streamhost['initiator']
- socks5obj = Socks5SenderClient(self.idlequeue, idx,
- self, _sock=None,host=str(streamhost['host']),
- port=int(streamhost['port']),fingerprint=fp,
- connected=False, file_props=file_props,
- initiator=streamhost['initiator'],
- target=streamhost['target'])
- socks5obj.streamhost = streamhost
- self.add_sockobj(account, socks5obj)
-
- streamhost['idx'] = socks5obj.queue_idx
-
- def _socket_connected(self, streamhost, file_props):
- """
- Called when there is a host connected to one of the senders's
- streamhosts. Stop other attempts for connections
- """
- log.debug('Connected to cid ' + streamhost['cid'])
- for host in file_props.streamhosts:
- if host != streamhost and 'idx' in host:
- if host['state'] == 1:
- # remove current
- if file_props.type_ == 's':
- self.remove_sender(streamhost['idx'], False)
- else:
- self.remove_receiver(streamhost['idx'])
- return
- # set state -2, meaning that this streamhost is stopped,
- # but it may be connectected later
- if host['state'] >= 0:
- if file_props.type_ == 's':
- self.remove_sender(host['idx'], False)
- else:
- self.remove_receiver(host['idx'])
- host['idx'] = -1
- host['state'] = -2
-
- def reconnect_client(self, client, streamhost):
- """
- Check the state of all streamhosts and if all has failed, then emit
- connection failure cb. If there are some which are still not connected
- try to establish connection to one of them
- """
- self.idlequeue.remove_timeout(client.fd)
- self.idlequeue.unplug_idle(client.fd)
- file_props = client.file_props
- streamhost['state'] = -1
- # boolean, indicates that there are hosts, which are not tested yet
- unused_hosts = False
- for host in file_props.streamhosts:
- if 'idx' in host:
- if host['state'] >= 0:
- return
- elif host['state'] == -2:
- unused_hosts = True
- if unused_hosts:
- for host in file_props.streamhosts:
- if host['state'] == -2:
- host['state'] = 0
- # FIXME: make the sender reconnect also
- client = Socks5ReceiverClient(self.idlequeue, host,
- host['sid'], file_props)
- self.add_sockobj(client.account, client)
- host['idx'] = client.queue_idx
- # we still have chances to connect
- return
- if file_props.received_len == 0:
- # there are no other streamhosts and transfer hasn't started
- self._connection_refused(streamhost, file_props, client.queue_idx)
- else:
- # transfer stopped, it is most likely stopped from sender
- client.disconnect()
- file_props.error = -1
- self.process_result(-1, client)
-
- def _connection_refused(self, streamhost, file_props, idx):
- """
- Called when we loose connection during transfer
- """
- log.debug('Connection refused to cid ' + streamhost['cid'])
- if file_props is None:
- return
- streamhost['state'] = -1
- # FIXME: should only the receiver be remove? what if we are sending?
- self.remove_receiver(idx, False)
- for host in file_props.streamhosts:
- if host['state'] != -1:
- return
- self.readers = {}
- # failure_cb exists - this means that it has never been called
- if file_props.failure_cb:
- file_props.failure_cb(file_props.sid)
- file_props.failure_cb = None
-
- def add_sockobj(self, account, sockobj):
- """
- Add new file a sockobj type receiver or sender, and use it to connect
- to server
- """
- if sockobj.file_props.type_ == 'r':
- self._add(sockobj, self.readers, sockobj.file_props, self.idx)
- else:
- self._add(sockobj, self.senders, sockobj.file_props, self.idx)
- sockobj.queue_idx = self.idx
- sockobj.queue = self
- sockobj.account = account
- self.idx += 1
- result = sockobj.connect()
- self.connected += 1
- if result is not None:
- result = sockobj.main()
- self.process_result(result, sockobj)
- return 1
- return None
-
- def _add(self, sockobj, sockobjects, file_props, hash_):
- '''
- Adds the sockobj to the current list of sockobjects
- '''
- keys = (file_props.sid, file_props.name, hash_)
- sockobjects[keys] = sockobj
-
- def result_sha(self, sha_str, idx):
- if sha_str in self.sha_handlers:
- props = self.sha_handlers[sha_str]
- props[0](props[1], idx)
-
- def activate_proxy(self, idx):
- if not self.isHashInSockObjs(self.senders, idx):
- return
- for key in self.senders.keys():
- if idx in key:
- sender = self.senders[key]
- if sender.file_props.type_ != 's':
- return
- sender.state = 6
- if sender.connected:
- sender.file_props.error = 0
- sender.file_props.disconnect_cb = sender.disconnect
- sender.file_props.started = True
- sender.file_props.completed = False
- sender.file_props.paused = False
- sender.file_props.stalled = False
- sender.file_props.elapsed_time = 0
- sender.file_props.last_time = time.time()
- sender.file_props.received_len = 0
- sender.pauses = 0
- # start sending file to proxy
- self.idlequeue.set_read_timeout(sender.fd, STALLED_TIMEOUT)
- self.idlequeue.plug_idle(sender, True, False)
- result = sender.write_next()
- self.process_result(result, sender)
-
- def send_file(self, file_props, account, mode):
- for key in self.senders.keys():
- if self.senders == {}:
- # Python acts very weird with this. When there is no keys
- # in the dictionary It says that it has a key.
- # Maybe it is my machine. Without this there is a KeyError
- # traceback.
- return
- if file_props.name in key and file_props.sid in key \
- and self.senders[key].mode == mode:
- log.info('socks5: sending file')
- sender = self.senders[key]
- file_props.streamhost_used = True
- sender.account = account
- sender.file_props = file_props
- result = sender.send_file()
- self.process_result(result, sender)
-
- def isHashInSockObjs(self, sockobjs, hash):
- '''
- It tells wether there is a particular hash in sockobjs or not
- '''
- for key in sockobjs:
- if hash in key:
- return True
- return False
-
- def on_connection_accepted(self, sock, listener):
- sock_hash = sock.__hash__()
- if listener.file_props.type_ == 's' and \
- not self.isHashInSockObjs(self.senders, sock_hash):
- sockobj = Socks5SenderServer(self.idlequeue, sock_hash, self,
- sock[0], sock[1][0], sock[1][1], fingerprint=None,
- file_props=listener.file_props)
- self._add(sockobj, self.senders, listener.file_props, sock_hash)
- # Start waiting for data
- self.idlequeue.plug_idle(sockobj, False, True)
- self.connected += 1
- if listener.file_props.type_ == 'r' and \
- not self.isHashInSockObjs(self.readers, sock_hash):
- sh = {}
- sh['host'] = sock[1][0]
- sh['port'] = sock[1][1]
- sh['initiator'] = None
- sh['target'] = None
- sockobj = Socks5ReceiverServer(idlequeue=self.idlequeue,
- streamhost=sh,sid=None, file_props=listener.file_props,
- fingerprint=None)
-
- self._add(sockobj, self.readers, listener.file_props, sock_hash)
- sockobj.set_sock(sock[0])
- sockobj.queue = self
- self.connected += 1
-
- def process_result(self, result, actor):
- """
- Take appropriate actions upon the result:
- [ 0, - 1 ] complete/end transfer
- [ > 0 ] send progress message
- [ None ] do nothing
- """
- if result is None:
- return
- if result in (0, -1) and self.complete_transfer_cb is not None:
- account = actor.account
- if account is None and actor.file_props.tt_account:
- account = actor.file_props.tt_account
- self.complete_transfer_cb(account, actor.file_props)
- elif self.progress_transfer_cb is not None:
- self.progress_transfer_cb(actor.account, actor.file_props)
-
- def remove_receiver_by_key(self, key, do_disconnect=True):
- reader = self.readers[key]
- self.idlequeue.unplug_idle(reader.fd)
- self.idlequeue.remove_timeout(reader.fd)
- if do_disconnect:
- reader.disconnect()
- else:
- if reader.streamhost is not None:
- reader.streamhost['state'] = -1
- del(self.readers[key])
-
- def remove_sender_by_key(self, key, do_disconnect=True):
- sender = self.senders[key]
- if do_disconnect:
- sender.disconnect()
- else:
- self.idlequeue.unplug_idle(sender.fd)
- self.idlequeue.remove_timeout(sender.fd)
- del(self.senders[key])
- if self.connected > 0:
- self.connected -= 1
-
- def remove_receiver(self, idx, do_disconnect=True, remove_all=False):
- """
- Remove reciver from the list and decrease the number of active
- connections with 1
- """
- if idx != -1:
- for key in list(self.readers.keys()):
- if idx in key:
- self.remove_receiver_by_key(key,
- do_disconnect=do_disconnect)
- if not remove_all:
- break
-
- def remove_sender(self, idx, do_disconnect=True, remove_all=False):
- """
- Remove sender from the list of senders and decrease the number of active
- connections with 1
- """
- if idx != -1:
- for key in self.senders.keys():
- if idx in key:
- self.remove_sender_by_key(key, do_disconnect=do_disconnect)
- if not remove_all:
- break
- if len(self.senders) == 0 and self.listener is not None:
- self.listener.disconnect()
- self.listener = None
- self.connected -= 1
-
- def remove_by_mode(self, sid, mode, do_disconnect=True):
- for (key, sock) in self.senders.copy().items():
- if key[0] == sid and sock.mode == mode:
- self.remove_sender_by_key(key)
- for (key, sock) in self.readers.copy().items():
- if key[0] == sid and sock.mode == mode:
- self.remove_receiver_by_key(key)
-
- def remove_server(self, sid, do_disconnect=True):
- self.remove_by_mode(sid, 'server')
-
- def remove_client(self, sid, do_disconnect=True):
- self.remove_by_mode(sid, 'client')
-
-class Socks5(object):
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- if host is not None:
- try:
- self.host = host
- self.ais = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror:
- self.ais = None
- self.idlequeue = idlequeue
- self.fd = -1
- self.port = port
- self.initiator = initiator
- self.target = target
- self.sid = sid
- self._sock = None
- self.account = None
- self.state = 0 # not connected
- self.pauses = 0
- self.size = 0
- self.remaining_buff = b''
- self.file = None
- self.connected = False
- self.mode = ''
- self.ssl_cert = None
- self.ssl_errnum = 0
-
- def _is_connected(self):
- if self.state < 5:
- return False
- return True
-
- def ssl_verify_cb(self, ssl_conn, cert, error_num, depth, return_code):
- if depth == 0:
- self.ssl_cert = cert
- self.ssl_errnum = error_num
- return True
-
- def connect(self):
- """
- Create the socket and plug it to the idlequeue
- """
- if self.ais is None:
- return None
- for ai in self.ais:
- try:
- self._sock = socket.socket(*ai[:3])
- if self.fingerprint is not None:
- if self.file_props.type_ == 's':
- remote_jid = gajim.get_jid_without_resource(
- self.file_props.receiver)
- else:
- remote_jid = gajim.get_jid_without_resource(
- self.file_props.sender)
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('client',
- verify_cb=self.ssl_verify_cb, remote_jid=remote_jid),
- self._sock)
- # this will not block the GUI
- self._sock.setblocking(False)
- self._server = ai[4]
- break
- except socket.error as e:
- if e.errno == EINPROGRESS:
- break
- # for all other errors, we try other addresses
- continue
- self.fd = self._sock.fileno()
- self.state = 0 # about to be connected
- self.idlequeue.plug_idle(self, True, False)
- self.do_connect()
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- return None
-
- def do_connect(self):
- try:
- self._sock.connect(self._server)
- self._send=self._sock.send
- self._recv=self._sock.recv
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError) as e:
- pass
- except Exception as ee:
- errnum = ee.errno
- self.connect_timeout += 1
- if errnum == 111 or self.connect_timeout > 1000:
- self.queue._connection_refused(self.streamhost, self.file_props,
- self.queue_idx)
- self.connected = False
- return None
- # win32 needs this
- elif errnum not in (10056, EISCONN) or self.state != 0:
- return None
- else: # socket is already connected
- self._sock.setblocking(False)
- self._send=self._sock.send
- self._recv=self._sock.recv
- self.buff = ''
- self.connected = True
- self.file_props.connected = True
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.paused = False
- self.state = 1 # connected
- # stop all others connections to sender's streamhosts
- self.queue._socket_connected(self.streamhost, self.file_props)
- self.idlequeue.plug_idle(self, True, False)
- return 1 # we are connected
-
- def read_timeout(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.state > 5:
- # no activity for foo seconds
- if self.file_props.stalled == False:
- self.file_props.stalled = True
- self.queue.process_result(-1, self)
- if not self.file_props.received_len:
- self.file_props.received_len = 0
- if SEND_TIMEOUT > 0:
- self.idlequeue.set_read_timeout(self.fd, SEND_TIMEOUT)
- else:
- # stop transfer, there is no error code for this
- self.pollend()
- else:
- if self.mode == 'client':
- self.queue.reconnect_client(self, self.streamhost)
-
- def open_file_for_reading(self):
- if self.file is None:
- try:
- self.file = open(self.file_props.file_name, 'rb')
- if self.file_props.offset:
- self.size = self.file_props.offset
- self.file.seek(self.size)
- self.file_props.received_len = self.size
- except IOError as e:
- self.close_file()
- raise IOError(str(e))
-
- def close_file(self):
- # Close file we're sending from
- if self.file:
- if not self.file.closed:
- try:
- self.file.close()
- except Exception:
- pass
- self.file = None
- # Close file we're receiving into
- if self.file_props.fd:
- try:
- self.file_props.fd.close()
- except Exception:
- pass
-
- def get_fd(self):
- """
- Test if file is already open and return its fd, or just open the file
- and return the fd
- """
- if self.file_props.fd:
- fd = self.file_props.fd
- else:
- offset = 0
- opt = 'wb'
- if self.file_props.offset:
- offset = self.file_props.offset
- opt = 'ab'
- fd = open(self.file_props.file_name, opt)
- self.file_props.fd = fd
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = offset
- return fd
-
- def rem_fd(self, fd):
- if self.file_props.fd:
- self.file_props.fd = None
- try:
- fd.close()
- except Exception:
- pass
-
- def receive(self):
- """
- Read small chunks of data. Call owner's disconnected() method if
- appropriate
- """
- received = b''
- try:
- add = self._recv(64)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request : ' + repr(e))
- raise e
- except Exception:
- add = b''
- received += add
- if len(add) == 0:
- self.disconnect()
- return add
-
- def send_raw(self, raw_data):
- """
- Write raw outgoing data
- """
- try:
- self._send(raw_data)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception:
- self.disconnect()
- return len(raw_data)
-
- def write_next(self):
- if self.remaining_buff != b'':
- buff = self.remaining_buff
- else:
- try:
- self.open_file_for_reading()
- except IOError:
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -7 # unable to read from file
- return -1
- buff = self.file.read(MAX_BUFF_LEN)
- if len(buff) > 0:
- lenn = 0
- try:
- lenn = self._send(buff)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception as e:
- if e.errno not in (EINTR, ENOBUFS, EWOULDBLOCK):
- # peer stopped reading
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -1
- return -1
- self.size += lenn
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len = self.size
- if self.size >= self.file_props.size:
- self.state = 8 # end connection
- self.file_props.error = 0
- self.disconnect()
- return -1
- if lenn != len(buff):
- self.remaining_buff = buff[lenn:]
- else:
- self.remaining_buff = b''
- self.state = 7 # continue to write in the socket
- if lenn == 0:
- return None
- self.file_props.stalled = False
- return lenn
- else:
- self.state = 8 # end connection
- self.disconnect()
- return -1
-
- def get_file_contents(self, timeout):
- """
- Read file contents from socket and write them to file
- """
- if self.file_props is None or not self.file_props.file_name:
- self.file_props.error = -2
- return None
- fd = None
- if self.remaining_buff != b'':
- try:
- fd = self.get_fd()
- except IOError:
- self.disconnect(False)
- self.file_props.error = -6 # file system error
- return 0
- fd.write(self.remaining_buff)
- lenn = len(self.remaining_buff)
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len += lenn
- self.remaining_buff = b''
- if self.file_props.received_len == self.file_props.size:
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = 0
- self.file_props.completed = True
- return 0
- else:
- try:
- fd = self.get_fd()
- except IOError:
- self.disconnect(False)
- self.file_props.error = -6 # file system error
- return 0
- try:
- buff = self._recv(MAX_BUFF_LEN)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('SSL rehandshake request :' + repr(e))
- raise e
- except Exception:
- buff = b''
- current_time = time.time()
- self.file_props.elapsed_time += current_time - \
- self.file_props.last_time
- self.file_props.last_time = current_time
- self.file_props.received_len += len(buff)
- if len(buff) == 0:
- # Transfer stopped somehow:
- # reset, paused or network error
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = -1
- return 0
- try:
- fd.write(buff)
- except IOError:
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = -6 # file system error
- return 0
- if self.file_props.received_len >= self.file_props.size:
- # transfer completed
- self.rem_fd(fd)
- self.disconnect()
- self.file_props.error = 0
- self.file_props.completed = True
- return 0
- # return number of read bytes. It can be used in progressbar
- if fd is not None:
- self.file_props.stalled = False
- if fd is None and self.file_props.stalled is False:
- return None
- if self.file_props.received_len:
- if self.file_props.received_len != 0:
- return self.file_props.received_len
- return None
-
- def disconnect(self):
- """
- Close open descriptors and remover socket descr. from idleque
- """
- # be sure that we don't leave open file
- self.close_file()
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- if self.mode == 'server' and self.queue.listener:
- try:
- self.queue.listener.connections.remove(self._sock)
- except ValueError:
- pass # Not in list
- if self.queue.listener.connections == []:
- self.queue.listener.disconnect()
- try:
- if isinstance(self._sock, OpenSSL.SSL.Connection):
- self._sock.shutdown()
- else:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except Exception:
- # socket is already closed
- pass
- self.connected = False
- self.fd = -1
- self.state = -1
-
- def _get_auth_buff(self):
- """
- Message, that we support 1 one auth mechanism: the 'no auth' mechanism
- """
- return struct.pack('!BBB', 0x05, 0x01, 0x00)
-
- def _parse_auth_buff(self, buff):
- """
- Parse the initial message and create a list of auth mechanisms
- """
- auth_mechanisms = []
- try:
- num_auth = struct.unpack('!xB', buff[:2])[0]
- for i in range(num_auth):
- mechanism, = struct.unpack('!B', buff[1 + i])
- auth_mechanisms.append(mechanism)
- except Exception:
- return None
- return auth_mechanisms
-
- def _get_auth_response(self):
- """
- Socks version(5), number of extra auth methods (we send 0x00 - no auth)
- """
- return struct.pack('!BB', 0x05, 0x00)
-
- def _get_connect_buff(self):
- """
- Connect request by domain name
- """
- buff = struct.pack('!BBBBB%dsBB' % len(self.host),
- 0x05, 0x01, 0x00, 0x03, len(self.host), self.host.encode('utf-8'),
- self.port >> 8, self.port & 0xff)
- return buff
-
- def _get_request_buff(self, msg, command = 0x01):
- """
- Connect request by domain name, sid sha, instead of domain name (jep
- 0096)
- """
- if isinstance(msg, str):
- msg = msg.encode('utf-8')
- buff = struct.pack('!BBBBB%dsBB' % len(msg), 0x05, command, 0x00, 0x03,
- len(msg), msg, 0, 0)
- return buff
-
- def _parse_request_buff(self, buff):
- try: # don't trust on what comes from the outside
- req_type, host_type, = struct.unpack('!xBxB', buff[:4])
- if host_type == 0x01:
- host_arr = struct.unpack('!iiii', buff[4:8])
- host, = '.'.join(str(s) for s in host_arr)
- host_len = len(host)
- elif host_type == 0x03:
- host_len, = struct.unpack('!B', buff[4])
- host, = struct.unpack('!%ds' % host_len, buff[5:5 + host_len])
- portlen = len(buff[host_len + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[host_len + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[host_len + 5:])
- # file data, comes with auth message (Gaim bug)
- else:
- port, = struct.unpack('!H', buff[host_len + 5: host_len + 7])
- self.remaining_buff = buff[host_len + 7:]
- except Exception:
- return (None, None, None)
- return (req_type, host, port)
-
- def read_connect(self):
- """
- Connect response: version, auth method
- """
- try:
- buff = self._recv().decode('utf-8')
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info("SSL rehandshake request : " + repr(e))
- raise e
- try:
- version, method = struct.unpack('!BB', buff)
- except Exception:
- version, method = None, None
- if version != 0x05 or method == 0xff:
- self.disconnect()
-
- def continue_paused_transfer(self):
- if self.state < 5:
- return
- if self.file_props.type_ == 'r':
- self.idlequeue.plug_idle(self, False, True)
- else:
- self.idlequeue.plug_idle(self, True, False)
-
- def _get_sha1_auth(self):
- """
- Get sha of sid + Initiator jid + Target jid
- """
- if self.file_props.is_a_proxy:
- return hashlib.sha1(('%s%s%s' % (self.sid,
- self.file_props.proxy_sender, self.file_props.proxy_receiver)).\
- encode('utf-8')).hexdigest()
- return hashlib.sha1(('%s%s%s' % (self.sid, self.initiator,
- self.target)).encode('utf-8')).hexdigest()
-
-
-class Socks5Sender(IdleObject):
- """
- Class for sending file to socket over socks5
- """
- def __init__(self, idlequeue, sock_hash, parent, _sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None):
- self.fingerprint = fingerprint
- self.queue_idx = sock_hash
- self.queue = parent
- self.file_props = file_props
- self.proxy = False
- self._sock = _sock
- if _sock is not None:
- if self.fingerprint is not None and not isinstance(self._sock,
- OpenSSL.SSL.Connection):
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('server'), _sock)
- else:
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = connected
- self.state = 1 # waiting for first bytes
- self.connect_timeout = 0
-
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.connected = True
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
-
- def start_transfer(self):
- """
- Send the file
- """
- return self.write_next()
-
- def set_connection_sock(self, _sock):
- self._sock = _sock
- if self.fingerprint is not None:
- self._sock = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('client'), _sock)
- else:
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = True
- self.state = 1 # waiting for first bytes
- self.file_props = None
- # start waiting for data
- self.idlequeue.plug_idle(self, False, True)
-
- def send_file(self):
- """
- Start sending the file over verified connection
- """
- self.pauses = 0
- self.state = 7
- # plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return self.write_next() # initial for nl byte
-
- def disconnect(self, cb=True):
- """
- Close the socket
- """
- # close connection and remove us from the queue
- Socks5.disconnect(self)
- if self.file_props is not None:
- self.file_props.connected = False
- self.file_props.disconnect_cb = None
- if self.queue is not None:
- self.queue.remove_sender(self.queue_idx, False)
-
-
-class Socks5Receiver(IdleObject):
- def __init__(self, idlequeue, streamhost, sid, file_props = None,
- fingerprint=None):
- """
- fingerprint: fingerprint of certificates we shall use, set to None if
- TLS connection not desired
- """
- self.queue_idx = -1
- self.streamhost = streamhost
- self.queue = None
- self.fingerprint = fingerprint
- self.connect_timeout = 0
- self.connected = False
- self.pauses = 0
- self.file_props = file_props
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.error = 0
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.received_len = 0
-
- def receive_file(self):
- """
- Start receiving the file over verified connection
- """
- if self.file_props.started:
- return
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.continue_cb = self.continue_paused_transfer
- self.file_props.stalled = False
- self.file_props.connected = True
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
- self.pauses = 0
- self.state = 7
- # plug for reading
- self.idlequeue.plug_idle(self, False, True)
- return self.get_file_contents(0) # initial for nl byte
-
- def start_transfer(self):
- """
- Receive the file
- """
- return self.get_file_contents(0)
-
- def set_sock(self, _sock):
- self._sock = _sock
- self._sock.setblocking(False)
- self.fd = _sock.fileno()
- self._recv = _sock.recv
- self._send = _sock.send
- self.connected = True
- self.state = 1 # waiting for first bytes
- # start waiting for data
- self.idlequeue.plug_idle(self, False, True)
-
- def disconnect(self, cb=True):
- """
- Close the socket. Remove self from queue if cb is True
- """
- # close connection
- Socks5.disconnect(self)
- if cb is True:
- self.file_props.disconnect_cb = None
- if self.queue is not None:
- self.queue.remove_receiver(self.queue_idx, False)
-
-class Socks5Server(Socks5):
-
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- Socks5.__init__(self, idlequeue, host, port, initiator, target, sid)
- self.mode = 'server'
-
- def main(self):
- """
- Initial requests for verifying the connection
- """
- if self.state == 1: # initial read
- buff = self.receive()
- if not self.connected:
- return -1
- mechs = self._parse_auth_buff(buff)
- if mechs is None:
- return -1 # invalid auth methods received
- elif self.state == 3: # get next request
- buff = self.receive()
- req_type, self.sha_msg = self._parse_request_buff(buff)[:2]
- if req_type != 0x01:
- return -1 # request is not of type 'connect'
- self.state += 1 # go to the next step
- # unplug & plug for writing
- self.idlequeue.plug_idle(self, True, False)
- return None
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.connected:
- try:
- if self.state < 5:
- result = self.main()
- if self.state == 4:
- self.queue.result_sha(self.sha_msg, self.queue_idx)
- if result == -1:
- self.disconnect()
- elif self.state == 5:
- self.state = 7
- if self.file_props.type_ == 's':
- # We wait for the end of the negotiation to
- # send the file
- self.idlequeue.plug_idle(self, False, False)
- else:
- # We plug for reading
- self.idlequeue.plug_idle(self, False, True)
- return
- elif self.state == 7:
- if self.file_props.paused:
- self.file_props.continue_cb = \
- self.continue_paused_transfer
- self.idlequeue.plug_idle(self, False, False)
- return
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- else:
- self.disconnect()
-
- def pollend(self):
- self.state = 8 # end connection
- self.disconnect()
- self.file_props.error = -1
- self.queue.process_result(-1, self)
-
- def pollout(self):
- if not self.connected:
- self.disconnect()
- return
- self.idlequeue.remove_timeout(self.fd)
- try:
- if self.state == 2: # send reply with desired auth type
- self.send_raw(self._get_auth_response())
- elif self.state == 4: # send positive response to the 'connect'
- self.send_raw(self._get_request_buff(self.sha_msg, 0x00))
- elif self.state == 7:
- if self.file_props.paused:
- self.file_props.continue_cb = self.continue_paused_transfer
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- if result is None or result <= 0:
- self.disconnect()
- return
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- elif self.state == 8:
- self.disconnect()
- return
- else:
- self.disconnect()
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- if self.state < 5:
- self.state += 1
- # unplug and plug this time for reading
- self.idlequeue.plug_idle(self, False, True)
-
-
-class Socks5Client(Socks5):
-
- def __init__(self, idlequeue, host, port, initiator, target, sid):
- Socks5.__init__(self, idlequeue, host, port, initiator, target, sid)
- self.mode = 'client'
-
- def main(self, timeout=0):
- """
- Begin negotiation. on success 'address' != 0
- """
- result = 1
- buff = self.receive()
- if buff == '':
- # end connection
- self.pollend()
- return
- if self.state == 2: # read auth response
- if buff is None or len(buff) != 2:
- return None
- version, method = struct.unpack('!BB', buff[:2])
- if version != 0x05 or method == 0xff:
- self.disconnect()
- elif self.state == 4: # get approve of our request
- if buff is None:
- return None
- sub_buff = buff[:4]
- if len(sub_buff) < 4:
- return None
- version, address_type = struct.unpack('!BxxB', buff[:4])
- addrlen = 0
- if address_type == 0x03:
- addrlen = buff[4]
- address = struct.unpack('!%ds' % addrlen, buff[5:addrlen + 5])
- portlen = len(buff[addrlen + 5:])
- if portlen == 1:
- port, = struct.unpack('!B', buff[addrlen + 5])
- elif portlen == 2:
- port, = struct.unpack('!H', buff[addrlen + 5:])
- else: # Gaim bug :)
- port, = struct.unpack('!H', buff[addrlen + 5:addrlen + 7])
- self.remaining_buff = buff[addrlen + 7:]
- self.state = 5 # for senders: init file_props and send '\n'
- if self.queue.on_success:
- result = self.queue.send_success_reply(self.file_props,
- self.streamhost)
- if self.file_props.type_ == 's' and self.proxy:
- self.queue.process_result(self.send_file(), self)
- return
- if result == 0:
- self.state = 8
- self.disconnect()
- # for senders: init file_props
- if result == 1 and self.state == 5:
- if self.file_props.type_ == 's':
- self.file_props.error = 0
- self.file_props.disconnect_cb = self.disconnect
- self.file_props.started = True
- self.file_props.completed = False
- self.file_props.paused = False
- self.file_props.stalled = False
- self.file_props.elapsed_time = 0
- self.file_props.last_time = time.time()
- self.file_props.received_len = 0
- self.pauses = 0
- # start sending file contents to socket
- #self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- #self.idlequeue.plug_idle(self, True, False)
- self.idlequeue.plug_idle(self, False, False)
- else:
- # receiving file contents from socket
- self.idlequeue.plug_idle(self, False, True)
- self.file_props.continue_cb = self.continue_paused_transfer
- # we have set up the connection, next - retrieve file
- self.state = 6
- if self.state < 5:
- self.idlequeue.plug_idle(self, True, False)
- self.state += 1
- return None
-
- def send_file(self):
- if self.ssl_errnum > 0:
- log.error('remote certificate does not match the announced one.' + \
- '\nSSL Error: %d\nCancelling file transfer' % self.ssl_errnum)
- self.file_props.error = -12
- return -1
- return super(Socks5Client, self).send_file()
-
- def pollin(self):
- self.idlequeue.remove_timeout(self.fd)
- if self.connected:
- try:
- if self.file_props.paused:
- self.idlequeue.plug_idle(self, False, False)
- return
- if self.state < 5:
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
- result = self.main(0)
- self.queue.process_result(result, self)
- elif self.state == 5: # wait for proxy reply
- pass
- elif self.file_props.type_ == 'r':
- self.idlequeue.set_read_timeout(self.fd, STALLED_TIMEOUT)
- result = self.start_transfer() # receive
- self.queue.process_result(result, self)
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- else:
- self.disconnect()
-
- def pollout(self):
- self.idlequeue.remove_timeout(self.fd)
- try:
- if self.state == 0:
- self.do_connect()
- return
- elif self.state == 1: # send initially: version and auth types
- self.send_raw(self._get_auth_buff())
- elif self.state == 3: # send 'connect' request
- self.send_raw(self._get_request_buff(self._get_sha1_auth()))
- elif self.file_props.type_ != 'r':
- if self.file_props.paused:
- self.idlequeue.plug_idle(self, False, False)
- return
- result = self.start_transfer() # send
- self.queue.process_result(result, self)
- return
- except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError,
- OpenSSL.SSL.WantX509LookupError) as e:
- log.info('caught SSL exception, ignored')
- return
- self.state += 1
- # unplug and plug for reading
- self.idlequeue.plug_idle(self, False, True)
- self.idlequeue.set_read_timeout(self.fd, CONNECT_TIMEOUT)
-
- def pollend(self):
- if self.state >= 5:
- # error during transfer
- self.disconnect()
- self.file_props.error = -1
- self.queue.process_result(-1, self)
- else:
- self.queue.reconnect_client(self, self.streamhost)
-
-
-class Socks5SenderClient(Socks5Client, Socks5Sender):
-
- def __init__(self, idlequeue, sock_hash, parent,_sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None,
- initiator=None, target=None):
- Socks5Client.__init__(self, idlequeue, host, port, initiator, target,
- file_props.sid)
- Socks5Sender.__init__(self,idlequeue, sock_hash, parent,_sock,
- host, port, fingerprint , connected, file_props)
-
-
-class Socks5SenderServer(Socks5Server, Socks5Sender):
-
- def __init__(self, idlequeue, sock_hash, parent,_sock, host=None,
- port=None, fingerprint = None, connected=True, file_props=None):
- Socks5Server.__init__(self, idlequeue, host, port, None, None,
- file_props.sid)
- Socks5Sender.__init__(self,idlequeue, sock_hash, parent, _sock,
- host, port, fingerprint , connected, file_props)
-
-
-class Socks5ReceiverClient(Socks5Client, Socks5Receiver):
- def __init__(self, idlequeue, streamhost, sid, file_props=None,
- fingerprint=None):
- Socks5Client.__init__(self, idlequeue, streamhost['host'],
- int(streamhost['port']), streamhost['initiator'],
- streamhost['target'], sid)
- Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props,
- fingerprint)
-
-
-class Socks5ReceiverServer(Socks5Server, Socks5Receiver):
-
- def __init__(self, idlequeue, streamhost, sid, file_props=None,
- fingerprint=None):
- Socks5Server.__init__(self, idlequeue, streamhost['host'],
- int(streamhost['port']), streamhost['initiator'],
- streamhost['target'], sid)
- Socks5Receiver.__init__(self, idlequeue, streamhost, sid, file_props,
- fingerprint)
-
-
-class Socks5Listener(IdleObject):
- def __init__(self, idlequeue, port, fp, fingerprint=None):
- """
- Handle all incomming connections on (0.0.0.0, port)
-
- This class implements IdleObject, but we will expect
- only pollin events though
-
- fingerprint: fingerprint of certificates we shall use, set to None if
- TLS connection not desired
- """
- self.port = port
- self.ais = socket.getaddrinfo(None, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, socket.SOL_TCP, socket.AI_PASSIVE)
- self.ais.sort(reverse=True) # Try IPv6 first
- self.queue_idx = -1
- self.idlequeue = idlequeue
- self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
- self.fingerprint = fingerprint
- self.file_props = fp
- self.connections = []
-
- def bind(self):
- for ai in self.ais:
- # try the different possibilities (ipv6, ipv4, etc.)
- try:
- self._serv = socket.socket(*ai[:3])
- if self.fingerprint is not None:
- self._serv = OpenSSL.SSL.Connection(
- jingle_xtls.get_context('server'), self._serv)
- except socket.error as e:
- if e.errno == EAFNOSUPPORT:
- self.ai = None
- continue
- raise
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- # Under windows Vista, we need that to listen on ipv6 AND ipv4
- # Doesn't work under windows XP
- if os.name == 'nt':
- ver = os.sys.getwindowsversion()
- if (ver[3], ver[0]) == (2, 6): # Win Vista +
- # 47 is socket.IPPROTO_IPV6
- # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
- self._serv.setsockopt(41, 27, 0)
- # will fail when port as busy, or we don't have rights to bind
- try:
- self._serv.bind(ai[4])
- f = ai[4]
- self.ai = ai
- break
- except Exception:
- self.ai = None
- continue
- if not self.ai:
- log.error('unable to bind to port ' + str(self.port))
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- self.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- self.queue.on_connection_accepted(sock, self)
-
- def disconnect(self):
- """
- Free all resources, we are not listening anymore
- """
- self.idlequeue.remove_timeout(self.fd)
- self.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.state = -1
- self.started = False
- try:
- self._serv.close()
- except Exception:
- pass
-
- def accept_conn(self):
- """
- Accept a new incomming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- self.connections.append(_sock[0])
- return _sock
-
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
deleted file mode 100644
index 523d8f64c..000000000
--- a/src/common/stanza_session.py
+++ /dev/null
@@ -1,1213 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/common/stanza_session.py
-##
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from common import gajim
-import nbxmpp
-from common.exceptions import DecryptionError, NegotiationError
-import nbxmpp.c14n
-
-import itertools
-import random
-import string
-import time
-import base64
-import os
-from hashlib import sha256
-from hmac import HMAC
-from common import crypto
-
-import logging
-log = logging.getLogger('gajim.c.stanza_session')
-
-if gajim.HAVE_PYCRYPTO:
- from Crypto.Cipher import AES
- from Crypto.PublicKey import RSA
-
- from common import dh
- import secrets
-
-XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
-
-class StanzaSession(object):
- '''
- '''
- def __init__(self, conn, jid, thread_id, type_):
- '''
- '''
- self.conn = conn
- self.jid = jid
- self.type_ = type_
- self.resource = jid.getResource()
-
- if thread_id:
- self.received_thread_id = True
- self.thread_id = thread_id
- else:
- self.received_thread_id = False
- if type_ == 'normal':
- self.thread_id = None
- else:
- self.thread_id = self.generate_thread_id()
-
- self.loggable = True
-
- self.last_send = 0
- self.last_receive = 0
- self.status = None
- self.negotiated = {}
-
- def is_loggable(self):
- return self.loggable and gajim.config.should_log(self.conn.name,
- self.jid.getStripped())
-
- def get_to(self):
- to = str(self.jid)
- return gajim.get_jid_without_resource(to) + '/' + self.resource
-
- def remove_events(self, types):
- """
- Remove events associated with this session from the queue
-
- Returns True if any events were removed (unlike events.py remove_events)
- """
- any_removed = False
-
- for j in (self.jid, self.jid.getStripped()):
- for event in gajim.events.get_events(self.conn.name, j, types=types):
- # the event wasn't in this session
- if (event.type_ == 'chat' and event.session != self) or \
- (event.type_ == 'printed_chat' and event.control.session != \
- self):
- continue
-
- # events.remove_events returns True when there were no events
- # for some reason
- r = gajim.events.remove_events(self.conn.name, j, event)
-
- if not r:
- any_removed = True
-
- return any_removed
-
- def generate_thread_id(self):
- return ''.join([f(string.ascii_letters) for f in itertools.repeat(
- random.choice, 32)])
-
- def send(self, msg):
- if self.thread_id:
- msg.NT.thread = self.thread_id
-
- msg.setAttr('to', self.get_to())
- self.conn.send_stanza(msg)
-
- if isinstance(msg, nbxmpp.Message):
- self.last_send = time.time()
-
- def reject_negotiation(self, body=None):
- msg = nbxmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='0'))
-
- feature.addChild(node=x)
-
- if body:
- msg.setBody(body)
-
- self.send(msg)
-
- self.cancelled_negotiation()
-
- def cancelled_negotiation(self):
- """
- A negotiation has been cancelled, so reset this session to its default
- state
- """
- if self.control:
- self.control.on_cancel_session_negotiation()
-
- self.status = None
- self.negotiated = {}
-
- def terminate(self, send_termination = True):
- # only send termination message if we've sent a message and think they
- # have XEP-0201 support
- if send_termination and self.last_send > 0 and \
- (self.received_thread_id or self.last_receive == 0):
- msg = nbxmpp.Message()
- feature = msg.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='terminate', value='1'))
-
- feature.addChild(node=x)
-
- self.send(msg)
-
- self.status = None
-
- def acknowledge_termination(self):
- # we could send an acknowledgement message to the remote client here
- self.status = None
-
-
-class ArchivingStanzaSession(StanzaSession):
- def __init__(self, conn, jid, thread_id, type_='chat'):
- StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
- self.archiving = False
-
- def archiving_logging_preference(self, initiator_options=None):
- return self.conn.logging_preference(self.jid, initiator_options)
-
- def negotiate_archiving(self):
- self.negotiated = {}
-
- request = nbxmpp.Message()
- feature = request.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='form')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='1',
- typ='boolean', required=True))
-
- x.addChild(node=nbxmpp.DataField(name='logging', typ='list-single',
- options=self.archiving_logging_preference(), required=True))
-
- x.addChild(node=nbxmpp.DataField(name='disclosure', typ='list-single',
- options=['never'], required=True))
- x.addChild(node=nbxmpp.DataField(name='security', typ='list-single',
- options=['none'], required=True))
-
- feature.addChild(node=x)
-
- self.status = 'requested-archiving'
-
- self.send(request)
-
- def respond_archiving(self, form):
- field = form.getField('logging')
- options = [x[1] for x in field.getOptions()]
- values = field.getValues()
-
- logging = self.archiving_logging_preference(options)
- self.negotiated['logging'] = logging
-
- response = nbxmpp.Message()
- feature = response.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='true'))
-
- x.addChild(node=nbxmpp.DataField(name='logging', value=logging))
-
- self.status = 'responded-archiving'
-
- feature.addChild(node=x)
-
- if not logging:
- response = nbxmpp.Error(response, nbxmpp.ERR_NOT_ACCEPTABLE)
-
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- n = nbxmpp.Node('field')
- n['var'] = 'logging'
- feature.addChild(node=n)
-
- response.T.error.addChild(node=feature)
-
- self.send(response)
-
- def we_accept_archiving(self, form):
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
- log.debug('archiving session accepted: %s' % self.loggable)
- self.status = 'active'
- self.archiving = True
- if self.control:
- self.control.print_archiving_session_details()
-
- def archiving_accepted(self, form):
- negotiated = {}
- ask_user = {}
- not_acceptable = []
-
- if form['logging'] not in self.archiving_logging_preference():
- raise
-
- self.negotiated['logging'] = form['logging']
-
- accept = nbxmpp.Message()
- feature = accept.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- result = nbxmpp.DataForm(typ='result')
-
- result.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
-
- feature.addChild(node=result)
-
- self.send(accept)
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
- log.debug('archiving session accepted: %s' % self.loggable)
- self.status = 'active'
- self.archiving = True
- if self.control:
- self.control.print_archiving_session_details()
-
- def stop_archiving_for_session(self):
- self.conn.stop_archiving_session(self.thread_id)
-
-
-class EncryptedStanzaSession(ArchivingStanzaSession):
- """
- An encrypted stanza negotiation has several states. They arerepresented as
- the following values in the 'status' attribute of the session object:
-
- 1. None:
- default state
- 2. 'requested-e2e':
- this client has initiated an esession negotiation and is waiting
- for a response
- 3. 'responded-e2e':
- this client has responded to an esession negotiation request and
- is waiting for the initiator to identify itself and complete the
- negotiation
- 4. 'identified-alice':
- this client identified itself and is waiting for the responder to
- identify itself and complete the negotiation
- 5. 'active':
- an encrypted session has been successfully negotiated. messages
- of any of the types listed in 'encryptable_stanzas' should be
- encrypted before they're sent.
-
- The transition between these states is handled in gajim.py's
- handle_session_negotiation method.
- """
-
- def __init__(self, conn, jid, thread_id, type_='chat'):
- ArchivingStanzaSession.__init__(self, conn, jid, thread_id,
- type_='chat')
-
- self.xes = {}
- self.es = {}
- self.n = 128
- self.enable_encryption = False
-
- # _s denotes 'self' (ie. this client)
- self._kc_s = None
- # _o denotes 'other' (ie. the client at the other end of the session)
- self._kc_o = None
-
- # has the remote contact's identity ever been verified?
- self.verified_identity = False
-
- def _get_contact(self):
- c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource)
- if not c:
- c = gajim.contacts.get_contact(self.conn.name, self.jid)
- return c
-
- def _is_buggy_gajim(self):
- c = self._get_contact()
- if c and c.supports(nbxmpp.NS_ROSTERX):
- return False
- return True
-
- def set_kc_s(self, value):
- """
- Keep the encrypter updated with my latest cipher key
- """
- self._kc_s = value
- self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
- counter=self.encryptcounter)
-
- def get_kc_s(self):
- return self._kc_s
-
- def set_kc_o(self, value):
- """
- Keep the decrypter updated with the other party's latest cipher key
- """
- self._kc_o = value
- self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
- counter=self.decryptcounter)
-
- def get_kc_o(self):
- return self._kc_o
-
- kc_s = property(get_kc_s, set_kc_s)
- kc_o = property(get_kc_o, set_kc_o)
-
- def encryptcounter(self):
- self.c_s = (self.c_s + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_s)
-
- def decryptcounter(self):
- self.c_o = (self.c_o + 1) % (2 ** self.n)
- return crypto.encode_mpi_with_padding(self.c_o)
-
- def sign(self, string):
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- hash_ = crypto.sha256(string)
- return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
-
- def encrypt_stanza(self, stanza):
- encryptable = [x for x in stanza.getChildren() if x.getName() not in
- ('error', 'amp', 'thread')]
-
- # FIXME can also encrypt contents of <error/> elements in stanzas @type =
- # 'error'
- # (except for <defined-condition
- # xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
-
- old_en_counter = self.c_s
-
- for element in encryptable:
- stanza.delChild(element)
-
- plaintext = ''.join(map(str, encryptable))
-
- m_compressed = self.compress(plaintext)
- m_final = self.encrypt(m_compressed)
-
- c = stanza.NT.c
- c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
- c.NT.data = base64.b64encode(m_final).decode('utf-8')
-
- # FIXME check for rekey request, handle <key/> elements
-
- m_content = (''.join(map(str, c.getChildren()))).encode('utf-8')
- c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
- crypto.encode_mpi(old_en_counter))).decode('utf-8')
-
- msgtxt = '[This is part of an encrypted session. ' \
- 'If you see this message, something went wrong.]'
- lang = os.getenv('LANG')
- if lang is not None and lang != 'en': # we're not english
- msgtxt = _('[This is part of an encrypted session. '
- 'If you see this message, something went wrong.]') + ' (' + \
- msgtxt + ')'
- stanza.setBody(msgtxt)
-
- return stanza
-
- def is_xep_200_encrypted(self, msg):
- msg.getTag('c', namespace=nbxmpp.NS_STANZA_CRYPTO)
-
- def hmac(self, key, content):
- return HMAC(key, content, self.hash_alg).digest()
-
- def generate_initiator_keys(self, k):
- return (self.hmac(k, b'Initiator Cipher Key'),
- self.hmac(k, b'Initiator MAC Key'),
- self.hmac(k, b'Initiator SIGMA Key'))
-
- def generate_responder_keys(self, k):
- return (self.hmac(k, b'Responder Cipher Key'),
- self.hmac(k, b'Responder MAC Key'),
- self.hmac(k, b'Responder SIGMA Key'))
-
- def compress(self, plaintext):
- if self.compression is None:
- return plaintext
-
- def decompress(self, compressed):
- if self.compression is None:
- return compressed
-
- def encrypt(self, encryptable):
- padded = crypto.pad_to_multiple(encryptable, 16, ' ', False)
-
- return self.encrypter.encrypt(padded)
-
- def decrypt_stanza(self, stanza):
- """
- Delete the unencrypted explanation body, if it exists
- """
- orig_body = stanza.getTag('body')
- if orig_body:
- stanza.delChild(orig_body)
-
- c = stanza.getTag(name='c',
- namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
-
- stanza.delChild(c)
-
- # contents of <c>, minus <mac>, minus whitespace
- macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
- macable = macable.encode('utf-8')
-
- received_mac = base64.b64decode(c.getTagData('mac'))
- calculated_mac = self.hmac(self.km_o, macable + \
- crypto.encode_mpi_with_padding(self.c_o))
-
- if not calculated_mac == received_mac:
- raise DecryptionError('bad signature')
-
- m_final = base64.b64decode(c.getTagData('data'))
- m_compressed = self.decrypt(m_final)
- plaintext = self.decompress(m_compressed).decode('utf-8')
-
- try:
- parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
- except Exception:
- raise DecryptionError('decrypted <data/> not parseable as XML')
-
- for child in parsed.getChildren():
- stanza.addChild(node=child)
-
- # replace non-character unicode
- body = stanza.getBody()
- if body:
- stanza.setBody(
- self.conn.connection.Dispatcher.replace_non_character(body))
-
- return stanza
-
- def decrypt(self, ciphertext):
- return self.decrypter.decrypt(ciphertext)
-
- def logging_preference(self):
- if gajim.config.get_per('accounts', self.conn.name,
- 'log_encrypted_sessions'):
- return ['may', 'mustnot']
- else:
- return ['mustnot', 'may']
-
- def get_shared_secret(self, e, y, p):
- if (not 1 < e < (p - 1)):
- raise NegotiationError('invalid DH value')
-
- return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
-
- def c7lize_mac_id(self, form):
- kids = form.getChildren()
- macable = [x for x in kids if x.getVar() not in ('mac', 'identity')]
- return ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
- macable)
-
- def verify_identity(self, form, dh_i, sigmai, i_o):
- m_o = base64.b64decode(form['mac'])
- id_o = base64.b64decode(form['identity'])
-
- m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
-
- if m_o_calculated != m_o:
- raise NegotiationError('calculated m_%s differs from received m_%s' %
- (i_o, i_o))
-
- if i_o == 'a' and self.sas_algs == 'sas28x5':
- # we don't need to calculate this if there's a verified retained secret
- # (but we do anyways)
- self.sas = crypto.sas_28x5(m_o, self.form_s.encode('utf-8'))
-
- if self.negotiated['recv_pubkey']:
- plaintext = self.decrypt(id_o)
- parsed = nbxmpp.Node(node='<node>' + plaintext + '</node>')
-
- if self.negotiated['recv_pubkey'] == 'hash':
- # fingerprint = parsed.getTagData('fingerprint')
- # FIXME find stored pubkey or terminate session
- raise NotImplementedError()
- else:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
-
- n, e = (crypto.decode_mpi(base64.b64decode(
- keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent'))
- eir_pubkey = RSA.construct((n, int(e)))
-
- pubkey_o = nbxmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
- else:
- # FIXME DSA, etc.
- raise NotImplementedError()
-
- enc_sig = parsed.getTag(name='SignatureValue',
- namespace=XmlDsig).getData()
- signature = (crypto.decode_mpi(base64.b64decode(enc_sig)), )
- else:
- mac_o = self.decrypt(id_o)
- pubkey_o = b''
-
- c7l_form = self.c7lize_mac_id(form)
-
- content = self.n_s + self.n_o + crypto.encode_mpi(dh_i) + pubkey_o
-
- if sigmai:
- self.form_o = c7l_form.encode('utf-8')
- content += self.form_o
- else:
- form_o2 = c7l_form.encode('utf-8')
- content += self.form_o.encode('utf-8') + form_o2
-
- mac_o_calculated = self.hmac(self.ks_o, content)
-
- if self.negotiated['recv_pubkey']:
- hash_ = crypto.sha256(mac_o_calculated)
-
- if not eir_pubkey.verify(hash_, signature):
- raise NegotiationError('public key signature verification failed!')
-
- elif mac_o_calculated != mac_o:
- raise NegotiationError('calculated mac_%s differs from received mac_%s'
- % (i_o, i_o))
-
- def make_identity(self, form, dh_i):
- if self.negotiated['send_pubkey']:
- if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- pubkey = secrets.secrets().my_pubkey(self.conn.name)
- fields = (pubkey.n, pubkey.e)
-
- cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
- fields]
-
- pubkey_s = b'<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
- '><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
- tuple(cb_fields)
- else:
- pubkey_s = b''
-
- form_s2 = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
- in form.getChildren())
-
- old_c_s = self.c_s
- content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
- self.form_s.encode('utf-8') + form_s2.encode('utf-8')
-
- mac_s = self.hmac(self.ks_s, content)
-
- if self.negotiated['send_pubkey']:
- signature = self.sign(mac_s)
-
- sign_s = '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">'
- '%s</SignatureValue>' % base64.b64encode(signature)
-
- if self.negotiated['send_pubkey'] == 'hash':
- b64ed = base64.b64encode(self.hash(pubkey_s))
- pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
-
- id_s = self.encrypt(pubkey_s + sign_s)
- else:
- id_s = self.encrypt(mac_s)
-
- m_s = self.hmac(self.km_s, crypto.encode_mpi(old_c_s) + id_s)
-
- if self.status == 'requested-e2e' and self.sas_algs == 'sas28x5':
- # we're alice; check for a retained secret
- # if none exists, prompt the user with the SAS
- self.sas = crypto.sas_28x5(m_s, self.form_o.encode('utf-8'))
-
- if self.sigmai:
- # FIXME save retained secret?
- self.check_identity(tuple)
-
- return (nbxmpp.DataField(name='identity',
- value=base64.b64encode(id_s).decode('utf-8')),
- nbxmpp.DataField(name='mac',
- value=base64.b64encode(m_s).decode('utf-8')))
-
- def negotiate_e2e(self, sigmai):
- self.negotiated = {}
-
- request = nbxmpp.Message()
- feature = request.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='form')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='1',
- typ='boolean', required=True))
-
- # this field is incorrectly called 'otr' in XEPs 0116 and 0217
- x.addChild(node=nbxmpp.DataField(name='logging', typ='list-single',
- options=self.logging_preference(), required=True))
-
- # unsupported options: 'disabled', 'enabled'
- x.addChild(node=nbxmpp.DataField(name='disclosure', typ='list-single',
- options=['never'], required=True))
- x.addChild(node=nbxmpp.DataField(name='security', typ='list-single',
- options=['e2e'], required=True))
- x.addChild(node=nbxmpp.DataField(name='crypt_algs', value='aes128-ctr',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='hash_algs', value='sha256',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='compress', value='none',
- typ='hidden'))
-
- # unsupported options: 'iq', 'presence'
- x.addChild(node=nbxmpp.DataField(name='stanzas', typ='list-multi',
- options=['message']))
-
- x.addChild(node=nbxmpp.DataField(name='init_pubkey', options=['none',
- 'key', 'hash'], typ='list-single'))
-
- # FIXME store key, use hash
- x.addChild(node=nbxmpp.DataField(name='resp_pubkey', options=['none',
- 'key'], typ='list-single'))
-
- x.addChild(node=nbxmpp.DataField(name='ver', value='1.0', typ='hidden'))
-
- x.addChild(node=nbxmpp.DataField(name='rekey_freq', value='4294967295',
- typ='hidden'))
-
- x.addChild(node=nbxmpp.DataField(name='sas_algs', value='sas28x5',
- typ='hidden'))
- x.addChild(node=nbxmpp.DataField(name='sign_algs',
- value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
-
- self.n_s = crypto.generate_nonce()
-
- x.addChild(node=nbxmpp.DataField(name='my_nonce',
- value=base64.b64encode(self.n_s).decode('utf-8'), typ='hidden'))
-
- modp_options = [ int(g) for g in gajim.config.get('esession_modp').split(
- ',') ]
-
- x.addChild(node=nbxmpp.DataField(name='modp', typ='list-single',
- options=[[None, y] for y in modp_options]))
-
- x.addChild(node=self.make_dhfield(modp_options, sigmai))
- self.sigmai = sigmai
-
- self.form_s = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in x.getChildren())
-
- feature.addChild(node=x)
-
- self.status = 'requested-e2e'
-
- self.send(request)
-
- def verify_options_bob(self, form):
- """
- 4.3 esession response (bob)
- """
- negotiated = {'recv_pubkey': None, 'send_pubkey': None}
- not_acceptable = []
- ask_user = {}
-
- fixed = { 'disclosure': 'never', 'security': 'e2e',
- 'crypt_algs': 'aes128-ctr', 'hash_algs': 'sha256', 'compress': 'none',
- 'stanzas': 'message', 'init_pubkey': 'none', 'resp_pubkey': 'none',
- 'ver': '1.0', 'sas_algs': 'sas28x5' }
-
- self.encryptable_stanzas = ['message']
-
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
-
- for name in form.asDict():
- field = form.getField(name)
- options = [x[1] for x in field.getOptions()]
- values = field.getValues()
-
- if not field.getType() in ('list-single', 'list-multi'):
- options = values
-
- if name in fixed:
- if fixed[name] in options:
- negotiated[name] = fixed[name]
- else:
- not_acceptable.append(name)
- elif name == 'rekey_freq':
- preferred = int(options[0])
- negotiated['rekey_freq'] = preferred
- self.rekey_freq = preferred
- elif name == 'logging':
- my_prefs = self.logging_preference()
-
- if my_prefs[0] in options: # our first choice is offered, select it
- pref = my_prefs[0]
- negotiated['logging'] = pref
- else: # see if other acceptable choices are offered
- for pref in my_prefs:
- if pref in options:
- ask_user['logging'] = pref
- break
-
- if not 'logging' in ask_user:
- not_acceptable.append(name)
- elif name == 'init_pubkey':
- for x in ('key'):
- if x in options:
- negotiated['recv_pubkey'] = x
- break
- elif name == 'resp_pubkey':
- for x in ('hash', 'key'):
- if x in options:
- negotiated['send_pubkey'] = x
- break
- elif name == 'sign_algs':
- if (XmlDsig + 'rsa-sha256') in options:
- negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
- else:
- # FIXME some things are handled elsewhere, some things are
- # not-implemented
- pass
-
- return (negotiated, not_acceptable, ask_user)
-
- def respond_e2e_bob(self, form, negotiated, not_acceptable):
- """
- 4.3 esession response (bob)
- """
- response = nbxmpp.Message()
- feature = response.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='accept', value='true'))
-
- for name in negotiated:
- # some fields are internal and should not be sent
- if not name in ('send_pubkey', 'recv_pubkey'):
- x.addChild(node=nbxmpp.DataField(name=name,
- value=negotiated[name]))
-
- self.negotiated = negotiated
-
- # the offset of the group we chose (need it to match up with the dhhash)
- group_order = 0
- modp_f = form.getField('modp')
- if not modp_f:
- return
- self.modp = int(modp_f.getOptions()[group_order][1])
- x.addChild(node=nbxmpp.DataField(name='modp', value=self.modp))
-
- g = dh.generators[self.modp]
- p = dh.primes[self.modp]
-
- self.n_o = base64.b64decode(form['my_nonce'])
-
- dhhashes_f = form.getField('dhhashes')
- if not dhhashes_f:
- return
- dhhashes = dhhashes_f.getValues()
- self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode(
- 'utf8'))
-
- bytes = int(self.n / 8)
-
- self.n_s = crypto.generate_nonce()
-
- # n-bit random number
- self.c_o = crypto.decode_mpi(crypto.random_bytes(bytes))
- self.c_s = self.c_o ^ (2 ** (self.n - 1))
-
- self.y = crypto.srand(2 ** (2 * self.n - 1), p - 1)
- self.d = crypto.powmod(g, self.y, p)
-
- to_add = {'my_nonce': self.n_s,
- 'dhkeys': crypto.encode_mpi(self.d),
- 'counter': crypto.encode_mpi(self.c_o),
- 'nonce': self.n_o}
-
- for name in to_add:
- b64ed = base64.b64encode(to_add[name]).decode('utf-8')
- x.addChild(node=nbxmpp.DataField(name=name, value=b64ed))
-
- self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in form.getChildren())
- self.form_s = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
- el in x.getChildren())
-
- self.status = 'responded-e2e'
-
- feature.addChild(node=x)
-
- if not_acceptable:
- response = nbxmpp.Error(response, nbxmpp.ERR_NOT_ACCEPTABLE)
-
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- for f in not_acceptable:
- n = nbxmpp.Node('field')
- n['var'] = f
- feature.addChild(node=n)
-
- response.T.error.addChild(node=feature)
-
- self.send(response)
-
- def verify_options_alice(self, form):
- """
- 'Alice Accepts'
- """
- negotiated = {}
- ask_user = {}
- not_acceptable = []
-
- if not form['logging'] in self.logging_preference():
- not_acceptable.append(form['logging'])
- elif form['logging'] != self.logging_preference()[0]:
- ask_user['logging'] = form['logging']
- else:
- negotiated['logging'] = self.logging_preference()[0]
-
- for r, a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey',
- 'init_pubkey')):
- negotiated[r] = None
-
- if a in form.asDict() and form[a] in ('key', 'hash'):
- negotiated[r] = form[a]
-
- if 'sign_algs' in form.asDict():
- if form['sign_algs'] in (XmlDsig + 'rsa-sha256', ):
- negotiated['sign_algs'] = form['sign_algs']
- else:
- not_acceptable.append(form['sign_algs'])
-
- return (negotiated, not_acceptable, ask_user)
-
- def accept_e2e_alice(self, form, negotiated):
- """
- 'Alice Accepts', continued
- """
- self.encryptable_stanzas = ['message']
- self.sas_algs = 'sas28x5'
- self.cipher = AES
- self.hash_alg = sha256
- self.compression = None
-
- self.negotiated = negotiated
-
- accept = nbxmpp.Message()
- feature = accept.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- result = nbxmpp.DataForm(typ='result')
-
- self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
- self.c_o = self.c_s ^ (2 ** (self.n - 1))
- self.n_o = base64.b64decode(form['my_nonce'])
-
- mod_p = int(form['modp'])
- p = dh.primes[mod_p]
- x = self.xes[mod_p]
- e = self.es[mod_p]
-
- self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- self.k = self.get_shared_secret(self.d, x, p)
-
- result.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- result.addChild(node=nbxmpp.DataField(name='accept', value='1'))
- result.addChild(node=nbxmpp.DataField(name='nonce',
- value=base64.b64encode(self.n_o).decode('utf-8')))
-
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
-
- if self.sigmai:
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
- self.verify_identity(form, self.d, True, 'b')
- else:
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses]
-
- if not rshashes:
- # we've never spoken before, but we'll pretend we have
- rshash_size = self.hash_alg().digest_size
- rshashes.append(crypto.random_bytes(rshash_size))
-
- rshashes = [base64.b64encode(rshash).decode('utf-8') for rshash in \
- rshashes]
- result.addChild(node=nbxmpp.DataField(name='rshashes',
- value=rshashes))
- result.addChild(node=nbxmpp.DataField(name='dhkeys',
- value=base64.b64encode(crypto.encode_mpi(e)).decode('utf-8')))
-
- self.form_o = ''.join(nbxmpp.c14n.c14n(el, self._is_buggy_gajim()) \
- for el in form.getChildren())
-
- # MUST securely destroy K unless it will be used later to generate the
- # final shared secret
-
- for datafield in self.make_identity(result, e):
- result.addChild(node=datafield)
-
- feature.addChild(node=result)
- self.send(accept)
-
- if self.sigmai:
- self.status = 'active'
- self.enable_encryption = True
- else:
- self.status = 'identified-alice'
-
- def accept_e2e_bob(self, form):
- """
- 4.5 esession accept (bob)
- """
- response = nbxmpp.Message()
-
- init = response.NT.init
- init.setNamespace(nbxmpp.NS_ESESSION_INIT)
-
- x = nbxmpp.DataForm(typ='result')
-
- for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
- # FIXME: will do nothing in real world...
- assert field in form.asDict(), "alice's form didn't have a %s field" \
- % field
-
- # 4.5.1 generating provisory session keys
- e = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
- p = dh.primes[self.modp]
-
- if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
- raise NegotiationError('SHA256(e) != He')
-
- k = self.get_shared_secret(e, self.y, p)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
-
- # 4.5.2 verifying alice's identity
- self.verify_identity(form, e, False, 'a')
-
- # 4.5.4 generating bob's final session keys
- srs = b''
-
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
- rshashes = [base64.b64decode(rshash) for rshash in form.getField(
- 'rshashes').getValues()]
-
- for s in srses:
- secret = s[0]
- if self.hmac(self.n_o, secret) in rshashes:
- srs = secret
- break
-
- # other shared secret
- # (we're not using one)
- oss = b''
-
- k = crypto.sha256(k + srs + oss)
-
- self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
-
- # 4.5.5
- if srs:
- srshash = self.hmac(srs, b'Shared Retained Secret')
- else:
- srshash = crypto.random_bytes(32)
-
- x.addChild(node=nbxmpp.DataField(name='FORM_TYPE',
- value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField(name='nonce', value=base64.b64encode(
- self.n_o).decode('utf-8')))
- x.addChild(node=nbxmpp.DataField(name='srshash', value=base64.b64encode(
- srshash).decode('utf-8')))
-
- for datafield in self.make_identity(x, self.d):
- x.addChild(node=datafield)
-
- init.addChild(node=x)
-
- self.send(response)
-
- self.do_retained_secret(k, srs)
-
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
-
- self.status = 'active'
- self.enable_encryption = True
-
- if self.control:
- self.control.print_esession_details()
-
- self.stop_archiving_for_session()
-
- def final_steps_alice(self, form):
- srs = b''
- srses = secrets.secrets().retained_secrets(self.conn.name,
- self.jid.getStripped())
-
- try:
- srshash = base64.b64decode(form['srshash'])
- except IndexError:
- return
-
- for s in srses:
- secret = s[0]
- if self.hmac(secret, b'Shared Retained Secret') == srshash:
- srs = secret
- break
-
- oss = b''
- k = crypto.sha256(self.k + srs + oss)
- del self.k
-
- self.do_retained_secret(k, srs)
-
- # ks_s doesn't need to be calculated here
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k)
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
-
- # 4.6.2 Verifying Bob's Identity
- self.verify_identity(form, self.d, False, 'b')
- # Note: If Alice discovers an error then she SHOULD ignore any encrypted
- # content she received in the stanza.
-
- if self.negotiated['logging'] == 'mustnot':
- self.loggable = False
-
- self.status = 'active'
- self.enable_encryption = True
-
- if self.control:
- self.control.print_esession_details()
-
- self.stop_archiving_for_session()
-
- def do_retained_secret(self, k, old_srs):
- """
- Calculate the new retained secret. determine if the user needs to check
- the remote party's identity. Set up callbacks for when the identity has
- been verified
- """
- new_srs = self.hmac(k, b'New Retained Secret')
- self.srs = new_srs
-
- account = self.conn.name
- bjid = self.jid.getStripped()
-
- self.verified_identity = False
-
- if old_srs:
- if secrets.secrets().srs_verified(account, bjid, old_srs):
- # already had a stored secret verified by the user.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs, True)
- # continue without warning.
- self.verified_identity = True
- else:
- # had a secret, but it wasn't verified.
- secrets.secrets().replace_srs(account, bjid, old_srs, new_srs,
- False)
- else:
- # we don't even have an SRS
- secrets.secrets().save_new_srs(account, bjid, new_srs, False)
-
- def _verified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, True)
-
- def _unverified_srs_cb(self):
- secrets.secrets().replace_srs(self.conn.name, self.jid.getStripped(),
- self.srs, self.srs, False)
-
- def make_dhfield(self, modp_options, sigmai):
- dhs = []
-
- for modp in modp_options:
- p = dh.primes[modp]
- g = dh.generators[modp]
-
- x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
-
- # FIXME this may be a source of performance issues
- e = crypto.powmod(g, x, p)
-
- self.xes[modp] = x
- self.es[modp] = e
-
- if sigmai:
- dhs.append(base64.b64encode(crypto.encode_mpi(e)).decode('utf-8'))
- name = 'dhkeys'
- else:
- He = crypto.sha256(crypto.encode_mpi(e))
- dhs.append(base64.b64encode(He).decode('utf-8'))
- name = 'dhhashes'
-
- return nbxmpp.DataField(name=name, typ='hidden', value=dhs)
-
- def terminate_e2e(self):
- self.enable_encryption = False
- if self.control:
- self.control.print_session_details()
- self.terminate()
-
- def acknowledge_termination(self):
- StanzaSession.acknowledge_termination(self)
- self.enable_encryption = False
-
- def fail_bad_negotiation(self, reason, fields=None):
- """
- Send an error and cancels everything
-
- If fields is None, the remote party has given us a bad cryptographic
- value of some kind. Otherwise, list the fields we haven't implemented.
- """
- err = nbxmpp.Error(nbxmpp.Message(), nbxmpp.ERR_FEATURE_NOT_IMPLEMENTED)
- err.T.error.T.text.setData(reason)
-
- if fields:
- feature = nbxmpp.Node(nbxmpp.NS_FEATURE + ' feature')
-
- for field in fields:
- fn = nbxmpp.Node('field')
- fn['var'] = field
- feature.addChild(node=feature)
-
- err.addChild(node=feature)
-
- self.send(err)
-
- self.status = None
- self.enable_encryption = False
-
- # this prevents the MAC check on decryption from succeeding,
- # preventing falsified messages from going through.
- self.km_o = ''
-
- def cancelled_negotiation(self):
- StanzaSession.cancelled_negotiation(self)
- self.enable_encryption = False
- self.km_o = ''
diff --git a/src/common/zeroconf/__init__.py b/src/common/zeroconf/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/src/common/zeroconf/__init__.py
+++ /dev/null
diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py
deleted file mode 100644
index 5a1bd541e..000000000
--- a/src/common/zeroconf/client_zeroconf.py
+++ /dev/null
@@ -1,864 +0,0 @@
-## common/zeroconf/client_zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-## 2006 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/>.
-##
-from common import gajim
-import nbxmpp
-from nbxmpp.idlequeue import IdleObject
-from nbxmpp import dispatcher_nb, simplexml
-from nbxmpp.plugin import *
-from nbxmpp.transports_nb import DATA_RECEIVED, DATA_SENT, DATA_ERROR
-from common.zeroconf import zeroconf
-
-from nbxmpp.protocol import *
-import socket
-import ssl
-import errno
-import sys
-import os
-import string
-from random import Random
-
-import logging
-log = logging.getLogger('gajim.c.z.client_zeroconf')
-
-from common.zeroconf import roster_zeroconf
-
-MAX_BUFF_LEN = 65536
-TYPE_SERVER, TYPE_CLIENT = range(2)
-
-# wait XX sec to establish a connection
-CONNECT_TIMEOUT_SECONDS = 10
-
-# after XX sec with no activity, close the stream
-ACTIVITY_TIMEOUT_SECONDS = 30
-
-class ZeroconfListener(IdleObject):
- def __init__(self, port, conn_holder):
- """
- Handle all incomming connections on ('0.0.0.0', port)
- """
- self.port = port
- self.queue_idx = -1
- #~ self.queue = None
- self.started = False
- self._sock = None
- self.fd = -1
- self.caller = conn_holder.caller
- self.conn_holder = conn_holder
-
- def bind(self):
- flags = socket.AI_PASSIVE
- if hasattr(socket, 'AI_ADDRCONFIG'):
- flags |= socket.AI_ADDRCONFIG
- ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, 0, flags)[0]
- self._serv = socket.socket(ai[0], ai[1])
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- if os.name == 'nt':
- ver = os.sys.getwindowsversion()
- if (ver[3], ver[0]) == (2, 6): # Win Vista +
- # 47 is socket.IPPROTO_IPV6
- # 27 is socket.IPV6_V6ONLY under windows, but not defined ...
- self._serv.setsockopt(41, 27, 0)
- # will fail when port is busy, or we don't have rights to bind
- try:
- self._serv.bind((ai[4][0], self.port))
- except Exception:
- # unable to bind, show error dialog
- return None
- self._serv.listen(socket.SOMAXCONN)
- self._serv.setblocking(False)
- self.fd = self._serv.fileno()
- gajim.idlequeue.plug_idle(self, False, True)
- self.started = True
-
- def pollend(self):
- """
- Called when we stop listening on (host, port)
- """
- self.disconnect()
-
- def pollin(self):
- """
- Accept a new incomming connection and notify queue
- """
- sock = self.accept_conn()
- # loop through roster to find who has connected to us
- from_jid = None
- ipaddr = sock[1][0]
- for jid in self.conn_holder.getRoster().keys():
- entry = self.conn_holder.getRoster().getItem(jid)
- for address in entry['addresses']:
- if (address['address'] == ipaddr):
- from_jid = jid
- break
- P2PClient(sock[0], [{'host': ipaddr, 'address': ipaddr, 'port': sock[1][1]}], self.conn_holder, [], from_jid)
-
- def disconnect(self, message=''):
- """
- Free all resources, we are not listening anymore
- """
- log.info('Disconnecting ZeroconfListener: %s' % message)
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- self.fd = -1
- self.started = False
- try:
- self._serv.close()
- except socket.error:
- pass
- self.conn_holder.kill_all_connections()
-
- def accept_conn(self):
- """
- Accept a new incoming connection
- """
- _sock = self._serv.accept()
- _sock[0].setblocking(False)
- return _sock
-
-class P2PClient(IdleObject):
- def __init__(self, _sock, addresses, conn_holder, stanzaqueue, to=None,
- on_ok=None, on_not_ok=None):
- self._owner = self
- self.Namespace = 'jabber:client'
- self.protocol_type = 'XMPP'
- self.defaultNamespace = self.Namespace
- self._component = 0
- self._registered_name = None
- self._caller = conn_holder.caller
- self.conn_holder = conn_holder
- self.stanzaqueue = stanzaqueue
- self.to = to
- #self.Server = addresses[0]['host']
- self.on_ok = on_ok
- self.on_not_ok = on_not_ok
- self.Connection = None
- self.sock_hash = None
- if _sock:
- self.sock_type = TYPE_SERVER
- else:
- self.sock_type = TYPE_CLIENT
- self.fd = -1
- conn = P2PConnection('', _sock, addresses, self._caller,
- self.on_connect, self)
- self.Server = conn.host # set Server to the last host name / address tried
- if not self.conn_holder:
- # An error occured, disconnect() has been called
- if on_not_ok:
- on_not_ok('Connection to host could not be established.')
- return
- self.sock_hash = conn._sock.__hash__
- self.fd = conn.fd
- self.conn_holder.add_connection(self, self.Server, conn.port, self.to)
- # count messages in queue
- for val in self.stanzaqueue:
- stanza, is_message = val
- if is_message:
- if self.fd == -1:
- if on_not_ok:
- on_not_ok(
- 'Connection to host could not be established.')
- return
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((
- id_, thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- self.on_responses = {}
-
- def add_stanza(self, stanza, is_message=False):
- if self.Connection:
- if self.Connection.state == -1:
- return False
- self.send(stanza, is_message)
- else:
- self.stanzaqueue.append((stanza, is_message))
-
- if is_message:
- thread_id = stanza.getThread()
- id_ = stanza.getID()
- if not id_:
- id_ = self.Dispatcher.getAnID()
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_,
- thread_id))
- else:
- self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_,
- thread_id)]
-
- return True
-
- def on_message_sent(self, connection_id):
- id_, thread_id = \
- self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0)
- if self.on_ok:
- self.on_ok(id_)
- # use on_ok only on first message. For others it's called in
- # ClientZeroconf
- self.on_ok = None
-
- def on_connect(self, conn):
- self.Connection = conn
- self.Connection.PlugIn(self)
- dispatcher_nb.Dispatcher().PlugIn(self)
- self._register_handlers()
-
- def StreamInit(self):
- """
- Send an initial stream header
- """
- self.Dispatcher.Stream = simplexml.NodeBuilder()
- self.Dispatcher.Stream._dispatch_depth = 2
- self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
- self.Dispatcher.Stream.stream_header_received = self._check_stream_start
- self.Dispatcher.Stream.features = None
- if self.sock_type == TYPE_CLIENT:
- self.send_stream_header()
-
- def send_stream_header(self):
- self.Dispatcher._metastream = Node('stream:stream')
- self.Dispatcher._metastream.setNamespace(self.Namespace)
- self.Dispatcher._metastream.setAttr('version', '1.0')
- self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
- self.Dispatcher._metastream.setAttr('from',
- self.conn_holder.zeroconf.name)
- if self.to:
- self.Dispatcher._metastream.setAttr('to', self.to)
- self.Dispatcher.send("<?xml version='1.0'?>%s>" % str(
- self.Dispatcher._metastream)[:-2])
-
- def _check_stream_start(self, ns, tag, attrs):
- if ns != NS_STREAMS or tag != 'stream':
- log.error('Incorrect stream start: (%s,%s).Terminating!' % (tag,
- ns), 'error')
- self.Connection.disconnect()
- if self.on_not_ok:
- self.on_not_ok('Connection to host could not be established: '
- 'Incorrect answer from server.')
- return
- if self.sock_type == TYPE_SERVER:
- if 'from' in attrs:
- self.to = attrs['from']
- self.send_stream_header()
- if 'version' in attrs and attrs['version'] == '1.0':
- # other part supports stream features
- features = Node('stream:features')
- self.Dispatcher.send(features)
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
- elif self.sock_type == TYPE_CLIENT:
- while self.stanzaqueue:
- stanza, is_message = self.stanzaqueue.pop(0)
- self.send(stanza, is_message)
-
- def on_disconnect(self):
- if self.conn_holder:
- if self.fd in self.conn_holder.ids_of_awaiting_messages:
- del self.conn_holder.ids_of_awaiting_messages[self.fd]
- self.conn_holder.remove_connection(self.sock_hash)
- if 'Dispatcher' in self.__dict__:
- self.Dispatcher.PlugOut()
- if 'P2PConnection' in self.__dict__:
- self.P2PConnection.PlugOut()
- self.Connection = None
- self._caller = None
- self.conn_holder = None
-
- def force_disconnect(self):
- if self.Connection:
- self.disconnect()
- else:
- self.on_disconnect()
-
- def _on_receive_document_attrs(self, data):
- if data:
- self.Dispatcher.ProcessNonBlocking(data)
- if not hasattr(self, 'Dispatcher') or \
- self.Dispatcher.Stream._document_attrs is None:
- return
- self.onreceive(None)
- if 'version' in self.Dispatcher.Stream._document_attrs and \
- self.Dispatcher.Stream._document_attrs['version'] == '1.0':
- #~ self.onreceive(self._on_receive_stream_features)
- #XXX continue with TLS
- return
- self.onreceive(None)
- return True
-
- def remove_timeout(self):
- pass
-
- def _register_handlers(self):
- self._caller.peerhost = self.Connection._sock.getsockname()
- self.RegisterHandler('message', lambda conn,
- data:self._caller._messageCB(self.Server, conn, data))
- self.RegisterHandler('iq', self._caller._siSetCB, 'set', nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
- nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._siResultCB, 'result',
- nbxmpp.NS_SI)
- self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
- nbxmpp.NS_BYTESTREAM)
- self.RegisterHandler('iq', self._caller._DiscoverItemsGetCB, 'get',
- nbxmpp.NS_DISCO_ITEMS)
- self.RegisterHandler('iq', self._caller._JingleCB, 'result')
- self.RegisterHandler('iq', self._caller._JingleCB, 'error')
- self.RegisterHandler('iq', self._caller._JingleCB, 'set',
- nbxmpp.NS_JINGLE)
-
-class P2PConnection(IdleObject, PlugIn):
- def __init__(self, sock_hash, _sock, addresses=None, caller=None,
- on_connect=None, client=None):
- IdleObject.__init__(self)
- self._owner = client
- PlugIn.__init__(self)
- self.sendqueue = []
- self.sendbuff = None
- self.buff_is_message = False
- self._sock = _sock
- self.sock_hash = None
- self.addresses = addresses
- self.on_connect = on_connect
- self.client = client
- self.writable = False
- self.readable = False
- self._exported_methods = [self.send, self.disconnect, self.onreceive]
- self.on_receive = None
- if _sock:
- self.host = addresses[0]['host']
- self.port = addresses[0]['port']
- self._sock = _sock
- self.state = 1
- self._sock.setblocking(False)
- self.fd = self._sock.fileno()
- self.on_connect(self)
- else:
- self.state = 0
- self.addresses_ = self.addresses
- self.get_next_addrinfo()
-
- def get_next_addrinfo(self):
- address = self.addresses_.pop(0)
- self.host = address['host']
- self.port = address['port']
- try:
- self.ais = socket.getaddrinfo(address['host'], address['port'], socket.AF_UNSPEC,
- socket.SOCK_STREAM)
- except socket.gaierror as e:
- log.info('Lookup failure for %s: %s[%s]', host, e[1],
- repr(e[0]), exc_info=True)
- if len(self.addresses_) > 0: return self.get_next_addrinfo()
- else:
- self.connect_to_next_ip()
-
- def connect_to_next_ip(self):
- if len(self.ais) == 0:
- log.error('Connection failure to %s', str(self.host), exc_info=True)
- if len(self.addresses_) > 0: return self.get_next_addrinfo()
- self.disconnect()
- return
- ai = self.ais.pop(0)
- log.info('Trying to connect to %s through %s:%s', str(self.host),
- ai[4][0], ai[4][1], exc_info=True)
- try:
- self._sock = socket.socket(*ai[:3])
- self._sock.setblocking(False)
- self._server = ai[4]
- except socket.error:
- if sys.exc_value[0] != errno.EINPROGRESS:
- # for all errors, we try other addresses
- self.connect_to_next_ip()
- return
- self.fd = self._sock.fileno()
- gajim.idlequeue.plug_idle(self, True, False)
- self.set_timeout(CONNECT_TIMEOUT_SECONDS)
- self.do_connect()
-
- def set_timeout(self, timeout):
- gajim.idlequeue.remove_timeout(self.fd)
- if self.state >= 0:
- gajim.idlequeue.set_read_timeout(self.fd, timeout)
-
- def plugin(self, owner):
- self.onreceive(owner._on_receive_document_attrs)
- self._plug_idle()
- return True
-
- def plugout(self):
- """
- Disconnect from the remote server and unregister self.disconnected
- method from the owner's dispatcher
- """
- self.disconnect()
- self._owner = None
-
- def onreceive(self, recv_handler):
- if not recv_handler:
- if hasattr(self._owner, 'Dispatcher'):
- self.on_receive = self._owner.Dispatcher.ProcessNonBlocking
- else:
- self.on_receive = None
- return
- _tmp = self.on_receive
- # make sure this cb is not overriden by recursive calls
- if not recv_handler(None) and _tmp == self.on_receive:
- self.on_receive = recv_handler
-
- def send(self, packet, is_message=False, now=False):
- """
- Append stanza to the queue of messages to be send if now is False, else
- send it instantly
- """
- if self.state <= 0:
- return
-
- r = packet
-
- if now:
- self.sendqueue.insert(0, (r, is_message))
- self._do_send()
- else:
- self.sendqueue.append((r, is_message))
- self._plug_idle()
-
- def read_timeout(self):
- ids = self.client.conn_holder.ids_of_awaiting_messages
- if self.fd in ids and len(ids[self.fd]) > 0:
- for (id_, thread_id) in ids[self.fd]:
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_ERROR, (
- self.client.to, thread_id))
- else:
- self._owner.on_not_ok('connection timeout')
- ids[self.fd] = []
- self.pollend()
-
- def do_connect(self):
- errnum = 0
- try:
- self._sock.connect(self._server[:2])
- self._sock.setblocking(False)
- except Exception as ee:
- errnum = ee.errno
- errstr = ee.strerror
- errors = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK)
- if 'WSAEINVAL' in errno.__dict__:
- errors += (errno.WSAEINVAL,)
- if errnum in errors:
- return
- # win32 needs this
- elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
- log.error('Could not connect to %s: %s [%s]', str(self.host),
- errnum, errstr)
- self.connect_to_next_ip()
- return
- else: # socket is already connected
- self._sock.setblocking(False)
- self.state = 1 # connected
- # we are connected
- self.on_connect(self)
-
- def pollout(self):
- if self.state == 0:
- self.do_connect()
- return
- gajim.idlequeue.remove_timeout(self.fd)
- self._do_send()
-
- def pollend(self):
- if self.state == 0: # error in connect()?
- #self.disconnect()
- self.connect_to_next_ip()
- else:
- self.state = -1
- self.disconnect()
-
- def pollin(self):
- """
- Reads all pending incoming data. Call owner's disconnected() method if
- appropriate
- """
- received = ''
- errnum = 0
- try:
- # get as many bites, as possible, but not more than RECV_BUFSIZE
- received = self._sock.recv(MAX_BUFF_LEN)
- except Exception as e:
- errnum = e.errno
- # "received" will be empty anyhow
- if errnum == ssl.SSL_ERROR_WANT_READ:
- pass
- elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
- self.pollend()
- # don't proccess result, cas it will raise error
- return
- elif not received :
- if errnum != ssl.SSL_ERROR_EOF:
- # 8 EOF occurred in violation of protocol
- self.pollend()
- if self.state >= 0:
- self.disconnect()
- return
-
- if self.state < 0:
- return
- if self.on_receive:
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- if received.strip():
- log.debug('received: %s', received)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_RECEIVED, received)
- self.on_receive(received)
- else:
- # This should never happed, so we need the debug
- log.error('Unhandled data received: %s' % received)
- self.disconnect()
- return True
-
- def disconnect(self, message=''):
- """
- Close the socket
- """
- gajim.idlequeue.remove_timeout(self.fd)
- gajim.idlequeue.unplug_idle(self.fd)
- try:
- self._sock.shutdown(socket.SHUT_RDWR)
- self._sock.close()
- except socket.error:
- # socket is already closed
- pass
- self.fd = -1
- self.state = -1
- if self._owner:
- self._owner.on_disconnect()
-
- def _do_send(self):
- if not self.sendbuff:
- if not self.sendqueue:
- return None # nothing to send
- self.sendbuff, self.buff_is_message = self.sendqueue.pop(0)
- self.sent_data = self.sendbuff
- try:
- send_count = self._sock.send(self.sendbuff)
- if send_count:
- self.sendbuff = self.sendbuff[send_count:]
- if not self.sendbuff and not self.sendqueue:
- if self.state < 0:
- gajim.idlequeue.unplug_idle(self.fd)
- self._on_send()
- self.disconnect()
- return
- # we are not waiting for write
- self._plug_idle()
- self._on_send()
-
- except socket.error as e:
- if e.errno == ssl.SSL_ERROR_WANT_WRITE:
- return True
- if self.state < 0:
- self.disconnect()
- return
- self._on_send_failure()
- return
- if self._owner.sock_type == TYPE_CLIENT:
- self.set_timeout(ACTIVITY_TIMEOUT_SECONDS)
- return True
-
- def _plug_idle(self):
- readable = self.state != 0
- if self.sendqueue or self.sendbuff:
- writable = True
- else:
- writable = False
- if self.writable != writable or self.readable != readable:
- gajim.idlequeue.plug_idle(self, writable, readable)
-
-
- def _on_send(self):
- if self.sent_data and self.sent_data.strip():
- log.debug('sent: %s' % self.sent_data)
- if hasattr(self._owner, 'Dispatcher'):
- self._owner.Dispatcher.Event('', DATA_SENT, self.sent_data)
- self.sent_data = None
- if self.buff_is_message:
- self._owner.on_message_sent(self.fd)
- self.buff_is_message = False
-
- def _on_send_failure(self):
- log.error('Socket error while sending data')
- self._owner.on_disconnect()
- self.sent_data = None
-
-class ClientZeroconf:
- def __init__(self, caller):
- self.caller = caller
- self.zeroconf = None
- self.roster = None
- self.last_msg = ''
- self.connections = {}
- self.recipient_to_hash = {}
- self.ip_to_hash = {}
- self.hash_to_port = {}
- self.listener = None
- self.ids_of_awaiting_messages = {}
- self.disconnect_handlers = []
- self.disconnecting = False
-
- def connect(self, show, msg):
- self.port = self.start_listener(self.caller.port)
- if not self.port:
- return False
- self.zeroconf_init(show, msg)
- if not self.zeroconf.connect():
- self.disconnect()
- return None
- self.roster = roster_zeroconf.Roster(self.zeroconf)
- return True
-
- def remove_announce(self):
- if self.zeroconf:
- return self.zeroconf.remove_announce()
-
- def announce(self):
- if self.zeroconf:
- return self.zeroconf.announce()
-
- def set_show_msg(self, show, msg):
- if self.zeroconf:
- self.zeroconf.txt['msg'] = msg
- self.last_msg = msg
- return self.zeroconf.update_txt(show)
-
- def resolve_all(self):
- if self.zeroconf:
- self.zeroconf.resolve_all()
-
- def reannounce(self, txt):
- self.remove_announce()
- self.zeroconf.txt = txt
- self.zeroconf.port = self.port
- self.zeroconf.username = self.caller.username
- return self.announce()
-
- def zeroconf_init(self, show, msg):
- self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service,
- self.caller._on_remove_service, self.caller._on_name_conflictCB,
- self.caller._on_disconnected, self.caller._on_error,
- self.caller.username, self.caller.host, self.port)
- self.zeroconf.txt['msg'] = msg
- self.zeroconf.txt['status'] = show
- self.zeroconf.txt['1st'] = self.caller.first
- self.zeroconf.txt['last'] = self.caller.last
- self.zeroconf.txt['jid'] = self.caller.jabber_id
- self.zeroconf.txt['email'] = self.caller.email
- self.zeroconf.username = self.caller.username
- self.zeroconf.host = self.caller.host
- self.zeroconf.port = self.port
- self.last_msg = msg
-
- def disconnect(self):
- # to avoid recursive calls
- if self.disconnecting:
- return
- if self.listener:
- self.listener.disconnect()
- self.listener = None
- if self.zeroconf:
- self.zeroconf.disconnect()
- self.zeroconf = None
- if self.roster:
- self.roster.zeroconf = None
- self.roster._data = None
- self.roster = None
- self.disconnecting = True
- for i in reversed(self.disconnect_handlers):
- log.debug('Calling disconnect handler %s' % i)
- i()
- self.disconnecting = False
-
- def start_disconnect(self):
- self.disconnect()
-
- def kill_all_connections(self):
- for connection in self.connections.values():
- connection.force_disconnect()
-
- def add_connection(self, connection, ip, port, recipient):
- sock_hash=connection.sock_hash
- if sock_hash not in self.connections:
- self.connections[sock_hash] = connection
- self.ip_to_hash[ip] = sock_hash
- self.hash_to_port[sock_hash] = port
- if recipient:
- self.recipient_to_hash[recipient] = sock_hash
-
- def remove_connection(self, sock_hash):
- if sock_hash in self.connections:
- del self.connections[sock_hash]
- for i in self.recipient_to_hash:
- if self.recipient_to_hash[i] == sock_hash:
- del self.recipient_to_hash[i]
- break
- for i in self.ip_to_hash:
- if self.ip_to_hash[i] == sock_hash:
- del self.ip_to_hash[i]
- break
- if sock_hash in self.hash_to_port:
- del self.hash_to_port[sock_hash]
-
- def start_listener(self, port):
- for p in range(port, port + 5):
- self.listener = ZeroconfListener(p, self)
- self.listener.bind()
- if self.listener.started:
- return p
- self.listener = None
- return False
-
- def getRoster(self):
- if self.roster:
- return self.roster.getRoster()
- return {}
-
- def send(self, stanza, is_message=False, now=False, on_ok=None,
- on_not_ok=None):
- to = stanza.getTo()
- if to is None:
- # Can’t send undirected stanza over Zeroconf.
- return -1
- to = to.getStripped()
- stanza.setFrom(self.roster.zeroconf.name)
-
- try:
- item = self.roster[to]
- except KeyError:
- # Contact offline
- return -1
-
- # look for hashed connections
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- the_address = None
- for address in item['addresses']:
- if address['address'] in self.ip_to_hash:
- the_address = address
- if the_address and the_address['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[the_address['address']]
- if self.hash_to_port[hash_] == the_address['port']:
- conn = self.connections[hash_]
- id_ = stanza.getID() or ''
- if conn.add_stanza(stanza, is_message):
- if on_ok:
- on_ok(id_)
- return
-
- # otherwise open new connection
- if not stanza.getID():
- stanza.setID('zero')
- addresses_ = []
- for address in item['addresses']:
- addresses_ += [{'host': address['address'], 'address': address['address'], 'port': address['port']}]
- P2PClient(None, addresses_, self,
- [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok)
-
- def getAnID(self):
- """
- Generate a random id
- """
- return ''.join(Random().sample(string.ascii_letters + string.digits, 6))
-
- def RegisterDisconnectHandler(self, handler):
- """
- Register handler that will be called on disconnect
- """
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self, handler):
- """
- Unregister handler that is called on disconnect
- """
- self.disconnect_handlers.remove(handler)
-
- def SendAndWaitForResponse(self, stanza, timeout=None, func=None,
- args=None):
- """
- Send stanza and wait for recipient's response to it. Will call
- transports on_timeout callback if response is not retrieved in time
-
- Be aware: Only timeout of latest call of SendAndWait is active.
- """
-# if timeout is None:
-# timeout = DEFAULT_TIMEOUT_SECONDS
- def on_ok(_waitid):
-# if timeout:
-# self._owner.set_timeout(timeout)
- to = stanza.getTo()
- to = gajim.get_jid_without_resource(to)
-
- try:
- item = self.roster[to]
- except KeyError:
- # Contact offline
- item = None
-
- conn = None
- if to in self.recipient_to_hash:
- conn = self.connections[self.recipient_to_hash[to]]
- elif item:
- the_address = None
- for address in item['addresses']:
- if address['address'] in self.ip_to_hash:
- the_address = address
- if the_address and the_address['address'] in self.ip_to_hash:
- hash_ = self.ip_to_hash[the_address['address']]
- if self.hash_to_port[hash_] == the_address['port']:
- conn = self.connections[hash_]
- if func:
- conn.Dispatcher.on_responses[_waitid] = (func, args)
- conn.onreceive(conn.Dispatcher._WaitForData)
- conn.Dispatcher._expected[_waitid] = None
- self.send(stanza, on_ok=on_ok)
-
- def SendAndCallForResponse(self, stanza, func=None, args=None):
- """
- Put stanza on the wire and call back when recipient replies. Additional
- callback arguments can be specified in args.
- """
- self.SendAndWaitForResponse(stanza, 0, func, args)
diff --git a/src/common/zeroconf/connection_handlers_zeroconf.py b/src/common/zeroconf/connection_handlers_zeroconf.py
deleted file mode 100644
index ddacc0e37..000000000
--- a/src/common/zeroconf/connection_handlers_zeroconf.py
+++ /dev/null
@@ -1,114 +0,0 @@
-##
-## Copyright (C) 2006 Gajim Team
-##
-## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
-## - Stefan Bethge <stefan@lanpartei.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import nbxmpp
-
-from common import gajim
-from common.commands import ConnectionCommands
-from common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
-from common.connection_handlers_events import ZeroconfMessageReceivedEvent
-
-import logging
-log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
-
-STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible']
-# kind of events we can wait for an answer
-VCARD_PUBLISHED = 'vcard_published'
-VCARD_ARRIVED = 'vcard_arrived'
-AGENT_REMOVED = 'agent_removed'
-HAS_IDLE = True
-try:
- import idle
-except Exception:
- log.debug(_('Unable to load idle module'))
- HAS_IDLE = False
-
-from common import connection_handlers
-
-class ConnectionVcard(connection_handlers.ConnectionVcard):
- def add_sha(self, p, send_caps = True):
- return p
-
- def add_caps(self, p):
- return p
-
- def request_vcard(self, jid = None, is_fake_jid = False):
- pass
-
- def send_vcard(self, vcard):
- pass
-
-
-class ConnectionHandlersZeroconf(ConnectionVcard,
-ConnectionSocks5BytestreamZeroconf, ConnectionCommands,
-connection_handlers.ConnectionPEP, connection_handlers.ConnectionHandlersBase,
-connection_handlers.ConnectionJingle):
- def __init__(self):
- ConnectionVcard.__init__(self)
- ConnectionSocks5BytestreamZeroconf.__init__(self)
- ConnectionCommands.__init__(self)
- connection_handlers.ConnectionJingle.__init__(self)
- connection_handlers.ConnectionHandlersBase.__init__(self)
-
- try:
- idle.init()
- except Exception:
- global HAS_IDLE
- HAS_IDLE = False
-
- def _messageCB(self, ip, con, msg):
- """
- Called when we receive a message
- """
- log.debug('Zeroconf MessageCB')
- gajim.nec.push_incoming_event(ZeroconfMessageReceivedEvent(None,
- conn=self, stanza=msg, ip=ip))
- return
-
- def store_metacontacts(self, tags):
- """
- Fake empty method
- """
- # serverside metacontacts are not supported with zeroconf
- # (there is no server)
- pass
-
- def _DiscoverItemsGetCB(self, con, iq_obj):
- log.debug('DiscoverItemsGetCB')
-
- if not self.connection or self.connected < 2:
- return
-
- if self.commandItemsQuery(con, iq_obj):
- raise nbxmpp.NodeProcessed
- node = iq_obj.getTagAttr('query', 'node')
- if node is None:
- result = iq_obj.buildReply('result')
- self.connection.send(result)
- raise nbxmpp.NodeProcessed
- if node==nbxmpp.NS_COMMANDS:
- self.commandListQuery(con, iq_obj)
- raise nbxmpp.NodeProcessed
diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py
deleted file mode 100644
index 2446a5e37..000000000
--- a/src/common/zeroconf/connection_zeroconf.py
+++ /dev/null
@@ -1,384 +0,0 @@
-## common/zeroconf/connection_zeroconf.py
-##
-## Contributors for this file:
-## - Yann Leboulanger <asterix@lagaule.org>
-## - Nikos Kouremenos <nkour@jabber.org>
-## - Dimitur Kirov <dkirov@gmail.com>
-## - Travis Shirk <travis@pobox.com>
-## - Stefan Bethge <stefan@lanpartei.de>
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix@lagaule.org>
-## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
-## Copyright (C) 2006 Nikos Kouremenos <nkour@jabber.org>
-## Dimitur Kirov <dkirov@gmail.com>
-## Travis Shirk <travis@pobox.com>
-## Norman Rasmussen <norman@rasmussen.co.za>
-## Stefan Bethge <stefan@lanpartei.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-import os
-import socket
-import random
-random.seed()
-
-import signal
-if os.name != 'nt':
- signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-import getpass
-from gi.repository import GLib
-
-from common.connection import CommonConnection
-from common import gajim
-from common import ged
-from common.zeroconf import client_zeroconf
-from common.zeroconf import zeroconf
-from common.zeroconf.connection_handlers_zeroconf import *
-from common.connection_handlers_events import *
-
-class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf):
- def __init__(self, name):
- ConnectionHandlersZeroconf.__init__(self)
- # system username
- self.username = None
- self.server_resource = '' # zeroconf has no resource, fake an empty one
- self.call_resolve_timeout = False
- # we don't need a password, but must be non-empty
- self.password = 'zeroconf'
- self.autoconnect = False
-
- CommonConnection.__init__(self, name)
- self.is_zeroconf = True
-
- gajim.ged.register_event_handler('stanza-message-outgoing', ged.OUT_CORE,
- self._nec_stanza_message_outgoing)
-
- def get_config_values_or_default(self):
- """
- Get name, host, port from config, or create zeroconf account with default
- values
- """
- if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'):
- gajim.log.debug('Creating zeroconf account')
- gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'autoconnect', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for',
- '')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password',
- 'zeroconf')
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'sync_with_global_status', True)
-
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port', 5298)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'is_zeroconf', True)
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'use_ft_proxies', False)
- self.host = socket.gethostname()
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname',
- self.host)
- self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- self.autoconnect = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'autoconnect')
- self.sync_with_global_status = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status')
- self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_email')
-
- if not self.username:
- self.username = getpass.getuser()
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name',
- self.username)
- else:
- self.username = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'name')
- # END __init__
-
- def check_jid(self, jid):
- return jid
-
- def reconnect(self):
- # Do not try to reco while we are already trying
- self.time_to_reconnect = None
- gajim.log.debug('reconnect')
-
- self.disconnect()
- self.change_status(self.old_show, self.status)
-
- def disable_account(self):
- self.disconnect()
-
- def _on_resolve_timeout(self):
- if self.connected:
- self.connection.resolve_all()
- diffs = self.roster.getDiffs()
- for key in diffs:
- self.roster.setItem(key)
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=key, nickname=self.roster.getName(key), sub='both',
- ask='no', groups=self.roster.getGroups(key)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=key, show=self.roster.getStatus(key),
- status=self.roster.getMessage(key)))
- #XXX open chat windows don't get refreshed (full name), add that
- return self.call_resolve_timeout
-
- # callbacks called from zeroconf
- def _on_new_service(self, jid):
- self.roster.setItem(jid)
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=self.roster.getName(jid), sub='both',
- ask='no', groups=self.roster.getGroups(jid)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
- status=self.roster.getMessage(jid)))
-
- def _on_remove_service(self, jid):
- self.roster.delItem(jid)
- # 'NOTIFY' (account, (jid, status, status message, resource, priority,
- # keyID, timestamp, contact_nickname))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show='offline', status=''))
-
- def disconnectedReconnCB(self):
- """
- Called when we are disconnected. Comes from network manager for example
- we don't try to reconnect, network manager will tell us when we can
- """
- if gajim.account_is_connected(self.name):
- # we cannot change our status to offline or connecting
- # after we auth to server
- self.old_show = STATUS_LIST[self.connected]
- self.connected = 0
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- # random number to show we wait network manager to send us a reconenct
- self.time_to_reconnect = 5
- self.on_purpose = False
-
- def _on_name_conflictCB(self, alt_name):
- self.disconnect()
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- gajim.nec.push_incoming_event(ZeroconfNameConflictEvent(None, conn=self,
- alt_name=alt_name))
-
- def _on_error(self, message):
- gajim.nec.push_incoming_event(InformationEvent(None, conn=self,
- level='error', pri_txt=_('Avahi error'), sec_txt=_('%s\nLink-local '
- 'messaging might not work properly.') % message))
-
- def connect(self, show='online', msg=''):
- self.get_config_values_or_default()
- if not self.connection:
- self.connection = client_zeroconf.ClientZeroconf(self)
- if not zeroconf.test_zeroconf():
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not connect to "%s"') % self.name,
- msg=_('Please check if Avahi or Bonjour is installed.')))
- self.disconnect()
- return
- result = self.connection.connect(show, msg)
- if not result:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- if result is False:
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not start local service'),
- msg=_('Unable to bind to port %d.' % self.port)))
- else: # result is None
- gajim.nec.push_incoming_event(ConnectionLostEvent(None,
- conn=self, title=_('Could not start local service'),
- msg=_('Please check if avahi-daemon is running.')))
- self.disconnect()
- return
- else:
- self.connection.announce()
- self.roster = self.connection.getRoster()
- gajim.nec.push_incoming_event(RosterReceivedEvent(None, conn=self,
- xmpp_roster=self.roster))
-
- # display contacts already detected and resolved
- for jid in self.roster.keys():
- gajim.nec.push_incoming_event(RosterInfoEvent(None, conn=self,
- jid=jid, nickname=self.roster.getName(jid), sub='both',
- ask='no', groups=self.roster.getGroups(jid)))
- gajim.nec.push_incoming_event(ZeroconfPresenceReceivedEvent(
- None, conn=self, fjid=jid, show=self.roster.getStatus(jid),
- status=self.roster.getMessage(jid)))
-
- self.connected = STATUS_LIST.index(show)
-
- # refresh all contacts data every five seconds
- self.call_resolve_timeout = True
- GLib.timeout_add_seconds(5, self._on_resolve_timeout)
- return True
-
- def disconnect(self, on_purpose=False):
- self.connected = 0
- self.time_to_reconnect = None
- if self.connection:
- self.connection.disconnect()
- self.connection = None
- # stop calling the timeout
- self.call_resolve_timeout = False
-
- def reannounce(self):
- if self.connected:
- txt = {}
- txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_first_name')
- txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_last_name')
- txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_jabber_id')
- txt['email'] = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'zeroconf_email')
- self.connection.reannounce(txt)
-
- def update_details(self):
- if self.connection:
- port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- if port != self.port:
- self.port = port
- last_msg = self.connection.last_msg
- self.disconnect()
- if not self.connect(self.status, last_msg):
- return
- if self.status != 'invisible':
- self.connection.announce()
- else:
- self.reannounce()
-
- def connect_and_init(self, show, msg, sign_msg):
- # to check for errors from zeroconf
- check = True
- if not self.connect(show, msg):
- return
- if show != 'invisible':
- check = self.connection.announce()
- else:
- self.connected = STATUS_LIST.index(show)
- gajim.nec.push_incoming_event(SignedInEvent(None, conn=self))
-
- # stay offline when zeroconf does something wrong
- if check:
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- else:
- # show notification that avahi or system bus is down
- self.connected = 0
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _change_to_invisible(self, msg):
- if self.connection.remove_announce():
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='invisible'))
- else:
- # show notification that avahi or system bus is down
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _change_from_invisible(self):
- self.connection.announce()
-
- def _update_status(self, show, msg):
- if self.connection.set_show_msg(show, msg):
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show=show))
- else:
- # show notification that avahi or system bus is down
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=self,
- show='offline'))
- self.status = 'offline'
- gajim.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
- title=_('Could not change status of account "%s"') % self.name,
- msg=_('Please check if avahi-daemon is running.')))
-
- def _nec_stanza_message_outgoing(self, obj):
-
- def on_send_ok(msg_id):
- gajim.nec.push_incoming_event(MessageSentEvent(None, conn=self,
- jid=obj.jid, message=obj.message, keyID=obj.keyID,
- automatic_message=obj.automatic_message, chatstate=None,
- msg_id=msg_id))
- if obj.callback:
- obj.callback(obj.msg_iq, *obj.callback_args)
-
- self.log_message(obj, obj.jid)
-
- def on_send_not_ok(reason):
- reason += ' ' + _('Your message could not be sent.')
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=obj.jid, error_code=-1, error_msg=reason, msg=None,
- time_=None, session=obj.session))
-
- ret = self.connection.send(
- obj.msg_iq, obj.message is not None,
- on_ok=on_send_ok, on_not_ok=on_send_not_ok)
-
- if ret == -1:
- # Contact Offline
- gajim.nec.push_incoming_event(MessageErrorEvent(None, conn=self,
- fjid=obj.jid, error_code=-1, error_msg=_(
- 'Contact is offline. Your message could not be sent.'),
- msg=None, time_=None, session=obj.session))
-
- def send_stanza(self, stanza):
- # send a stanza untouched
- if not self.connection:
- return
- if not isinstance(stanza, nbxmpp.Node):
- stanza = nbxmpp.Protocol(node=stanza)
- self.connection.send(stanza)
-
- def _event_dispatcher(self, realm, event, data):
- CommonConnection._event_dispatcher(self, realm, event, data)
- if realm == '':
- if event == nbxmpp.transports_nb.DATA_ERROR:
- thread_id = data[1]
- frm = data[0]
- session = self.get_or_create_session(frm, thread_id)
- gajim.nec.push_incoming_event(MessageErrorEvent(
- None, conn=self, fjid=frm, error_code=-1, error_msg=_(
- 'Connection to host could not be established: Timeout while '
- 'sending data.'), msg=None, time_=None, session=session))
-
-# END ConnectionZeroconf
diff --git a/src/common/zeroconf/roster_zeroconf.py b/src/common/zeroconf/roster_zeroconf.py
deleted file mode 100644
index eeb5c8991..000000000
--- a/src/common/zeroconf/roster_zeroconf.py
+++ /dev/null
@@ -1,159 +0,0 @@
-## common/zeroconf/roster_zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-##
-## 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/>.
-##
-
-
-from common.zeroconf import zeroconf
-from common.zeroconf.zeroconf import Constant, ConstantRI
-
-class Roster:
- def __init__(self, zeroconf):
- self._data = None
- self.zeroconf = zeroconf # our zeroconf instance
- self.version = ''
- self.received_from_server = True
-
- def update_roster(self):
- for val in self.zeroconf.contacts.values():
- self.setItem(val[Constant.NAME])
-
- def getRoster(self):
- if self._data is None:
- self._data = {}
- self.update_roster()
- return self
-
- def getDiffs(self):
- """
- Update the roster with new data and return dict with jid -> new status
- pairs to do notifications and stuff
- """
- diffs = {}
- old_data = self._data.copy()
- self.update_roster()
- for key in old_data.keys():
- if key in self._data:
- if old_data[key] != self._data[key]:
- diffs[key] = self._data[key]['status']
- return diffs
-
- def setItem(self, jid, name='', groups=''):
- contact = self.zeroconf.get_contact(jid)
- if not contact:
- return
-
- addresses = []
- i = 0
- for ri in contact[Constant.RESOLVED_INFO]:
- addresses += [{}]
- addresses[i]['host'] = ri[ConstantRI.HOST]
- addresses[i]['address'] = ri[ConstantRI.ADDRESS]
- addresses[i]['port'] = ri[ConstantRI.PORT]
- i += 1
- txt = contact[Constant.TXT]
-
- self._data[jid]={}
- self._data[jid]['ask'] = 'none'
- self._data[jid]['subscription'] = 'both'
- self._data[jid]['groups'] = []
- self._data[jid]['resources'] = {}
- self._data[jid]['addresses'] = addresses
- txt_dict = self.zeroconf.txt_array_to_dict(txt)
- status = txt_dict.get('status', '')
- if not status:
- status = 'avail'
- nm = txt_dict.get('1st', '')
- if 'last' in txt_dict:
- if nm != '':
- nm += ' '
- nm += txt_dict['last']
- if nm:
- self._data[jid]['name'] = nm
- else:
- self._data[jid]['name'] = jid
- if status == 'avail':
- status = 'online'
- self._data[jid]['txt_dict'] = txt_dict
- if 'msg' not in self._data[jid]['txt_dict']:
- self._data[jid]['txt_dict']['msg'] = ''
- self._data[jid]['status'] = status
- self._data[jid]['show'] = status
-
- def setItemMulti(self, items):
- for i in items:
- self.setItem(jid=i['jid'], name=i['name'], groups=i['groups'])
-
- def delItem(self, jid):
- if jid in self._data:
- del self._data[jid]
-
- def getItem(self, jid):
- if jid in self._data:
- return self._data[jid]
-
- def __getitem__(self, jid):
- return self._data[jid]
-
- def getItems(self):
- # Return list of all [bare] JIDs that the roster currently tracks.
- return self._data.keys()
-
- def keys(self):
- return self._data.keys()
-
- def getRaw(self):
- return self._data
-
- def getResources(self, jid):
- return {}
-
- def getGroups(self, jid):
- return self._data[jid]['groups']
-
- def getName(self, jid):
- if jid in self._data:
- return self._data[jid]['name']
-
- def getStatus(self, jid):
- if jid in self._data:
- return self._data[jid]['status']
-
- def getMessage(self, jid):
- if jid in self._data:
- return self._data[jid]['txt_dict']['msg']
-
- def getShow(self, jid):
- return self.getStatus(jid)
-
- def getPriority(self, jid):
- return 5
-
- def getSubscription(self, jid):
- return 'both'
-
- def Subscribe(self, jid):
- pass
-
- def Unsubscribe(self, jid):
- pass
-
- def Authorize(self, jid):
- pass
-
- def Unauthorize(self, jid):
- pass
diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py
deleted file mode 100644
index 58bba6d25..000000000
--- a/src/common/zeroconf/zeroconf.py
+++ /dev/null
@@ -1,63 +0,0 @@
-## common/zeroconf/zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-##
-## 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/>.
-##
-
-from enum import IntEnum, unique
-
-@unique
-class Constant(IntEnum):
- NAME = 0
- DOMAIN = 1
- RESOLVED_INFO = 2
- BARE_NAME = 3
- TXT = 4
-
-@unique
-class ConstantRI(IntEnum):
- INTERFACE = 0
- PROTOCOL = 1
- HOST = 2
- APROTOCOL = 3
- ADDRESS = 4
- PORT = 5
-
-def test_avahi():
- try:
- import avahi
- except ImportError:
- return False
- return True
-
-def test_bonjour():
- try:
- import pybonjour
- except ImportError:
- return False
- except WindowsError:
- return False
- return True
-
-def test_zeroconf():
- return test_avahi() or test_bonjour()
-
-if test_avahi():
- from common.zeroconf import zeroconf_avahi
- Zeroconf = zeroconf_avahi.Zeroconf
-elif test_bonjour():
- from common.zeroconf import zeroconf_bonjour
- Zeroconf = zeroconf_bonjour.Zeroconf
diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py
deleted file mode 100644
index 51bad907e..000000000
--- a/src/common/zeroconf/zeroconf_avahi.py
+++ /dev/null
@@ -1,482 +0,0 @@
-## common/zeroconf/zeroconf.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import logging
-log = logging.getLogger('gajim.c.z.zeroconf_avahi')
-
-try:
- import dbus.exceptions
-except ImportError:
- pass
-
-from common.zeroconf.zeroconf import Constant, ConstantRI
-
-class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.avahi = None
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = {} # service data
-
- #XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.service_browser = None
- self.domain_browser = None
- self.bus = None
- self.server = None
- self.contacts = {} # all current local contacts with data
- self.entrygroup = None
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
-
-
- ## handlers for dbus callbacks
- def entrygroup_commit_error_CB(self, err):
- # left blank for possible later usage
- pass
-
- def error_callback1(self, err):
- log.debug('Error while resolving: ' + str(err))
-
- def error_callback(self, err):
- log.debug(str(err))
- # timeouts are non-critical
- if str(err) != 'Timeout reached':
- self.disconnect()
- self.disconnected_CB()
-
- def new_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Found service %s in domain %s on %i.%i.' % (name, domain,
- interface, protocol))
- if not self.connected:
- return
-
- # synchronous resolving
- self.server.ResolveService( int(interface), int(protocol), name, stype,
- domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_callback,
- error_handler=self.error_callback1)
-
- def remove_service_callback(self, interface, protocol, name, stype, domain,
- flags):
- log.debug('Service %s in domain %s on %i.%i disappeared.' % (name,
- domain, interface, protocol))
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- val = self.contacts[key]
- if val[Constant.BARE_NAME] == name:
- # try to reduce instead of delete first
- resolved_info = val[Constant.RESOLVED_INFO]
- if len(resolved_info) > 1:
- for i in range(len(resolved_info)):
- if resolved_info[i][ConstantRI.INTERFACE] == interface and resolved_info[i][ConstantRI.PROTOCOL] == protocol:
- del self.contacts[key][Constant.RESOLVED_INFO][i]
- # if still something left, don't remove
- if len(self.contacts[key][Constant.RESOLVED_INFO]) > 1: return
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_service_type(self, interface, protocol, stype, domain, flags):
- # Are we already browsing this domain for this type?
- if self.service_browser:
- return
-
- object_path = self.server.ServiceBrowserNew(interface, protocol, \
- stype, domain, dbus.UInt32(0))
-
- self.service_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, object_path),
- self.avahi.DBUS_INTERFACE_SERVICE_BROWSER)
- self.service_browser.connect_to_signal('ItemNew',
- self.new_service_callback)
- self.service_browser.connect_to_signal('ItemRemove',
- self.remove_service_callback)
- self.service_browser.connect_to_signal('Failure', self.error_callback)
-
- def new_domain_callback(self, interface, protocol, domain, flags):
- if domain != 'local':
- self.browse_domain(interface, protocol, domain)
-
- def txt_array_to_dict(self, txt_array):
- txt_dict = {}
- for els in txt_array:
- key, val = '', None
- for c in els:
- c = chr(c)
- if val is None:
- if c == '=':
- val = ''
- else:
- key += c
- else:
- val += c
- if val is None: # missing '='
- val = ''
- txt_dict[key] = val
- return txt_dict
-
- def service_resolved_callback(self, interface, protocol, name, stype, domain,
- host, aprotocol, address, port, txt, flags):
- log.debug('Service data for service %s in domain %s on %i.%i:'
- % (name, domain, interface, protocol))
- log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address,
- port, self.txt_array_to_dict(txt)))
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- resolved_info = [(interface, protocol, host, aprotocol, address, port)]
- if name in self.contacts:
- # Decide whether to try to merge with existing resolved info:
- old_name, old_domain, old_resolved_info, old_bare_name, old_txt = self.contacts[name]
- if name == old_name and domain == old_domain and bare_name == old_bare_name:
- # Seems similar enough, try to merge resolved info:
- for i in range(len(old_resolved_info)):
- # for now, keep a single record for each (interface, protocol) pair
- #
- # Note that, theoretically, we could both get IPv4 and
- # IPv6 aprotocol responses via the same protocol,
- # so this probably needs to be revised again.
- if old_resolved_info[i][0:2] == (interface, protocol):
- log.debug('Deleting resolved info for interface %i, protocol %i, host %s, aprotocol %i, address %s, port %i' % old_resolved_info[i])
- del old_resolved_info[i]
- break
- resolved_info = resolved_info + old_resolved_info
- log.debug('Collected resolved info is now: %s' % (resolved_info,))
- self.contacts[name] = (name, domain, resolved_info, bare_name, txt)
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain,
- (interface, protocol, host, aprotocol, address, port),
- bare_name, txt)
-
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, interface, protocol, name, stype,
- domain, host, aprotocol, address, port, txt, flags):
- if not self.connected:
- return
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
- # update TXT data only, as intended according to resolve_all comment
- old_contact = self.contacts[name]
- self.contacts[name] = old_contact[0:Constant.TXT] + (txt,) + old_contact[Constant.TXT+1:]
-
- def service_added_callback(self):
- log.debug('Service successfully added')
-
- def service_committed_callback(self):
- log.debug('Service successfully committed')
-
- def service_updated_callback(self):
- log.debug('Service successfully updated')
-
- def service_add_fail_callback(self, err):
- log.debug('Error while adding service. %s' % str(err))
- if 'Local name collision' in str(err):
- alternative_name = self.server.GetAlternativeServiceName(self.username)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- def server_state_changed_callback(self, state, error):
- log.debug('server state changed to %s' % state)
- if state == self.avahi.SERVER_RUNNING:
- self.create_service()
- elif state in (self.avahi.SERVER_COLLISION,
- self.avahi.SERVER_REGISTERING):
- self.disconnect()
- self.entrygroup.Reset()
- elif state == self.avahi.CLIENT_FAILURE:
- # does it ever go here?
- log.debug('CLIENT FAILURE')
-
- def entrygroup_state_changed_callback(self, state, error):
- # the name is already present, so recreate
- if state == self.avahi.ENTRY_GROUP_COLLISION:
- log.debug('zeroconf.py: local name collision')
- self.service_add_fail_callback('Local name collision')
- elif state == self.avahi.ENTRY_GROUP_FAILURE:
- self.disconnect()
- self.entrygroup.Reset()
- log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that'
- ' should not happen)')
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def avahi_txt(self):
- return self.avahi.dict_to_txt_array(self.txt)
-
- def create_service(self):
- try:
- if not self.entrygroup:
- # create an EntryGroup for publishing
- self.entrygroup = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.EntryGroupNew()),
- self.avahi.DBUS_INTERFACE_ENTRY_GROUP)
- self.entrygroup.connect_to_signal('StateChanged',
- self.entrygroup_state_changed_callback)
-
- txt = {}
-
- # remove empty keys
- for key, val in self.txt.items():
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = txt
- log.debug('Publishing service %s of type %s' % (self.name,
- self.stype))
- self.entrygroup.AddService(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- '', dbus.UInt16(self.port), self.avahi_txt(),
- reply_handler=self.service_added_callback,
- error_handler=self.service_add_fail_callback)
-
- self.entrygroup.Commit(reply_handler=self.service_committed_callback,
- error_handler=self.entrygroup_commit_error_CB)
-
- return True
-
- except dbus.DBusException as e:
- log.debug(str(e))
- return False
-
- def announce(self):
- if not self.connected:
- return False
-
- state = self.server.GetState()
- if state == self.avahi.SERVER_RUNNING:
- if self.create_service():
- self.announced = True
- return True
- return False
-
- def remove_announce(self):
- if self.announced == False:
- return False
- try:
- if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE:
- self.entrygroup.Reset()
- self.entrygroup.Free()
- # .Free() has mem leaks
- self.entrygroup._obj._bus = None
- self.entrygroup._obj = None
- self.entrygroup = None
- self.announced = False
-
- return True
- else:
- return False
- except dbus.DBusException:
- log.debug("Can't remove service. That should not happen")
-
- def browse_domain(self, interface, protocol, domain):
- self.new_service_type(interface, protocol, self.stype, domain, '')
-
- def avahi_dbus_connect_cb(self, a, connect, disconnect):
- if connect != "":
- log.debug('Lost connection to avahi-daemon')
- self.disconnect()
- if self.disconnected_CB:
- self.disconnected_CB()
- else:
- log.debug('We are connected to avahi-daemon')
-
- # connect to dbus
- def connect_dbus(self):
- try:
- import dbus
- except ImportError:
- log.debug('Error: python-dbus needs to be installed. No '
- 'zeroconf support.')
- return False
- if self.bus:
- return True
- try:
- self.bus = dbus.SystemBus()
- self.bus.add_signal_receiver(self.avahi_dbus_connect_cb,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.freedesktop.Avahi')
- except Exception as e:
- # System bus is not present
- self.bus = None
- log.debug(str(e))
- return False
- else:
- return True
-
- # connect to avahi
- def connect_avahi(self):
- if not self.connect_dbus():
- return False
- try:
- import avahi
- self.avahi = avahi
- except ImportError:
- log.debug('Error: python-avahi needs to be installed. No '
- 'zeroconf support.')
- return False
-
- if self.server:
- return True
- try:
- self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME,
- self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER)
- self.server.connect_to_signal('StateChanged',
- self.server_state_changed_callback)
- except Exception as e:
- # Avahi service is not present
- self.server = None
- log.debug(str(e))
- return False
- else:
- return True
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
- if not self.connect_avahi():
- return False
-
- self.connected = True
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- 'local')
-
- # Browse for other browsable domains
- self.domain_browser = dbus.Interface(self.bus.get_object(
- self.avahi.DBUS_NAME, self.server.DomainBrowserNew(
- self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, '',
- self.avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))),
- self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
- self.domain_browser.connect_to_signal('ItemNew',
- self.new_domain_callback)
- self.domain_browser.connect_to_signal('Failure', self.error_callback)
- else:
- self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC,
- self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- if self.service_browser:
- try:
- self.service_browser.Free()
- except dbus.DBusException as e:
- log.debug(str(e))
- self.service_browser._obj._bus = None
- self.service_browser._obj = None
- if self.domain_browser:
- try:
- self.domain_browser.Free()
- except dbus.DBusException as e:
- log.debug(str(e))
- self.domain_browser._obj._bus = None
- self.domain_browser._obj = None
- self.remove_announce()
- self.server._obj._bus = None
- self.server._obj = None
- self.server = None
- self.service_browser = None
- self.domain_browser = None
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
- for val in self.contacts.values():
- # get txt data from last recorded resolved info
- # TODO: Better try to get it from last IPv6 mDNS, then last IPv4?
- ri = val[Constant.RESOLVED_INFO][0]
- self.server.ResolveService(int(ri[ConstantRI.INTERFACE]), int(ri[ConstantRI.PROTOCOL]),
- val[Constant.BARE_NAME], self.stype, val[Constant.DOMAIN],
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0),
- reply_handler=self.service_resolved_all_callback,
- error_handler=self.error_callback)
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- txt = self.avahi_txt()
- if self.connected and self.entrygroup:
- self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC,
- self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '',
- txt, reply_handler=self.service_updated_callback,
- error_handler=self.error_callback)
- return True
- else:
- return False
-
-
-# END Zeroconf
diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py
deleted file mode 100644
index f68d412dd..000000000
--- a/src/common/zeroconf/zeroconf_bonjour.py
+++ /dev/null
@@ -1,335 +0,0 @@
-## common/zeroconf/zeroconf_bonjour.py
-##
-## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
-##
-## 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/>.
-##
-
-from common import gajim
-import select
-import re
-from common.zeroconf.zeroconf import Constant
-
-try:
- import pybonjour
-except ImportError:
- pass
-
-
-resolve_timeout = 1
-
-class Zeroconf:
- def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB,
- disconnected_CB, error_CB, name, host, port):
- self.domain = None # specific domain to browse
- self.stype = '_presence._tcp'
- self.port = port # listening port that gets announced
- self.username = name
- self.host = host
- self.txt = pybonjour.TXTRecord() # service data
-
- # XXX these CBs should be set to None when we destroy the object
- # (go offline), because they create a circular reference
- self.new_serviceCB = new_serviceCB
- self.remove_serviceCB = remove_serviceCB
- self.name_conflictCB = name_conflictCB
- self.disconnected_CB = disconnected_CB
- self.error_CB = error_CB
-
- self.contacts = {} # all current local contacts with data
- self.connected = False
- self.announced = False
- self.invalid_self_contact = {}
- self.resolved = []
-
-
- def browse_callback(self, sdRef, flags, interfaceIndex, errorCode, serviceName, regtype, replyDomain):
- gajim.log.debug('Found service %s in domain %s on %i(type: %s).' % (serviceName, replyDomain, interfaceIndex, regtype))
- if not self.connected:
- return
- if errorCode != pybonjour.kDNSServiceErr_NoError:
- return
- if not (flags & pybonjour.kDNSServiceFlagsAdd):
- self.remove_service_callback(serviceName)
- return
-
- # asynchronous resolving
- resolve_sdRef = pybonjour.DNSServiceResolve(0, interfaceIndex, serviceName, regtype, replyDomain, self.service_resolved_callback)
-
- try:
- while not self.resolved:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- else:
- self.resolved.pop()
- finally:
- resolve_sdRef.close()
-
- def remove_service_callback(self, name):
- gajim.log.debug('Service %s disappeared.' % name)
- if not self.connected:
- return
- if name != self.name:
- for key in self.contacts.keys():
- if self.contacts[key][Constant.BARE_NAME] == name:
- del self.contacts[key]
- self.remove_serviceCB(key)
- return
-
- def new_domain_callback(self, interface, protocol, domain, flags):
- if domain != "local":
- self.browse_domain(interface, protocol, domain)
-
- # takes a TXTRecord instance
- def txt_array_to_dict(self, txt):
- items = pybonjour.TXTRecord.parse(txt)._items
- return dict((v[0], v[1]) for v in items.values())
-
- def service_resolved_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname,
- hosttarget, port, txtRecord):
-
- # TODO: do proper decoding...
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- # Split on '.' but do not split on '\.'
- result = re.split('(?<!\\\\)\.', fullname)
- name = result[0]
- protocol, domain = result[2:4]
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- txt = pybonjour.TXTRecord.parse(txtRecord)
-
- gajim.log.debug('Service data for service %s on %i:' % (fullname, interfaceIndex))
- gajim.log.debug('Host %s, port %i, TXT data: %s' % (hosttarget, port, txt._items))
-
- if not self.connected:
- return
-
- bare_name = name
- if '@' not in name:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- resolved_info = [(interfaceIndex, protocol, hosttarget, -1, port)]
- self.contacts[name] = (name, domain, resolved_info, bare_name, txtRecord)
-
- self.new_serviceCB(name)
- else:
- # remember data
- # In case this is not our own record but of another
- # gajim instance on the same machine,
- # it will be used when we get a new name.
- self.invalid_self_contact[name] = (name, domain, (interfaceIndex, protocol, hosttarget, -1, port), bare_name, txtRecord)
- # count services
- self.resolved.append(True)
-
- # different handler when resolving all contacts
- def service_resolved_all_callback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtRecord):
- if not self.connected:
- return
-
- escaping= {
- r'\.': '.',
- r'\032': ' ',
- r'\064': '@',
- }
-
- name, stype, protocol, domain, dummy = fullname.split('.')
-
- # Replace the escaped values
- for src, trg in escaping.items():
- name = name.replace(src, trg)
-
- bare_name = name
- if name.find('@') == -1:
- name = name + '@' + name
-
- # we don't want to see ourselves in the list
- if name != self.name:
- # update TXT data only, as intended according to resolve_all comment
- old_contact = self.contacts[name]
- self.contacts[name] = old_contact[0:Constant.TXT] + (self.txt,) + old_contact[Constant.TXT+1:]
-
-
- def service_added_callback(self, sdRef, flags, errorCode, name, regtype, domain):
- if errorCode == pybonjour.kDNSServiceErr_NoError:
- gajim.log.debug('Service successfully added')
-
- def service_add_fail_callback(self, err):
- if err[0][0] == pybonjour.kDNSServiceErr_NameConflict:
- gajim.log.debug('Error while adding service. %s' % str(err))
- parts = self.username.split(' ')
-
- #check if last part is a number and if, increment it
- try:
- stripped = str(int(parts[-1]))
- except Exception:
- stripped = 1
- alternative_name = self.username + str(stripped+1)
- self.name_conflictCB(alternative_name)
- return
- self.error_CB(_('Error while adding service. %s') % str(err))
- self.disconnect()
-
- # make zeroconf-valid names
- def replace_show(self, show):
- if show in ['chat', 'online', '']:
- return 'avail'
- elif show == 'xa':
- return 'away'
- return show
-
- def create_service(self):
- txt = {}
-
- #remove empty keys
- for key, val in self.txt:
- if val:
- txt[key] = val
-
- txt['port.p2pj'] = self.port
- txt['version'] = 1
- txt['txtvers'] = 1
-
- # replace gajim's show messages with compatible ones
- if 'status' in self.txt:
- txt['status'] = self.replace_show(self.txt['status'])
- else:
- txt['status'] = 'avail'
-
- self.txt = pybonjour.TXTRecord(txt, strict=True)
-
- try:
- sdRef = pybonjour.DNSServiceRegister(name = self.name,
- regtype = self.stype, port = self.port, txtRecord = self.txt,
- callBack = self.service_added_callback)
- self.service_sdRef = sdRef
- except pybonjour.BonjourError as e:
- self.service_add_fail_callback(e)
- else:
- gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype))
-
- ready = select.select([sdRef], [], [], resolve_timeout)
- if sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(sdRef)
-
- def announce(self):
- if not self.connected:
- return False
-
- self.create_service()
- self.announced = True
- return True
-
- def remove_announce(self):
- if not self.announced:
- return False
- try:
- self.service_sdRef.close()
- self.announced = False
- return True
- except pybonjour.BonjourError as e:
- gajim.log.debug(e)
- return False
-
-
- def connect(self):
- self.name = self.username + '@' + self.host # service name
-
- self.connected = True
-
- # start browsing
- if self.domain is None:
- # Explicitly browse .local
- self.browse_domain()
-
- # Browse for other browsable domains
- #self.domain_sdRef = pybonjour.DNSServiceEnumerateDomains(flags, interfaceIndex=0, callBack=self.new_domain_callback)
-
- else:
- self.browse_domain(self.domain)
-
- return True
-
- def disconnect(self):
- if self.connected:
- self.connected = False
- if hasattr(self, 'browse_sdRef'):
- self.browse_sdRef.close()
- self.remove_announce()
-
- def browse_domain(self, domain=None):
- gajim.log.debug('starting to browse')
- try:
- self.browse_sdRef = pybonjour.DNSServiceBrowse(regtype=self.stype, domain=domain, callBack=self.browse_callback)
- except pybonjour.BonjourError as e:
- self.error_CB("Error while browsing: %s" % str(e))
-
- def browse_loop(self):
- ready = select.select([self.browse_sdRef], [], [], 0)
- if self.browse_sdRef in ready[0]:
- pybonjour.DNSServiceProcessResult(self.browse_sdRef)
-
- # refresh txt data of all contacts manually (no callback available)
- def resolve_all(self):
- if not self.connected:
- return
-
- # for now put here as this is synchronous
- self.browse_loop()
-
- for val in self.contacts.values():
- resolve_sdRef = pybonjour.DNSServiceResolve(0,
- pybonjour.kDNSServiceInterfaceIndexAny, val[Constant.BARE_NAME],
- self.stype + '.', val[Constant.DOMAIN] + '.',
- self.service_resolved_all_callback)
-
- try:
- ready = select.select([resolve_sdRef], [], [], resolve_timeout)
- if resolve_sdRef not in ready[0]:
- gajim.log.debug('Resolve timed out (in resolve_all)')
- break
- pybonjour.DNSServiceProcessResult(resolve_sdRef)
- finally:
- resolve_sdRef.close()
-
- def get_contacts(self):
- return self.contacts
-
- def get_contact(self, jid):
- if not jid in self.contacts:
- return None
- return self.contacts[jid]
-
- def update_txt(self, show = None):
- if show:
- self.txt['status'] = self.replace_show(show)
-
- try:
- pybonjour.DNSServiceUpdateRecord(self.service_sdRef, None, 0, self.txt)
- except pybonjour.BonjourError:
- return False
- return True
diff --git a/src/config.py b/src/config.py
deleted file mode 100644
index aa2d84a45..000000000
--- a/src/config.py
+++ /dev/null
@@ -1,4346 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/config.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import Pango
-from gi.repository import GObject
-from gi.repository import GLib
-import os
-import common.config
-import common.sleepy
-from common.i18n import Q_
-
-import gtkgui_helpers
-import dialogs
-import cell_renderer_image
-import message_control
-from chat_control_base import ChatControlBase
-import dataforms_widget
-import profile_window
-import gui_menu_builder
-
-try:
- import gtkspell
- HAS_GTK_SPELL = True
-except (ImportError, ValueError):
- HAS_GTK_SPELL = False
-
-from common import helpers
-from common import gajim
-from common import connection
-from common import passwords
-from common.zeroconf import connection_zeroconf
-from common import dataforms
-from common import gpg
-from common import ged
-
-try:
- from common.multimedia_helpers import AudioInputManager, AudioOutputManager
- from common.multimedia_helpers import VideoInputManager, VideoOutputManager
- HAS_GST = True
-except (ImportError, ValueError):
- HAS_GST = False
-
-from common.exceptions import GajimGeneralException
-from common.connection_handlers_events import InformationEvent
-
-#---------- PreferencesWindow class -------------#
-class PreferencesWindow:
- """
- Class for Preferences window
- """
-
- def on_preferences_window_destroy(self, widget):
- """
- Close window
- """
- del gajim.interface.instances['preferences']
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def __init__(self):
- """
- Initialize Preferences window
- """
- self.xml = gtkgui_helpers.get_gtk_builder('preferences_window.ui')
- self.window = self.xml.get_object('preferences_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.notebook = self.xml.get_object('preferences_notebook')
- self.one_window_type_combobox = self.xml.get_object(
- 'one_window_type_combobox')
- self.iconset_combobox = self.xml.get_object('iconset_combobox')
- self.notify_on_signin_checkbutton = self.xml.get_object(
- 'notify_on_signin_checkbutton')
- self.notify_on_signout_checkbutton = self.xml.get_object(
- 'notify_on_signout_checkbutton')
- self.auto_popup_away_checkbutton = self.xml.get_object(
- 'auto_popup_away_checkbutton')
- self.auto_popup_chat_opened_checkbutton = self.xml.get_object(
- 'auto_popup_chat_opened_checkbutton')
- self.sound_dnd_checkbutton = self.xml.get_object('sound_dnd_checkbutton')
- self.auto_away_checkbutton = self.xml.get_object('auto_away_checkbutton')
- self.auto_away_time_spinbutton = self.xml.get_object(
- 'auto_away_time_spinbutton')
- self.auto_away_message_entry = self.xml.get_object(
- 'auto_away_message_entry')
- self.auto_xa_checkbutton = self.xml.get_object('auto_xa_checkbutton')
- self.auto_xa_time_spinbutton = self.xml.get_object(
- 'auto_xa_time_spinbutton')
- self.auto_xa_message_entry = self.xml.get_object('auto_xa_message_entry')
-
- ### General tab ###
- # Display avatars in roster
- st = gajim.config.get('show_avatars_in_roster')
- self.xml.get_object('show_avatars_in_roster_checkbutton'). \
- set_active(st)
-
- # Display status msg under contact name in roster
- st = gajim.config.get('show_status_msgs_in_roster')
- self.xml.get_object('show_status_msgs_in_roster_checkbutton'). \
- set_active( st)
-
- # Display PEP in roster
- st1 = gajim.config.get('show_mood_in_roster')
- st2 = gajim.config.get('show_activity_in_roster')
- st3 = gajim.config.get('show_tunes_in_roster')
- st4 = gajim.config.get('show_location_in_roster')
- w = self.xml.get_object('show_pep_in_roster_checkbutton')
- if st1 == st2 == st3 == st4:
- w.set_active(st1)
- else:
- w.set_inconsistent(True)
-
- # Sort contacts by show
- st = gajim.config.get('sort_by_show_in_roster')
- self.xml.get_object('sort_by_show_in_roster_checkbutton').set_active(st)
- st = gajim.config.get('sort_by_show_in_muc')
- self.xml.get_object('sort_by_show_in_muc_checkbutton').set_active(st)
-
- # emoticons
- emoticons_combobox = self.xml.get_object('emoticons_combobox')
- emoticons_list = os.listdir(os.path.join(gajim.DATA_DIR, 'emoticons'))
- # user themes
- if os.path.isdir(gajim.MY_EMOTS_PATH):
- emoticons_list += os.listdir(gajim.MY_EMOTS_PATH)
- emoticons_list.sort()
- renderer_text = Gtk.CellRendererText()
- emoticons_combobox.pack_start(renderer_text, True)
- emoticons_combobox.add_attribute(renderer_text, 'text', 0)
- model = Gtk.ListStore(str)
- emoticons_combobox.set_model(model)
- l = [_('Disabled')]
- for dir_ in emoticons_list:
- if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'emoticons', dir_)) \
- and not os.path.isdir(os.path.join(gajim.MY_EMOTS_PATH, dir_)) :
- continue
- if dir_ != '.svn':
- l.append(dir_)
- for i in range(len(l)):
- model.append([l[i]])
- if gajim.config.get('emoticons_theme') == l[i]:
- emoticons_combobox.set_active(i)
- if not gajim.config.get('emoticons_theme'):
- emoticons_combobox.set_active(0)
-
- # Set default for single window type
- choices = common.config.opt_one_window_types
- type_ = gajim.config.get('one_message_window')
- if type_ in choices:
- self.one_window_type_combobox.set_active(choices.index(type_))
- else:
- self.one_window_type_combobox.set_active(0)
-
- # Show roster on startup
- show_roster_combobox = self.xml.get_object('show_roster_on_startup')
- choices = common.config.opt_show_roster_on_startup
- type_ = gajim.config.get('show_roster_on_startup')
- if type_ in choices:
- show_roster_combobox.set_active(choices.index(type_))
- else:
- show_roster_combobox.set_active(0)
-
- # Compact View
- st = gajim.config.get('compact_view')
- self.xml.get_object('compact_view_checkbutton').set_active(st)
-
- # Ignore XHTML
- st = gajim.config.get('ignore_incoming_xhtml')
- self.xml.get_object('xhtml_checkbutton').set_active(st)
-
- # use speller
- if HAS_GTK_SPELL:
- st = gajim.config.get('use_speller')
- self.xml.get_object('speller_checkbutton').set_active(st)
- else:
- self.xml.get_object('speller_checkbutton').set_sensitive(False)
-
- # XEP-0184 positive ack
- st = gajim.config.get('positive_184_ack')
- self.xml.get_object('positive_184_ack_checkbutton').set_active(st)
-
- # Show avatar in tabs
- st = gajim.config.get('show_avatar_in_tabs')
- self.xml.get_object('show_avatar_in_tabs_checkbutton').set_active(st)
-
- ### Style tab ###
- # Themes
- theme_combobox = self.xml.get_object('theme_combobox')
- cell = Gtk.CellRendererText()
- theme_combobox.pack_start(cell, True)
- theme_combobox.add_attribute(cell, 'text', 0)
- self.update_theme_list()
-
- # iconset
- iconsets_list = os.listdir(os.path.join(gajim.DATA_DIR, 'iconsets'))
- if os.path.isdir(gajim.MY_ICONSETS_PATH):
- iconsets_list += os.listdir(gajim.MY_ICONSETS_PATH)
- # new model, image in 0, string in 1
- model = Gtk.ListStore(Gtk.Image, str)
- renderer_image = cell_renderer_image.CellRendererImage(0, 0)
- renderer_text = Gtk.CellRendererText()
- renderer_text.set_property('xpad', 5)
- self.iconset_combobox.pack_start(renderer_image, False)
- self.iconset_combobox.pack_start(renderer_text, True)
- self.iconset_combobox.add_attribute(renderer_text, 'text', 1)
- self.iconset_combobox.add_attribute(renderer_image, 'image', 0)
- self.iconset_combobox.set_model(model)
- l = []
- for dir in iconsets_list:
- if not os.path.isdir(os.path.join(gajim.DATA_DIR, 'iconsets', dir)) \
- and not os.path.isdir(os.path.join(gajim.MY_ICONSETS_PATH, dir)):
- continue
- if dir != '.svn' and dir != 'transports':
- l.append(dir)
- if l.count == 0:
- l.append(' ')
- for i in range(len(l)):
- preview = Gtk.Image()
- files = []
- files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
- 'online.png'))
- files.append(os.path.join(helpers.get_iconset_path(l[i]), '16x16',
- 'online.gif'))
- for file_ in files:
- if os.path.exists(file_):
- preview.set_from_file(file_)
- model.append([preview, l[i]])
- if gajim.config.get('iconset') == l[i]:
- self.iconset_combobox.set_active(i)
-
- # Use transports iconsets
- st = gajim.config.get('use_transports_iconsets')
- self.xml.get_object('transports_iconsets_checkbutton').set_active(st)
-
- # Color widgets
- self.draw_color_widgets()
-
- # Font for messages
- font = gajim.config.get('conversation_font')
- # try to set default font for the current desktop env
- fontbutton = self.xml.get_object('conversation_fontbutton')
- if font == '':
- fontbutton.set_sensitive(False)
- self.xml.get_object('default_chat_font').set_active(True)
- else:
- fontbutton.set_font_name(font)
-
- ### Personal Events tab ###
- # outgoing send chat state notifications
- st = gajim.config.get('outgoing_chat_state_notifications')
- combo = self.xml.get_object('outgoing_chat_states_combobox')
- if st == 'all':
- combo.set_active(0)
- elif st == 'composing_only':
- combo.set_active(1)
- else: # disabled
- combo.set_active(2)
-
- # displayed send chat state notifications
- st = gajim.config.get('displayed_chat_state_notifications')
- combo = self.xml.get_object('displayed_chat_states_combobox')
- if st == 'all':
- combo.set_active(0)
- elif st == 'composing_only':
- combo.set_active(1)
- else: # disabled
- combo.set_active(2)
-
-
- ### Notifications tab ###
- # On new event
- on_event_combobox = self.xml.get_object('on_event_combobox')
- if gajim.config.get('autopopup'):
- on_event_combobox.set_active(0)
- elif gajim.config.get('notify_on_new_message'):
- on_event_combobox.set_active(1)
- else:
- on_event_combobox.set_active(2)
-
- # notify on online statuses
- st = gajim.config.get('notify_on_signin')
- self.notify_on_signin_checkbutton.set_active(st)
-
- # notify on offline statuses
- st = gajim.config.get('notify_on_signout')
- self.notify_on_signout_checkbutton.set_active(st)
-
- # autopopupaway
- st = gajim.config.get('autopopupaway')
- self.auto_popup_away_checkbutton.set_active(st)
-
- # autopopup_chat_opened
- st = gajim.config.get('autopopup_chat_opened')
- self.auto_popup_chat_opened_checkbutton.set_active(st)
-
- # sounddnd
- st = gajim.config.get('sounddnd')
- self.sound_dnd_checkbutton.set_active(st)
-
- # Systray
- systray_combobox = self.xml.get_object('systray_combobox')
- if gajim.config.get('trayicon') == 'never':
- systray_combobox.set_active(0)
- elif gajim.config.get('trayicon') == 'on_event':
- systray_combobox.set_active(1)
- else:
- systray_combobox.set_active(2)
-
- # sounds
- if gajim.config.get('sounds_on'):
- self.xml.get_object('play_sounds_checkbutton').set_active(True)
- else:
- self.xml.get_object('manage_sounds_button').set_sensitive(False)
-
- # Notify user of new gmail e-mail messages,
- # make checkbox sensitive if user has a gtalk account
- frame_gmail = self.xml.get_object('frame_gmail')
- notify_gmail_checkbutton = self.xml.get_object('notify_gmail_checkbutton')
- notify_gmail_extra_checkbutton = self.xml.get_object(
- 'notify_gmail_extra_checkbutton')
-
- for account in gajim.config.get_per('accounts'):
- jid = gajim.get_jid_from_account(account)
- if gajim.get_server_from_jid(jid) in gajim.gmail_domains:
- frame_gmail.set_sensitive(True)
- st = gajim.config.get('notify_on_new_gmail_email')
- notify_gmail_checkbutton.set_active(st)
- st = gajim.config.get('notify_on_new_gmail_email_extra')
- notify_gmail_extra_checkbutton.set_active(st)
- break
-
- #### Status tab ###
- # Autoaway
- st = gajim.config.get('autoaway')
- self.auto_away_checkbutton.set_active(st)
-
- # Autoawaytime
- st = gajim.config.get('autoawaytime')
- self.auto_away_time_spinbutton.set_value(st)
- self.auto_away_time_spinbutton.set_sensitive(gajim.config.get('autoaway'))
-
- # autoaway message
- st = gajim.config.get('autoaway_message')
- self.auto_away_message_entry.set_text(st)
- self.auto_away_message_entry.set_sensitive(gajim.config.get('autoaway'))
-
- # Autoxa
- st = gajim.config.get('autoxa')
- self.auto_xa_checkbutton.set_active(st)
-
- # Autoxatime
- st = gajim.config.get('autoxatime')
- self.auto_xa_time_spinbutton.set_value(st)
- self.auto_xa_time_spinbutton.set_sensitive(gajim.config.get('autoxa'))
-
- # autoxa message
- st = gajim.config.get('autoxa_message')
- self.auto_xa_message_entry.set_text(st)
- self.auto_xa_message_entry.set_sensitive(gajim.config.get('autoxa'))
-
- from common import sleepy
- if not sleepy.SUPPORTED:
- self.xml.get_object('autoaway_table').set_sensitive(False)
-
- # ask_status when online / offline
- st = gajim.config.get('ask_online_status')
- self.xml.get_object('prompt_online_status_message_checkbutton').\
- set_active(st)
- st = gajim.config.get('ask_offline_status')
- self.xml.get_object('prompt_offline_status_message_checkbutton').\
- set_active(st)
-
- # Default Status messages
- self.default_msg_tree = self.xml.get_object('default_msg_treeview')
- self.fill_default_msg_treeview()
-
- # Status messages
- self.msg_tree = self.xml.get_object('msg_treeview')
- renderer = Gtk.CellRendererText()
- renderer.connect('edited', self.on_msg_cell_edited)
- renderer.set_property('editable', True)
- col = Gtk.TreeViewColumn('name', renderer, text=0)
- self.msg_tree.append_column(col)
- self.fill_msg_treeview()
-
- buf = self.xml.get_object('msg_textview').get_buffer()
- buf.connect('end-user-action', self.on_msg_textview_changed)
-
- ### Audio / Video tab ###
- def create_av_combobox(opt_name, device_dict, config_name=None,
- key=None):
- combobox = self.xml.get_object(opt_name + '_combobox')
- cell = Gtk.CellRendererText()
- cell.set_property('ellipsize', Pango.EllipsizeMode.END)
- cell.set_property('ellipsize-set', True)
- combobox.pack_start(cell, True)
- combobox.add_attribute(cell, 'text', 0)
- model = Gtk.ListStore(str, str)
- combobox.set_model(model)
- if config_name:
- config = gajim.config.get(config_name)
- else:
- config = gajim.config.get(opt_name + '_device')
-
- for index, (name, value) in enumerate(sorted(device_dict.items(),
- key=key)):
- model.append((name, value))
- if config == value:
- combobox.set_active(index)
-
- if HAS_GST and gajim.HAVE_FARSTREAM:
- create_av_combobox('audio_input', AudioInputManager().get_devices())
- create_av_combobox('audio_output', AudioOutputManager().get_devices(
- ))
- create_av_combobox('video_input', VideoInputManager().get_devices())
- create_av_combobox('video_output', VideoOutputManager().get_devices(
- ))
-
- create_av_combobox('video_framerate', {_('Default'): '',
- '15fps': '15/1', '10fps': '10/1', '5fps': '5/1',
- '2.5fps': '5/2'}, 'video_framerate', key=lambda x: -1 if \
- not x[1] else float(x[0][:-3]))
- create_av_combobox('video_size', {_('Default'): '',
- '800x600': '800x600', '640x480': '640x480',
- '320x240': '320x240'}, 'video_size', key=lambda x: -1 if \
- not x[1] else int(x[0][:3]))
- st = gajim.config.get('video_see_self')
- self.xml.get_object('video_see_self_checkbutton').set_active(st)
-
- else:
- for opt_name in ('audio_input', 'audio_output', 'video_input',
- 'video_output', 'video_framerate', 'video_size'):
- combobox = self.xml.get_object(opt_name + '_combobox')
- combobox.set_sensitive(False)
-
- # STUN
- cb = self.xml.get_object('stun_checkbutton')
- st = gajim.config.get('use_stun_server')
- cb.set_active(st)
-
- entry = self.xml.get_object('stun_server_entry')
- entry.set_text(gajim.config.get('stun_server'))
- if not st:
- entry.set_sensitive(False)
-
- ### Advanced tab ###
- # open links with
- if os.name == 'nt':
- applications_frame = self.xml.get_object('applications_frame')
- applications_frame.set_no_show_all(True)
- applications_frame.hide()
- else:
- self.applications_combobox = self.xml.get_object(
- 'applications_combobox')
- self.xml.get_object('custom_apps_frame').hide()
- self.xml.get_object('custom_apps_frame').set_no_show_all(True)
-
- if gajim.config.get('autodetect_browser_mailer'):
- self.applications_combobox.set_active(0)
- else:
- self.applications_combobox.set_active(1)
- self.xml.get_object('custom_apps_frame').show()
-
- self.xml.get_object('custom_browser_entry').set_text(
- gajim.config.get('custombrowser'))
- self.xml.get_object('custom_mail_client_entry').set_text(
- gajim.config.get('custommailapp'))
- self.xml.get_object('custom_file_manager_entry').set_text(
- gajim.config.get('custom_file_manager'))
-
- # log status changes of contacts
- st = gajim.config.get('log_contact_status_changes')
- self.xml.get_object('log_show_changes_checkbutton').set_active(st)
-
- # log encrypted chat sessions
- w = self.xml.get_object('log_encrypted_chats_checkbutton')
- st = self.get_per_account_option('log_encrypted_sessions')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # send os info
- w = self.xml.get_object('send_os_info_checkbutton')
- st = self.get_per_account_option('send_os_info')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # send absolute time info
- w = self.xml.get_object('send_time_info_checkbutton')
- st = self.get_per_account_option('send_time_info')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- # send idle time
- w = self.xml.get_object('send_idle_time_checkbutton')
- st = self.get_per_account_option('send_idle_time')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- self.update_proxy_list()
-
- # Ignore messages from unknown contacts
- w = self.xml.get_object('ignore_events_from_unknown_contacts_checkbutton')
- st = self.get_per_account_option('ignore_unknown_contacts')
- if st == 'mixed':
- w.set_inconsistent(True)
- else:
- w.set_active(st)
-
- self.xml.connect_signals(self)
-
- self.msg_tree.get_model().connect('row-changed',
- self.on_msg_treemodel_row_changed)
- self.msg_tree.get_model().connect('row-deleted',
- self.on_msg_treemodel_row_deleted)
- self.default_msg_tree.get_model().connect('row-changed',
- self.on_default_msg_treemodel_row_changed)
-
- self.theme_preferences = None
- self.sounds_preferences = None
-
- self.notebook.set_current_page(0)
- self.xml.get_object('close_button').grab_focus()
-
- self.window.show_all()
- gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
-
- def on_preferences_notebook_switch_page(self, widget, page, page_num):
- GLib.idle_add(self.xml.get_object('close_button').grab_focus)
-
- def on_preferences_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.hide()
-
- def get_per_account_option(self, opt):
- """
- Return the value of the option opt if it's the same in all accounts else
- returns "mixed"
- """
- if len(gajim.connections) == 0:
- # a non existant key return default value
- return gajim.config.get_per('accounts', '__default__', opt)
- val = None
- for account in gajim.connections:
- v = gajim.config.get_per('accounts', account, opt)
- if val is None:
- val = v
- elif val != v:
- return 'mixed'
- return val
-
- def on_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets=None):
- gajim.config.set(config_name, widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
-
- def on_per_account_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets=None):
- for account in gajim.connections:
- gajim.config.set_per('accounts', account, config_name,
- widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
-
- def _get_all_controls(self):
- for ctrl in gajim.interface.msg_win_mgr.get_controls():
- yield ctrl
- for account in gajim.connections:
- for ctrl in gajim.interface.minimized_controls[account].values():
- yield ctrl
-
- def _get_all_muc_controls(self):
- for ctrl in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC):
- yield ctrl
- for account in gajim.connections:
- for ctrl in gajim.interface.minimized_controls[account].values():
- yield ctrl
-
- def on_sort_by_show_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sort_by_show_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_sort_by_show_in_muc_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sort_by_show_in_muc')
- # Redraw groupchats
- for ctrl in self._get_all_muc_controls():
- ctrl.draw_roster()
-
- def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
- # Redraw groupchats (in an ugly way)
- for ctrl in self._get_all_muc_controls():
- ctrl.draw_roster()
-
- def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
- for ctrl in self._get_all_muc_controls():
- ctrl.update_ui()
-
- def on_show_pep_in_roster_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_mood_in_roster')
- self.on_checkbutton_toggled(widget, 'show_activity_in_roster')
- self.on_checkbutton_toggled(widget, 'show_tunes_in_roster')
- self.on_checkbutton_toggled(widget, 'show_location_in_roster')
- gajim.interface.roster.setup_and_draw_roster()
-
- def on_emoticons_combobox_changed(self, widget):
- active = widget.get_active()
- model = widget.get_model()
- emot_theme = model[active][0]
- if emot_theme == _('Disabled'):
- gajim.config.set('emoticons_theme', '')
- else:
- gajim.config.set('emoticons_theme', emot_theme)
-
- gajim.interface.init_emoticons(need_reload = True)
- gajim.interface.make_regexps()
- self.toggle_emoticons()
-
- def toggle_emoticons(self):
- """
- Update emoticons state in Opened Chat Windows
- """
- for ctrl in self._get_all_controls():
- ctrl.toggle_emoticons()
-
- def on_one_window_type_combo_changed(self, widget):
- active = widget.get_active()
- config_type = common.config.opt_one_window_types[active]
- gajim.config.set('one_message_window', config_type)
- gajim.interface.msg_win_mgr.reconfig()
-
- def on_show_roster_on_startup_changed(self, widget):
- active = widget.get_active()
- config_type = common.config.opt_show_roster_on_startup[active]
- gajim.config.set('show_roster_on_startup', config_type)
-
- def on_compact_view_checkbutton_toggled(self, widget):
- active = widget.get_active()
- for ctrl in self._get_all_controls():
- ctrl.chat_buttons_set_visible(active)
- gajim.config.set('compact_view', active)
-
- def on_xhtml_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ignore_incoming_xhtml')
- helpers.update_optional_features()
-
- def apply_speller(self):
- for ctrl in self._get_all_controls():
- if isinstance(ctrl, ChatControlBase):
- try:
- spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
- except (TypeError, RuntimeError, OSError):
- spell_obj = None
-
- if not spell_obj:
- ctrl.set_speller()
-
- def remove_speller(self):
- for ctrl in self._get_all_controls():
- if isinstance(ctrl, ChatControlBase):
- try:
- spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
- except (TypeError, RuntimeError):
- spell_obj = None
- if spell_obj:
- spell_obj.detach()
-
- def on_speller_checkbutton_toggled(self, widget):
- active = widget.get_active()
- gajim.config.set('use_speller', active)
- if active:
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- tv = Gtk.TextView()
- try:
- gtkspell.Spell(tv, lang)
- except (TypeError, RuntimeError, OSError):
- dialogs.ErrorDialog(
- _('Dictionary for lang %s not available') % lang,
- _('You have to install %s dictionary to use spellchecking, or '
- 'choose another language by setting the speller_language option.'
- ) % lang)
- gajim.config.set('use_speller', False)
- widget.set_active(False)
- else:
- gajim.config.set('speller_language', lang)
- self.apply_speller()
- else:
- self.remove_speller()
-
- def on_positive_184_ack_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'positive_184_ack')
-
- def on_show_avatar_in_tabs_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'show_avatar_in_tabs')
-
- def on_theme_combobox_changed(self, widget):
- model = widget.get_model()
- active = widget.get_active()
- config_theme = model[active][0].replace(' ', '_')
-
- gajim.config.set('roster_theme', config_theme)
-
- # begin repainting themed widgets throughout
- gajim.interface.roster.repaint_themed_widgets()
- gajim.interface.roster.change_roster_style(None)
- gtkgui_helpers.load_css()
-
- def update_theme_list(self):
- theme_combobox = self.xml.get_object('theme_combobox')
- model = Gtk.ListStore(str)
- theme_combobox.set_model(model)
- i = 0
- for config_theme in gajim.config.get_per('themes'):
- theme = config_theme.replace('_', ' ')
- model.append([theme])
- if gajim.config.get('roster_theme') == config_theme:
- theme_combobox.set_active(i)
- i += 1
-
- def on_manage_theme_button_clicked(self, widget):
- if self.theme_preferences is None:
- self.theme_preferences = dialogs.GajimThemesWindow()
- else:
- self.theme_preferences.window.present()
- self.theme_preferences.select_active_theme()
-
- def on_iconset_combobox_changed(self, widget):
- model = widget.get_model()
- active = widget.get_active()
- icon_string = model[active][1]
- gajim.config.set('iconset', icon_string)
- gtkgui_helpers.reload_jabber_state_images()
-
- def on_transports_iconsets_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_transports_iconsets')
- gtkgui_helpers.reload_jabber_state_images()
-
- def on_outgoing_chat_states_combobox_changed(self, widget):
- active = widget.get_active()
- old_value = gajim.config.get('outgoing_chat_state_notifications')
- if active == 0: # all
- gajim.config.set('outgoing_chat_state_notifications', 'all')
- elif active == 1: # only composing
- gajim.config.set('outgoing_chat_state_notifications', 'composing_only')
- else: # disabled
- gajim.config.set('outgoing_chat_state_notifications', 'disabled')
- new_value = gajim.config.get('outgoing_chat_state_notifications')
- if 'disabled' in (old_value, new_value):
- # we changed from disabled to sth else or vice versa
- helpers.update_optional_features()
-
- def on_displayed_chat_states_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0: # all
- gajim.config.set('displayed_chat_state_notifications', 'all')
- elif active == 1: # only composing
- gajim.config.set('displayed_chat_state_notifications',
- 'composing_only')
- else: # disabled
- gajim.config.set('displayed_chat_state_notifications', 'disabled')
-
- def on_ignore_events_from_unknown_contacts_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'ignore_unknown_contacts')
-
- def on_on_event_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0:
- gajim.config.set('autopopup', True)
- gajim.config.set('notify_on_new_message', False)
- elif active == 1:
- gajim.config.set('autopopup', False)
- gajim.config.set('notify_on_new_message', True)
- else:
- gajim.config.set('autopopup', False)
- gajim.config.set('notify_on_new_message', False)
-
- def on_notify_on_signin_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_signin')
-
- def on_notify_on_signout_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_signout')
-
- def on_auto_popup_away_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autopopupaway')
-
- def on_auto_popup_chat_opened_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autopopup_chat_opened')
-
- def on_sound_dnd_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sounddnd')
-
- def on_systray_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 0:
- gajim.config.set('trayicon', 'never')
- gajim.interface.systray_enabled = False
- gajim.interface.systray.hide_icon()
- elif active == 1:
- gajim.config.set('trayicon', 'on_event')
- gajim.interface.systray_enabled = True
- gajim.interface.systray.show_icon()
- else:
- gajim.config.set('trayicon', 'always')
- gajim.interface.systray_enabled = True
- gajim.interface.systray.show_icon()
-
- def on_play_sounds_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'sounds_on',
- [self.xml.get_object('manage_sounds_button')])
-
- def on_manage_sounds_button_clicked(self, widget):
- if self.sounds_preferences is None:
- self.sounds_preferences = ManageSoundsWindow()
- else:
- self.sounds_preferences.window.present()
-
- def update_text_tags(self):
- """
- Update color tags in opened chat windows
- """
- for ctrl in self._get_all_controls():
- ctrl.update_tags()
-
- def on_preference_widget_color_set(self, widget, text):
- color = widget.get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- gajim.config.set(text, color_string)
- self.update_text_tags()
-
- def on_preference_widget_font_set(self, widget, text):
- if widget:
- font = widget.get_font_name()
- else:
- font = ''
- gajim.config.set(text, font)
- gtkgui_helpers.load_css()
-
- def on_incoming_nick_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'inmsgcolor')
-
- def on_outgoing_nick_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'outmsgcolor')
-
- def on_incoming_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'inmsgtxtcolor')
-
- def on_outgoing_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'outmsgtxtcolor')
-
- def on_url_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'urlmsgcolor')
-
- def on_status_msg_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'statusmsgcolor')
-
- def on_muc_highlight_colorbutton_color_set(self, widget):
- self.on_preference_widget_color_set(widget, 'markedmsgcolor')
-
- def on_conversation_fontbutton_font_set(self, widget):
- self.on_preference_widget_font_set(widget, 'conversation_font')
-
- def on_default_chat_font_toggled(self, widget):
- font_widget = self.xml.get_object('conversation_fontbutton')
- if widget.get_active():
- font_widget.set_sensitive(False)
- font_widget = None
- else:
- font_widget.set_sensitive(True)
- self.on_preference_widget_font_set(font_widget, 'conversation_font')
-
- def draw_color_widgets(self):
- col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
- 'outmsgcolor': 'outgoing_nick_colorbutton',
- 'inmsgtxtcolor': ['incoming_msg_colorbutton',
- 'incoming_msg_checkbutton'],
- 'outmsgtxtcolor': ['outgoing_msg_colorbutton',
- 'outgoing_msg_checkbutton'],
- 'statusmsgcolor': 'status_msg_colorbutton',
- 'urlmsgcolor': 'url_msg_colorbutton',
- 'markedmsgcolor': 'muc_highlight_colorbutton'}
- for c in col_to_widget:
- col = gajim.config.get(c)
- if col:
- if isinstance(col_to_widget[c], list):
- rgba = Gdk.RGBA()
- rgba.parse(col)
- self.xml.get_object(col_to_widget[c][0]).set_rgba(rgba)
- self.xml.get_object(col_to_widget[c][0]).set_sensitive(True)
- self.xml.get_object(col_to_widget[c][1]).set_active(True)
- else:
- rgba = Gdk.RGBA()
- rgba.parse(col)
- self.xml.get_object(col_to_widget[c]).set_rgba(rgba)
- else:
- rgba = Gdk.RGBA()
- rgba.parse('#000000')
- if isinstance(col_to_widget[c], list):
- self.xml.get_object(col_to_widget[c][0]).set_rgba(rgba)
- self.xml.get_object(col_to_widget[c][0]).set_sensitive(False)
- self.xml.get_object(col_to_widget[c][1]).set_active(False)
- else:
- self.xml.get_object(col_to_widget[c]).set_rgba(rgba)
-
- def on_reset_colors_button_clicked(self, widget):
- col_to_widget = {'inmsgcolor': 'incoming_nick_colorbutton',
- 'outmsgcolor': 'outgoing_nick_colorbutton',
- 'inmsgtxtcolor': 'incoming_msg_colorbutton',
- 'outmsgtxtcolor': 'outgoing_msg_colorbutton',
- 'statusmsgcolor': 'status_msg_colorbutton',
- 'urlmsgcolor': 'url_msg_colorbutton',
- 'markedmsgcolor': 'muc_highlight_colorbutton'}
- for c in col_to_widget:
- gajim.config.set(c, gajim.interface.default_colors[c])
- self.draw_color_widgets()
-
- self.update_text_tags()
-
- def _set_color(self, state, widget_name, option):
- """
- Set color value in prefs and update the UI
- """
- if state:
- color = self.xml.get_object(widget_name).get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- else:
- color_string = ''
- gajim.config.set(option, color_string)
-
- def on_incoming_msg_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.xml.get_object('incoming_msg_colorbutton').set_sensitive(state)
- self._set_color(state, 'incoming_msg_colorbutton', 'inmsgtxtcolor')
-
- def on_outgoing_msg_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.xml.get_object('outgoing_msg_colorbutton').set_sensitive(state)
- self._set_color(state, 'outgoing_msg_colorbutton', 'outmsgtxtcolor')
-
- def on_auto_away_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autoaway',
- [self.auto_away_time_spinbutton, self.auto_away_message_entry])
-
- def on_auto_away_time_spinbutton_value_changed(self, widget):
- aat = widget.get_value_as_int()
- gajim.config.set('autoawaytime', aat)
- gajim.interface.sleeper = common.sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60,
- gajim.config.get('autoxatime') * 60)
-
- def on_auto_away_message_entry_changed(self, widget):
- gajim.config.set('autoaway_message', widget.get_text())
-
- def on_auto_xa_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'autoxa',
- [self.auto_xa_time_spinbutton, self.auto_xa_message_entry])
-
- def on_auto_xa_time_spinbutton_value_changed(self, widget):
- axt = widget.get_value_as_int()
- gajim.config.set('autoxatime', axt)
- gajim.interface.sleeper = common.sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60,
- gajim.config.get('autoxatime') * 60)
-
- def on_auto_xa_message_entry_changed(self, widget):
- gajim.config.set('autoxa_message', widget.get_text())
-
- def on_prompt_online_status_message_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ask_online_status')
-
- def on_prompt_offline_status_message_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'ask_offline_status')
-
- def fill_default_msg_treeview(self):
- model = self.default_msg_tree.get_model()
- model.clear()
- status = []
- for status_ in gajim.config.get_per('defaultstatusmsg'):
- status.append(status_)
- status.sort()
- for status_ in status:
- msg = gajim.config.get_per('defaultstatusmsg', status_, 'message')
- msg = helpers.from_one_line(msg)
- enabled = gajim.config.get_per('defaultstatusmsg', status_, 'enabled')
- iter_ = model.append()
- uf_show = helpers.get_uf_show(status_)
- model.set(iter_, 0, status_, 1, uf_show, 2, msg, 3, enabled)
-
- def on_default_msg_cell_edited(self, cell, row, new_text):
- model = self.default_msg_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- model.set_value(iter_, 2, new_text)
-
- def default_msg_toggled_cb(self, cell, path):
- model = self.default_msg_tree.get_model()
- model[path][3] = not model[path][3]
-
- def on_default_msg_treemodel_row_changed(self, model, path, iter_):
- status = model[iter_][0]
- message = model[iter_][2]
- message = helpers.to_one_line(message)
- gajim.config.set_per('defaultstatusmsg', status, 'enabled',
- model[iter_][3])
- gajim.config.set_per('defaultstatusmsg', status, 'message', message)
-
- def on_default_status_expander_activate(self, expander):
- eventbox = self.xml.get_object('default_status_eventbox')
- vbox = self.xml.get_object('status_vbox')
- vbox.set_child_packing(eventbox, not expander.get_expanded(), True, 0,
- Gtk.PACK_START)
-
- def save_status_messages(self, model):
- for msg in gajim.config.get_per('statusmsg'):
- gajim.config.del_per('statusmsg', msg)
- iter_ = model.get_iter_first()
- while iter_:
- val = model[iter_][0]
- if model[iter_][1]: # we have a preset message
- if not val: # no title, use message text for title
- val = model[iter_][1]
- gajim.config.add_per('statusmsg', val)
- msg = helpers.to_one_line(model[iter_][1])
- gajim.config.set_per('statusmsg', val, 'message', msg)
- i = 2
- # store mood / activity
- for subname in ('activity', 'subactivity', 'activity_text',
- 'mood', 'mood_text'):
- val2 = model[iter_][i]
- if not val2:
- val2 = ''
- gajim.config.set_per('statusmsg', val, subname, val2)
- i += 1
- iter_ = model.iter_next(iter_)
-
- def on_msg_treemodel_row_changed(self, model, path, iter_):
- self.save_status_messages(model)
-
- def on_msg_treemodel_row_deleted(self, model, path):
- self.save_status_messages(model)
-
- def on_av_combobox_changed(self, combobox, config_name):
- model = combobox.get_model()
- active = combobox.get_active()
- device = model[active][1]
- gajim.config.set(config_name, device)
-
- def on_audio_input_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'audio_input_device')
-
- def on_audio_output_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'audio_output_device')
-
- def on_video_input_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_input_device')
-
- def on_video_output_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_output_device')
-
- def on_video_framerate_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_framerate')
-
- def on_video_size_combobox_changed(self, widget):
- self.on_av_combobox_changed(widget, 'video_size')
-
- def on_video_see_self_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'video_see_self')
-
- def on_stun_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_stun_server',
- [self.xml.get_object('stun_server_entry')])
-
- def stun_server_entry_changed(self, widget):
- gajim.config.set('stun_server', widget.get_text())
-
- def on_applications_combobox_changed(self, widget):
- if widget.get_active() == 0:
- gajim.config.set('autodetect_browser_mailer', True)
- self.xml.get_object('custom_apps_frame').hide()
- elif widget.get_active() == 1:
- gajim.config.set('autodetect_browser_mailer', False)
- self.xml.get_object('custom_apps_frame').show()
-
- def on_custom_browser_entry_changed(self, widget):
- gajim.config.set('custombrowser', widget.get_text())
-
- def on_custom_mail_client_entry_changed(self, widget):
- gajim.config.set('custommailapp', widget.get_text())
-
- def on_custom_file_manager_entry_changed(self, widget):
- gajim.config.set('custom_file_manager', widget.get_text())
-
- def on_log_show_changes_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'log_contact_status_changes')
-
- def on_log_encrypted_chats_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'log_encrypted_sessions')
-
- def on_send_os_info_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'send_os_info')
-
- def on_send_time_info_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'send_time_info')
-
- def on_send_idle_time_checkbutton_toggled(self, widget):
- widget.set_inconsistent(False)
- self.on_per_account_checkbutton_toggled(widget, 'send_idle_time')
-
- def on_notify_gmail_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email')
-
- def on_notify_gmail_extra_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email_extra')
-
- def fill_msg_treeview(self):
- self.xml.get_object('delete_msg_button').set_sensitive(False)
- model = self.msg_tree.get_model()
- model.clear()
- preset_status = []
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name.startswith('_last_'):
- continue
- preset_status.append(msg_name)
- preset_status.sort()
- for msg_name in preset_status:
- msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
- msg_text = helpers.from_one_line(msg_text)
- activity = gajim.config.get_per('statusmsg', msg_name, 'activity')
- subactivity = gajim.config.get_per('statusmsg', msg_name,
- 'subactivity')
- activity_text = gajim.config.get_per('statusmsg', msg_name,
- 'activity_text')
- mood = gajim.config.get_per('statusmsg', msg_name, 'mood')
- mood_text = gajim.config.get_per('statusmsg', msg_name, 'mood_text')
- iter_ = model.append()
- model.set(iter_, 0, msg_name, 1, msg_text, 2, activity, 3,
- subactivity, 4, activity_text, 5, mood, 6, mood_text)
-
- def on_msg_cell_edited(self, cell, row, new_text):
- model = self.msg_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- model.set_value(iter_, 0, new_text)
-
- def on_msg_treeview_cursor_changed(self, widget, data = None):
- sel = self.msg_tree.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- self.xml.get_object('delete_msg_button').set_sensitive(True)
- buf = self.xml.get_object('msg_textview').get_buffer()
- msg = model[iter_][1]
- buf.set_text(msg)
-
- def on_new_msg_button_clicked(self, widget, data = None):
- model = self.msg_tree.get_model()
- iter_ = model.append()
- model.set(iter_, 0, _('status message title'), 1,
- _('status message text'))
- self.msg_tree.set_cursor(model.get_path(iter_))
-
- def on_delete_msg_button_clicked(self, widget, data = None):
- sel = self.msg_tree.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- buf = self.xml.get_object('msg_textview').get_buffer()
- model.remove(iter_)
- buf.set_text('')
- self.xml.get_object('delete_msg_button').set_sensitive(False)
-
- def on_msg_textview_changed(self, widget, data = None):
- sel = self.msg_tree.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- buf = self.xml.get_object('msg_textview').get_buffer()
- first_iter, end_iter = buf.get_bounds()
- model.set_value(iter_, 1, buf.get_text(first_iter, end_iter, True))
-
- def on_msg_treeview_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Delete:
- self.on_delete_msg_button_clicked(widget)
-
- def on_proxies_combobox_changed(self, widget):
- active = widget.get_active()
- proxy = widget.get_model()[active][0]
- if proxy == _('None'):
- proxy = ''
-
- gajim.config.set('global_proxy', proxy)
-
- def on_manage_proxies_button_clicked(self, widget):
- if 'manage_proxies' in gajim.interface.instances:
- gajim.interface.instances['manage_proxies'].window.present()
- else:
- gajim.interface.instances['manage_proxies'] = ManageProxiesWindow(
- self.window)
-
- def update_proxy_list(self):
- our_proxy = gajim.config.get('global_proxy')
- if not our_proxy:
- our_proxy = _('None')
- proxy_combobox = self.xml.get_object('proxies_combobox')
- model = proxy_combobox.get_model()
- model.clear()
- l = gajim.config.get_per('proxies')
- l.insert(0, _('None'))
- for i in range(len(l)):
- model.append([l[i]])
- if our_proxy == l[i]:
- proxy_combobox.set_active(i)
-
- def on_open_advanced_editor_button_clicked(self, widget, data = None):
- if 'advanced_config' in gajim.interface.instances:
- gajim.interface.instances['advanced_config'].window.present()
- else:
- gajim.interface.instances['advanced_config'] = \
- dialogs.AdvancedConfigurationWindow()
-
-#---------- ManageProxiesWindow class -------------#
-class ManageProxiesWindow:
- def __init__(self, transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_proxies_window.ui')
- self.window = self.xml.get_object('manage_proxies_window')
- self.window.set_transient_for(transient_for)
- self.proxies_treeview = self.xml.get_object('proxies_treeview')
- self.proxyname_entry = self.xml.get_object('proxyname_entry')
- self.proxytype_combobox = self.xml.get_object('proxytype_combobox')
-
- self.init_list()
- self.block_signal = False
- self.xml.connect_signals(self)
- self.window.show_all()
- # hide the BOSH fields by default
- self.show_bosh_fields()
-
- def show_bosh_fields(self, show=True):
- if show:
- self.xml.get_object('boshuri_entry').show()
- self.xml.get_object('boshuri_label').show()
- self.xml.get_object('boshuseproxy_checkbutton').show()
- else:
- cb = self.xml.get_object('boshuseproxy_checkbutton')
- cb.hide()
- cb.set_active(True)
- self.on_boshuseproxy_checkbutton_toggled(cb)
- self.xml.get_object('boshuri_entry').hide()
- self.xml.get_object('boshuri_label').hide()
-
-
- def fill_proxies_treeview(self):
- model = self.proxies_treeview.get_model()
- model.clear()
- iter_ = model.append()
- model.set(iter_, 0, _('None'))
- for p in gajim.config.get_per('proxies'):
- iter_ = model.append()
- model.set(iter_, 0, p)
-
- def init_list(self):
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.proxytype_combobox.set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- model = Gtk.ListStore(str)
- self.proxies_treeview.set_model(model)
- col = Gtk.TreeViewColumn('Proxies')
- self.proxies_treeview.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 0)
- self.fill_proxies_treeview()
- self.xml.get_object('proxytype_combobox').set_active(0)
-
- def on_manage_proxies_window_destroy(self, widget):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].\
- update_proxy_list()
- del gajim.interface.instances['manage_proxies']
-
- def on_add_proxy_button_clicked(self, widget):
- model = self.proxies_treeview.get_model()
- proxies = gajim.config.get_per('proxies')
- i = 1
- while ('proxy' + str(i)) in proxies:
- i += 1
- iter_ = model.append()
- model.set(iter_, 0, 'proxy' + str(i))
- gajim.config.add_per('proxies', 'proxy' + str(i))
- self.proxies_treeview.set_cursor(model.get_path(iter_))
-
- def on_remove_proxy_button_clicked(self, widget):
- sel = self.proxies_treeview.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- proxy = model[iter_][0]
- model.remove(iter_)
- gajim.config.del_per('proxies', proxy)
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.block_signal = True
- self.on_proxies_treeview_cursor_changed(self.proxies_treeview)
- self.block_signal = False
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_useauth_checkbutton_toggled(self, widget):
- if self.block_signal:
- return
- act = widget.get_active()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'useauth', act)
- self.xml.get_object('proxyuser_entry').set_sensitive(act)
- self.xml.get_object('proxypass_entry').set_sensitive(act)
-
- def on_boshuseproxy_checkbutton_toggled(self, widget):
- if self.block_signal:
- return
- act = widget.get_active()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'bosh_useproxy', act)
- self.xml.get_object('proxyhost_entry').set_sensitive(act)
- self.xml.get_object('proxyport_entry').set_sensitive(act)
-
- def on_proxies_treeview_cursor_changed(self, widget):
- #FIXME: check if off proxy settings are correct (see
- # http://trac.gajim.org/changeset/1921#file2 line 1221
- proxyhost_entry = self.xml.get_object('proxyhost_entry')
- proxyport_entry = self.xml.get_object('proxyport_entry')
- proxyuser_entry = self.xml.get_object('proxyuser_entry')
- proxypass_entry = self.xml.get_object('proxypass_entry')
- boshuri_entry = self.xml.get_object('boshuri_entry')
- useauth_checkbutton = self.xml.get_object('useauth_checkbutton')
- boshuseproxy_checkbutton = self.xml.get_object('boshuseproxy_checkbutton')
- self.block_signal = True
- proxyhost_entry.set_text('')
- proxyport_entry.set_text('')
- proxyuser_entry.set_text('')
- proxypass_entry.set_text('')
- boshuri_entry.set_text('')
-
- #boshuseproxy_checkbutton.set_active(False)
- #self.on_boshuseproxy_checkbutton_toggled(boshuseproxy_checkbutton)
-
- #useauth_checkbutton.set_active(False)
- #self.on_useauth_checkbutton_toggled(useauth_checkbutton)
-
- sel = widget.get_selection()
- if sel:
- (model, iter_) = sel.get_selected()
- else:
- iter_ = None
- if not iter_:
- self.xml.get_object('proxyname_entry').set_text('')
- self.xml.get_object('proxytype_combobox').set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- self.block_signal = False
- return
-
- proxy = model[iter_][0]
- self.xml.get_object('proxyname_entry').set_text(proxy)
-
- if proxy == _('None'): # special proxy None
- self.show_bosh_fields(False)
- self.proxyname_entry.set_editable(False)
- self.xml.get_object('remove_proxy_button').set_sensitive(False)
- self.xml.get_object('proxytype_combobox').set_sensitive(False)
- self.xml.get_object('proxy_table').set_sensitive(False)
- else:
- proxytype = gajim.config.get_per('proxies', proxy, 'type')
-
- self.show_bosh_fields(proxytype=='bosh')
-
- self.proxyname_entry.set_editable(True)
- self.xml.get_object('remove_proxy_button').set_sensitive(True)
- self.xml.get_object('proxytype_combobox').set_sensitive(True)
- self.xml.get_object('proxy_table').set_sensitive(True)
- proxyhost_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'host'))
- proxyport_entry.set_text(str(gajim.config.get_per('proxies',
- proxy, 'port')))
- proxyuser_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'user'))
- proxypass_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'pass'))
- boshuri_entry.set_text(gajim.config.get_per('proxies', proxy,
- 'bosh_uri'))
- types = ['http', 'socks5', 'bosh']
- self.proxytype_combobox.set_active(types.index(proxytype))
- boshuseproxy_checkbutton.set_active(
- gajim.config.get_per('proxies', proxy, 'bosh_useproxy'))
- useauth_checkbutton.set_active(
- gajim.config.get_per('proxies', proxy, 'useauth'))
- self.block_signal = False
-
- def on_proxies_treeview_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Delete:
- self.on_remove_proxy_button_clicked(widget)
-
- def on_proxyname_entry_changed(self, widget):
- if self.block_signal:
- return
- sel = self.proxies_treeview.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- old_name = model.get_value(iter_, 0)
- new_name = widget.get_text()
- if new_name == '':
- return
- if new_name == old_name:
- return
- config = gajim.config.get_per('proxies', old_name)
- gajim.config.del_per('proxies', old_name)
- gajim.config.add_per('proxies', new_name)
- for option in config:
- gajim.config.set_per('proxies', new_name, option, config[option])
- model.set_value(iter_, 0, new_name)
-
- def on_proxytype_combobox_changed(self, widget):
- if self.block_signal:
- return
- types = ['http', 'socks5', 'bosh']
- type_ = self.proxytype_combobox.get_active()
- self.show_bosh_fields(types[type_]=='bosh')
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'type', types[type_])
-
- def on_proxyhost_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'host', value)
-
- def on_proxyport_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'port', value)
-
- def on_proxyuser_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'user', value)
-
- def on_boshuri_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'bosh_uri', value)
-
- def on_proxypass_entry_changed(self, widget):
- if self.block_signal:
- return
- value = widget.get_text()
- proxy = self.proxyname_entry.get_text()
- gajim.config.set_per('proxies', proxy, 'pass', value)
-
-
-#---------- AccountsWindow class -------------#
-class AccountsWindow:
- """
- Class for accounts window: list of accounts
- """
-
- def on_accounts_window_destroy(self, widget):
- del gajim.interface.instances['accounts']
-
- def on_close_button_clicked(self, widget):
- self.check_resend_relog()
- self.window.destroy()
-
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
- self.window = self.xml.get_object('accounts_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.accounts_treeview = self.xml.get_object('accounts_treeview')
- self.remove_button = self.xml.get_object('remove_button')
- self.rename_button = self.xml.get_object('rename_button')
- self.change_password_button = self.xml.get_object('change_password_button1')
- path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input')
- img = self.xml.get_object('rename_image')
- img.set_from_file(path_to_kbd_input_img)
- self.notebook = self.xml.get_object('notebook')
- # Name
- model = Gtk.ListStore(str)
- self.accounts_treeview.set_model(model)
- # column
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn()
- col.set_title(_('Name'))
- col.pack_start(renderer, False)
- col.add_attribute(renderer, 'text', 0)
- self.accounts_treeview.insert_column(col, -1)
-
- self.current_account = None
- # When we fill info, we don't want to handle the changed signals
- self.ignore_events = False
- self.need_relogin = False
- self.resend_presence = False
-
- self.update_proxy_list()
- self.xml.connect_signals(self)
- self.init_accounts()
- self.window.show_all()
-
- # Merge accounts
- st = gajim.config.get('mergeaccounts')
- checkbutton = self.xml.get_object('merge_checkbutton')
- checkbutton.set_active(st)
- # prevent roster redraws by connecting the signal after button state is
- # set
- checkbutton.connect('toggled', self.on_merge_checkbutton_toggled)
-
- self.avahi_available = True
- try:
- import avahi
- except ImportError:
- self.avahi_available = False
-
- self.xml.get_object('close_button').grab_focus()
-
- def on_accounts_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.check_resend_relog()
- self.window.destroy()
-
- def select_account(self, account):
- model = self.accounts_treeview.get_model()
- iter_ = model.get_iter_first()
- while iter_:
- acct = model[iter_][0]
- if account == acct:
- self.accounts_treeview.set_cursor(model.get_path(iter_))
- return
- iter_ = model.iter_next(iter_)
-
- def init_accounts(self):
- """
- Initialize listStore with existing accounts
- """
- self.remove_button.set_sensitive(False)
- self.rename_button.set_sensitive(False)
- self.change_password_button.set_sensitive(False)
- self.current_account = None
- model = self.accounts_treeview.get_model()
- model.clear()
- list_ = gajim.config.get_per('accounts')
- list_.sort()
- for account in list_:
- iter_ = model.append()
- model.set(iter_, 0, account)
-
- self.selection = self.accounts_treeview.get_selection()
- self.selection.select_iter(model.get_iter_first())
-
- def resend(self, account):
- if not account in gajim.connections:
- return
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.connections[account].status
- gajim.connections[account].change_status(show, status)
-
- def check_resend_relog(self):
- if self.need_relogin and self.current_account == \
- gajim.ZEROCONF_ACC_NAME:
- if gajim.ZEROCONF_ACC_NAME in gajim.connections:
- gajim.connections[gajim.ZEROCONF_ACC_NAME].update_details()
- return
-
- elif self.need_relogin and self.current_account and \
- gajim.connections[self.current_account].connected > 0:
- def login(account, show_before, status_before):
- """
- Login with previous status
- """
- # first make sure connection is really closed,
- # 0.5 may not be enough
- gajim.connections[account].disconnect(True)
- gajim.interface.roster.send_status(account, show_before,
- status_before)
-
- def relog(account):
- self.dialog.destroy()
- show_before = gajim.SHOW_LIST[gajim.connections[account].\
- connected]
- status_before = gajim.connections[account].status
- gajim.interface.roster.send_status(account, 'offline',
- _('Be right back.'))
- GLib.timeout_add(500, login, account, show_before,
- status_before)
-
- def on_yes(checked, account):
- relog(account)
- def on_no(account):
- if self.resend_presence:
- self.resend(account)
- if self.current_account in gajim.connections:
- self.dialog = dialogs.YesNoDialog(_('Relogin now?'),
- _('If you want all the changes to apply instantly, '
- 'you must relogin.'), on_response_yes=(on_yes,
- self.current_account), on_response_no=(on_no,
- self.current_account))
- elif self.resend_presence:
- self.resend(self.current_account)
-
- self.need_relogin = False
- self.resend_presence = False
-
- def on_accounts_treeview_cursor_changed(self, widget):
- """
- Activate modify buttons when a row is selected, update accounts info
- """
- sel = self.accounts_treeview.get_selection()
- if sel:
- (model, iter_) = sel.get_selected()
- if iter_:
- account = model[iter_][0]
- else:
- account = None
- else:
- iter_ = account = None
- if self.current_account and self.current_account == account:
- # We're comming back to our current account, no need to update
- # widgets
- return
- # Save config for previous account if needed cause focus_out event is
- # called after the changed event
- if self.current_account and self.window.get_focus():
- focused_widget = self.window.get_focus()
- focused_widget_name = focused_widget.get_name()
- if focused_widget_name in ('jid_entry1', 'resource_entry1',
- 'custom_port_entry', 'cert_entry1'):
- if focused_widget_name == 'jid_entry1':
- func = self.on_jid_entry1_focus_out_event
- elif focused_widget_name == 'resource_entry1':
- func = self.on_resource_entry1_focus_out_event
- elif focused_widget_name == 'custom_port_entry':
- func = self.on_custom_port_entry_focus_out_event
- elif focused_widget_name == 'cert_entry1':
- func = self.on_cert_entry1_focus_out_event
- if func(focused_widget, None):
- # Error detected in entry, don't change account,
- # re-put cursor on previous row
- self.select_account(self.current_account)
- return True
- self.window.set_focus(widget)
-
- self.check_resend_relog()
-
- if account:
- self.remove_button.set_sensitive(True)
- self.rename_button.set_sensitive(True)
-
- if (account != gajim.ZEROCONF_ACC_NAME and
- account in gajim.connections):
- self.change_password_button.set_sensitive(
- gajim.connections[account].register_supported)
-
- else:
- self.remove_button.set_sensitive(False)
- self.rename_button.set_sensitive(False)
- self.change_password_button.set_sensitive(False)
- if iter_:
- self.current_account = account
- if account == gajim.ZEROCONF_ACC_NAME:
- self.remove_button.set_sensitive(False)
- self.init_account()
- self.update_proxy_list()
-
- def on_browse_for_client_cert_button_clicked(self, widget, data=None):
- def on_ok(widget, path_to_clientcert_file):
- self.dialog.destroy()
- if not path_to_clientcert_file:
- return
- self.xml.get_object('cert_entry1').set_text(path_to_clientcert_file)
- gajim.config.set_per('accounts', self.current_account,
- 'client_cert', path_to_clientcert_file)
-
- def on_cancel(widget):
- self.dialog.destroy()
-
- path_to_clientcert_file = self.xml.get_object('cert_entry1').get_text()
- self.dialog = dialogs.ClientCertChooserDialog(path_to_clientcert_file,
- on_ok, on_cancel)
-
- def update_proxy_list(self):
- if self.current_account:
- our_proxy = gajim.config.get_per('accounts', self.current_account,
- 'proxy')
- else:
- our_proxy = ''
-
- if not our_proxy:
- our_proxy = _('None')
- proxy_combobox = self.xml.get_object('proxies_combobox1')
- model = Gtk.ListStore(str)
- proxy_combobox.set_model(model)
- l = gajim.config.get_per('proxies')
- l.insert(0, _('None'))
- for i in range(len(l)):
- model.append([l[i]])
- if our_proxy == l[i]:
- proxy_combobox.set_active(i)
-
- def init_account(self):
- if not self.current_account:
- self.notebook.set_current_page(0)
- return
- if gajim.config.get_per('accounts', self.current_account,
- 'is_zeroconf'):
- self.ignore_events = True
- self.init_zeroconf_account()
- self.ignore_events = False
- self.notebook.set_current_page(2)
- return
- self.ignore_events = True
- self.init_normal_account()
- self.ignore_events = False
- self.notebook.set_current_page(1)
-
- def init_zeroconf_account(self):
- active = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'active')
- self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active)
- if not gajim.HAVE_ZEROCONF:
- self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive(
- False)
- self.xml.get_object('zeroconf_notebook').set_sensitive(active)
- # General tab
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'autoconnect')
- self.xml.get_object('autoconnect_checkbutton2').set_active(st)
-
- list_no_log_for = gajim.config.get_per('accounts',
- gajim.ZEROCONF_ACC_NAME, 'no_log_for').split()
- if gajim.ZEROCONF_ACC_NAME in list_no_log_for:
- self.xml.get_object('log_history_checkbutton2').set_active(0)
- else:
- self.xml.get_object('log_history_checkbutton2').set_active(1)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'sync_with_global_status')
- self.xml.get_object('sync_with_global_status_checkbutton2').set_active(
- st)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'use_custom_host')
- self.xml.get_object('custom_port_checkbutton2').set_active(st)
- self.xml.get_object('custom_port_entry2').set_sensitive(st)
-
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port')
- if not st:
- gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'custom_port', '5298')
- st = '5298'
- self.xml.get_object('custom_port_entry2').set_text(str(st))
-
- # Personal tab
- gpg_key_label = self.xml.get_object('gpg_key_label2')
- if gajim.ZEROCONF_ACC_NAME in gajim.connections and \
- gajim.connections[gajim.ZEROCONF_ACC_NAME].gpg:
- self.xml.get_object('gpg_choose_button2').set_sensitive(True)
- self.init_account_gpg()
- else:
- gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
- self.xml.get_object('gpg_choose_button2').set_sensitive(False)
-
- for opt in ('first_name', 'last_name', 'jabber_id', 'email'):
- st = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'zeroconf_' + opt)
- self.xml.get_object(opt + '_entry2').set_text(st)
-
- def init_account_gpg(self):
- account = self.current_account
- keyid = gajim.config.get_per('accounts', account, 'keyid')
- keyname = gajim.config.get_per('accounts', account, 'keyname')
- use_gpg_agent = gajim.config.get('use_gpg_agent')
-
- if account == gajim.ZEROCONF_ACC_NAME:
- widget_name_add = '2'
- else:
- widget_name_add = '1'
-
- gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add)
- gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add)
- use_gpg_agent_checkbutton = self.xml.get_object(
- 'use_gpg_agent_checkbutton' + widget_name_add)
-
- if not keyid:
- use_gpg_agent_checkbutton.set_sensitive(False)
- gpg_key_label.set_text(_('No key selected'))
- gpg_name_label.set_text('')
- return
-
- gpg_key_label.set_text(keyid)
- gpg_name_label.set_text(keyname)
- use_gpg_agent_checkbutton.set_sensitive(True)
- use_gpg_agent_checkbutton.set_active(use_gpg_agent)
-
- def draw_normal_jid(self):
- account = self.current_account
- self.ignore_events = True
- active = gajim.config.get_per('accounts', account, 'active')
- self.xml.get_object('enable_checkbutton1').set_active(active)
- self.xml.get_object('normal_notebook1').set_sensitive(active)
- if gajim.config.get_per('accounts', account, 'anonymous_auth'):
- self.xml.get_object('anonymous_checkbutton1').set_active(True)
- self.xml.get_object('jid_label1').set_text(_('Server:'))
- save_password = self.xml.get_object('save_password_checkbutton1')
- save_password.set_active(False)
- save_password.set_sensitive(False)
- password_entry = self.xml.get_object('password_entry1')
- password_entry.set_text('')
- password_entry.set_sensitive(False)
- jid = gajim.config.get_per('accounts', account, 'hostname')
- else:
- self.xml.get_object('anonymous_checkbutton1').set_active(False)
- self.xml.get_object('jid_label1').set_text(_('JID:'))
- savepass = gajim.config.get_per('accounts', account, 'savepass')
- save_password = self.xml.get_object('save_password_checkbutton1')
- save_password.set_sensitive(True)
- save_password.set_active(savepass)
- password_entry = self.xml.get_object('password_entry1')
- if savepass:
- passstr = passwords.get_password(account) or ''
- password_entry.set_sensitive(True)
- else:
- passstr = ''
- password_entry.set_sensitive(False)
- password_entry.set_text(passstr)
-
- jid = gajim.config.get_per('accounts', account, 'name') \
- + '@' + gajim.config.get_per('accounts', account, 'hostname')
- self.xml.get_object('jid_entry1').set_text(jid)
- self.ignore_events = False
-
- def init_normal_account(self):
- account = self.current_account
- # Account tab
- self.draw_normal_jid()
- self.xml.get_object('resource_entry1').set_text(gajim.config.get_per(
- 'accounts', account, 'resource'))
-
- client_cert = gajim.config.get_per('accounts', account, 'client_cert')
- self.xml.get_object('cert_entry1').set_text(client_cert)
- client_cert_encrypted = gajim.config.get_per('accounts', account,
- 'client_cert_encrypted')
- self.xml.get_object('client_cert_encrypted_checkbutton1').\
- set_active(client_cert_encrypted)
-
- self.xml.get_object('adjust_priority_with_status_checkbutton1').\
- set_active(gajim.config.get_per('accounts', account,
- 'adjust_priority_with_status'))
- spinbutton = self.xml.get_object('priority_spinbutton1')
- if gajim.config.get('enable_negative_priority'):
- spinbutton.set_range(-128, 127)
- else:
- spinbutton.set_range(0, 127)
- spinbutton.set_value(gajim.config.get_per('accounts', account,
- 'priority'))
-
- # Connection tab
- use_env_http_proxy = gajim.config.get_per('accounts', account,
- 'use_env_http_proxy')
- self.xml.get_object('use_env_http_proxy_checkbutton1').set_active(
- use_env_http_proxy)
- self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy)
-
- warn_when_insecure_ssl = gajim.config.get_per('accounts', account,
- 'warn_when_insecure_ssl_connection')
- self.xml.get_object('warn_when_insecure_connection_checkbutton1').\
- set_active(warn_when_insecure_ssl)
-
- self.xml.get_object('send_keepalive_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'keep_alives_enabled'))
-
- use_custom_host = gajim.config.get_per('accounts', account,
- 'use_custom_host')
- self.xml.get_object('custom_host_port_checkbutton1').set_active(
- use_custom_host)
- custom_host = gajim.config.get_per('accounts', account, 'custom_host')
- if not custom_host:
- custom_host = gajim.config.get_per('accounts', account, 'hostname')
- gajim.config.set_per('accounts', account, 'custom_host',
- custom_host)
- self.xml.get_object('custom_host_entry1').set_text(custom_host)
- custom_port = gajim.config.get_per('accounts', account, 'custom_port')
- if not custom_port:
- custom_port = 5222
- gajim.config.set_per('accounts', account, 'custom_port',
- custom_port)
- self.xml.get_object('custom_port_entry1').set_text(str(custom_port))
-
- # Personal tab
- gpg_key_label = self.xml.get_object('gpg_key_label1')
- if gajim.HAVE_GPG:
- self.xml.get_object('gpg_choose_button1').set_sensitive(True)
- self.init_account_gpg()
- else:
- gpg_key_label.set_text(_('OpenPGP is not usable on this computer'))
- self.xml.get_object('gpg_choose_button1').set_sensitive(False)
-
- # General tab
- self.xml.get_object('autoconnect_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'autoconnect'))
- self.xml.get_object('autoreconnect_checkbutton1').set_active(gajim.
- config.get_per('accounts', account, 'autoreconnect'))
-
- list_no_log_for = gajim.config.get_per('accounts', account,
- 'no_log_for').split()
- if account in list_no_log_for:
- self.xml.get_object('log_history_checkbutton1').set_active(False)
- else:
- self.xml.get_object('log_history_checkbutton1').set_active(True)
-
- self.xml.get_object('sync_logs_with_server_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'sync_logs_with_server'))
- self.xml.get_object('sync_with_global_status_checkbutton1').set_active(
- gajim.config.get_per('accounts', account,
- 'sync_with_global_status'))
- self.xml.get_object('carbons_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'enable_message_carbons'))
- self.xml.get_object('use_ft_proxies_checkbutton1').set_active(
- gajim.config.get_per('accounts', account, 'use_ft_proxies'))
-
- def on_add_button_clicked(self, widget):
- """
- When add button is clicked: open an account information window
- """
- if 'account_creation_wizard' in gajim.interface.instances:
- gajim.interface.instances['account_creation_wizard'].window.present()
- else:
- gajim.interface.instances['account_creation_wizard'] = \
- AccountCreationWizardWindow()
-
- def on_remove_button_clicked(self, widget):
- """
- When delete button is clicked: Remove an account from the listStore and
- from the config file
- """
- if not self.current_account:
- return
- account = self.current_account
- if len(gajim.events.get_events(account)):
- dialogs.ErrorDialog(_('Unread events'),
- _('Read all pending events before removing this account.'),
- transient_for=self.window)
- return
-
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- # Should never happen as button is insensitive
- return
-
- win_opened = False
- if gajim.interface.msg_win_mgr.get_controls(acct=account):
- win_opened = True
- elif account in gajim.interface.instances:
- for key in gajim.interface.instances[account]:
- if gajim.interface.instances[account][key] and key != \
- 'remove_account':
- win_opened = True
- break
- # Detect if we have opened windows for this account
- def remove(account):
- if account in gajim.interface.instances and \
- 'remove_account' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['remove_account'].window.\
- present()
- else:
- if not account in gajim.interface.instances:
- gajim.interface.instances[account] = {}
- gajim.interface.instances[account]['remove_account'] = \
- RemoveAccountWindow(account)
- if win_opened:
- dialogs.ConfirmationDialog(
- _('You have opened chat in account %s') % account,
- _('All chat and groupchat windows will be closed. Do you want to '
- 'continue?'),
- on_response_ok = (remove, account))
- else:
- remove(account)
-
- def on_rename_button_clicked(self, widget):
- if not self.current_account:
- return
- active = gajim.config.get_per('accounts', self.current_account,
- 'active') and self.current_account in gajim.connections
- if active and gajim.connections[self.current_account].connected != 0:
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To change the account name, you must be disconnected.'),
- transient_for=self.window)
- return
- if len(gajim.events.get_events(self.current_account)):
- dialogs.ErrorDialog(_('Unread events'),
- _('To change the account name, you must read all pending '
- 'events.'), transient_for=self.window)
- return
- # Get the new name
- def on_renamed(new_name, old_name):
- if new_name in gajim.connections:
- dialogs.ErrorDialog(_('Account Name Already Used'),
- _('This name is already used by another of your accounts. '
- 'Please choose another name.'), transient_for=self.window)
- return
- if (new_name == ''):
- dialogs.ErrorDialog(_('Invalid account name'),
- _('Account name cannot be empty.'),
- transient_for=self.window)
- return
- if new_name.find(' ') != -1:
- dialogs.ErrorDialog(_('Invalid account name'),
- _('Account name cannot contain spaces.'),
- transient_for=self.window)
- return
- if active:
- # update variables
- gajim.interface.instances[new_name] = gajim.interface.instances[
- old_name]
- gajim.interface.minimized_controls[new_name] = \
- gajim.interface.minimized_controls[old_name]
- gajim.nicks[new_name] = gajim.nicks[old_name]
- gajim.block_signed_in_notifications[new_name] = \
- gajim.block_signed_in_notifications[old_name]
- gajim.groups[new_name] = gajim.groups[old_name]
- gajim.gc_connected[new_name] = gajim.gc_connected[old_name]
- gajim.automatic_rooms[new_name] = gajim.automatic_rooms[
- old_name]
- gajim.newly_added[new_name] = gajim.newly_added[old_name]
- gajim.to_be_removed[new_name] = gajim.to_be_removed[old_name]
- gajim.sleeper_state[new_name] = gajim.sleeper_state[old_name]
- gajim.encrypted_chats[new_name] = gajim.encrypted_chats[
- old_name]
- gajim.last_message_time[new_name] = \
- gajim.last_message_time[old_name]
- gajim.status_before_autoaway[new_name] = \
- gajim.status_before_autoaway[old_name]
- gajim.transport_avatar[new_name] = gajim.transport_avatar[old_name]
- gajim.gajim_optional_features[new_name] = \
- gajim.gajim_optional_features[old_name]
- gajim.caps_hash[new_name] = gajim.caps_hash[old_name]
-
- gajim.contacts.change_account_name(old_name, new_name)
- gajim.events.change_account_name(old_name, new_name)
-
- # change account variable for chat / gc controls
- gajim.interface.msg_win_mgr.change_account_name(old_name, new_name)
- # upgrade account variable in opened windows
- for kind in ('infos', 'disco', 'gc_config', 'search',
- 'online_dialog', 'sub_request'):
- for j in gajim.interface.instances[new_name][kind]:
- gajim.interface.instances[new_name][kind][j].account = \
- new_name
-
- # ServiceCache object keep old property account
- if hasattr(gajim.connections[old_name], 'services_cache'):
- gajim.connections[old_name].services_cache.account = \
- new_name
- del gajim.interface.instances[old_name]
- del gajim.interface.minimized_controls[old_name]
- del gajim.nicks[old_name]
- del gajim.block_signed_in_notifications[old_name]
- del gajim.groups[old_name]
- del gajim.gc_connected[old_name]
- del gajim.automatic_rooms[old_name]
- del gajim.newly_added[old_name]
- del gajim.to_be_removed[old_name]
- del gajim.sleeper_state[old_name]
- del gajim.encrypted_chats[old_name]
- del gajim.last_message_time[old_name]
- del gajim.status_before_autoaway[old_name]
- del gajim.transport_avatar[old_name]
- del gajim.gajim_optional_features[old_name]
- del gajim.caps_hash[old_name]
- gajim.connections[old_name].name = new_name
- gajim.connections[old_name].pep_change_account_name(new_name)
- gajim.connections[old_name].caps_change_account_name(new_name)
- gajim.connections[new_name] = gajim.connections[old_name]
- del gajim.connections[old_name]
- gajim.config.add_per('accounts', new_name)
- old_config = gajim.config.get_per('accounts', old_name)
- for opt in old_config:
- gajim.config.set_per('accounts', new_name, opt, old_config[opt])
- gajim.config.del_per('accounts', old_name)
- if self.current_account == old_name:
- self.current_account = new_name
- if old_name == gajim.ZEROCONF_ACC_NAME:
- gajim.ZEROCONF_ACC_NAME = new_name
- # refresh roster
- gajim.interface.roster.setup_and_draw_roster()
- self.init_accounts()
- self.select_account(new_name)
-
- title = _('Rename Account')
- message = _('Enter a new name for account %s') % self.current_account
- old_text = self.current_account
- dialogs.InputDialog(title, message, old_text, is_modal=False,
- ok_handler=(on_renamed, self.current_account),
- transient_for=self.window)
-
- def option_changed(self, option, value):
- return gajim.config.get_per('accounts', self.current_account, option) \
- != value
-
- def on_jid_entry1_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- jid = widget.get_text()
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as s:
- if not widget.is_focus():
- pritext = _('Invalid JID')
- dialogs.ErrorDialog(pritext, str(s), transient_for=self.window)
- GLib.idle_add(lambda: widget.grab_focus())
- return True
-
- jid_splited = jid.split('@', 1)
- if len(jid_splited) != 2 and not gajim.config.get_per('accounts',
- self.current_account, 'anonymous_auth'):
- if not widget.is_focus():
- pritext = _('Invalid JID')
- sectext = \
- _('A JID must be in the form "user@servername".')
- dialogs.ErrorDialog(pritext, sectext, transient_for=self.window)
- GLib.idle_add(lambda: widget.grab_focus())
- return True
-
-
- if gajim.config.get_per('accounts', self.current_account,
- 'anonymous_auth'):
- gajim.config.set_per('accounts', self.current_account, 'hostname',
- jid_splited[0])
- if self.option_changed('hostname', jid_splited[0]):
- self.need_relogin = True
- else:
- if self.option_changed('name', jid_splited[0]) or \
- self.option_changed('hostname', jid_splited[1]):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'name',
- jid_splited[0])
- gajim.config.set_per('accounts', self.current_account, 'hostname',
- jid_splited[1])
-
- def on_cert_entry1_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- client_cert = widget.get_text()
- if self.option_changed('client_cert', client_cert):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'client_cert',
- client_cert)
-
- def on_anonymous_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- active = widget.get_active()
- gajim.config.set_per('accounts', self.current_account, 'anonymous_auth',
- active)
- self.draw_normal_jid()
-
- def on_password_entry1_changed(self, widget):
- if self.ignore_events:
- return
- passwords.save_password(self.current_account, widget.get_text())
-
- def on_save_password_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- active = widget.get_active()
- password_entry = self.xml.get_object('password_entry1')
- password_entry.set_sensitive(active)
- gajim.config.set_per('accounts', self.current_account, 'savepass',
- active)
- if active:
- password = password_entry.get_text()
- passwords.save_password(self.current_account, password)
- else:
- passwords.save_password(self.current_account, '')
-
- def on_resource_entry1_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- resource = self.xml.get_object('resource_entry1').get_text()
- try:
- resource = helpers.parse_resource(resource)
- except helpers.InvalidFormat as s:
- if not widget.is_focus():
- pritext = _('Invalid JID')
- dialogs.ErrorDialog(pritext, str(s), transient_for=self.window)
- GLib.idle_add(lambda: widget.grab_focus())
- return True
-
- if self.option_changed('resource', resource):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'resource',
- resource)
-
- def on_adjust_priority_with_status_checkbutton1_toggled(self, widget):
- self.xml.get_object('priority_spinbutton1').set_sensitive(
- not widget.get_active())
- self.on_checkbutton_toggled(widget, 'adjust_priority_with_status',
- account = self.current_account)
-
- def on_priority_spinbutton1_value_changed(self, widget):
- prio = widget.get_value_as_int()
-
- if self.option_changed('priority', prio):
- self.resend_presence = True
-
- gajim.config.set_per('accounts', self.current_account, 'priority', prio)
-
- def on_synchronise_contacts_button1_clicked(self, widget):
- try:
- dialogs.SynchroniseSelectAccountDialog(self.current_account)
- except GajimGeneralException:
- # If we showed ErrorDialog, there will not be dialog instance
- return
-
- def on_change_password_button1_clicked(self, widget):
- def on_changed(new_password):
- if new_password is not None:
- gajim.connections[self.current_account].change_password(
- new_password)
- if self.xml.get_object('save_password_checkbutton1').\
- get_active():
- self.xml.get_object('password_entry1').set_text(
- new_password)
-
- try:
- dialogs.ChangePasswordDialog(self.current_account, on_changed,
- self.window)
- except GajimGeneralException:
- # if we showed ErrorDialog, there will not be dialog instance
- return
-
- def on_client_cert_encrypted_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'client_cert_encrypted',
- account=self.current_account)
-
- def on_autoconnect_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'autoconnect',
- account=self.current_account)
-
- def on_autoreconnect_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'autoreconnect',
- account=self.current_account)
-
- def on_log_history_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- list_no_log_for = gajim.config.get_per('accounts', self.current_account,
- 'no_log_for').split()
- if self.current_account in list_no_log_for:
- list_no_log_for.remove(self.current_account)
-
- if not widget.get_active():
- list_no_log_for.append(self.current_account)
- gajim.config.set_per('accounts', self.current_account, 'no_log_for',
- ' '.join(list_no_log_for))
-
- def on_sync_logs_with_server_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'sync_logs_with_server',
- account=self.current_account)
-
- def on_sync_with_global_status_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'sync_with_global_status',
- account=self.current_account)
- gajim.interface.roster.update_status_combobox()
-
- def on_carbons_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'enable_message_carbons',
- account=self.current_account)
-
- def on_use_ft_proxies_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'use_ft_proxies',
- account=self.current_account)
-
- def on_use_env_http_proxy_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'use_env_http_proxy',
- account=self.current_account)
- hbox = self.xml.get_object('proxy_hbox1')
- hbox.set_sensitive(not widget.get_active())
-
- def on_proxies_combobox1_changed(self, widget):
- active = widget.get_active()
- proxy = widget.get_model()[active][0]
- if proxy == _('None'):
- proxy = ''
-
- if self.option_changed('proxy', proxy):
- self.need_relogin = True
-
- gajim.config.set_per('accounts', self.current_account, 'proxy', proxy)
-
- def on_manage_proxies_button1_clicked(self, widget):
- if 'manage_proxies' in gajim.interface.instances:
- gajim.interface.instances['manage_proxies'].window.present()
- else:
- gajim.interface.instances['manage_proxies'] = ManageProxiesWindow(
- self.window)
-
- def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
-
- self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection',
- account=self.current_account)
-
- def on_send_keepalive_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- self.on_checkbutton_toggled(widget, 'keep_alives_enabled',
- account=self.current_account)
- gajim.config.set_per('accounts', self.current_account,
- 'ping_alives_enabled', widget.get_active())
-
- def on_custom_host_port_checkbutton1_toggled(self, widget):
- if self.option_changed('use_custom_host', widget.get_active()):
- self.need_relogin = True
-
- self.on_checkbutton_toggled(widget, 'use_custom_host',
- account=self.current_account)
- active = widget.get_active()
- self.xml.get_object('custom_host_port_hbox1').set_sensitive(active)
-
- def on_custom_host_entry1_changed(self, widget):
- if self.ignore_events:
- return
- host = widget.get_text()
- if self.option_changed('custom_host', host):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'custom_host',
- host)
-
- def on_custom_port_entry_focus_out_event(self, widget, event):
- if self.ignore_events:
- return
- custom_port = widget.get_text()
- try:
- custom_port = int(custom_port)
- except Exception:
- if not widget.is_focus():
- dialogs.ErrorDialog(_('Invalid entry'),
- _('Custom port must be a port number.'),
- transient_for=self.window)
- GLib.idle_add(lambda: widget.grab_focus())
- return True
- if self.option_changed('custom_port', custom_port):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account, 'custom_port',
- custom_port)
-
- def on_gpg_choose_button_clicked(self, widget, data = None):
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].gpg:
- secret_keys = gajim.connections[self.current_account].\
- ask_gpg_secrete_keys()
-
- # self.current_account is None and/or gajim.connections is {}
- else:
- if gajim.HAVE_GPG:
- secret_keys = gpg.GnuPG().get_secret_keys()
- else:
- secret_keys = []
- if not secret_keys:
- dialogs.ErrorDialog(_('Failed to get secret keys'),
- _('There is no OpenPGP secret key available.'),
- transient_for=self.window)
- secret_keys[_('None')] = _('None')
-
- def on_key_selected(keyID):
- if keyID is None:
- return
- if self.current_account == gajim.ZEROCONF_ACC_NAME:
- wiget_name_ext = '2'
- else:
- wiget_name_ext = '1'
- gpg_key_label = self.xml.get_object('gpg_key_label' + \
- wiget_name_ext)
- gpg_name_label = self.xml.get_object('gpg_name_label' + \
- wiget_name_ext)
- use_gpg_agent_checkbutton = self.xml.get_object(
- 'use_gpg_agent_checkbutton' + wiget_name_ext)
- if keyID[0] == _('None'):
- gpg_key_label.set_text(_('No key selected'))
- gpg_name_label.set_text('')
- use_gpg_agent_checkbutton.set_sensitive(False)
- if self.option_changed('keyid', ''):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'keyname', '')
- gajim.config.set_per('accounts', self.current_account, 'keyid',
- '')
- else:
- gpg_key_label.set_text(keyID[0])
- gpg_name_label.set_text(keyID[1])
- use_gpg_agent_checkbutton.set_sensitive(True)
- if self.option_changed('keyid', keyID[0]):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'keyname', keyID[1])
- gajim.config.set_per('accounts', self.current_account, 'keyid',
- keyID[0])
-
- dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
- _('Choose your OpenPGP key'), secret_keys, on_key_selected,
- transient_for=self.window)
-
- def on_use_gpg_agent_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'use_gpg_agent')
-
- def on_edit_details_button1_clicked(self, widget):
- if self.current_account not in gajim.interface.instances:
- dlg = dialogs.ErrorDialog(_('No such account available'),
- _('You must create your account before editing your personal '
- 'information.'), transient_for=self.window)
- return
-
- # show error dialog if account is newly created (not in gajim.connections)
- if self.current_account not in gajim.connections or \
- gajim.connections[self.current_account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not edit your personal '
- 'information.'), transient_for=self.window)
- return
-
- if not gajim.connections[self.current_account].vcard_supported:
- dialogs.ErrorDialog(_("Your server does not have vCard support"),
- _("Your server can't save your personal information."),
- transient_for=self.window)
- return
-
- jid = gajim.get_jid_from_account(self.current_account)
- if 'profile' not in gajim.interface.instances[self.current_account]:
- gajim.interface.instances[self.current_account]['profile'] = \
- profile_window.ProfileWindow(self.current_account, transient_for=self.window)
- gajim.connections[self.current_account].request_vcard(jid)
-
- def on_checkbutton_toggled(self, widget, config_name,
- change_sensitivity_widgets = None, account = None):
- if account:
- gajim.config.set_per('accounts', account, config_name,
- widget.get_active())
- else:
- gajim.config.set(config_name, widget.get_active())
- if change_sensitivity_widgets:
- for w in change_sensitivity_widgets:
- w.set_sensitive(widget.get_active())
-
- def on_merge_checkbutton_toggled(self, widget):
- self.on_checkbutton_toggled(widget, 'mergeaccounts')
- if len(gajim.connections) >= 2: # Do not merge accounts if only one active
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
-
- def _disable_account(self, account):
- gajim.interface.roster.close_all(account)
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.connections[account].disable_account()
- gajim.connections[account].cleanup()
- del gajim.connections[account]
- del gajim.interface.instances[account]
- del gajim.interface.minimized_controls[account]
- del gajim.nicks[account]
- del gajim.block_signed_in_notifications[account]
- del gajim.groups[account]
- gajim.contacts.remove_account(account)
- del gajim.gc_connected[account]
- del gajim.automatic_rooms[account]
- del gajim.to_be_removed[account]
- del gajim.newly_added[account]
- del gajim.sleeper_state[account]
- del gajim.encrypted_chats[account]
- del gajim.last_message_time[account]
- del gajim.status_before_autoaway[account]
- del gajim.transport_avatar[account]
- del gajim.gajim_optional_features[account]
- del gajim.caps_hash[account]
- if len(gajim.connections) >= 2:
- # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.app.remove_account_actions(account)
- gui_menu_builder.build_accounts_menu()
-
- def _enable_account(self, account):
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.connections[account] = connection_zeroconf.ConnectionZeroconf(
- account)
- if gajim.connections[account].gpg:
- self.xml.get_object('gpg_choose_button2').set_sensitive(True)
- else:
- gajim.connections[account] = common.connection.Connection(account)
- if gajim.connections[account].gpg:
- self.xml.get_object('gpg_choose_button1').set_sensitive(True)
- self.init_account_gpg()
- # update variables
- gajim.interface.instances[account] = {'infos': {},
- 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {},
- 'sub_request': {}}
- gajim.interface.minimized_controls[account] = {}
- gajim.connections[account].connected = 0
- gajim.groups[account] = {}
- gajim.contacts.add_account(account)
- gajim.gc_connected[account] = {}
- gajim.automatic_rooms[account] = {}
- gajim.newly_added[account] = []
- gajim.to_be_removed[account] = []
- if account == gajim.ZEROCONF_ACC_NAME:
- gajim.nicks[account] = gajim.ZEROCONF_ACC_NAME
- else:
- gajim.nicks[account] = gajim.config.get_per('accounts', account,
- 'name')
- gajim.block_signed_in_notifications[account] = True
- gajim.sleeper_state[account] = 'off'
- gajim.encrypted_chats[account] = []
- gajim.last_message_time[account] = {}
- gajim.status_before_autoaway[account] = ''
- gajim.transport_avatar[account] = {}
- gajim.gajim_optional_features[account] = []
- gajim.caps_hash[account] = ''
- helpers.update_optional_features(account)
- # refresh roster
- if len(gajim.connections) >= 2:
- # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.app.add_account_actions(account)
- gui_menu_builder.build_accounts_menu()
-
- def on_enable_zeroconf_checkbutton2_toggled(self, widget):
- # don't do anything if there is an account with the local name but is a
- # normal account
- if self.ignore_events:
- return
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].connected > 0:
- self.ignore_events = True
- self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True)
- self.ignore_events = False
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To disable the account, you must be disconnected.'),
- transient_for=self.window)
- return
- if gajim.ZEROCONF_ACC_NAME in gajim.connections and not \
- gajim.connections[gajim.ZEROCONF_ACC_NAME].is_zeroconf:
- gajim.nec.push_incoming_event(InformationEvent(None,
- conn=gajim.connections[gajim.ZEROCONF_ACC_NAME],
- level='error', pri_txt=_('Account Local already exists.'),
- sec_txt=_('Please rename or remove it before enabling '
- 'link-local messaging.')))
- return
-
- if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
- and not widget.get_active():
- self.xml.get_object('zeroconf_notebook').set_sensitive(False)
- # disable
- self._disable_account(gajim.ZEROCONF_ACC_NAME)
-
- elif not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME,
- 'active') and widget.get_active():
- self.xml.get_object('zeroconf_notebook').set_sensitive(True)
- # enable (will create new account if not present)
- self._enable_account(gajim.ZEROCONF_ACC_NAME)
-
- self.on_checkbutton_toggled(widget, 'active',
- account=gajim.ZEROCONF_ACC_NAME)
-
- def on_enable_checkbutton1_toggled(self, widget):
- if self.ignore_events:
- return
- if self.current_account in gajim.connections and \
- gajim.connections[self.current_account].connected > 0:
- # connecting or connected
- self.ignore_events = True
- self.xml.get_object('enable_checkbutton1').set_active(True)
- self.ignore_events = False
- dialogs.ErrorDialog(
- _('You are currently connected to the server'),
- _('To disable the account, you must be disconnected.'),
- transient_for=self.window)
- return
- # add/remove account in roster and all variables
- if widget.get_active():
- # enable
- self._enable_account(self.current_account)
- else:
- # disable
- self._disable_account(self.current_account)
- self.on_checkbutton_toggled(widget, 'active',
- account=self.current_account, change_sensitivity_widgets=[
- self.xml.get_object('normal_notebook1')])
-
- def on_custom_port_checkbutton2_toggled(self, widget):
- self.xml.get_object('custom_port_entry2').set_sensitive(
- widget.get_active())
- self.on_checkbutton_toggled(widget, 'use_custom_host',
- account=self.current_account)
- if not widget.get_active():
- self.xml.get_object('custom_port_entry2').set_text('5298')
-
- def on_first_name_entry2_changed(self, widget):
- if self.ignore_events:
- return
- name = widget.get_text()
- if self.option_changed('zeroconf_first_name', name):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_first_name', name)
-
- def on_last_name_entry2_changed(self, widget):
- if self.ignore_events:
- return
- name = widget.get_text()
- if self.option_changed('zeroconf_last_name', name):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_last_name', name)
-
- def on_jabber_id_entry2_changed(self, widget):
- if self.ignore_events:
- return
- id_ = widget.get_text()
- if self.option_changed('zeroconf_jabber_id', id_):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_jabber_id', id_)
-
- def on_email_entry2_changed(self, widget):
- if self.ignore_events:
- return
- email = widget.get_text()
- if self.option_changed('zeroconf_email', email):
- self.need_relogin = True
- gajim.config.set_per('accounts', self.current_account,
- 'zeroconf_email', email)
-
-class FakeDataForm(Gtk.Table, object):
- """
- Class for forms that are in XML format <entry1>value1</entry1> infos in a
- table {entry1: value1}
- """
-
- def __init__(self, infos, selectable=False):
- GObject.GObject.__init__(self)
- self.infos = infos
- self.selectable = selectable
- self.entries = {}
- self._draw_table()
-
- def _draw_table(self):
- """
- Draw the table
- """
- nbrow = 0
- if 'instructions' in self.infos:
- nbrow = 1
- self.resize(rows = nbrow, columns = 2)
- label = Gtk.Label(label=self.infos['instructions'])
- if self.selectable:
- label.set_selectable(True)
- self.attach(label, 0, 2, 0, 1, 0, 0, 0, 0)
- for name in self.infos.keys():
- if name in ('key', 'instructions', 'x', 'registered'):
- continue
- if not name:
- continue
-
- nbrow = nbrow + 1
- self.resize(rows = nbrow, columns = 2)
- label = Gtk.Label(label=name.capitalize() + ':')
- self.attach(label, 0, 1, nbrow - 1, nbrow, 0, 0, 0, 0)
- entry = Gtk.Entry()
- entry.set_activates_default(True)
- if self.infos[name]:
- entry.set_text(self.infos[name])
- if name == 'password':
- entry.set_visibility(False)
- self.attach(entry, 1, 2, nbrow - 1, nbrow, 0, 0, 0, 0)
- self.entries[name] = entry
- if nbrow == 1:
- entry.grab_focus()
-
- def get_infos(self):
- for name in self.entries.keys():
- self.infos[name] = self.entries[name].get_text()
- return self.infos
-
-class ServiceRegistrationWindow:
- """
- Class for Service registration window. Window that appears when we want to
- subscribe to a service if is_form we use dataforms_widget else we use
- service_registarion_window
- """
- def __init__(self, service, infos, account, is_form):
- self.service = service
- self.account = account
- self.is_form = is_form
- self.xml = gtkgui_helpers.get_gtk_builder('service_registration_window.ui')
- self.window = self.xml.get_object('service_registration_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- if self.is_form:
- dataform = dataforms.ExtendForm(node = infos)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- if self.data_form_widget.title:
- self.window.set_title('%s - Gajim' % self.data_form_widget.title)
- grid = self.xml.get_object('grid')
- grid.attach(self.data_form_widget, 0, 0, 2, 1)
- else:
- if 'registered' in infos:
- self.window.set_title(_('Edit %s') % service)
- else:
- self.window.set_title(_('Register to %s') % service)
- self.data_form_widget = FakeDataForm(infos)
- grid = self.xml.get_object('grid')
- grid.attach(self.data_form_widget, 0, 0, 2, 1)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_ok_button_clicked(self, widget):
- # send registration info to the core
- if self.is_form:
- form = self.data_form_widget.data_form
- gajim.connections[self.account].register_agent(self.service,
- form, True) # True is for is_form
- else:
- infos = self.data_form_widget.get_infos()
- if 'instructions' in infos:
- del infos['instructions']
- if 'registered' in infos:
- del infos['registered']
- gajim.connections[self.account].register_agent(self.service, infos)
-
- self.window.destroy()
-
-class GroupchatConfigWindow:
-
- def __init__(self, account, room_jid, form=None):
- self.account = account
- self.room_jid = room_jid
- self.form = form
- self.remove_button = {}
- self.affiliation_treeview = {}
- self.start_users_dict = {} # list at the beginning
- self.affiliation_labels = {'outcast': _('Ban List'),
- 'member': _('Member List'), 'owner': _('Owner List'),
- 'admin':_('Administrator List')}
-
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
- 'data_form_window')
- self.window = self.xml.get_object('data_form_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- if self.form:
- config_vbox = self.xml.get_object('config_vbox')
- self.data_form_widget = dataforms_widget.DataFormWidget(self.form)
- # hide scrollbar of this data_form_widget, we already have in this
- # widget
- sw = self.data_form_widget.xml.get_object(
- 'single_form_scrolledwindow')
- sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
- if self.form.title:
- self.xml.get_object('title_label').set_text(self.form.title)
- else:
- self.xml.get_object('title_hseparator').set_no_show_all(True)
- self.xml.get_object('title_hseparator').hide()
-
- self.data_form_widget.show()
- config_vbox.pack_start(self.data_form_widget, True, True, 0)
- else:
- self.xml.get_object('title_label').set_no_show_all(True)
- self.xml.get_object('title_label').hide()
- self.xml.get_object('title_hseparator').set_no_show_all(True)
- self.xml.get_object('title_hseparator').hide()
- self.xml.get_object('config_hseparator').set_no_show_all(True)
- self.xml.get_object('config_hseparator').hide()
-
- # Draw the edit affiliation list things
- add_on_vbox = self.xml.get_object('add_on_vbox')
-
- for affiliation in self.affiliation_labels.keys():
- self.start_users_dict[affiliation] = {}
- hbox = Gtk.HBox(spacing=5)
- add_on_vbox.pack_start(hbox, False, True, 0)
-
- label = Gtk.Label(label=self.affiliation_labels[affiliation])
- hbox.pack_start(label, False, True, 0)
-
- bb = Gtk.HButtonBox()
- bb.set_layout(Gtk.ButtonBoxStyle.END)
- bb.set_spacing(5)
- hbox.pack_start(bb, True, True, 0)
- add_button = Gtk.Button(stock=Gtk.STOCK_ADD)
- add_button.connect('clicked', self.on_add_button_clicked,
- affiliation)
- bb.pack_start(add_button, True, True, 0)
- self.remove_button[affiliation] = Gtk.Button(stock=Gtk.STOCK_REMOVE)
- self.remove_button[affiliation].set_sensitive(False)
- self.remove_button[affiliation].connect('clicked',
- self.on_remove_button_clicked, affiliation)
- bb.pack_start(self.remove_button[affiliation], True, True, 0)
-
- # jid, reason, nick, role
- liststore = Gtk.ListStore(str, str, str, str)
- self.affiliation_treeview[affiliation] = Gtk.TreeView(liststore)
- self.affiliation_treeview[affiliation].get_selection().set_mode(
- Gtk.SelectionMode.MULTIPLE)
- self.affiliation_treeview[affiliation].connect('cursor-changed',
- self.on_affiliation_treeview_cursor_changed, affiliation)
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('JID'), renderer)
- col.add_attribute(renderer, 'text', 0)
- col.set_resizable(True)
- col.set_sort_column_id(0)
- self.affiliation_treeview[affiliation].append_column(col)
-
- if affiliation == 'outcast':
- renderer = Gtk.CellRendererText()
- renderer.set_property('editable', True)
- renderer.connect('edited', self.on_cell_edited)
- col = Gtk.TreeViewColumn(_('Reason'), renderer)
- col.add_attribute(renderer, 'text', 1)
- col.set_resizable(True)
- col.set_sort_column_id(1)
- self.affiliation_treeview[affiliation].append_column(col)
- elif affiliation == 'member':
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Nick'), renderer)
- col.add_attribute(renderer, 'text', 2)
- col.set_resizable(True)
- col.set_sort_column_id(2)
- self.affiliation_treeview[affiliation].append_column(col)
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Role'), renderer)
- col.add_attribute(renderer, 'text', 3)
- col.set_resizable(True)
- col.set_sort_column_id(3)
- self.affiliation_treeview[affiliation].append_column(col)
-
- sw = Gtk.ScrolledWindow()
- sw.add(self.affiliation_treeview[affiliation])
- add_on_vbox.pack_start(sw, True, True, 0)
- gajim.connections[self.account].get_affiliation_list(self.room_jid,
- affiliation)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_cell_edited(self, cell, path, new_text):
- model = self.affiliation_treeview['outcast'].get_model()
- new_text = new_text
- iter_ = model.get_iter(path)
- model[iter_][1] = new_text
-
- def on_add_button_clicked(self, widget, affiliation):
- if affiliation == 'outcast':
- title = _('Banning…')
- #You can move '\n' before user@domain if that line is TOO BIG
- prompt = _('<b>Whom do you want to ban?</b>\n\n')
- elif affiliation == 'member':
- title = _('Adding Member…')
- prompt = _('<b>Whom do you want to make a member?</b>\n\n')
- elif affiliation == 'owner':
- title = _('Adding Owner…')
- prompt = _('<b>Whom do you want to make an owner?</b>\n\n')
- else:
- title = _('Adding Administrator…')
- prompt = _('<b>Whom do you want to make an administrator?</b>\n\n')
- prompt += _('Can be one of the following:\n'
- '1. user@domain/resource (only that resource matches).\n'
- '2. user@domain (any resource matches).\n'
- '3. domain/resource (only that resource matches).\n'
- '4. domain (the domain itself matches, as does any user@domain,\n'
- 'domain/resource, or address containing a subdomain).')
-
- def on_ok(jid):
- if not jid:
- return
- model = self.affiliation_treeview[affiliation].get_model()
- model.append((jid, '', '', ''))
- dialogs.InputDialog(title, prompt, ok_handler=on_ok)
-
- def on_remove_button_clicked(self, widget, affiliation):
- selection = self.affiliation_treeview[affiliation].get_selection()
- model, paths = selection.get_selected_rows()
- row_refs = []
- for path in paths:
- row_refs.append(Gtk.TreeRowReference.new(model, path))
- for row_ref in row_refs:
- path = row_ref.get_path()
- iter_ = model.get_iter(path)
- model.remove(iter_)
- self.remove_button[affiliation].set_sensitive(False)
-
- def on_affiliation_treeview_cursor_changed(self, widget, affiliation):
- self.remove_button[affiliation].set_sensitive(True)
-
- def affiliation_list_received(self, users_dict):
- """
- Fill the affiliation treeview
- """
- for jid in users_dict:
- affiliation = users_dict[jid]['affiliation']
- if affiliation not in self.affiliation_labels.keys():
- # Unknown affiliation or 'none' affiliation, do not show it
- continue
- self.start_users_dict[affiliation][jid] = users_dict[jid]
- tv = self.affiliation_treeview[affiliation]
- model = tv.get_model()
- reason = users_dict[jid].get('reason', '')
- nick = users_dict[jid].get('nick', '')
- role = users_dict[jid].get('role', '')
- model.append((jid, reason, nick, role))
-
- def on_data_form_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['gc_config'][self.room_jid]
-
- def on_ok_button_clicked(self, widget):
- if self.form:
- form = self.data_form_widget.data_form
- gajim.connections[self.account].send_gc_config(self.room_jid, form)
- for affiliation in self.affiliation_labels.keys():
- users_dict = {}
- actual_jid_list = []
- model = self.affiliation_treeview[affiliation].get_model()
- iter_ = model.get_iter_first()
- # add new jid
- while iter_:
- jid = model[iter_][0]
- actual_jid_list.append(jid)
- if jid not in self.start_users_dict[affiliation] or \
- (affiliation == 'outcast' and 'reason' in self.start_users_dict[
- affiliation][jid] and self.start_users_dict[affiliation][jid]\
- ['reason'] != model[iter_][1]):
- users_dict[jid] = {'affiliation': affiliation}
- if affiliation == 'outcast':
- users_dict[jid]['reason'] = model[iter_][1]
- iter_ = model.iter_next(iter_)
- # remove removed one
- for jid in self.start_users_dict[affiliation]:
- if jid not in actual_jid_list:
- users_dict[jid] = {'affiliation': 'none'}
- if users_dict:
- gajim.connections[self.account].send_gc_affiliation_list(
- self.room_jid, users_dict)
- self.window.destroy()
-
-#---------- RemoveAccountWindow class -------------#
-class RemoveAccountWindow:
- """
- Ask for removing from gajim only or from gajim and server too and do
- removing of the account given
- """
-
- def on_remove_account_window_destroy(self, widget):
- if self.account in gajim.interface.instances:
- del gajim.interface.instances[self.account]['remove_account']
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def __init__(self, account):
- self.account = account
- xml = gtkgui_helpers.get_gtk_builder('remove_account_window.ui')
- self.window = xml.get_object('remove_account_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.remove_and_unregister_radiobutton = xml.get_object(
- 'remove_and_unregister_radiobutton')
- self.window.set_title(_('Removing %s account') % self.account)
- xml.connect_signals(self)
- self.window.show_all()
-
- def on_remove_button_clicked(self, widget):
- def remove():
- if self.account in gajim.connections and \
- gajim.connections[self.account].connected and \
- not self.remove_and_unregister_radiobutton.get_active():
- # change status to offline only if we will not remove this JID from
- # server
- gajim.connections[self.account].change_status('offline', 'offline')
- if self.remove_and_unregister_radiobutton.get_active():
- if not self.account in gajim.connections:
- dialogs.ErrorDialog(
- _('Account is disabled'),
- _('To unregister from a server, account must be '
- 'enabled.'))
- return
- if not gajim.connections[self.account].password:
- def on_ok(passphrase, checked):
- if passphrase == -1:
- # We don't remove account cause we canceled pw window
- return
- gajim.connections[self.account].password = passphrase
- gajim.connections[self.account].unregister_account(
- self._on_remove_success)
-
- dialogs.PassphraseDialog(
- _('Password Required'),
- _('Enter your password for account %s') % self.account,
- _('Save password'), ok_handler=on_ok)
- return
- gajim.connections[self.account].unregister_account(
- self._on_remove_success)
- else:
- self._on_remove_success(True)
-
- if self.account in gajim.connections and \
- gajim.connections[self.account].connected:
- dialogs.ConfirmationDialog(
- _('Account "%s" is connected to the server') % self.account,
- _('If you remove it, the connection will be lost.'),
- on_response_ok=remove)
- else:
- remove()
-
- def on_remove_responce_ok(self, is_checked):
- if is_checked[0]:
- self._on_remove_success(True)
-
- def _on_remove_success(self, res):
- # action of unregistration has failed, we don't remove the account
- # Error message is send by connect_and_auth()
- if not res:
- dialogs.ConfirmationDialogDoubleRadio(
- _('Connection to server %s failed') % self.account,
- _('What would you like to do?'),
- _('Remove only from Gajim'),
- _('Don\'t remove anything. I\'ll try again later'),
- on_response_ok=self.on_remove_responce_ok, is_modal=False)
- return
- # Close all opened windows
- gajim.interface.roster.close_all(self.account, force=True)
- if self.account in gajim.connections:
- gajim.connections[self.account].disconnect(on_purpose=True)
- gajim.connections[self.account].cleanup()
- del gajim.connections[self.account]
- gajim.logger.remove_roster(gajim.get_jid_from_account(self.account))
- gajim.config.del_per('accounts', self.account)
- del gajim.interface.instances[self.account]
- if self.account in gajim.nicks:
- del gajim.interface.minimized_controls[self.account]
- del gajim.nicks[self.account]
- del gajim.block_signed_in_notifications[self.account]
- del gajim.groups[self.account]
- gajim.contacts.remove_account(self.account)
- del gajim.gc_connected[self.account]
- del gajim.automatic_rooms[self.account]
- del gajim.to_be_removed[self.account]
- del gajim.newly_added[self.account]
- del gajim.sleeper_state[self.account]
- del gajim.encrypted_chats[self.account]
- del gajim.last_message_time[self.account]
- del gajim.status_before_autoaway[self.account]
- del gajim.transport_avatar[self.account]
- del gajim.gajim_optional_features[self.account]
- del gajim.caps_hash[self.account]
- if len(gajim.connections) >= 2: # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.app.remove_account_actions(self.account)
- gui_menu_builder.build_accounts_menu()
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].init_accounts()
- gajim.interface.instances['accounts'].init_account()
- self.window.destroy()
-
-#---------- ManageBookmarksWindow class -------------#
-class ManageBookmarksWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_bookmarks_window.ui')
- self.window = self.xml.get_object('manage_bookmarks_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- self.ignore_events = False
-
- # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick,
- # Show_Status
- self.treestore = Gtk.TreeStore(str, str, str, bool, bool, str, str, str)
- self.treestore.set_sort_column_id(1, Gtk.SortType.ASCENDING)
-
- # Store bookmarks in treeview.
- for account in gajim.connections:
- if gajim.connections[account].connected <= 1:
- continue
- if gajim.connections[account].is_zeroconf:
- continue
- if not gajim.connections[account].private_storage_supported:
- continue
- iter_ = self.treestore.append(None, [None, account, None, None,
- None, None, None, None])
-
- for bookmark in gajim.connections[account].bookmarks:
- if not bookmark['name']:
- # No name was given for this bookmark.
- # Use the first part of JID instead...
- name = bookmark['jid'].split("@")[0]
- bookmark['name'] = name
-
- # make '1', '0', 'true', 'false' (or other) to True/False
- autojoin = helpers.from_xs_boolean_to_python_boolean(
- bookmark['autojoin'])
-
- minimize = helpers.from_xs_boolean_to_python_boolean(
- bookmark['minimize'])
-
- print_status = bookmark.get('print_status', '')
- if print_status not in ('', 'all', 'in_and_out', 'none'):
- print_status = ''
- self.treestore.append(iter_, [
- account,
- bookmark['name'],
- bookmark['jid'],
- autojoin,
- minimize,
- bookmark['password'],
- bookmark['nick'],
- print_status ])
-
- self.print_status_combobox = self.xml.get_object('print_status_combobox')
- model = Gtk.ListStore(str, str)
-
- self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'),
- 'in_and_out': _('Enter and leave only'),
- 'none': Q_('?print_status:None')}
- opts = sorted(self.option_list.keys())
- for opt in opts:
- model.append([self.option_list[opt], opt])
-
- self.print_status_combobox.set_model(model)
- self.print_status_combobox.set_active(1)
-
- self.view = self.xml.get_object('bookmarks_treeview')
- self.view.set_model(self.treestore)
- self.view.expand_all()
-
- renderer = Gtk.CellRendererText()
- column = Gtk.TreeViewColumn('Bookmarks', renderer, text=1)
- self.view.append_column(column)
-
- self.selection = self.view.get_selection()
- self.selection.connect('changed', self.bookmark_selected)
-
- #Prepare input fields
- self.title_entry = self.xml.get_object('title_entry')
- self.title_entry.connect('changed', self.on_title_entry_changed)
- self.nick_entry = self.xml.get_object('nick_entry')
- self.nick_entry.connect('changed', self.on_nick_entry_changed)
- self.server_entry = self.xml.get_object('server_entry')
- self.server_entry.connect('changed', self.on_server_entry_changed)
- self.room_entry = self.xml.get_object('room_entry')
- self.room_entry_changed_id = self.room_entry.connect('changed',
- self.on_room_entry_changed)
- self.pass_entry = self.xml.get_object('pass_entry')
- self.pass_entry.connect('changed', self.on_pass_entry_changed)
- self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton')
- self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton')
-
- self.xml.connect_signals(self)
- self.window.show_all()
- # select root iter
- first_iter = self.treestore.get_iter_first()
- if first_iter:
- self.selection.select_iter(first_iter)
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_add_bookmark_button_clicked(self, widget):
- """
- Add a new bookmark
- """
- # Get the account that is currently used
- # (the parent of the currently selected item)
- (model, iter_) = self.selection.get_selected()
- if not iter_: # Nothing selected, do nothing
- return
-
- parent = model.iter_parent(iter_)
-
- if parent:
- # We got a bookmark selected, so we add_to the parent
- add_to = parent
- else:
- # No parent, so we got an account -> add to this.
- add_to = iter_
-
- account = model[add_to][1]
- nick = gajim.nicks[account]
- iter_ = self.treestore.append(add_to, [account, _('New Group Chat'),
- '@', False, False, '', nick, 'in_and_out'])
-
- self.view.expand_row(model.get_path(add_to), True)
- self.view.set_cursor(model.get_path(iter_))
-
- def on_remove_bookmark_button_clicked(self, widget):
- """
- Remove selected bookmark
- """
- (model, iter_) = self.selection.get_selected()
- if not iter_: # Nothing selected
- return
-
- if not model.iter_parent(iter_):
- # Don't remove account iters
- return
-
- self.ignore_events = True
- model.remove(iter_)
- self.selection.unselect_all()
- self.clear_fields()
- self.ignore_events = False
-
- def check_valid_bookmark(self):
- """
- Check if all neccessary fields are entered correctly
- """
- (model, iter_) = self.selection.get_selected()
-
- if not model.iter_parent(iter_):
- #Account data can't be changed
- return
-
- if self.server_entry.get_text() == '' or \
- self.room_entry.get_text() == '':
- dialogs.ErrorDialog(_('This bookmark has invalid data'),
- _('Please be sure to fill out server and room fields or remove this'
- ' bookmark.'))
- return False
-
- return True
-
- def on_ok_button_clicked(self, widget):
- """
- Parse the treestore data into our new bookmarks array, then send the new
- bookmarks to the server.
- """
- (model, iter_) = self.selection.get_selected()
- if iter_ and model.iter_parent(iter_):
- #bookmark selected, check it
- if not self.check_valid_bookmark():
- return
-
- for account in self.treestore:
- acct = account[1]
- gajim.connections[acct].bookmarks = []
-
- for bm in account.iterchildren():
- # Convert True/False/None to '1' or '0'
- autojoin = str(int(bm[3]))
- minimize = str(int(bm[4]))
- name = bm[1]
- jid = bm[2]
- pw = bm[5]
- nick = bm[6]
-
- # create the bookmark-dict
- bmdict = { 'name': name, 'jid': jid, 'autojoin': autojoin,
- 'minimize': minimize, 'password': pw, 'nick': nick,
- 'print_status': bm[7]}
-
- gajim.connections[acct].bookmarks.append(bmdict)
-
- gajim.connections[acct].store_bookmarks()
- gui_menu_builder.build_bookmark_menu(acct)
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def bookmark_selected(self, selection):
- """
- Fill in the bookmark's data into the fields.
- """
- (model, iter_) = selection.get_selected()
-
- if not iter_:
- # After removing the last bookmark for one account
- # this will be None, so we will just:
- return
-
- widgets = [ self.title_entry, self.nick_entry, self.room_entry,
- self.server_entry, self.pass_entry, self.autojoin_checkbutton,
- self.minimize_checkbutton, self.print_status_combobox]
-
- if model.iter_parent(iter_):
- # make the fields sensitive
- for field in widgets:
- field.set_sensitive(True)
- else:
- # Top-level has no data (it's the account fields)
- # clear fields & make them insensitive
- self.clear_fields()
- for field in widgets:
- field.set_sensitive(False)
- return
-
- # Fill in the data for childs
- self.title_entry.set_text(model[iter_][1])
- room_jid = model[iter_][2]
- (room, server) = room_jid.split('@')
- self.room_entry.handler_block(self.room_entry_changed_id)
- self.room_entry.set_text(room)
- self.room_entry.handler_unblock(self.room_entry_changed_id)
- self.server_entry.set_text(server)
-
- self.autojoin_checkbutton.set_active(model[iter_][3])
- self.minimize_checkbutton.set_active(model[iter_][4])
- # sensitive only if auto join is checked
- self.minimize_checkbutton.set_sensitive(model[iter_][3])
-
- if model[iter_][5] is not None:
- password = model[iter_][5]
- else:
- password = None
-
- if password:
- self.pass_entry.set_text(password)
- else:
- self.pass_entry.set_text('')
- nick = model[iter_][6]
- if nick:
- self.nick_entry.set_text(nick)
- else:
- self.nick_entry.set_text('')
-
- print_status = model[iter_][7]
- opts = sorted(self.option_list.keys())
- self.print_status_combobox.set_active(opts.index(print_status))
-
- def on_title_entry_changed(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if iter_: # After removing a bookmark, we got nothing selected
- if model.iter_parent(iter_):
- # Don't clear the title field for account nodes
- model[iter_][1] = self.title_entry.get_text()
-
- def on_nick_entry_changed(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if iter_:
- nick = self.nick_entry.get_text()
- try:
- nick = helpers.parse_resource(nick)
- except helpers.InvalidFormat:
- dialogs.ErrorDialog(_('Invalid nickname'),
- _('Character not allowed'), transient_for=self.window)
- self.nick_entry.set_text(model[iter_][6])
- return True
- model[iter_][6] = nick
-
- def on_server_entry_changed(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if not iter_:
- return
- server = widget.get_text()
- if not server:
- return
- if '@' in server:
- dialogs.ErrorDialog(_('Invalid server'),
- _('Character not allowed'), transient_for=self.window)
- widget.set_text(server.replace('@', ''))
-
- room = self.room_entry.get_text().strip()
- if not room:
- return
- room_jid = room + '@' + server.strip()
- try:
- room_jid = helpers.parse_jid(room_jid)
- except helpers.InvalidFormat as e:
- dialogs.ErrorDialog(_('Invalid server'),
- _('Character not allowed'), transient_for=self.window)
- self.server_entry.set_text(model[iter_][2].split('@')[1])
- return True
- model[iter_][2] = room_jid
-
- def on_room_entry_changed(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if not iter_:
- return
- room = widget.get_text()
- if not room:
- return
- if '@' in room:
- room, server = room.split('@', 1)
- widget.set_text(room)
- if server:
- self.server_entry.set_text(server)
- self.server_entry.grab_focus()
- server = self.server_entry.get_text().strip()
- if not server:
- return
- room_jid = room.strip() + '@' + server
- try:
- room_jid = helpers.parse_jid(room_jid)
- except helpers.InvalidFormat:
- dialogs.ErrorDialog(_('Invalid room'),
- _('Character not allowed'), transient_for=self.window)
- return True
- model[iter_][2] = room_jid
-
- def on_pass_entry_changed(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][5] = self.pass_entry.get_text()
-
- def on_autojoin_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][3] = self.autojoin_checkbutton.get_active()
- self.minimize_checkbutton.set_sensitive(model[iter_][3])
-
- def on_minimize_checkbutton_toggled(self, widget):
- if self.ignore_events:
- return
- (model, iter_) = self.selection.get_selected()
- if iter_:
- model[iter_][4] = self.minimize_checkbutton.get_active()
-
- def on_print_status_combobox_changed(self, widget):
- if self.ignore_events:
- return
- active = widget.get_active()
- model = widget.get_model()
- print_status = model[active][1]
- (model2, iter_) = self.selection.get_selected()
- if iter_:
- model2[iter_][7] = print_status
-
- def clear_fields(self):
- widgets = [ self.title_entry, self.nick_entry, self.room_entry,
- self.server_entry, self.pass_entry ]
- for field in widgets:
- field.set_text('')
- self.autojoin_checkbutton.set_active(False)
- self.minimize_checkbutton.set_active(False)
- self.print_status_combobox.set_active(1)
-
-class AccountCreationWizardWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'account_creation_wizard_window.ui')
- self.window = self.xml.get_object('account_creation_wizard_window')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- # Connect events from comboboxtext_entry
- server_comboboxtext = self.xml.get_object('server_comboboxtext')
- entry = self.xml.get_object('server_comboboxtext_entry')
- entry.connect('key_press_event',
- self.on_server_comboboxentry_key_press_event, server_comboboxtext)
-
- server_comboboxtext1 = self.xml.get_object('server_comboboxtext1')
-
- self.update_proxy_list()
-
- # parse servers.xml
- servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml')
- servers = gtkgui_helpers.parse_server_xml(servers_xml)
- servers_model = self.xml.get_object('server_liststore')
- for server in servers:
- servers_model.append((server,))
-
- server_comboboxtext.set_model(servers_model)
- server_comboboxtext1.set_model(servers_model)
-
- # Generic widgets
- self.notebook = self.xml.get_object('notebook')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.back_button = self.xml.get_object('back_button')
- self.forward_button = self.xml.get_object('forward_button')
- self.finish_button = self.xml.get_object('finish_button')
- self.advanced_button = self.xml.get_object('advanced_button')
- self.finish_label = self.xml.get_object('finish_label')
- self.go_online_checkbutton = self.xml.get_object(
- 'go_online_checkbutton')
- self.show_vcard_checkbutton = self.xml.get_object(
- 'show_vcard_checkbutton')
- self.progressbar = self.xml.get_object('progressbar')
-
- # some vars
- self.update_progressbar_timeout_id = None
-
- self.notebook.set_current_page(0)
- self.xml.connect_signals(self)
- self.window.show_all()
- gajim.ged.register_event_handler('new-account-connected', ged.GUI1,
- self._nec_new_acc_connected)
- gajim.ged.register_event_handler('new-account-not-connected', ged.GUI1,
- self._nec_new_acc_not_connected)
- gajim.ged.register_event_handler('account-created', ged.GUI1,
- self._nec_acc_is_ok)
- gajim.ged.register_event_handler('account-not-created', ged.GUI1,
- self._nec_acc_is_not_ok)
-
- def on_wizard_window_destroy(self, widget):
- page = self.notebook.get_current_page()
- if page in (4, 5) and self.account in gajim.connections:
- # connection instance is saved in gajim.connections and we canceled
- # the addition of the account
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- gajim.ged.remove_event_handler('new-account-connected', ged.GUI1,
- self._nec_new_acc_connected)
- gajim.ged.remove_event_handler('new-account-not-connected', ged.GUI1,
- self._nec_new_acc_not_connected)
- gajim.ged.remove_event_handler('account-created', ged.GUI1,
- self._nec_acc_is_ok)
- gajim.ged.remove_event_handler('account-not-created', ged.GUI1,
- self._nec_acc_is_not_ok)
- del gajim.interface.instances['account_creation_wizard']
-
- def on_register_server_features_button_clicked(self, widget):
- helpers.launch_browser_mailer('url',
- 'http://www.jabber.org/network/oldnetwork.shtml')
-
- def on_save_password_checkbutton_toggled(self, widget):
- self.xml.get_object('password_entry').grab_focus()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_back_button_clicked(self, widget):
- cur_page = self.notebook.get_current_page()
- self.forward_button.set_sensitive(True)
- if cur_page in (1, 2):
- self.notebook.set_current_page(0)
- self.back_button.set_sensitive(False)
- elif cur_page == 3:
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- self.notebook.set_current_page(2) # show server page
- elif cur_page == 4:
- if self.account in gajim.connections:
- del gajim.connections[self.account]
- self.notebook.set_current_page(2)
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- elif cur_page == 6: # finish page
- self.forward_button.show()
- if self.modify:
- self.notebook.set_current_page(1) # Go to parameters page
- else:
- self.notebook.set_current_page(2) # Go to server page
-
- def on_anonymous_checkbutton1_toggled(self, widget):
- active = widget.get_active()
- self.xml.get_object('username_entry').set_sensitive(not active)
- self.xml.get_object('password_entry').set_sensitive(not active)
- self.xml.get_object('save_password_checkbutton').set_sensitive(
- not active)
-
- def show_finish_page(self):
- self.cancel_button.hide()
- self.back_button.hide()
- self.forward_button.hide()
- if self.modify:
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('Account has been added successfully'),
- _('You can set advanced account options by pressing the '
- 'Advanced button, or later by choosing the Accounts menu item '
- 'under the Edit menu from the main window.'))
- else:
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('Your new account has been created successfully'),
- _('You can set advanced account options by pressing the '
- 'Advanced button, or later by choosing the Accounts menu item '
- 'under the Edit menu from the main window.'))
- self.finish_label.set_markup(finish_text)
- self.finish_button.show()
- self.finish_button.set_property('has-default', True)
- self.advanced_button.show()
- self.go_online_checkbutton.show()
- img = self.xml.get_object('finish_image')
- if self.modify:
- img.set_from_stock(Gtk.STOCK_APPLY, Gtk.IconSize.DIALOG)
- else:
- path_to_file = gtkgui_helpers.get_icon_path('org.gajim.Gajim', 48)
- img.set_from_file(path_to_file)
- self.show_vcard_checkbutton.set_active(not self.modify)
- self.notebook.set_current_page(6) # show finish page
-
- def on_forward_button_clicked(self, widget):
- cur_page = self.notebook.get_current_page()
-
- if cur_page == 0:
- widget = self.xml.get_object('use_existing_account_radiobutton')
- if widget.get_active():
- self.modify = True
- self.notebook.set_current_page(1)
- else:
- self.modify = False
- self.notebook.set_current_page(2)
- self.back_button.set_sensitive(True)
- return
-
- elif cur_page == 1:
- # We are adding an existing account
- anonymous = self.xml.get_object('anonymous_checkbutton1').\
- get_active()
- username = self.xml.get_object('username_entry').get_text().strip()
- if not username and not anonymous:
- pritext = _('Invalid username')
- sectext = _(
- 'You must provide a username to configure this account.')
- dialogs.ErrorDialog(pritext, sectext)
- return
- server = self.xml.get_object('server_comboboxtext_entry').\
- get_text().strip()
- savepass = self.xml.get_object('save_password_checkbutton').\
- get_active()
- password = self.xml.get_object('password_entry').get_text()
-
- if anonymous:
- jid = ''
- else:
- jid = username + '@'
- jid += server
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as s:
- pritext = _('Invalid JID')
- dialogs.ErrorDialog(pritext, str(s))
- return
-
- self.account = server
- i = 1
- while self.account in gajim.connections:
- self.account = server + str(i)
- i += 1
-
- username, server = gajim.get_name_and_server_from_jid(jid)
- if self.xml.get_object('anonymous_checkbutton1').get_active():
- self.save_account('', server, False, '', anonymous=True)
- else:
- self.save_account(username, server, savepass, password)
- self.show_finish_page()
- elif cur_page == 2:
- # We are creating a new account
- server = self.xml.get_object('server_comboboxtext_entry1').\
- get_text()
-
- if not server:
- dialogs.ErrorDialog(_('Invalid server'),
- _('Please provide a server on which you want to register.'))
- return
- self.account = server
- i = 1
- while self.account in gajim.connections:
- self.account = server + str(i)
- i += 1
-
- config = self.get_config('', server, '', '')
- # Get advanced options
- proxies_combobox = self.xml.get_object('proxies_combobox')
- active = proxies_combobox.get_active()
- proxy = proxies_combobox.get_model()[active][0]
- if proxy == _('None'):
- proxy = ''
- config['proxy'] = proxy
-
- config['use_custom_host'] = self.xml.get_object(
- 'custom_host_port_checkbutton').get_active()
- custom_port = self.xml.get_object('custom_port_entry').get_text()
- try:
- custom_port = int(custom_port)
- except Exception:
- dialogs.ErrorDialog(_('Invalid entry'),
- _('Custom port must be a port number.'))
- return
- config['custom_port'] = custom_port
- config['custom_host'] = self.xml.get_object(
- 'custom_host_entry').get_text()
-
- if self.xml.get_object('anonymous_checkbutton2').get_active():
- self.modify = True
- self.save_account('', server, False, '', anonymous=True)
- self.show_finish_page()
- else:
- self.notebook.set_current_page(5) # show creating page
- self.back_button.hide()
- self.forward_button.hide()
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
- # Get form from serveur
- con = connection.Connection(self.account)
- gajim.connections[self.account] = con
- con.new_account(self.account, config)
- elif cur_page == 3:
- checked = self.xml.get_object('ssl_checkbutton').get_active()
- if checked:
- hostname = gajim.connections[self.account].new_account_info[
- 'hostname']
- # Check if cert is already in file
- certs = ''
- if os.path.isfile(gajim.MY_CACERTS):
- f = open(gajim.MY_CACERTS)
- certs = f.read()
- f.close()
- if self.ssl_cert in certs:
- dialogs.ErrorDialog(_('Certificate Already in File'),
- _('This certificate is already in file %s, so it\'s '
- 'not added again.') % gajim.MY_CACERTS)
- else:
- f = open(gajim.MY_CACERTS, 'a')
- f.write(hostname + '\n')
- f.write(self.ssl_cert + '\n\n')
- f.close()
- gajim.connections[self.account].new_account_info[
- 'ssl_fingerprint_sha1'] = self.ssl_fingerprint_sha1
- gajim.connections[self.account].new_account_info[
- 'ssl_fingerprint_sha256'] = self.ssl_fingerprint_sha256
- self.notebook.set_current_page(4) # show fom page
- elif cur_page == 4:
- if self.is_form:
- form = self.data_form_widget.data_form
- else:
- form = self.data_form_widget.get_infos()
- gajim.connections[self.account].send_new_account_infos(form,
- self.is_form)
- self.xml.get_object('form_vbox').remove(self.data_form_widget)
- self.xml.get_object('progressbar_label').set_markup(
- '<b>Account is being created</b>\n\nPlease wait…')
- self.notebook.set_current_page(5) # show creating page
- self.back_button.hide()
- self.forward_button.hide()
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
-
- def update_proxy_list(self):
- proxies_combobox = self.xml.get_object('proxies_combobox')
- model = Gtk.ListStore(str)
- proxies_combobox.set_model(model)
- l = gajim.config.get_per('proxies')
- l.insert(0, _('None'))
- for i in range(len(l)):
- model.append([l[i]])
- proxies_combobox.set_active(0)
-
- def on_manage_proxies_button_clicked(self, widget):
- if 'manage_proxies' in gajim.interface.instances:
- gajim.interface.instances['manage_proxies'].window.present()
- else:
- gajim.interface.instances['manage_proxies'] = \
- ManageProxiesWindow()
-
- def on_custom_host_port_checkbutton_toggled(self, widget):
- self.xml.get_object('custom_host_hbox').set_sensitive(widget.\
- get_active())
-
- def update_progressbar(self):
- self.progressbar.pulse()
- return True # loop forever
-
- def _nec_new_acc_connected(self, obj):
- """
- Connection to server succeded, present the form to the user
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- self.back_button.show()
- self.forward_button.show()
- self.is_form = obj.is_form
- empty_config = True
- if obj.is_form:
- dataform = dataforms.ExtendForm(node=obj.config)
- self.data_form_widget = dataforms_widget.DataFormWidget()
- self.data_form_widget.selectable = True
- self.data_form_widget.set_data_form(dataform)
- empty_config = False
- else:
- self.data_form_widget = FakeDataForm(obj.config, selectable=True)
- for field in obj.config:
- if field in ('key', 'instructions', 'x', 'registered'):
- continue
- empty_config = False
- break
- self.data_form_widget.show_all()
- self.xml.get_object('form_vbox').pack_start(self.data_form_widget, True, True, 0)
- if empty_config:
- self.forward_button.set_sensitive(False)
- self.notebook.set_current_page(4) # show form page
- return
- self.ssl_fingerprint_sha1 = obj.ssl_fingerprint_sha1
- self.ssl_fingerprint_sha256 = obj.ssl_fingerprint_sha256
- self.ssl_cert = obj.ssl_cert
- if obj.ssl_msg:
- # An SSL warning occured, show it
- hostname = gajim.connections[self.account].new_account_info[
- 'hostname']
- self.xml.get_object('ssl_label').set_markup(_(
- '<b>Security Warning</b>'
- '\n\nThe authenticity of the %(hostname)s SSL certificate could'
- ' be invalid.\nSSL Error: %(error)s\n'
- 'Do you still want to connect to this server?') % {
- 'hostname': hostname, 'error': obj.ssl_msg})
- if obj.errnum in (18, 27):
- text = _('Add this certificate to the list of trusted '
- 'certificates.\nSHA-1 fingerprint of the certificate:\n%s'
- '\nSHA-256 fingerprint of the certificate:\n%s') \
- % (obj.ssl_fingerprint_sha1, obj.ssl_fingerprint_sha256)
- self.xml.get_object('ssl_checkbutton').set_label(text)
- else:
- self.xml.get_object('ssl_checkbutton').set_no_show_all(True)
- self.xml.get_object('ssl_checkbutton').hide()
- self.notebook.set_current_page(3) # show SSL page
- else:
- self.notebook.set_current_page(4) # show form page
-
- def _nec_new_acc_not_connected(self, obj):
- """
- Account creation failed: connection to server failed
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- if self.account not in gajim.connections:
- return
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- self.back_button.show()
- self.cancel_button.show()
- self.go_online_checkbutton.hide()
- self.show_vcard_checkbutton.hide()
- img = self.xml.get_object('finish_image')
- img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.DIALOG)
- finish_text = '<big><b>%s</b></big>\n\n%s' % (
- _('An error occurred during account creation'), obj.reason)
- self.finish_label.set_markup(finish_text)
- self.notebook.set_current_page(6) # show finish page
-
- def _nec_acc_is_ok(self, obj):
- """
- Account creation succeeded
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- self.create_vars(obj.account_info)
- self.show_finish_page()
-
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
-
- def _nec_acc_is_not_ok(self, obj):
- """
- Account creation failed
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- self.back_button.show()
- self.cancel_button.show()
- self.go_online_checkbutton.hide()
- self.show_vcard_checkbutton.hide()
- del gajim.connections[self.account]
- if self.account in gajim.config.get_per('accounts'):
- gajim.config.del_per('accounts', self.account)
- img = self.xml.get_object('finish_image')
- img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.DIALOG)
- finish_text = '<big><b>%s</b></big>\n\n%s' % (_(
- 'An error occurred during account creation'), obj.reason)
- self.finish_label.set_markup(finish_text)
- self.notebook.set_current_page(6) # show finish page
-
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
-
- def on_advanced_button_clicked(self, widget):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = AccountsWindow()
- gajim.interface.instances['accounts'].select_account(self.account)
- self.window.destroy()
-
- def on_finish_button_clicked(self, widget):
- go_online = self.xml.get_object('go_online_checkbutton').get_active()
- show_vcard = self.xml.get_object('show_vcard_checkbutton').get_active()
- self.window.destroy()
- if show_vcard:
- gajim.interface.show_vcard_when_connect.append(self.account)
- if go_online:
- gajim.interface.roster.send_status(self.account, 'online', '')
-
- def on_username_entry_key_press_event(self, widget, event):
- # Check for pressed @ and jump to combobox if found
- if event.keyval == Gdk.KEY_at:
- entry = self.xml.get_object('server_comboboxtext_entry')
- entry.grab_focus()
- entry.set_position(-1)
- return True
-
- def on_server_comboboxentry_key_press_event(self, widget, event, combobox):
- # If backspace is pressed in empty field, return to the nick entry field
- backspace = event.keyval == Gdk.KEY_BackSpace
- empty = len(combobox.get_active_text()) == 0
- if backspace and empty and self.modify:
- username_entry = self.xml.get_object('username_entry')
- username_entry.grab_focus()
- username_entry.set_position(-1)
- return True
-
- def get_config(self, login, server, savepass, password, anonymous=False):
- config = {}
- config['name'] = login
- config['hostname'] = server
- config['savepass'] = savepass
- config['password'] = password
- config['anonymous_auth'] = anonymous
- config['priority'] = 5
- config['autoconnect'] = True
- config['no_log_for'] = ''
- config['sync_with_global_status'] = True
- config['proxy'] = ''
- config['use_custom_host'] = False
- config['custom_port'] = 0
- config['custom_host'] = ''
- config['keyname'] = ''
- config['keyid'] = ''
- return config
-
- def save_account(self, login, server, savepass, password, anonymous=False):
- if self.account in gajim.connections:
- dialogs.ErrorDialog(_('Account name is in use'),
- _('You already have an account using this name.'))
- return
- con = connection.Connection(self.account)
- con.password = password
-
- config = self.get_config(login, server, savepass, password, anonymous)
-
- if not self.modify:
- con.new_account(self.account, config)
- return
- gajim.connections[self.account] = con
- self.create_vars(config)
-
- def create_vars(self, config):
- gajim.config.add_per('accounts', self.account)
-
- if not config['savepass']:
- config['password'] = ''
-
- for opt in config:
- gajim.config.set_per('accounts', self.account, opt, config[opt])
-
- # update variables
- gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
- 'gc_config': {}, 'search': {}, 'online_dialog': {},
- 'sub_request': {}}
- gajim.interface.minimized_controls[self.account] = {}
- gajim.connections[self.account].connected = 0
- gajim.connections[self.account].keepalives = gajim.config.get_per(
- 'accounts', self.account, 'keep_alive_every_foo_secs')
- gajim.groups[self.account] = {}
- gajim.contacts.add_account(self.account)
- gajim.gc_connected[self.account] = {}
- gajim.automatic_rooms[self.account] = {}
- gajim.newly_added[self.account] = []
- gajim.to_be_removed[self.account] = []
- gajim.nicks[self.account] = config['name']
- gajim.block_signed_in_notifications[self.account] = True
- gajim.sleeper_state[self.account] = 'off'
- gajim.encrypted_chats[self.account] = []
- gajim.last_message_time[self.account] = {}
- gajim.status_before_autoaway[self.account] = ''
- gajim.transport_avatar[self.account] = {}
- gajim.gajim_optional_features[self.account] = []
- gajim.caps_hash[self.account] = ''
- helpers.update_optional_features(self.account)
- # refresh accounts window
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].init_accounts()
- # refresh roster
- if len(gajim.connections) >= 2:
- # Do not merge accounts if only one exists
- gajim.interface.roster.regroup = gajim.config.get('mergeaccounts')
- else:
- gajim.interface.roster.regroup = False
- gajim.interface.roster.setup_and_draw_roster()
- gajim.app.add_account_actions(self.account)
- gui_menu_builder.build_accounts_menu()
-
-class ManagePEPServicesWindow:
- def __init__(self, account):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_pep_services_window.ui')
- self.window = self.xml.get_object('manage_pep_services_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.xml.get_object('configure_button').set_sensitive(False)
- self.xml.get_object('delete_button').set_sensitive(False)
- self.xml.connect_signals(self)
- self.account = account
-
- self.init_services()
- self.xml.get_object('services_treeview').get_selection().connect(
- 'changed', self.on_services_selection_changed)
-
- gajim.ged.register_event_handler('pep-config-received', ged.GUI1,
- self._nec_pep_config_received)
- gajim.ged.register_event_handler('agent-items-received', ged.GUI1,
- self._nec_agent_items_received)
-
- self.window.show_all()
-
- def on_manage_pep_services_window_destroy(self, widget):
- '''close window'''
- del gajim.interface.instances[self.account]['pep_services']
- gajim.ged.remove_event_handler('pep-config-received', ged.GUI1,
- self._nec_pep_config_received)
- gajim.ged.remove_event_handler('agent-items-received', ged.GUI1,
- self._nec_agent_items_received)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_services_selection_changed(self, sel):
- self.xml.get_object('configure_button').set_sensitive(True)
- self.xml.get_object('delete_button').set_sensitive(True)
-
- def init_services(self):
- self.treeview = self.xml.get_object('services_treeview')
- # service, access_model, group
- self.treestore = Gtk.ListStore(str)
- self.treeview.set_model(self.treestore)
-
- col = Gtk.TreeViewColumn('Service')
- self.treeview.append_column(col)
-
- cellrenderer_text = Gtk.CellRendererText()
- col.pack_start(cellrenderer_text, True, True, 0)
- col.add_attribute(cellrenderer_text, 'text', 0)
-
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].discoverItems(our_jid)
-
- def _nec_agent_items_received(self, obj):
- our_jid = gajim.get_jid_from_account(self.account)
- for item in obj.items:
- if 'jid' in item and item['jid'] == our_jid and 'node' in item:
- self.treestore.append([item['node']])
-
- def node_removed(self, jid, node):
- if jid != gajim.get_jid_from_account(self.account):
- return
- model = self.treeview.get_model()
- iter_ = model.get_iter_first()
- while iter_:
- if model[iter_][0] == node:
- model.remove(iter_)
- break
- iter_ = model.iter_next(iter_)
-
- def node_not_removed(self, jid, node, msg):
- if jid != gajim.get_jid_from_account(self.account):
- return
- dialogs.WarningDialog(_('PEP node was not removed'),
- _('PEP node %(node)s was not removed: %(message)s') % {'node': node,
- 'message': msg})
-
- def on_delete_button_clicked(self, widget):
- selection = self.treeview.get_selection()
- if not selection:
- return
- model, iter_ = selection.get_selected()
- node = model[iter_][0]
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].send_pb_delete(our_jid, node,
- on_ok=self.node_removed, on_fail=self.node_not_removed)
-
- def on_configure_button_clicked(self, widget):
- selection = self.treeview.get_selection()
- if not selection:
- return
- model, iter_ = selection.get_selected()
- node = model[iter_][0]
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].request_pb_configuration(our_jid, node)
-
- def _nec_pep_config_received(self, obj):
- def on_ok(form, node):
- form.type_ = 'submit'
- our_jid = gajim.get_jid_from_account(self.account)
- gajim.connections[self.account].send_pb_configure(our_jid, node, form)
- window = dialogs.DataFormWindow(obj.form, (on_ok, obj.node))
- title = _('Configure %s') % obj.node
- window.set_title(title)
- window.show_all()
-
-class ManageSoundsWindow:
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('manage_sounds_window.ui')
- self.window = self.xml.get_object('manage_sounds_window')
- self.window.set_transient_for(
- gajim.interface.instances['preferences'].window)
- # sounds treeview
- self.sound_tree = self.xml.get_object('sounds_treeview')
-
- # active, event ui name, path to sound file, event_config_name
- model = Gtk.ListStore(bool, str, str, str)
- self.sound_tree.set_model(model)
-
- col = Gtk.TreeViewColumn(_('Active'))
- self.sound_tree.append_column(col)
- renderer = Gtk.CellRendererToggle()
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.sound_toggled_cb)
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'active', 0)
-
- col = Gtk.TreeViewColumn(_('Event'))
- self.sound_tree.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 1)
-
- self.fill_sound_treeview()
-
- self.xml.connect_signals(self)
-
- self.sound_tree.get_model().connect('row-changed',
- self.on_sounds_treemodel_row_changed)
-
- self.window.show_all()
-
- def on_sounds_treemodel_row_changed(self, model, path, iter_):
- sound_event = model[iter_][3]
- gajim.config.set_per('soundevents', sound_event, 'enabled',
- bool(model[path][0]))
- gajim.config.set_per('soundevents', sound_event, 'path',
- model[iter_][2])
-
- def sound_toggled_cb(self, cell, path):
- model = self.sound_tree.get_model()
- model[path][0] = not model[path][0]
-
- def fill_sound_treeview(self):
- model = self.sound_tree.get_model()
- model.clear()
- model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
-
- # NOTE: sounds_ui_names MUST have all items of
- # sounds = gajim.config.get_per('soundevents') as keys
- sounds_dict = {
- 'attention_received': _('Attention Message Received'),
- 'first_message_received': _('First Message Received'),
- 'next_message_received_focused': _('Next Message Received Focused'),
- 'next_message_received_unfocused':
- _('Next Message Received Unfocused'),
- 'contact_connected': _('Contact Connected'),
- 'contact_disconnected': _('Contact Disconnected'),
- 'message_sent': _('Message Sent'),
- 'muc_message_highlight': _('Group Chat Message Highlight'),
- 'muc_message_received': _('Group Chat Message Received'),
- 'gmail_received': _('GMail Email Received')
- }
-
- for sound_event_config_name, sound_ui_name in sounds_dict.items():
- enabled = gajim.config.get_per('soundevents',
- sound_event_config_name, 'enabled')
- path = gajim.config.get_per('soundevents',
- sound_event_config_name, 'path')
- model.append((enabled, sound_ui_name, path, sound_event_config_name))
-
- def on_treeview_sounds_cursor_changed(self, widget, data = None):
- sounds_entry = self.xml.get_object('sounds_entry')
- sel = self.sound_tree.get_selection()
- if not sel:
- sounds_entry.set_text('')
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- sounds_entry.set_text('')
- return
- path_to_snd_file = model[iter_][2]
- sounds_entry.set_text(path_to_snd_file)
-
- def on_browse_for_sounds_button_clicked(self, widget, data = None):
- sel = self.sound_tree.get_selection()
- if not sel:
- return
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- def on_ok(widget, path_to_snd_file):
- self.dialog.destroy()
- model, iter_ = self.sound_tree.get_selection().get_selected()
- if not path_to_snd_file:
- model[iter_][2] = ''
- self.xml.get_object('sounds_entry').set_text('')
- model[iter_][0] = False
- return
- directory = os.path.dirname(path_to_snd_file)
- gajim.config.set('last_sounds_dir', directory)
- path_to_snd_file = helpers.strip_soundfile_path(path_to_snd_file)
- self.xml.get_object('sounds_entry').set_text(path_to_snd_file)
-
- model[iter_][2] = path_to_snd_file # set new path to sounds_model
- model[iter_][0] = True # set the sound to enabled
-
- def on_cancel(widget):
- self.dialog.destroy()
-
- path_to_snd_file = model[iter_][2]
- self.dialog = dialogs.SoundChooserDialog(path_to_snd_file, on_ok,
- on_cancel, transient_for=self.window)
-
- def on_sounds_entry_changed(self, widget):
- path_to_snd_file = widget.get_text()
- model, iter_ = self.sound_tree.get_selection().get_selected()
- model[iter_][2] = path_to_snd_file # set new path to sounds_model
-
- def on_play_button_clicked(self, widget):
- sel = self.sound_tree.get_selection()
- if not sel:
- return
- model, iter_ = sel.get_selected()
- if not iter_:
- return
- snd_event_config_name = model[iter_][3]
- helpers.play_sound(snd_event_config_name)
-
- def on_close_button_clicked(self, widget):
- self.window.hide()
-
- def on_manage_sounds_window_delete_event(self, widget, event):
- self.window.hide()
- return True # do NOT destroy the window
diff --git a/src/conversation_textview.py b/src/conversation_textview.py
deleted file mode 100644
index 40924b2e1..000000000
--- a/src/conversation_textview.py
+++ /dev/null
@@ -1,1422 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/conversation_textview.py
-##
-## Copyright (C) 2005 Norman Rasmussen <norman AT rasmussen.co.za>
-## Copyright (C) 2005-2006 Alex Mauer <hawke AT hawkesnest.net>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-from threading import Timer # for smooth scrolling
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import Pango
-from gi.repository import GObject
-from gi.repository import GLib
-import time
-import os
-import tooltips
-import dialogs
-import queue
-import urllib
-
-import gtkgui_helpers
-from common import gajim
-from common import helpers
-from common import i18n
-from calendar import timegm
-from common.fuzzyclock import FuzzyClock
-
-from htmltextview import HtmlTextView
-from common.exceptions import GajimGeneralException
-
-NOT_SHOWN = 0
-ALREADY_RECEIVED = 1
-SHOWN = 2
-
-import logging
-log = logging.getLogger('gajim.conversation_textview')
-
-def is_selection_modified(mark):
- name = mark.get_name()
- if name and name in ('selection_bound', 'insert'):
- return True
- else:
- return False
-
-def has_focus(widget):
- return widget.get_state_flags() & Gtk.StateFlags.FOCUSED == \
- Gtk.StateFlags.FOCUSED
-
-class TextViewImage(Gtk.Image):
-
- def __init__(self, anchor, text):
- super(TextViewImage, self).__init__()
- self.anchor = anchor
- self._selected = False
- self._disconnect_funcs = []
- self.connect('parent-set', self.on_parent_set)
- self.set_tooltip_markup(text)
- self.anchor.plaintext = text
-
- def _get_selected(self):
- parent = self.get_parent()
- if not parent or not self.anchor: return False
- buffer_ = parent.get_buffer()
- position = buffer_.get_iter_at_child_anchor(self.anchor)
- bounds = buffer_.get_selection_bounds()
- if bounds and position.in_range(*bounds):
- return True
- else:
- return False
-
- def get_state(self):
- parent = self.get_parent()
- if not parent:
- return Gtk.StateType.NORMAL
- if self._selected:
- if has_focus(parent):
- return Gtk.StateType.SELECTED
- else:
- return Gtk.StateType.ACTIVE
- else:
- return Gtk.StateType.NORMAL
-
- def _update_selected(self):
- selected = self._get_selected()
- if self._selected != selected:
- self._selected = selected
- self.queue_draw()
-
- def _do_connect(self, widget, signal, callback):
- id_ = widget.connect(signal, callback)
- def disconnect():
- widget.disconnect(id_)
- self._disconnect_funcs.append(disconnect)
-
- def _disconnect_signals(self):
- for func in self._disconnect_funcs:
- func()
- self._disconnect_funcs = []
-
- def on_parent_set(self, widget, old_parent):
- parent = self.get_parent()
- if not parent:
- self._disconnect_signals()
- return
- if isinstance(parent, Gtk.EventBox):
- parent = parent.get_parent()
- if not parent:
- self._disconnect_signals()
- return
-
- self._do_connect(parent, 'style-set', self.do_queue_draw)
- self._do_connect(parent, 'focus-in-event', self.do_queue_draw)
- self._do_connect(parent, 'focus-out-event', self.do_queue_draw)
-
- textbuf = parent.get_buffer()
- self._do_connect(textbuf, 'mark-set', self.on_mark_set)
- self._do_connect(textbuf, 'mark-deleted', self.on_mark_deleted)
-
- def do_queue_draw(self, *args):
- self.queue_draw()
- return False
-
- def on_mark_set(self, buf, iterat, mark):
- self.on_mark_modified(mark)
- return False
-
- def on_mark_deleted(self, buf, mark):
- self.on_mark_modified(mark)
- return False
-
- def on_mark_modified(self, mark):
- if is_selection_modified(mark):
- self._update_selected()
-
-class ConversationTextview(GObject.GObject):
- """
- Class for the conversation textview (where user reads already said messages)
- for chat/groupchat windows
- """
- __gsignals__ = dict(
- quote = (GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
- None, # return value
- (str, ) # arguments
- )
- )
-
- MESSAGE_CORRECTED_PIXBUF = gtkgui_helpers.get_icon_pixmap(
- 'document-edit-symbolic')
- MESSAGE_ENCRYPTED_PIXBUF = gtkgui_helpers.get_icon_pixmap(
- 'channel-secure-croped-symbolic')
-
- # smooth scroll constants
- MAX_SCROLL_TIME = 0.4 # seconds
- SCROLL_DELAY = 33 # milliseconds
-
- def __init__(self, account, used_in_history_window = False):
- """
- If used_in_history_window is True, then we do not show Clear menuitem in
- context menu
- """
- GObject.GObject.__init__(self)
- self.used_in_history_window = used_in_history_window
- self.line = 0
- self.message_list = []
- self.corrected_text_list = {}
- self.fc = FuzzyClock()
-
- # no need to inherit TextView, use it as atrribute is safer
- self.tv = HtmlTextView()
- # we have to override HtmlTextView Event handlers
- # because we dont inherit
- self.tv.hyperlink_handler = self.hyperlink_handler
- self.tv.connect_tooltip(self.query_tooltip)
-
- # set properties
- self.tv.set_border_width(1)
- self.tv.set_accepts_tab(True)
- self.tv.set_editable(False)
- self.tv.set_cursor_visible(False)
- self.tv.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
- self.tv.set_left_margin(2)
- self.tv.set_right_margin(2)
- self.handlers = {}
- self.images = []
- self.image_cache = {}
- self.xep0184_marks = {}
- # self.last_sent_message_id = msg_stanza_id
- self.last_sent_message_id = None
- # last_received_message_id[name] = (msg_stanza_id, line_start_mark)
- self.last_received_message_id = {}
-
- # connect signals
- id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
- self.handlers[id_] = self.tv
- id_ = self.tv.connect('button_press_event',
- self.on_textview_button_press_event)
- self.handlers[id_] = self.tv
- id_ = self.tv.connect('draw', self.on_textview_draw)
- self.handlers[id_] = self.tv
-
- self.account = account
- self.cursor_changed = False
- self.last_time_printout = 0
-
- style = self.tv.get_style_context()
- style.add_class('font_custom')
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- buffer_.create_mark('end', end_iter, False)
-
- self.tagIn = buffer_.create_tag('incoming')
- color = gajim.config.get('inmsgcolor')
- font = Pango.FontDescription(gajim.config.get('inmsgfont'))
- self.tagIn.set_property('foreground', color)
- self.tagIn.set_property('font-desc', font)
-
- self.tagOut = buffer_.create_tag('outgoing')
- color = gajim.config.get('outmsgcolor')
- font = Pango.FontDescription(gajim.config.get('outmsgfont'))
- self.tagOut.set_property('foreground', color)
- self.tagOut.set_property('font-desc', font)
-
- self.tagStatus = buffer_.create_tag('status')
- color = gajim.config.get('statusmsgcolor')
- font = Pango.FontDescription(gajim.config.get('satusmsgfont'))
- self.tagStatus.set_property('foreground', color)
- self.tagStatus.set_property('font-desc', font)
-
- self.tagInText = buffer_.create_tag('incomingtxt')
- color = gajim.config.get('inmsgtxtcolor')
- font = Pango.FontDescription(gajim.config.get('inmsgtxtfont'))
- if color:
- self.tagInText.set_property('foreground', color)
- self.tagInText.set_property('font-desc', font)
-
- self.tagOutText = buffer_.create_tag('outgoingtxt')
- color = gajim.config.get('outmsgtxtcolor')
- if color:
- font = Pango.FontDescription(gajim.config.get('outmsgtxtfont'))
- self.tagOutText.set_property('foreground', color)
- self.tagOutText.set_property('font-desc', font)
-
- colors = gajim.config.get('gc_nicknames_colors')
- colors = colors.split(':')
- for i, color in enumerate(colors):
- tagname = 'gc_nickname_color_' + str(i)
- tag = buffer_.create_tag(tagname)
- tag.set_property('foreground', color)
-
- self.tagMarked = buffer_.create_tag('marked')
- color = gajim.config.get('markedmsgcolor')
- self.tagMarked.set_property('foreground', color)
- self.tagMarked.set_property('weight', Pango.Weight.BOLD)
-
- tag = buffer_.create_tag('time_sometimes')
- tag.set_property('foreground', 'darkgrey')
- #Pango.SCALE_SMALL
- tag.set_property('scale', 0.8333333333333)
- tag.set_property('justification', Gtk.Justification.CENTER)
-
- tag = buffer_.create_tag('small')
- #Pango.SCALE_SMALL
- tag.set_property('scale', 0.8333333333333)
-
- tag = buffer_.create_tag('restored_message')
- color = gajim.config.get('restored_messages_color')
- tag.set_property('foreground', color)
-
- self.tv.create_tags()
-
- tag = buffer_.create_tag('bold')
- tag.set_property('weight', Pango.Weight.BOLD)
-
- tag = buffer_.create_tag('italic')
- tag.set_property('style', Pango.Style.ITALIC)
-
- tag = buffer_.create_tag('underline')
- tag.set_property('underline', Pango.Underline.SINGLE)
-
- buffer_.create_tag('focus-out-line', justification = Gtk.Justification.CENTER)
- self.displaymarking_tags = {}
-
- tag = buffer_.create_tag('xep0184-received')
- tag.set_property('foreground', '#73d216')
-
- # One mark at the begining then 2 marks between each lines
- size = gajim.config.get('max_conversation_lines')
- size = 2 * size - 1
- self.marks_queue = queue.Queue(size)
-
- self.allow_focus_out_line = True
- # holds a mark at the end of --- line
- self.focus_out_end_mark = None
-
- self.just_cleared = False
-
- def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
- window = widget.get_window(Gtk.TextWindowType.TEXT)
- x_pos, y_pos = self.tv.window_to_buffer_coords(
- Gtk.TextWindowType.TEXT, x_pos, y_pos)
- if Gtk.MINOR_VERSION > 18:
- iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[1]
- else:
- iter_ = self.tv.get_iter_at_position(x_pos, y_pos)[0]
- for tag in iter_.get_tags():
- tag_name = tag.get_property('name')
- if tag_name == 'focus-out-line':
- tooltip.set_text(_(
- 'Text below this line is what has '
- 'been said since the\nlast time you paid attention to this '
- 'group chat'))
- return True
- if getattr(tag, 'is_anchor', False):
- text = getattr(tag, 'title', False)
- if text:
- if len(text) > 50:
- text = text[:47] + '…'
- tooltip.set_text(text)
- window.set_cursor(gtkgui_helpers.get_cursor('HAND2'))
- self.cursor_changed = True
- return True
- if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
- window.set_cursor(gtkgui_helpers.get_cursor('HAND2'))
- self.cursor_changed = True
- return False
- try:
- text = self.corrected_text_list[tag_name]
- tooltip.set_markup(text)
- return True
- except KeyError:
- pass
- if self.cursor_changed:
- window.set_cursor(gtkgui_helpers.get_cursor('XTERM'))
- self.cursor_changed = False
- return False
-
- def del_handlers(self):
- for i in self.handlers.keys():
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers
- self.tv.destroy()
-
- def update_tags(self):
- self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
- self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
- self.tagStatus.set_property('foreground',
- gajim.config.get('statusmsgcolor'))
- self.tagMarked.set_property('foreground',
- gajim.config.get('markedmsgcolor'))
- color = gajim.config.get('urlmsgcolor')
- self.tv.tagURL.set_property('foreground', color)
- self.tv.tagMail.set_property('foreground', color)
- self.tv.tagXMPP.set_property('foreground', color)
- self.tv.tagSthAtSth.set_property('foreground', color)
-
- def at_the_end(self):
- return gtkgui_helpers.at_the_end(self.tv.get_parent())
-
- def scroll_to_end_iter(self):
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- if not end_iter:
- return False
- self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
- return False # when called in an idle_add, just do it once
-
- def correct_message(self, correct_id, kind, name):
- allowed = True
- if kind == 'incoming':
- try:
- if correct_id in self.last_received_message_id[name]:
- start_mark = self.last_received_message_id[name][1]
- else:
- allowed = False
- except KeyError:
- allowed = False
- elif kind == 'outgoing':
- if self.last_sent_message_id[0] == correct_id:
- start_mark = self.last_sent_message_id[1]
- else:
- allowed = False
- else:
- allowed = False
-
- if not allowed:
- log.debug('Message correctiong not allowed')
- return None
-
- end_mark, index = self.get_end_mark(correct_id, start_mark)
- if not index:
- log.debug('Could not find line to correct')
- return None
-
- buffer_ = self.tv.get_buffer()
- if not end_mark:
- end_iter = self.tv.get_buffer().get_end_iter()
- else:
- end_iter = buffer_.get_iter_at_mark(end_mark)
-
- start_iter = buffer_.get_iter_at_mark(start_mark)
-
- old_txt = buffer_.get_text(start_iter, end_iter, True)
- buffer_.delete(start_iter, end_iter)
- buffer_.delete_mark(start_mark)
-
- return index, end_mark, old_txt
-
- def add_xep0184_mark(self, id_):
- if id_ in self.xep0184_marks:
- return
-
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
-
- self.xep0184_marks[id_] = buffer_.create_mark(
- None, buffer_.get_end_iter(), left_gravity=True)
-
- buffer_.end_user_action()
-
- def show_xep0184_ack(self, id_):
- if id_ not in self.xep0184_marks:
- return
-
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
-
- if gajim.config.get('positive_184_ack'):
- begin_iter = buffer_.get_iter_at_mark(self.xep0184_marks[id_])
- buffer_.insert_with_tags_by_name(begin_iter, ' ✓',
- 'xep0184-received')
-
- buffer_.end_user_action()
- del self.xep0184_marks[id_]
-
- def show_focus_out_line(self, scroll=True):
- if not self.allow_focus_out_line:
- # if room did not receive focus-in from the last time we added
- # --- line then do not readd
- return
-
- print_focus_out_line = False
- buffer_ = self.tv.get_buffer()
-
- if self.focus_out_end_mark is None:
- # this happens only first time we focus out on this room
- print_focus_out_line = True
-
- else:
- focus_out_end_iter = buffer_.get_iter_at_mark(self.focus_out_end_mark)
- focus_out_end_iter_offset = focus_out_end_iter.get_offset()
- if focus_out_end_iter_offset != buffer_.get_end_iter().get_offset():
- # this means after last-focus something was printed
- # (else end_iter's offset is the same as before)
- # only then print ---- line (eg. we avoid printing many following
- # ---- lines)
- print_focus_out_line = True
-
- if print_focus_out_line and buffer_.get_char_count() > 0:
- buffer_.begin_user_action()
-
- # remove previous focus out line if such focus out line exists
- if self.focus_out_end_mark is not None:
- end_iter_for_previous_line = buffer_.get_iter_at_mark(
- self.focus_out_end_mark)
- begin_iter_for_previous_line = end_iter_for_previous_line.copy()
- # img_char+1 (the '\n')
- begin_iter_for_previous_line.backward_chars(21)
-
- # remove focus out line
- buffer_.delete(begin_iter_for_previous_line,
- end_iter_for_previous_line)
- buffer_.delete_mark(self.focus_out_end_mark)
-
- # add the new focus out line
- end_iter = buffer_.get_end_iter()
- buffer_.insert(end_iter, '\n' + '―' * 20)
-
- end_iter = buffer_.get_end_iter()
- before_img_iter = end_iter.copy()
- # one char back (an image also takes one char)
- before_img_iter.backward_chars(20)
- buffer_.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
-
- self.allow_focus_out_line = False
-
- # update the iter we hold to make comparison the next time
- self.focus_out_end_mark = buffer_.create_mark(None,
- buffer_.get_end_iter(), left_gravity=True)
-
- buffer_.end_user_action()
-
- if scroll:
- # scroll to the end (via idle in case the scrollbar has
- # appeared)
- GLib.idle_add(self.scroll_to_end_iter)
-
- def on_textview_draw(self, widget, ctx):
- return
- #TODO
- expalloc = event.area
- exp_x0 = expalloc.x
- exp_y0 = expalloc.y
- exp_x1 = exp_x0 + expalloc.width
- exp_y1 = exp_y0 + expalloc.height
-
- try:
- tryfirst = [self.image_cache[(exp_x0, exp_y0)]]
- except KeyError:
- tryfirst = []
-
- for image in tryfirst + self.images:
- imgalloc = image.allocation
- img_x0 = imgalloc.x
- img_y0 = imgalloc.y
- img_x1 = img_x0 + imgalloc.width
- img_y1 = img_y0 + imgalloc.height
-
- if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \
- exp_x1 <= img_x1 and exp_y1 <= img_y1:
- self.image_cache[(img_x0, img_y0)] = image
- widget.propagate_expose(image, event)
- return True
- return False
-
- def clear(self, tv = None):
- """
- Clear text in the textview
- """
- buffer_ = self.tv.get_buffer()
- start, end = buffer_.get_bounds()
- buffer_.delete(start, end)
- size = gajim.config.get('max_conversation_lines')
- size = 2 * size - 1
- self.marks_queue = queue.Queue(size)
- self.focus_out_end_mark = None
- self.just_cleared = True
-
- def visit_url_from_menuitem(self, widget, link):
- """
- Basically it filters out the widget instance
- """
- helpers.launch_browser_mailer('url', link)
-
- def on_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend Clear (only if
- used_in_history_window is False) and if we have sth selected we show a
- submenu with actions on the phrase (see
- on_conversation_textview_button_press_event)
- """
- separator_menuitem_was_added = False
- if not self.used_in_history_window:
- item = Gtk.SeparatorMenuItem.new()
- menu.prepend(item)
- separator_menuitem_was_added = True
-
- item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
- menu.prepend(item)
- id_ = item.connect('activate', self.clear)
- self.handlers[id_] = item
-
- if self.selected_phrase:
- if not separator_menuitem_was_added:
- item = Gtk.SeparatorMenuItem.new()
- menu.prepend(item)
-
- if not self.used_in_history_window:
- item = Gtk.MenuItem.new_with_mnemonic(_('_Quote'))
- id_ = item.connect('activate', self.on_quote)
- self.handlers[id_] = item
- menu.prepend(item)
-
- _selected_phrase = helpers.reduce_chars_newlines(
- self.selected_phrase, 25, 2)
- item = Gtk.MenuItem.new_with_mnemonic(
- _('_Actions for "%s"') % _selected_phrase)
- menu.prepend(item)
- submenu = Gtk.Menu()
- item.set_submenu(submenu)
- phrase_for_url = urllib.parse.quote(self.selected_phrase.encode(
- 'utf-8'))
-
- always_use_en = gajim.config.get('always_english_wikipedia')
- if always_use_en:
- link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\
- % phrase_for_url
- else:
- link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
- % (gajim.LANG, phrase_for_url)
- item = Gtk.MenuItem.new_with_mnemonic(_('Read _Wikipedia Article'))
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('Look it up in _Dictionary'))
- dict_link = gajim.config.get('dictionary_url')
- if dict_link == 'WIKTIONARY':
- # special link (yeah undocumented but default)
- always_use_en = gajim.config.get('always_english_wiktionary')
- if always_use_en:
- link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\
- % phrase_for_url
- else:
- link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
- % (gajim.LANG, phrase_for_url)
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- else:
- if dict_link.find('%s') == -1:
- # we must have %s in the url if not WIKTIONARY
- item = Gtk.MenuItem.new_with_label(_(
- 'Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
- item.set_property('sensitive', False)
- else:
- link = dict_link % phrase_for_url
- id_ = item.connect('activate', self.visit_url_from_menuitem,
- link)
- self.handlers[id_] = item
- submenu.append(item)
-
-
- search_link = gajim.config.get('search_engine')
- if search_link.find('%s') == -1:
- # we must have %s in the url
- item = Gtk.MenuItem.new_with_label(
- _('Web Search URL is missing an "%s"'))
- item.set_property('sensitive', False)
- else:
- item = Gtk.MenuItem.new_with_mnemonic(_('Web _Search for it'))
- link = search_link % phrase_for_url
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('Open as _Link'))
- id_ = item.connect('activate', self.visit_url_from_menuitem, link)
- self.handlers[id_] = item
- submenu.append(item)
-
- menu.show_all()
-
- def on_quote(self, widget):
- self.emit('quote', self.selected_phrase)
-
- def on_textview_button_press_event(self, widget, event):
- # If we clicked on a taged text do NOT open the standard popup menu
- # if normal text check if we have sth selected
- self.selected_phrase = '' # do not move belove event button check!
-
- if event.button != 3: # if not right click
- return False
-
- x, y = self.tv.window_to_buffer_coords(Gtk.TextWindowType.TEXT,
- int(event.x), int(event.y))
- iter_ = self.tv.get_iter_at_location(x, y)
- if isinstance(iter_, tuple):
- iter_ = iter_[1]
- tags = iter_.get_tags()
-
- if tags: # we clicked on sth special (it can be status message too)
- for tag in tags:
- tag_name = tag.get_property('name')
- if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'):
- return True # we block normal context menu
-
- # we check if sth was selected and if it was we assign
- # selected_phrase variable
- # so on_conversation_textview_populate_popup can use it
- buffer_ = self.tv.get_buffer()
- return_val = buffer_.get_selection_bounds()
- if return_val: # if sth was selected when we right-clicked
- # get the selected text
- start_sel, finish_sel = return_val[0], return_val[1]
- self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True)
- elif iter_.get_char() and ord(iter_.get_char()) > 31:
- # we clicked on a word, do as if it's selected for context menu
- start_sel = iter_.copy()
- if not start_sel.starts_word():
- start_sel.backward_word_start()
- finish_sel = iter_.copy()
- if not finish_sel.ends_word():
- finish_sel.forward_word_end()
- self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True)
-
- def on_open_link_activate(self, widget, kind, text):
- helpers.launch_browser_mailer(kind, text)
-
- def on_copy_link_activate(self, widget, text):
- clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
- clip.set_text(text, -1)
-
- def on_start_chat_activate(self, widget, jid):
- gajim.interface.new_chat_from_jid(self.account, jid)
-
- def on_join_group_chat_menuitem_activate(self, widget, room_jid):
- if 'join_gc' in gajim.interface.instances[self.account]:
- instance = gajim.interface.instances[self.account]['join_gc']
- instance.xml.get_object('room_jid_entry').set_text(room_jid)
- gajim.interface.instances[self.account]['join_gc'].window.present()
- else:
- try:
- dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid)
- except GajimGeneralException:
- pass
-
- def on_add_to_roster_activate(self, widget, jid):
- dialogs.AddNewContactWindow(self.account, jid)
-
- def make_link_menu(self, event, kind, text):
- xml = gtkgui_helpers.get_gtk_builder('chat_context_menu.ui')
- menu = xml.get_object('chat_context_menu')
- childs = menu.get_children()
- if kind == 'url':
- id_ = childs[0].connect('activate', self.on_copy_link_activate, text)
- self.handlers[id_] = childs[0]
- id_ = childs[1].connect('activate', self.on_open_link_activate, kind,
- text)
- self.handlers[id_] = childs[1]
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[6].hide() # join group chat
- childs[7].hide() # add to roster
- else: # It's a mail or a JID
- # load muc icon
- join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
-
- text = text.lower()
- if text.startswith('xmpp:'):
- text = text[5:]
- id_ = childs[2].connect('activate', self.on_copy_link_activate, text)
- self.handlers[id_] = childs[2]
- id_ = childs[3].connect('activate', self.on_open_link_activate, kind,
- text)
- self.handlers[id_] = childs[3]
- id_ = childs[5].connect('activate', self.on_start_chat_activate, text)
- self.handlers[id_] = childs[5]
- id_ = childs[6].connect('activate',
- self.on_join_group_chat_menuitem_activate, text)
- self.handlers[id_] = childs[6]
-
- if self.account and gajim.connections[self.account].\
- roster_supported:
- id_ = childs[7].connect('activate',
- self.on_add_to_roster_activate, text)
- self.handlers[id_] = childs[7]
- childs[7].show() # show add to roster menuitem
- else:
- childs[7].hide() # hide add to roster menuitem
-
- if kind == 'xmpp':
- id_ = childs[0].connect('activate', self.on_copy_link_activate,
- 'xmpp:' + text)
- self.handlers[id_] = childs[0]
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- childs[4].hide() # jid section separator
- elif kind == 'mail':
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[6].hide() # join group chat
- childs[7].hide() # add to roster
-
- if kind != 'xmpp':
- childs[0].hide() # copy link location
- childs[1].hide() # open link in browser
-
- menu.attach_to_widget(self.tv, None)
- menu.popup(None, None, None, None, event.button.button, event.time)
-
- def hyperlink_handler(self, texttag, widget, event, iter_, kind):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- begin_iter = iter_.copy()
- # we get the begining of the tag
- while not begin_iter.begins_tag(texttag):
- begin_iter.backward_char()
- end_iter = iter_.copy()
- # we get the end of the tag
- while not end_iter.ends_tag(texttag):
- end_iter.forward_char()
-
- # Detect XHTML-IM link
- word = getattr(texttag, 'href', None)
- if word:
- if word.startswith('xmpp'):
- kind = 'xmpp'
- elif word.startswith('mailto:'):
- kind = 'mail'
- elif gajim.interface.sth_at_sth_dot_sth_re.match(word):
- # it's a JID or mail
- kind = 'sth_at_sth'
- else:
- word = self.tv.get_buffer().get_text(begin_iter, end_iter, True)
- if event.button.button == 3: # right click
- self.make_link_menu(event, kind, word)
- return True
- else:
- self.plugin_modified = False
- gajim.plugin_manager.extension_point(
- 'hyperlink_handler', word, kind, self,
- self.tv.get_toplevel())
- if self.plugin_modified:
- return
-
- # we launch the correct application
- if kind == 'xmpp':
- word = word[5:]
- if '?' in word:
- (jid, action) = word.split('?')
- if action == 'join':
- self.on_join_group_chat_menuitem_activate(None, jid)
- else:
- self.on_start_chat_activate(None, jid)
- else:
- self.on_start_chat_activate(None, word)
- else:
- helpers.launch_browser_mailer(kind, word)
-
- def detect_and_print_special_text(self, otext, other_tags, graphics=True,
- iter_=None, additional_data=None):
- """
- Detect special text (emots & links & formatting), print normal text
- before any special text it founds, then print special text (that happens
- many times until last special text is printed) and then return the index
- after *last* special text, so we can print it in
- print_conversation_line()
- """
- if not otext:
- return
- if additional_data is None:
- additional_data = {}
- buffer_ = self.tv.get_buffer()
- if other_tags:
- insert_tags_func = buffer_.insert_with_tags_by_name
- else:
- insert_tags_func = buffer_.insert
- # detect_and_print_special_text() is also used by
- # HtmlHandler.handle_specials() and there tags is Gtk.TextTag objects,
- # not strings
- if other_tags and isinstance(other_tags[0], Gtk.TextTag):
- insert_tags_func = buffer_.insert_with_tags
-
- index = 0
-
- # Too many special elements (emoticons, LaTeX formulas, etc)
- # may cause Gajim to freeze (see #5129).
- # We impose an arbitrary limit of 100 specials per message.
- specials_limit = 100
-
- # basic: links + mail + formatting is always checked (we like that)
- if gajim.config.get('emoticons_theme') and graphics:
- # search for emoticons & urls
- iterator = gajim.interface.emot_and_basic_re.finditer(otext)
- else: # search for just urls + mail + formatting
- iterator = gajim.interface.basic_pattern_re.finditer(otext)
- if iter_:
- end_iter = iter_
- else:
- end_iter = buffer_.get_end_iter()
- for match in iterator:
- start, end = match.span()
- special_text = otext[start:end]
- if start > index:
- text_before_special_text = otext[index:start]
- if not iter_:
- end_iter = buffer_.get_end_iter()
- # we insert normal text
- if other_tags:
- insert_tags_func(end_iter, text_before_special_text, *other_tags)
- else:
- buffer_.insert(end_iter, text_before_special_text)
- index = end # update index
-
- # now print it
- self.print_special_text(special_text, other_tags, graphics=graphics,
- iter_=end_iter, additional_data=additional_data)
- specials_limit -= 1
- if specials_limit <= 0:
- break
-
- # add the rest of text located in the index and after
- insert_tags_func(end_iter, otext[index:], *other_tags)
-
- return end_iter
-
- def print_special_text(self, special_text, other_tags, graphics=True,
- iter_=None, additional_data=None):
- """
- Is called by detect_and_print_special_text and prints special text
- (emots, links, formatting)
- """
- if additional_data is None:
- additional_data = {}
-
- # PluginSystem: adding GUI extension point for ConversationTextview
- self.plugin_modified = False
- gajim.plugin_manager.extension_point('print_special_text', self,
- special_text, other_tags, graphics, additional_data)
- if self.plugin_modified:
- return
-
- tags = []
- use_other_tags = True
- text_is_valid_uri = False
- is_xhtml_link = None
- show_ascii_formatting_chars = \
- gajim.config.get('show_ascii_formatting_chars')
- buffer_ = self.tv.get_buffer()
-
- # Detect XHTML-IM link
- ttt = buffer_.get_tag_table()
- tags_ = [(ttt.lookup(t) if isinstance(t, str) else t) for t in other_tags]
- for t in tags_:
- is_xhtml_link = getattr(t, 'href', None)
- if is_xhtml_link:
- break
-
- # Check if we accept this as an uri
- schemes = gajim.config.get('uri_schemes').split()
- for scheme in schemes:
- if special_text.startswith(scheme):
- text_is_valid_uri = True
-
- possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
- if iter_:
- end_iter = iter_
- else:
- end_iter = buffer_.get_end_iter()
- if gajim.config.get('emoticons_theme') and \
- possible_emot_ascii_caps in gajim.interface.emoticons.keys() and graphics:
- # it's an emoticon
- emot_ascii = possible_emot_ascii_caps
- anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor,
- GLib.markup_escape_text(special_text))
- animations = gajim.interface.emoticons_animations
- if not emot_ascii in animations:
- animations[emot_ascii] = GdkPixbuf.PixbufAnimation.new_from_file(
- gajim.interface.emoticons[emot_ascii])
- img.set_from_animation(animations[emot_ascii])
- img.show()
- self.images.append(img)
- # add with possible animation
- self.tv.add_child_at_anchor(img, anchor)
- elif special_text.startswith('www.') or \
- special_text.startswith('ftp.') or \
- text_is_valid_uri and not is_xhtml_link:
- tags.append('url')
- elif special_text.startswith('mailto:') and not is_xhtml_link:
- tags.append('mail')
- elif special_text.startswith('xmpp:') and not is_xhtml_link:
- tags.append('xmpp')
- elif gajim.interface.sth_at_sth_dot_sth_re.match(special_text) and\
- not is_xhtml_link:
- # it's a JID or mail
- tags.append('sth_at_sth')
- elif special_text.startswith('*'): # it's a bold text
- tags.append('bold')
- if special_text[1] == '/' and special_text[-2] == '/' and\
- len(special_text) > 4: # it's also italic
- tags.append('italic')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove */ /*
- elif special_text[1] == '_' and special_text[-2] == '_' and \
- len(special_text) > 4: # it's also underlined
- tags.append('underline')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove *_ _*
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove * *
- elif special_text.startswith('/'): # it's an italic text
- tags.append('italic')
- if special_text[1] == '*' and special_text[-2] == '*' and \
- len(special_text) > 4: # it's also bold
- tags.append('bold')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove /* */
- elif special_text[1] == '_' and special_text[-2] == '_' and \
- len(special_text) > 4: # it's also underlined
- tags.append('underline')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove /_ _/
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove / /
- elif special_text.startswith('_'): # it's an underlined text
- tags.append('underline')
- if special_text[1] == '*' and special_text[-2] == '*' and \
- len(special_text) > 4: # it's also bold
- tags.append('bold')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove _* *_
- elif special_text[1] == '/' and special_text[-2] == '/' and \
- len(special_text) > 4: # it's also italic
- tags.append('italic')
- if not show_ascii_formatting_chars:
- special_text = special_text[2:-2] # remove _/ /_
- else:
- if not show_ascii_formatting_chars:
- special_text = special_text[1:-1] # remove _ _
- else:
- # It's nothing special
- if use_other_tags:
- insert_tags_func = buffer_.insert_with_tags_by_name
- if other_tags and isinstance(other_tags[0], Gtk.TextTag):
- insert_tags_func = buffer_.insert_with_tags
- if other_tags:
- insert_tags_func(end_iter, special_text, *other_tags)
- else:
- buffer_.insert(end_iter, special_text)
-
- if tags:
- all_tags = tags[:]
- if use_other_tags:
- all_tags += other_tags
- # convert all names to TextTag
- all_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in all_tags]
- buffer_.insert_with_tags(end_iter, special_text, *all_tags)
- if 'url' in tags:
- puny_text = helpers.puny_encode_url(special_text)
- if puny_text != special_text:
- puny_tags = []
- if use_other_tags:
- puny_tags += other_tags
- if not puny_text:
- puny_text = _('Invalid URL')
- puny_tags = [(ttt.lookup(t) if isinstance(t, str) else t) for t in puny_tags]
- buffer_.insert_with_tags(end_iter, " (%s)" % puny_text, *puny_tags)
-
- def print_empty_line(self, iter_=None):
- buffer_ = self.tv.get_buffer()
- if not iter_:
- iter_ = buffer_.get_end_iter()
- buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
- self.just_cleared = False
-
- def get_end_mark(self, msg_stanza_id, start_mark):
- for index, msg in enumerate(self.message_list):
- if msg[2] == msg_stanza_id and msg[1] == start_mark:
- try:
- end_mark = self.message_list[index + 1][1]
- end_mark_name = end_mark.get_name()
- except IndexError:
- # We are at the last message
- end_mark = None
- end_mark_name = None
-
- log.debug('start mark: %s, end mark: %s, '
- 'replace message-list index: %s',
- start_mark.get_name(), end_mark_name, index)
-
- return end_mark, index
- log.debug('stanza-id not in message list')
- return None, None
-
- def get_insert_mark(self, timestamp):
- # message_list = [(timestamp, line_start_mark, msg_stanza_id)]
- # We check if this is a new Message
- try:
- if self.message_list[-1][0] <= timestamp:
- return None, None
- except IndexError:
- # We have no Messages in the TextView
- return None, None
-
- # Not a new Message
- # Search for insertion point
- for index, msg in enumerate(self.message_list):
- if msg[0] > timestamp:
- return msg[1], index
-
- # Should not happen, but who knows
- return None, None
-
- def print_conversation_line(self, text, jid, kind, name, tim,
- other_tags_for_name=None, other_tags_for_time=None, other_tags_for_text=None,
- subject=None, old_kind=None, xhtml=None, simple=False, graphics=True,
- displaymarking=None, msg_stanza_id=None, correct_id=None, additional_data=None,
- encrypted=None):
- """
- Print 'chat' type messages
- """
- buffer_ = self.tv.get_buffer()
- buffer_.begin_user_action()
- insert_mark = None
- insert_mark_name = None
-
- if kind == 'incoming_queue':
- kind = 'incoming'
- if old_kind == 'incoming_queue':
- old_kind = 'incoming'
-
- if not tim:
- # For outgoing Messages and Status prints
- tim = time.time()
-
- corrected = False
- if correct_id:
- try:
- index, insert_mark, old_txt = \
- self.correct_message(correct_id, kind, name)
- if correct_id in self.corrected_text_list:
- self.corrected_text_list[msg_stanza_id] = \
- self.corrected_text_list[correct_id] + '\n{}' \
- .format(GLib.markup_escape_text(old_txt))
- del self.corrected_text_list[correct_id]
- else:
- self.corrected_text_list[msg_stanza_id] = \
- '<b>Message corrected. Previous message:</b>\n{}' \
- .format(GLib.markup_escape_text(old_txt))
- corrected = True
- except TypeError:
- log.debug('Message was not corrected !')
-
- if not corrected:
- # Get insertion point into TextView
- insert_mark, index = self.get_insert_mark(tim)
-
- if insert_mark:
- insert_mark_name = insert_mark.get_name()
-
- log.debug(
- 'Printed Line: %s, %s, %s, inserted after: %s'
- ', stanza-id: %s, correct-id: %s',
- self.line, text, tim, insert_mark_name,
- msg_stanza_id, correct_id)
-
- if not insert_mark: # Texview is empty or Message is new
- iter_ = buffer_.get_end_iter()
- # Insert new Line if Textview is not empty
- if buffer_.get_char_count() > 0 and not corrected:
- buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
- else:
- iter_ = buffer_.get_iter_at_mark(insert_mark)
-
- # Create a temporary mark at the start of the line
- # with gravity=Left, so it will not move
- # even if we insert directly at the mark iter
- temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True)
-
- if text.startswith('/me '):
- direction_mark = i18n.paragraph_direction_mark(str(text[3:]))
- else:
- direction_mark = i18n.paragraph_direction_mark(text)
- # don't apply direction mark if it's status message
- if kind == 'status':
- direction_mark = i18n.direction_mark
-
- # print the time stamp
- self.print_time(text, kind, tim, simple, direction_mark,
- other_tags_for_time, iter_)
-
- if encrypted:
- buffer_.insert_pixbuf(iter_, self.MESSAGE_ENCRYPTED_PIXBUF)
-
- # If there's a displaymarking, print it here.
- if displaymarking:
- self.print_displaymarking(displaymarking, iter_=iter_)
-
- # kind = info, we print things as if it was a status: same color, ...
- if kind in ('error', 'info'):
- kind = 'status'
- other_text_tag = self.detect_other_text_tag(text, kind)
- text_tags = []
- if other_tags_for_text:
- text_tags = other_tags_for_text[:] # create a new list
- if other_text_tag:
- text_tags.append(other_text_tag)
-
- else: # not status nor /me
- if gajim.config.get('chat_merge_consecutive_nickname'):
- if kind != old_kind or self.just_cleared:
- self.print_name(name, kind, other_tags_for_name,
- direction_mark=direction_mark, iter_=iter_)
- else:
- self.print_real_text(gajim.config.get(
- 'chat_merge_consecutive_nickname_indent'),
- mark=insert_mark, additional_data=additional_data)
- else:
- self.print_name(name, kind, other_tags_for_name,
- direction_mark=direction_mark, iter_=iter_)
- if kind == 'incoming':
- text_tags.append('incomingtxt')
- elif kind == 'outgoing':
- text_tags.append('outgoingtxt')
-
- self.print_subject(subject, iter_=iter_)
-
- iter_ = self.print_real_text(text, text_tags, name, xhtml, graphics=graphics,
- mark=insert_mark, additional_data=additional_data)
-
- if corrected:
- # Show Correction Icon
- buffer_.create_tag(tag_name=msg_stanza_id)
- buffer_.insert(iter_, ' ')
- buffer_.insert_pixbuf(
- iter_, ConversationTextview.MESSAGE_CORRECTED_PIXBUF)
- tag_start_iter = iter_.copy()
- tag_start_iter.backward_chars(2)
- buffer_.apply_tag_by_name(msg_stanza_id, tag_start_iter, iter_)
-
- # If we inserted a Line we add a new line at the end
- if insert_mark:
- buffer_.insert_with_tags_by_name(iter_, '\n', 'eol')
- # We delete the temp mark and replace it with a mark
- # that has gravity=right
- temp_iter = buffer_.get_iter_at_mark(temp_mark)
- buffer_.delete_mark(temp_mark)
- new_mark = buffer_.create_mark(
- str(self.line), temp_iter, left_gravity=False)
-
- if not index:
- # New Message
- self.message_list.append((tim, new_mark, msg_stanza_id))
- elif corrected:
- # Replace the corrected message
- self.message_list[index] = (tim, new_mark, msg_stanza_id)
- else:
- # We insert the message at index
- self.message_list.insert(index, (tim, new_mark, msg_stanza_id))
-
- if kind == 'incoming':
- self.last_received_message_id[name] = (msg_stanza_id, new_mark)
- elif kind == 'outgoing':
- self.last_sent_message_id = (msg_stanza_id, new_mark)
-
- if not insert_mark:
- if self.at_the_end() or kind == 'outgoing':
- # we are at the end or we are sending something
- GLib.idle_add(self.scroll_to_end_iter)
-
- self.just_cleared = False
- buffer_.end_user_action()
-
- self.line += 1
- return iter_
-
- def get_time_to_show(self, tim, direction_mark=''):
- """
- Get the time, with the day before if needed and return it. It DOESN'T
- format a fuzzy time
- """
- format_ = ''
- # get difference in days since epoch (86400 = 24*3600)
- # number of days since epoch for current time (in GMT) -
- # number of days since epoch for message (in GMT)
- diff_day = int(int(timegm(time.localtime())) / 86400 -\
- int(timegm(tim)) / 86400)
- if diff_day == 0.0:
- day_str = ''
- else:
- #%i is day in year (1-365)
- day_str = i18n.ngettext('Yesterday',
- '%(nb_days)i days ago', diff_day, {'nb_days': diff_day},
- {'nb_days': diff_day})
- if day_str:
- # strftime Windows bug has problems with Unicode
- # see: http://bugs.python.org/issue8304
- if os.name != 'nt':
- format_ += i18n.direction_mark + day_str + direction_mark + ' '
- else:
- format_ += day_str + ' '
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- format_ += timestamp_str
- tim_format = time.strftime(format_, tim)
- return tim_format
-
- def detect_other_text_tag(self, text, kind):
- if kind == 'status':
- return kind
- elif text.startswith('/me ') or text.startswith('/me\n'):
- return kind
-
- def print_time(self, text, kind, tim, simple, direction_mark, other_tags_for_time, iter_):
- local_tim = time.localtime(tim)
- buffer_ = self.tv.get_buffer()
- current_print_time = gajim.config.get('print_time')
-
- if current_print_time == 'always' and kind != 'info' and not simple:
- timestamp_str = self.get_time_to_show(local_tim, direction_mark)
- timestamp = time.strftime(timestamp_str, local_tim)
- timestamp = direction_mark + timestamp + direction_mark
- if other_tags_for_time:
- buffer_.insert_with_tags_by_name(iter_, timestamp,
- *other_tags_for_time)
- else:
- buffer_.insert(iter_, timestamp)
- elif current_print_time == 'sometimes' and kind != 'info' and not simple:
- every_foo_seconds = 60 * gajim.config.get(
- 'print_ichat_every_foo_minutes')
- seconds_passed = tim - self.last_time_printout
- if seconds_passed > every_foo_seconds:
- self.last_time_printout = tim
- if gajim.config.get('print_time_fuzzy') > 0:
- tim_format = self.fc.fuzzy_time(
- gajim.config.get('print_time_fuzzy'), tim)
- else:
- tim_format = self.get_time_to_show(local_tim, direction_mark)
- buffer_.insert_with_tags_by_name(iter_, tim_format + '\n',
- 'time_sometimes')
-
- def print_displaymarking(self, displaymarking, iter_=None):
- bgcolor = displaymarking.getAttr('bgcolor') or '#FFF'
- fgcolor = displaymarking.getAttr('fgcolor') or '#000'
- text = displaymarking.getData()
- if text:
- buffer_ = self.tv.get_buffer()
- if iter_:
- end_iter = iter_
- else:
- end_iter = buffer_.get_end_iter()
- tag = self.displaymarking_tags.setdefault(bgcolor + '/' + fgcolor,
- buffer_.create_tag(None, background=bgcolor, foreground=fgcolor))
- buffer_.insert_with_tags(end_iter, '[' + text + ']', tag)
- end_iter = buffer_.get_end_iter()
- buffer_.insert_with_tags(end_iter, ' ')
-
- def print_name(self, name, kind, other_tags_for_name, direction_mark='',
- iter_=None):
- if name:
- name_tags = []
- buffer_ = self.tv.get_buffer()
- if iter_:
- end_iter = iter_
- else:
- end_iter = buffer_.get_end_iter()
-
- if other_tags_for_name:
- name_tags = other_tags_for_name[:] # create a new list
- name_tags.append(kind)
- before_str = gajim.config.get('before_nickname')
- before_str = helpers.from_one_line(before_str)
- after_str = gajim.config.get('after_nickname')
- after_str = helpers.from_one_line(after_str)
- format_ = before_str + name + direction_mark + after_str + ' '
- buffer_.insert_with_tags_by_name(end_iter, format_, *name_tags)
-
- def print_subject(self, subject, iter_=None):
- if subject: # if we have subject, show it too!
- subject = _('Subject: %s\n') % subject
- buffer_ = self.tv.get_buffer()
- if iter_:
- end_iter = iter_
- else:
- end_iter = buffer_.get_end_iter()
- buffer_.insert(end_iter, subject)
- self.print_empty_line(end_iter)
-
- def print_real_text(self, text, text_tags=[], name=None, xhtml=None,
- graphics=True, mark=None, additional_data=None):
- """
- Add normal and special text. call this to add text
- """
- if additional_data is None:
- additional_data = {}
- buffer_ = self.tv.get_buffer()
- if not mark:
- iter_ = buffer_.get_end_iter()
- else:
- iter_ = buffer_.get_iter_at_mark(mark)
- if xhtml:
- try:
- if name and (text.startswith('/me ') or text.startswith('/me\n')):
- xhtml = xhtml.replace('/me', '<i>* %s</i>' % (name,), 1)
- self.tv.display_html(xhtml, self.tv, self, iter_=iter_)
- return
- except Exception as e:
- gajim.log.debug('Error processing xhtml: ' + str(e))
- gajim.log.debug('with |' + xhtml + '|')
-
- # /me is replaced by name if name is given
- if name and (text.startswith('/me ') or text.startswith('/me\n')):
- text = '* ' + name + text[3:]
- text_tags.append('italic')
-
- # PluginSystem: adding GUI extension point for ConversationTextview
- self.plugin_modified = False
- gajim.plugin_manager.extension_point('print_real_text', self,
- text, text_tags, graphics, iter_, additional_data)
-
- if self.plugin_modified:
- if not mark:
- return buffer_.get_end_iter()
- else:
- return buffer_.get_iter_at_mark(mark)
-
- if not mark:
- iter_ = buffer_.get_end_iter()
- else:
- iter_ = buffer_.get_iter_at_mark(mark)
-
- # detect urls formatting and if the user has it on emoticons
- return self.detect_and_print_special_text(text, text_tags, graphics=graphics,
- iter_=iter_, additional_data=additional_data)
diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py
deleted file mode 100644
index 913200f65..000000000
--- a/src/dataforms_widget.py
+++ /dev/null
@@ -1,687 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/dataforms_widget.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-''' This module contains widget that can display data form (JEP-0004).
-Words single and multiple refers here to types of data forms:
-single means these with one record of data (without <reported/> element),
-multiple - these which may contain more data (with <reported/> element).'''
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GObject
-from gi.repository import GLib
-import base64
-
-import gtkgui_helpers
-import dialogs
-
-import common.dataforms as dataforms
-from common import helpers
-
-import itertools
-
-class DataFormWidget(Gtk.Alignment, object):
-# "public" interface
- """
- Data Form widget. Use like any other widget
- """
-
- __gsignals__ = dict(
- validated = (GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, None, ())
- )
-
- def __init__(self, dataformnode=None):
- ''' Create a widget. '''
- GObject.GObject.__init__(self, xscale=1.0, yscale=1.0)
-
- self._data_form = None
- self.selectable = False
-
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
- 'data_form_vbox')
- self.xml.connect_signals(self)
- for name in ('instructions_label', 'instructions_hseparator',
- 'single_form_viewport', 'data_form_types_notebook',
- 'single_form_scrolledwindow', 'multiple_form_hbox',
- 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button',
- 'edit_button', 'up_button', 'down_button', 'clear_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.add(self.xml.get_object('data_form_vbox'))
-
- if dataformnode is not None:
- self.set_data_form(dataformnode)
-
- selection = self.records_treeview.get_selection()
- selection.connect('changed', self.on_records_selection_changed)
- selection.set_mode(Gtk.SelectionMode.MULTIPLE)
-
- def on_data_form_vbox_key_press_event(self, widget, event):
- print('key pressed')
-
- def set_data_form(self, dataform):
- """
- Set the data form (nbxmpp.DataForm) displayed in widget
- """
- assert isinstance(dataform, dataforms.DataForm)
-
- self.del_data_form()
- self._data_form = dataform
- if isinstance(dataform, dataforms.SimpleDataForm):
- self.build_single_data_form()
- else:
- self.build_multiple_data_form()
-
- # create appropriate description for instructions field if there isn't any
- if dataform.instructions == '':
- self.instructions_label.set_no_show_all(True)
- self.instructions_label.hide()
- else:
- self.instructions_label.set_text(dataform.instructions)
- gtkgui_helpers.label_set_autowrap(self.instructions_label)
-
- def get_data_form(self):
- """
- Data form displayed in the widget or None if no form
- """
- return self._data_form
-
- def del_data_form(self):
- self.clean_data_form()
- self._data_form = None
-
- data_form = property(get_data_form, set_data_form, del_data_form,
- 'Data form presented in a widget')
-
- def get_title(self):
- """
- Get the title of data form. If no title or no form,
- returns ''. Useful for setting window title
- """
- if self._data_form is not None:
- if self._data_form.title is not None:
- return self._data_form.title
- return ''
-
- title = property(get_title, None, None, 'Data form title')
-
- def show(self):
- ''' Treat 'us' as one widget. '''
- self.show_all()
-
-# "private" methods
-
-# we have actually two different kinds of data forms: one is a simple form to fill,
-# second is a table with several records;
-
- def empty_method(self):
- pass
-
- def clean_data_form(self):
- """
- Remove data about existing form. This metod is empty, because it is
- rewritten by build_*_data_form, according to type of form which is
- actually displayed
- """
- pass
-
- def build_single_data_form(self):
- '''Invoked when new single form is to be created.'''
- assert isinstance(self._data_form, dataforms.SimpleDataForm)
-
- self.clean_data_form()
-
- self.singleform = SingleForm(self._data_form,
- selectable=self.selectable)
- def _on_validated(widget):
- self.emit('validated')
- self.singleform.connect('validated', _on_validated)
- self.singleform.show()
- self.single_form_viewport.add(self.singleform)
- self.data_form_types_notebook.set_current_page(
- self.data_form_types_notebook.page_num(
- self.single_form_scrolledwindow))
-
- self.clean_data_form = self.clean_single_data_form
-
- def clean_single_data_form(self):
- """
- Called as clean_data_form, read the docs of clean_data_form(). Remove
- form from widget
- """
- self.singleform.destroy()
- self.clean_data_form = self.empty_method # we won't call it twice
- del self.singleform
-
- def build_multiple_data_form(self):
- """
- Invoked when new multiple form is to be created
- """
- assert isinstance(self._data_form, dataforms.MultipleDataForm)
-
- self.clean_data_form()
-
- # creating model for form...
- fieldtypes = []
- fieldvars = []
- for field in self._data_form.reported.iter_fields():
- # note: we store also text-private and hidden fields,
- # we just do not display them.
- # TODO: boolean fields
- #elif field.type_=='boolean': fieldtypes.append(bool)
- fieldtypes.append(str)
- fieldvars.append(field.var)
-
- self.multiplemodel = Gtk.ListStore(*fieldtypes)
-
- # moving all data to model
- for item in self._data_form.iter_records():
- iter_ = self.multiplemodel.append()
- for field in item.iter_fields():
- if field.var in fieldvars:
- self.multiplemodel.set_value(iter_,
- fieldvars.index(field.var), field.value)
-
- # constructing columns...
- for field, counter in zip(self._data_form.reported.iter_fields(),
- itertools.count()):
- self.records_treeview.append_column(
- Gtk.TreeViewColumn(field.label, Gtk.CellRendererText(),
- text=counter))
-
- self.records_treeview.set_model(self.multiplemodel)
- self.records_treeview.show_all()
-
- self.data_form_types_notebook.set_current_page(
- self.data_form_types_notebook.page_num(
- self.multiple_form_hbox))
-
- self.clean_data_form = self.clean_multiple_data_form
-
- readwrite = self._data_form.type_ != 'result'
- if not readwrite:
- self.buttons_vbox.set_no_show_all(True)
- self.buttons_vbox.hide()
- else:
- self.buttons_vbox.set_no_show_all(False)
- # refresh list look
- self.refresh_multiple_buttons()
-
- def clean_multiple_data_form(self):
- """
- Called as clean_data_form, read the docs of clean_data_form(). Remove
- form from widget
- """
- self.clean_data_form = self.empty_method # we won't call it twice
- del self.multiplemodel
-
- def refresh_multiple_buttons(self):
- """
- Checks for treeview state and makes control buttons sensitive
- """
- selection = self.records_treeview.get_selection()
- model = self.records_treeview.get_model()
- count = selection.count_selected_rows()
- if count == 0:
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(False)
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
- elif count == 1:
- self.remove_button.set_sensitive(True)
- self.edit_button.set_sensitive(True)
- _, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- if model.iter_next(iter_) is None:
- self.up_button.set_sensitive(True)
- self.down_button.set_sensitive(False)
- elif path == (0, ):
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(True)
- else:
- self.up_button.set_sensitive(True)
- self.down_button.set_sensitive(True)
- else:
- self.remove_button.set_sensitive(True)
- self.edit_button.set_sensitive(True)
- self.up_button.set_sensitive(False)
- self.down_button.set_sensitive(False)
-
- if len(model) == 0:
- self.clear_button.set_sensitive(False)
- else:
- self.clear_button.set_sensitive(True)
-
- def on_clear_button_clicked(self, widget):
- self.records_treeview.get_model().clear()
-
- def on_remove_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, rowrefs = selection.get_selected_rows()
- # rowref is a list of paths
- for i in range(len(rowrefs)):
- rowrefs[i] = Gtk.TreeRowReference.new(model, rowrefs[i])
- # rowref is a list of row references; need to convert because we will
- # modify the model, paths would change
- for rowref in rowrefs:
- del model[rowref.get_path()]
-
- def on_up_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- # constructing path for previous iter
- previter = model.get_iter((path[0]-1,))
- model.swap(iter_, previter)
-
- self.refresh_multiple_buttons()
-
- def on_down_button_clicked(self, widget):
- selection = self.records_treeview.get_selection()
- model, (path,) = selection.get_selected_rows()
- iter_ = model.get_iter(path)
- nextiter = model.iter_next(iter_)
- model.swap(iter_, nextiter)
-
- self.refresh_multiple_buttons()
-
- def on_records_selection_changed(self, widget):
- self.refresh_multiple_buttons()
-
-class SingleForm(Gtk.Table, object):
- """
- Widget that represent DATAFORM_SINGLE mode form. Because this is used not
- only to display single forms, but to form input windows of multiple-type
- forms, it is in another class
- """
-
- __gsignals__ = dict(
- validated = (GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, None, ())
- )
-
- def __init__(self, dataform, selectable=False):
- assert isinstance(dataform, dataforms.SimpleDataForm)
-
- GObject.GObject.__init__(self)
- self.set_col_spacings(12)
- self.set_row_spacings(6)
-
- def decorate_with_tooltip(widget, field):
- """
- Adds a tooltip containing field's description to a widget. Creates
- EventBox if widget doesn't have its own gdk window. Returns decorated
- widget
- """
- if field.description != '':
- if not widget.get_window():
- #if widget.flags() & Gtk.NO_WINDOW:
- evbox = Gtk.EventBox()
- evbox.add(widget)
- widget = evbox
- widget.set_tooltip_text(field.description)
- return widget
-
- self._data_form = dataform
-
- # building widget
- linecounter = 0
-
- # is the form changeable?
- readwrite = dataform.type_ != 'result'
-
- # for each field...
- for field in self._data_form.iter_fields():
- if field.type_ == 'hidden': continue
-
- commonlabel = True
- commonlabelcenter = False
- commonwidget = True
- widget = None
-
- if field.type_ == 'boolean':
- commonlabelcenter = True
- widget = Gtk.CheckButton()
- widget.connect('toggled', self.on_boolean_checkbutton_toggled,
- field)
- widget.set_active(field.value)
-
- elif field.type_ == 'fixed':
- leftattach = 1
- rightattach = 2
- if field.label is None:
- commonlabel = False
- leftattach = 0
-
- commonwidget = False
- widget = Gtk.Label(label=field.value)
- widget.set_property('selectable', selectable)
- widget.set_line_wrap(True)
- self.attach(widget, leftattach, rightattach, linecounter,
- linecounter+1, xoptions=Gtk.AttachOptions.FILL,
- yoptions=Gtk.AttachOptions.FILL)
-
- elif field.type_ == 'list-single':
- # TODO: What if we have radio buttons and non-required field?
- # TODO: We cannot deactivate them all...
- if len(field.options) < 6:
- # 5 option max: show radiobutton
- widget = Gtk.VBox()
- first_radio = None
- for value, label in field.iter_options():
- if not label:
- label = value
- radio = Gtk.RadioButton.new_with_label_from_widget(
- first_radio, label)
- radio.connect('toggled',
- self.on_list_single_radiobutton_toggled, field, value)
- if first_radio is None:
- first_radio = radio
- if field.value == '': # TODO: is None when done
- field.value = value
- if value == field.value:
- radio.set_active(True)
- widget.pack_start(radio, False, True, 0)
- else:
- # more than 5 options: show combobox
- def on_list_single_combobox_changed(combobox, f):
- iter_ = combobox.get_active_iter()
- if iter_:
- model = combobox.get_model()
- f.value = model[iter_][1]
- else:
- f.value = ''
- widget = gtkgui_helpers.create_combobox(field.options,
- field.value)
- widget.connect('changed', on_list_single_combobox_changed, field)
- widget.set_sensitive(readwrite)
-
- elif field.type_ == 'list-multi':
- # TODO: When more than few choices, make a list
- if len(field.options) < 6:
- # 5 option max: show checkbutton
- widget = Gtk.VBox()
- for value, label in field.iter_options():
- check = Gtk.CheckButton(label=label,
- use_underline=False)
- check.set_active(value in field.values)
- check.connect('toggled',
- self.on_list_multi_checkbutton_toggled, field, value)
- widget.pack_start(check, False, True, 0)
- widget.set_sensitive(readwrite)
- else:
- # more than 5 options: show combobox
- def on_list_multi_treeview_changed(selection, f):
- def for_selected(treemodel, path, iter):
- vals.append(treemodel[iter][1])
- vals = []
- selection.selected_foreach(for_selected)
- field.values = vals[:]
- widget = Gtk.ScrolledWindow()
- widget.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- tv = gtkgui_helpers.create_list_multi(field.options,
- field.values)
- widget.add(tv)
- widget.set_size_request(-1, 120)
- tv.get_selection().connect('changed',
- on_list_multi_treeview_changed, field)
- tv.set_sensitive(readwrite)
-
- elif field.type_ == 'jid-single':
- widget = Gtk.Entry()
- widget.connect('changed', self.on_text_single_entry_changed, field)
- widget.set_text(field.value)
-
- elif field.type_ == 'jid-multi':
- commonwidget = False
-
- xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui',
- 'multiple_form_hbox')
- widget = xml.get_object('multiple_form_hbox')
- treeview = xml.get_object('records_treeview')
-
- listmodel = Gtk.ListStore(str)
- for value in field.iter_values():
- # nobody will create several megabytes long stanza
- listmodel.insert(999999, (value,))
-
- treeview.set_model(listmodel)
-
- renderer = Gtk.CellRendererText()
- renderer.set_property('editable', True)
- renderer.connect('edited',
- self.on_jid_multi_cellrenderertext_edited, treeview, listmodel,
- field)
-
- treeview.append_column(Gtk.TreeViewColumn(None, renderer,
- text=0))
-
- decorate_with_tooltip(treeview, field)
-
- add_button=xml.get_object('add_button')
- add_button.connect('clicked',
- self.on_jid_multi_add_button_clicked, treeview, listmodel, field)
- edit_button=xml.get_object('edit_button')
- edit_button.connect('clicked',
- self.on_jid_multi_edit_button_clicked, treeview)
- remove_button=xml.get_object('remove_button')
- remove_button.connect('clicked',
- self.on_jid_multi_remove_button_clicked, treeview, field)
- clear_button=xml.get_object('clear_button')
- clear_button.connect('clicked',
- self.on_jid_multi_clean_button_clicked, listmodel, field)
- if not readwrite:
- add_button.set_no_show_all(True)
- edit_button.set_no_show_all(True)
- remove_button.set_no_show_all(True)
- clear_button.set_no_show_all(True)
-
- widget.set_sensitive(readwrite)
- self.attach(widget, 1, 2, linecounter, linecounter+1)
-
- del xml
-
- elif field.type_ == 'text-private':
- commonlabelcenter = True
- widget = Gtk.Entry()
- widget.connect('changed', self.on_text_single_entry_changed, field)
- widget.set_visibility(False)
- widget.set_text(field.value)
-
- elif field.type_ == 'text-multi':
- # TODO: bigger text view
- commonwidget = False
-
- textwidget = Gtk.TextView()
- textwidget.set_wrap_mode(Gtk.WrapMode.WORD)
- textwidget.get_buffer().connect('changed',
- self.on_text_multi_textbuffer_changed, field)
- textwidget.get_buffer().set_text(field.value)
- if readwrite:
- textwidget.set_sensitive(True)
- else:
- if selectable:
- textwidget.set_editable(True)
- else:
- textwidget.set_sensitive(False)
-
- widget = Gtk.ScrolledWindow()
- widget.add(textwidget)
-
- widget=decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1)
-
- else:
- # field.type_ == 'text-single' or field.type_ is nonstandard:
- # JEP says that if we don't understand some type, we
- # should handle it as text-single
- commonlabelcenter = True
- if readwrite:
- widget = Gtk.Entry()
- def kpe(widget, event):
- if event.keyval == Gdk.KEY_Return or \
- event.keyval == Gdk.KEY_KP_Enter:
- self.emit('validated')
- widget.connect('key-press-event', kpe)
- widget.connect('changed', self.on_text_single_entry_changed,
- field)
- widget.set_sensitive(readwrite)
- if field.value is None:
- field.value = ''
- widget.set_text(field.value)
- else:
- commonwidget=False
- widget = Gtk.Label(label=field.value)
- widget.set_property('selectable', selectable)
- widget.set_sensitive(True)
- widget.set_halign(Gtk.Align.START)
- widget.set_valign(Gtk.Align.CENTER)
- widget=decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1,
- yoptions=Gtk.AttachOptions.FILL)
-
- if commonlabel and field.label is not None:
- label = Gtk.Label(label=field.label)
- if commonlabelcenter:
- label.set_halign(Gtk.Align.START)
- label.set_valign(Gtk.Align.CENTER)
- else:
- label.set_halign(Gtk.Align.START)
- label.set_valign(Gtk.Align.START)
- label = decorate_with_tooltip(label, field)
- self.attach(label, 0, 1, linecounter, linecounter+1,
- xoptions=Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.FILL)
-
- if field.media is not None:
- for uri in field.media.uris:
- if uri.type_.startswith('image/'):
- try:
- img_data = base64.b64decode(uri.uri_data)
- pixbuf_l = GdkPixbuf.PixbufLoader()
- pixbuf_l.write(img_data)
- pixbuf_l.close()
- media = Gtk.Image.new_from_pixbuf(pixbuf_l.\
- get_pixbuf())
- except Exception:
- media = Gtk.Label(label=_('Unable to load image'))
- else:
- media = Gtk.Label(label=_('Media type not supported: %s') % \
- uri.type_)
- linecounter += 1
- self.attach(media, 0, 1, linecounter, linecounter+1,
- xoptions=Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.FILL)
-
- if commonwidget:
- assert widget is not None
- widget.set_sensitive(readwrite)
- widget = decorate_with_tooltip(widget, field)
- self.attach(widget, 1, 2, linecounter, linecounter+1,
- yoptions=Gtk.AttachOptions.FILL)
-
- if field.required:
- label = Gtk.Label(label='*')
- label.set_tooltip_text(_('This field is required'))
- self.attach(label, 2, 3, linecounter, linecounter+1, xoptions=0,
- yoptions=0)
-
- linecounter+=1
- if self.get_property('visible'):
- self.show_all()
-
- def show(self):
- # simulate that we are one widget
- self.show_all()
-
- def on_boolean_checkbutton_toggled(self, widget, field):
- field.value = widget.get_active()
-
- def on_list_single_radiobutton_toggled(self, widget, field, value):
- field.value = value
-
- def on_list_multi_checkbutton_toggled(self, widget, field, value):
- # TODO: make some methods like add_value and remove_value
- if widget.get_active() and value not in field.values:
- field.values += [value]
- elif not widget.get_active() and value in field.values:
- field.values = [v for v in field.values if v!=value]
-
- def on_text_single_entry_changed(self, widget, field):
- field.value = widget.get_text()
-
- def on_text_multi_textbuffer_changed(self, widget, field):
- field.value = widget.get_text(widget.get_start_iter(),
- widget.get_end_iter(), True)
-
- def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, treeview,
- model, field):
- old = model[path][0]
- if old == newtext:
- return
- try:
- newtext = helpers.parse_jid(newtext)
- except helpers.InvalidFormat as s:
- dialogs.ErrorDialog(_('Invalid JID'), str(s))
- return
- if newtext in field.values:
- dialogs.ErrorDialog(
- _('JID already in list'),
- _('The JID you entered is already in the list. Choose another one.'))
- GLib.idle_add(treeview.set_cursor, path)
- return
- model[path][0]=newtext
-
- values = field.values
- values[values.index(old)]=newtext
- field.values = values
-
- def on_jid_multi_add_button_clicked(self, widget, treeview, model, field):
- #Default jid
- jid = _('new@jabber.id')
- if jid in field.values:
- i = 1
- while _('new%d@jabber.id') % i in field.values:
- i += 1
- jid = _('new%d@jabber.id') % i
- iter_ = model.insert(999999, (jid,))
- treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
- field.values = field.values + [jid]
-
- def on_jid_multi_edit_button_clicked(self, widget, treeview):
- model, iter_ = treeview.get_selection().get_selected()
- assert iter_ is not None
-
- treeview.set_cursor(model.get_path(iter_), treeview.get_column(0), True)
-
- def on_jid_multi_remove_button_clicked(self, widget, treeview, field):
- selection = treeview.get_selection()
- deleted = []
-
- def remove(model, path, iter_, deleted):
- deleted+=model[iter_]
- model.remove(iter_)
-
- selection.selected_foreach(remove, deleted)
- field.values = (v for v in field.values if v not in deleted)
-
- def on_jid_multi_clean_button_clicked(self, widget, model, field):
- model.clear()
- del field.values
diff --git a/src/dialogs.py b/src/dialogs.py
deleted file mode 100644
index 48203b566..000000000
--- a/src/dialogs.py
+++ /dev/null
@@ -1,5590 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/dialogs.py
-##
-## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2005-2008 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GObject
-from gi.repository import GLib
-import os
-import nbxmpp
-import time
-
-import gtkgui_helpers
-import vcard
-import conversation_textview
-import dataforms_widget
-
-from common import defs
-from random import randrange
-from common import pep
-from common import ged
-
-try:
- import gtkspell
- HAS_GTK_SPELL = True
-except (ImportError, ValueError):
- HAS_GTK_SPELL = False
-
-# those imports are not used in this file, but in files that 'import dialogs'
-# so they can do dialog.GajimThemesWindow() for example
-from filetransfers_window import FileTransfersWindow
-from gajim_themes_window import GajimThemesWindow
-from advanced_configuration_window import AdvancedConfigurationWindow
-
-from common import gajim
-from common import helpers
-from common import i18n
-from common import dataforms
-from common.exceptions import GajimGeneralException
-from common.connection_handlers_events import MessageOutgoingEvent
-
-import logging
-log = logging.getLogger('gajim.dialogs')
-
-
-class EditGroupsDialog:
- """
- Class for the edit group dialog window
- """
-
- def __init__(self, list_):
- """
- list_ is a list of (contact, account) tuples
- """
- self.xml = gtkgui_helpers.get_gtk_builder('edit_groups_dialog.ui')
- self.dialog = self.xml.get_object('edit_groups_dialog')
- self.dialog.set_transient_for(gajim.interface.roster.window)
- self.list_ = list_
- self.changes_made = False
- self.treeview = self.xml.get_object('groups_treeview')
- if len(list_) == 1:
- contact = list_[0][0]
- self.xml.get_object('nickname_label').set_markup(
- _('Contact name: <i>%s</i>') % contact.get_shown_name())
- self.xml.get_object('jid_label').set_markup(
- _('JID: <i>%s</i>') % contact.jid)
- else:
- self.xml.get_object('nickname_label').set_no_show_all(True)
- self.xml.get_object('nickname_label').hide()
- self.xml.get_object('jid_label').set_no_show_all(True)
- self.xml.get_object('jid_label').hide()
-
- self.xml.connect_signals(self)
- self.init_list()
-
- self.dialog.show_all()
- if self.changes_made:
- for (contact, account) in self.list_:
- gajim.connections[account].update_contact(contact.jid,
- contact.name, contact.groups)
-
- def on_edit_groups_dialog_response(self, widget, response_id):
- if response_id == Gtk.ResponseType.CLOSE:
- self.dialog.destroy()
-
- def remove_group(self, group):
- """
- Remove group group from all contacts and all their brothers
- """
- for (contact, account) in self.list_:
- gajim.interface.roster.remove_contact_from_groups(contact.jid,
- account, [group])
-
- # FIXME: Ugly workaround.
- gajim.interface.roster.draw_group(_('General'), account)
-
- def add_group(self, group):
- """
- Add group group to all contacts and all their brothers
- """
- for (contact, account) in self.list_:
- gajim.interface.roster.add_contact_to_groups(contact.jid, account,
- [group])
-
- # FIXME: Ugly workaround.
- # Maybe we haven't been in any group (defaults to General)
- gajim.interface.roster.draw_group(_('General'), account)
-
- def on_add_button_clicked(self, widget):
- group = self.xml.get_object('group_entry').get_text()
- if not group:
- return
- # Do not allow special groups
- if group in helpers.special_groups:
- return
- # check if it already exists
- model = self.treeview.get_model()
- iter_ = model.get_iter_first()
- while iter_:
- if model.get_value(iter_, 0) == group:
- return
- iter_ = model.iter_next(iter_)
- self.changes_made = True
- model.append((group, True, False))
- self.add_group(group)
- self.init_list() # Re-draw list to sort new item
-
- def group_toggled_cb(self, cell, path):
- self.changes_made = True
- model = self.treeview.get_model()
- if model[path][2]:
- model[path][2] = False
- model[path][1] = True
- else:
- model[path][1] = not model[path][1]
- group = model[path][0]
- if model[path][1]:
- self.add_group(group)
- else:
- self.remove_group(group)
-
- def init_list(self):
- store = Gtk.ListStore(str, bool, bool)
- self.treeview.set_model(store)
- for column in self.treeview.get_columns():
- # Clear treeview when re-drawing
- self.treeview.remove_column(column)
- accounts = []
- # Store groups in a list so we can sort them and the number of contacts in
- # it
- groups = {}
- for (contact, account) in self.list_:
- if account not in accounts:
- accounts.append(account)
- for g in gajim.groups[account].keys():
- if g in groups:
- continue
- groups[g] = 0
- c_groups = contact.groups
- for g in c_groups:
- groups[g] += 1
- group_list = []
- # Remove special groups if they are empty
- for group in groups:
- if group not in helpers.special_groups or groups[group] > 0:
- group_list.append(group)
- group_list.sort()
- for group in group_list:
- iter_ = store.append()
- store.set(iter_, 0, group) # Group name
- if groups[group] == 0:
- store.set(iter_, 1, False)
- else:
- store.set(iter_, 1, True)
- if groups[group] == len(self.list_):
- # all contacts are in this group
- store.set(iter_, 2, False)
- else:
- store.set(iter_, 2, True)
- column = Gtk.TreeViewColumn(_('Group'))
- column.set_expand(True)
- self.treeview.append_column(column)
- renderer = Gtk.CellRendererText()
- column.pack_start(renderer, True)
- column.add_attribute(renderer, 'text', 0)
-
- column = Gtk.TreeViewColumn(_('In the group'))
- column.set_expand(False)
- self.treeview.append_column(column)
- renderer = Gtk.CellRendererToggle()
- column.pack_start(renderer, True)
- renderer.set_property('activatable', True)
- renderer.connect('toggled', self.group_toggled_cb)
- column.add_attribute(renderer, 'active', 1)
- column.add_attribute(renderer, 'inconsistent', 2)
-
-class PassphraseDialog:
- """
- Class for Passphrase dialog
- """
- def __init__(self, titletext, labeltext, checkbuttontext=None,
- ok_handler=None, cancel_handler=None):
- self.xml = gtkgui_helpers.get_gtk_builder('passphrase_dialog.ui')
- self.window = self.xml.get_object('passphrase_dialog')
- self.passphrase_entry = self.xml.get_object('passphrase_entry')
- self.passphrase = -1
- self.window.set_title(titletext)
- self.xml.get_object('message_label').set_text(labeltext)
-
- self.ok = False
-
- self.cancel_handler = cancel_handler
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('ok_button')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancel_button')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
-
- self.xml.connect_signals(self)
- self.window.set_transient_for(gajim.interface.roster.window)
- self.window.show_all()
-
- self.check = bool(checkbuttontext)
- checkbutton = self.xml.get_object('save_passphrase_checkbutton')
- if self.check:
- checkbutton.set_label(checkbuttontext)
- else:
- checkbutton.hide()
-
- def on_okbutton_clicked(self, widget):
- if not self.ok_handler:
- return
-
- passph = self.passphrase_entry.get_text()
-
- if self.check:
- checked = self.xml.get_object('save_passphrase_checkbutton').\
- get_active()
- else:
- checked = False
-
- self.ok = True
-
- self.window.destroy()
-
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](passph, checked, *self.ok_handler[1:])
- else:
- self.ok_handler(passph, checked)
-
- def on_cancelbutton_clicked(self, widget):
- self.window.destroy()
-
- def on_passphrase_dialog_destroy(self, widget):
- if self.cancel_handler and not self.ok:
- self.cancel_handler()
-
-class ChooseGPGKeyDialog:
- """
- Class for GPG key dialog
- """
-
- def __init__(self, title_text, prompt_text, secret_keys, on_response,
- selected=None, transient_for=None):
- '''secret_keys : {keyID: userName, ...}'''
- self.on_response = on_response
- xml = gtkgui_helpers.get_gtk_builder('choose_gpg_key_dialog.ui')
- self.window = xml.get_object('choose_gpg_key_dialog')
- self.window.set_title(title_text)
- self.window.set_transient_for(transient_for)
- self.keys_treeview = xml.get_object('keys_treeview')
- prompt_label = xml.get_object('prompt_label')
- prompt_label.set_text(prompt_text)
- model = Gtk.ListStore(str, str)
- model.set_sort_func(1, self.sort_keys)
- model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
- self.keys_treeview.set_model(model)
- #columns
- renderer = Gtk.CellRendererText()
- self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
- renderer, text=0)
- col = self.keys_treeview.get_column(0)
- col.set_sort_column_id(0)
- renderer = Gtk.CellRendererText()
- self.keys_treeview.insert_column_with_attributes(-1, _('Contact name'),
- renderer, text=1)
- col = self.keys_treeview.get_column(1)
- col.set_sort_column_id(1)
- self.keys_treeview.set_search_column(1)
- self.fill_tree(secret_keys, selected)
- self.window.connect('response', self.on_dialog_response)
- self.window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
- self.window.show_all()
-
- def sort_keys(self, model, iter1, iter2, data=None):
- value1 = model[iter1][1]
- value2 = model[iter2][1]
- if value1 == _('None'):
- return -1
- elif value2 == _('None'):
- return 1
- elif value1 < value2:
- return -1
- return 1
-
- def on_dialog_response(self, dialog, response):
- selection = self.keys_treeview.get_selection()
- (model, iter_) = selection.get_selected()
- if iter_ and response == Gtk.ResponseType.OK:
- keyID = [ model[iter_][0], model[iter_][1] ]
- else:
- keyID = None
- self.on_response(keyID)
- self.window.destroy()
-
- def fill_tree(self, list_, selected):
- model = self.keys_treeview.get_model()
- for keyID in list_.keys():
- iter_ = model.append((keyID, list_[keyID]))
- if keyID == selected:
- path = model.get_path(iter_)
- self.keys_treeview.set_cursor(path)
-
-
-class ChangeActivityDialog:
- PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming',
- 'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling',
- 'working']
-
- def __init__(self, on_response, activity=None, subactivity=None, text=''):
- self.on_response = on_response
- self.activity = activity
- self.subactivity = subactivity
- self.text = text
- self.xml = gtkgui_helpers.get_gtk_builder('change_activity_dialog.ui')
- self.window = self.xml.get_object('change_activity_dialog')
- self.window.set_transient_for(gajim.interface.roster.window)
-
- self.checkbutton = self.xml.get_object('enable_checkbutton')
- self.notebook = self.xml.get_object('notebook')
- self.entry = self.xml.get_object('description_entry')
-
- rbtns = {}
- group = None
-
- for category in pep.ACTIVITIES:
- item = self.xml.get_object(category + '_image')
- item.set_from_pixbuf(
- gtkgui_helpers.load_activity_icon(category).get_pixbuf())
- item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
-
- vbox = self.xml.get_object(category + '_vbox')
- vbox.set_border_width(5)
-
- # Other
- act = category + '_other'
-
- if group:
- rbtns[act] = Gtk.RadioButton()
- rbtns[act].join_group(group)
- else:
- rbtns[act] = group = Gtk.RadioButton()
-
- hbox = Gtk.HBox(homogeneous=False, spacing=5)
- hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
- activity), False, False, 0)
- lbl = Gtk.Label(label='<b>' + pep.ACTIVITIES[category]['category'] \
- + '</b>')
- lbl.set_use_markup(True)
- hbox.pack_start(lbl, False, False, 0)
- rbtns[act].add(hbox)
- rbtns[act].connect('toggled', self.on_rbtn_toggled,
- [category, 'other'])
- vbox.pack_start(rbtns[act], False, False, 0)
-
- activities = []
- for activity in pep.ACTIVITIES[category]:
- activities.append(activity)
- activities.sort()
- for activity in activities:
- if activity == 'category':
- continue
-
- act = category + '_' + activity
-
- if group:
- rbtns[act] = Gtk.RadioButton()
- rbtns[act].join_group(group)
- else:
- rbtns[act] = group = Gtk.RadioButton()
-
- hbox = Gtk.HBox(homogeneous=False, spacing=5)
- hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
- activity), False, False, 0)
- hbox.pack_start(Gtk.Label(pep.ACTIVITIES[category][activity]),
- False, False, 0)
- rbtns[act].connect('toggled', self.on_rbtn_toggled,
- [category, activity])
- rbtns[act].add(hbox)
- vbox.pack_start(rbtns[act], False, False, 0)
-
-
- self.default_radio = rbtns['doing_chores_other']
-
- if self.activity in pep.ACTIVITIES:
- if not self.subactivity in pep.ACTIVITIES[self.activity]:
- self.subactivity = 'other'
-
- rbtns[self.activity + '_' + self.subactivity].set_active(True)
-
- self.checkbutton.set_active(True)
- self.notebook.set_sensitive(True)
- self.entry.set_sensitive(True)
-
- self.notebook.set_current_page(
- self.PAGELIST.index(self.activity))
-
- self.entry.set_text(text)
-
- else:
- self.checkbutton.set_active(False)
-
- self.xml.connect_signals(self)
- self.window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
- self.window.show_all()
-
- def on_enable_checkbutton_toggled(self, widget):
- self.notebook.set_sensitive(widget.get_active())
- self.entry.set_sensitive(widget.get_active())
- if not self.activity:
- self.default_radio.set_active(True)
-
- def on_rbtn_toggled(self, widget, data):
- if widget.get_active():
- self.activity = data[0]
- self.subactivity = data[1]
-
- def on_ok_button_clicked(self, widget):
- """
- Return activity and messsage (None if no activity selected)
- """
- if self.checkbutton.get_active():
- self.on_response(self.activity, self.subactivity,
- self.entry.get_text())
- else:
- self.on_response(None, None, '')
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
-class ChangeMoodDialog:
- COLS = 11
-
- def __init__(self, on_response, mood=None, text=''):
- self.on_response = on_response
- self.mood = mood
- self.text = text
- self.xml = gtkgui_helpers.get_gtk_builder('change_mood_dialog.ui')
-
- self.window = self.xml.get_object('change_mood_dialog')
- self.window.set_transient_for(gajim.interface.roster.window)
- self.window.set_title(_('Set Mood'))
-
- table = self.xml.get_object('mood_icons_table')
- self.label = self.xml.get_object('mood_label')
- self.entry = self.xml.get_object('description_entry')
-
- no_mood_button = self.xml.get_object('no_mood_button')
- no_mood_button.set_mode(False)
- no_mood_button.connect('clicked',
- self.on_mood_button_clicked, None)
-
- x = 1
- y = 0
- self.mood_buttons = {}
-
- # Order them first
- self.MOODS = []
- for mood in pep.MOODS:
- self.MOODS.append(mood)
- self.MOODS.sort()
-
- for mood in self.MOODS:
- self.mood_buttons[mood] = Gtk.RadioButton()
- self.mood_buttons[mood].join_group(no_mood_button)
- self.mood_buttons[mood].set_mode(False)
- self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
- self.mood_buttons[mood].set_relief(Gtk.ReliefStyle.NONE)
- self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood])
- self.mood_buttons[mood].connect('clicked',
- self.on_mood_button_clicked, mood)
- table.attach(self.mood_buttons[mood], x, y, 1, 1)
-
- # Calculate the next position
- x += 1
- if x >= self.COLS:
- x = 0
- y += 1
-
- if self.mood in pep.MOODS:
- self.mood_buttons[self.mood].set_active(True)
- self.label.set_text(pep.MOODS[self.mood])
- self.entry.set_sensitive(True)
- if self.text:
- self.entry.set_text(self.text)
- else:
- self.label.set_text(_('None'))
- self.entry.set_text('')
- self.entry.set_sensitive(False)
-
- self.xml.connect_signals(self)
- self.window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
- self.window.show_all()
-
- def on_mood_button_clicked(self, widget, data):
- if data:
- self.label.set_text(pep.MOODS[data])
- self.entry.set_sensitive(True)
- else:
- self.label.set_text(_('None'))
- self.entry.set_text('')
- self.entry.set_sensitive(False)
- self.mood = data
-
- def on_ok_button_clicked(self, widget):
- '''Return mood and messsage (None if no mood selected)'''
- message = self.entry.get_text()
- self.on_response(self.mood, message)
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
-class TimeoutDialog:
- """
- Class designed to be derivated to create timeout'd dialogs (dialogs that
- closes automatically after a timeout)
- """
- def __init__(self, timeout, on_timeout):
- self.countdown_left = timeout
- self.countdown_enabled = True
- self.title_text = ''
- self.on_timeout = on_timeout
-
- def run_timeout(self):
- if self.countdown_left > 0:
- self.countdown()
- GLib.timeout_add_seconds(1, self.countdown)
-
- def on_timeout():
- """
- To be implemented in derivated classes
- """
- pass
-
- def countdown(self):
- if self.countdown_enabled:
- if self.countdown_left <= 0:
- self.on_timeout()
- return False
- self.dialog.set_title('%s [%s]' % (self.title_text,
- str(self.countdown_left)))
- self.countdown_left -= 1
- return True
- else:
- self.dialog.set_title(self.title_text)
- return False
-
-class ChangeStatusMessageDialog(TimeoutDialog):
- def __init__(self, on_response, show=None, show_pep=True):
- countdown_time = gajim.config.get('change_status_window_timeout')
- TimeoutDialog.__init__(self, countdown_time, self.on_timeout)
- self.show = show
- self.pep_dict = {}
- self.show_pep = show_pep
- self.on_response = on_response
- self.xml = gtkgui_helpers.get_gtk_builder('change_status_message_dialog.ui')
- self.dialog = self.xml.get_object('change_status_message_dialog')
- self.dialog.set_transient_for(gajim.interface.roster.window)
- msg = None
- if show:
- uf_show = helpers.get_uf_show(show)
- self.title_text = _('%s Status Message') % uf_show
- msg = gajim.config.get_per('statusmsg', '_last_' + self.show,
- 'message')
- self.pep_dict['activity'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'activity')
- self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'subactivity')
- self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'activity_text')
- self.pep_dict['mood'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'mood')
- self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg',
- '_last_' + self.show, 'mood_text')
- else:
- self.title_text = _('Status Message')
- self.dialog.set_title(self.title_text)
-
- message_textview = self.xml.get_object('message_textview')
- self.message_buffer = message_textview.get_buffer()
- self.message_buffer.connect('changed', self.on_message_buffer_changed)
- if not msg:
- msg = ''
- msg = helpers.from_one_line(msg)
- self.message_buffer.set_text(msg)
-
- # have an empty string selectable, so user can clear msg
- self.preset_messages_dict = {'': ['', '', '', '', '', '']}
- for msg_name in gajim.config.get_per('statusmsg'):
- if msg_name.startswith('_last_'):
- continue
- opts = []
- for opt in ['message', 'activity', 'subactivity', 'activity_text',
- 'mood', 'mood_text']:
- opts.append(gajim.config.get_per('statusmsg', msg_name, opt))
- opts[0] = helpers.from_one_line(opts[0])
- self.preset_messages_dict[msg_name] = opts
- sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict)
-
- self.message_liststore = Gtk.ListStore(str) # msg_name
- self.message_combobox = self.xml.get_object('message_combobox')
- self.message_combobox.set_model(self.message_liststore)
- cellrenderertext = Gtk.CellRendererText()
- self.message_combobox.pack_start(cellrenderertext, True)
- self.message_combobox.add_attribute(cellrenderertext, 'text', 0)
- for msg_name in sorted_keys_list:
- self.message_liststore.append((msg_name,))
-
- if show_pep:
- self.draw_activity()
- self.draw_mood()
- else:
- # remove acvtivity / mood lines
- self.xml.get_object('activity_label').set_no_show_all(True)
- self.xml.get_object('activity_button').set_no_show_all(True)
- self.xml.get_object('mood_label').set_no_show_all(True)
- self.xml.get_object('mood_button').set_no_show_all(True)
- self.xml.get_object('activity_label').hide()
- self.xml.get_object('activity_button').hide()
- self.xml.get_object('mood_label').hide()
- self.xml.get_object('mood_button').hide()
-
- self.xml.connect_signals(self)
- self.run_timeout()
- self.dialog.connect('response', self.on_dialog_response)
- self.dialog.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
- self.dialog.show_all()
-
- def draw_activity(self):
- """
- Set activity button
- """
- img = self.xml.get_object('activity_image')
- label = self.xml.get_object('activity_button_label')
- if 'activity' in self.pep_dict and self.pep_dict['activity'] in \
- pep.ACTIVITIES:
- if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] \
- in pep.ACTIVITIES[self.pep_dict['activity']]:
- img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
- self.pep_dict['activity'], self.pep_dict['subactivity']).\
- get_pixbuf())
- else:
- img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
- self.pep_dict['activity']).get_pixbuf())
- if self.pep_dict['activity_text']:
- label.set_text(self.pep_dict['activity_text'])
- else:
- label.set_text('')
- else:
- img.set_from_pixbuf(None)
- label.set_text('')
-
- def draw_mood(self):
- """
- Set mood button
- """
- img = self.xml.get_object('mood_image')
- label = self.xml.get_object('mood_button_label')
- if 'mood' in self.pep_dict and self.pep_dict['mood'] in pep.MOODS:
- img.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
- self.pep_dict['mood']).get_pixbuf())
- if self.pep_dict['mood_text']:
- label.set_text(self.pep_dict['mood_text'])
- else:
- label.set_text('')
- else:
- img.set_from_pixbuf(None)
- label.set_text('')
-
- def on_timeout(self):
- # Prevent GUI freeze when the combobox menu is opened on close
- self.message_combobox.popdown()
- self.dialog.response(Gtk.ResponseType.OK)
-
- def on_dialog_response(self, dialog, response):
- if response == Gtk.ResponseType.OK:
- beg, end = self.message_buffer.get_bounds()
- message = self.message_buffer.get_text(beg, end, True).strip()
- message = helpers.remove_invalid_xml_chars(message)
- msg = helpers.to_one_line(message)
- if self.show:
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'message', msg)
- if self.show_pep:
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'activity', self.pep_dict['activity'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'subactivity', self.pep_dict['subactivity'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'activity_text', self.pep_dict['activity_text'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'mood', self.pep_dict['mood'])
- gajim.config.set_per('statusmsg', '_last_' + self.show,
- 'mood_text', self.pep_dict['mood_text'])
- else:
- message = None # user pressed Cancel button or X wm button
- self.dialog.destroy()
- self.on_response(message, self.pep_dict)
-
- def on_message_combobox_changed(self, widget):
- self.countdown_enabled = False
- model = widget.get_model()
- active = widget.get_active()
- if active < 0:
- return None
- name = model[active][0]
- self.message_buffer.set_text(self.preset_messages_dict[name][0])
- self.pep_dict['activity'] = self.preset_messages_dict[name][1]
- self.pep_dict['subactivity'] = self.preset_messages_dict[name][2]
- self.pep_dict['activity_text'] = self.preset_messages_dict[name][3]
- self.pep_dict['mood'] = self.preset_messages_dict[name][4]
- self.pep_dict['mood_text'] = self.preset_messages_dict[name][5]
- self.draw_activity()
- self.draw_mood()
-
- def on_change_status_message_dialog_key_press_event(self, widget, event):
- self.countdown_enabled = False
- if event.keyval == Gdk.KEY_Return or \
- event.keyval == Gdk.KEY_KP_Enter: # catch CTRL+ENTER
- if (event.get_state() & Gdk.ModifierType.CONTROL_MASK):
- self.dialog.response(Gtk.ResponseType.OK)
- # Stop the event
- return True
-
- def on_message_buffer_changed(self, widget):
- self.countdown_enabled = False
- self.toggle_sensitiviy_of_save_as_preset()
-
- def toggle_sensitiviy_of_save_as_preset(self):
- btn = self.xml.get_object('save_as_preset_button')
- if self.message_buffer.get_char_count() == 0:
- btn.set_sensitive(False)
- else:
- btn.set_sensitive(True)
-
- def on_save_as_preset_button_clicked(self, widget):
- self.countdown_enabled = False
- start_iter, finish_iter = self.message_buffer.get_bounds()
- status_message_to_save_as_preset = self.message_buffer.get_text(
- start_iter, finish_iter, True)
- def on_ok(msg_name):
- msg_text = status_message_to_save_as_preset
- msg_text_1l = helpers.to_one_line(msg_text)
- if not msg_name: # msg_name was ''
- msg_name = msg_text_1l
-
- def on_ok2():
- self.preset_messages_dict[msg_name] = [
- msg_text, self.pep_dict.get('activity'),
- self.pep_dict.get('subactivity'),
- self.pep_dict.get('activity_text'),
- self.pep_dict.get('mood'), self.pep_dict.get('mood_text')]
- gajim.config.set_per('statusmsg', msg_name, 'message',
- msg_text_1l)
- gajim.config.set_per('statusmsg', msg_name, 'activity',
- self.pep_dict.get('activity'))
- gajim.config.set_per('statusmsg', msg_name, 'subactivity',
- self.pep_dict.get('subactivity'))
- gajim.config.set_per('statusmsg', msg_name, 'activity_text',
- self.pep_dict.get('activity_text'))
- gajim.config.set_per('statusmsg', msg_name, 'mood',
- self.pep_dict.get('mood'))
- gajim.config.set_per('statusmsg', msg_name, 'mood_text',
- self.pep_dict.get('mood_text'))
- if msg_name in self.preset_messages_dict:
- ConfirmationDialog(_('Overwrite Status Message?'),
- _('This name is already used. Do you want to overwrite this '
- 'status message?'), on_response_ok=on_ok2, transient_for=self.dialog)
- return
- gajim.config.add_per('statusmsg', msg_name)
- on_ok2()
- iter_ = self.message_liststore.append((msg_name,))
- # select in combobox the one we just saved
- self.message_combobox.set_active_iter(iter_)
- InputDialog(_('Save as Preset Status Message'),
- _('Please type a name for this status message'), is_modal=False,
- ok_handler=on_ok)
-
- def on_activity_button_clicked(self, widget):
- self.countdown_enabled = False
- def on_response(activity, subactivity, text):
- self.pep_dict['activity'] = activity or ''
- self.pep_dict['subactivity'] = subactivity or ''
- self.pep_dict['activity_text'] = text
- self.draw_activity()
- ChangeActivityDialog(on_response, self.pep_dict['activity'],
- self.pep_dict['subactivity'], self.pep_dict['activity_text'])
-
- def on_mood_button_clicked(self, widget):
- self.countdown_enabled = False
- def on_response(mood, text):
- self.pep_dict['mood'] = mood or ''
- self.pep_dict['mood_text'] = text
- self.draw_mood()
- ChangeMoodDialog(on_response, self.pep_dict['mood'],
- self.pep_dict['mood_text'])
-
-class AddNewContactWindow:
- """
- Class for AddNewContactWindow
- """
-
- uid_labels = {'jabber': _('JID:'),
- 'aim': _('AIM Address:'),
- 'gadu-gadu': _('GG Number:'),
- 'icq': _('ICQ Number:'),
- 'msn': _('MSN Address:'),
- 'yahoo': _('Yahoo! Address:')}
-
- def __init__(self, account=None, jid=None, user_nick=None, group=None):
- self.account = account
- self.adding_jid = False
- if account is None:
- # fill accounts with active accounts
- accounts = []
- for account in gajim.connections.keys():
- if gajim.connections[account].connected > 1:
- accounts.append(account)
- if not accounts:
- return
- if len(accounts) == 1:
- self.account = account
- else:
- accounts = [self.account]
- if self.account:
- location = gajim.interface.instances[self.account]
- else:
- location = gajim.interface.instances
- if 'add_contact' in location:
- location['add_contact'].window.present()
- # An instance is already opened
- return
- location['add_contact'] = self
- self.xml = gtkgui_helpers.get_gtk_builder('add_new_contact_window.ui')
- self.xml.connect_signals(self)
- self.window = self.xml.get_object('add_new_contact_window')
- for w in ('account_combobox', 'account_hbox', 'account_label',
- 'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
- 'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
- 'save_message_checkbutton', 'register_hbox', 'subscription_table',
- 'add_button', 'message_textview', 'connected_label',
- 'group_comboboxentry', 'auto_authorize_checkbutton'):
- self.__dict__[w] = self.xml.get_object(w)
- if account and len(gajim.connections) >= 2:
- self.default_desc = _('Please fill in the data of the contact you want\n'
- 'to add to your account <b>%s</b>') % account
- else:
- self.default_desc = _('Please fill in the data of the contact you '
- 'want to add')
- self.xml.get_object('prompt_label').set_markup(self.default_desc)
- self.agents = {'jabber': []}
- self.gateway_prompt = {}
- # types to which we are not subscribed but account has an agent for it
- self.available_types = []
- for acct in accounts:
- for j in gajim.contacts.get_jid_list(acct):
- if gajim.jid_is_transport(j):
- type_ = gajim.get_transport_name_from_jid(j, False)
- if not type_:
- continue
- if type_ in self.agents:
- self.agents[type_].append(j)
- else:
- self.agents[type_] = [j]
- self.gateway_prompt[j] = {'desc': None, 'prompt': None}
- # Now add the one to which we can register
- for acct in accounts:
- for type_ in gajim.connections[acct].available_transports:
- if type_ in self.agents:
- continue
- self.agents[type_] = []
- for jid_ in gajim.connections[acct].available_transports[type_]:
- if not jid_ in self.agents[type_]:
- self.agents[type_].append(jid_)
- self.gateway_prompt[jid_] = {'desc': None,
- 'prompt': None}
- self.available_types.append(type_)
- # Combobox with transport/jabber icons
- liststore = Gtk.ListStore(str, GdkPixbuf.Pixbuf, str)
- cell = Gtk.CellRendererPixbuf()
- self.protocol_combobox.pack_start(cell, False)
- self.protocol_combobox.add_attribute(cell, 'pixbuf', 1)
- cell = Gtk.CellRendererText()
- cell.set_property('xpad', 5)
- self.protocol_combobox.pack_start(cell, True)
- self.protocol_combobox.add_attribute(cell, 'text', 0)
- self.protocol_combobox.set_model(liststore)
- uf_type = {'jabber': 'XMPP', 'aim': 'AIM', 'gadu-gadu': 'Gadu Gadu',
- 'icq': 'ICQ', 'msn': 'MSN', 'yahoo': 'Yahoo'}
- # Jabber as first
- img = gajim.interface.jabber_state_images['16']['online']
- liststore.append(['XMPP', img.get_pixbuf(), 'jabber'])
- for type_ in self.agents:
- if type_ == 'jabber':
- continue
- imgs = gajim.interface.roster.transports_state_images
- img = None
- if type_ in imgs['16'] and 'online' in imgs['16'][type_]:
- img = imgs['16'][type_]['online']
- if type_ in uf_type:
- liststore.append([uf_type[type_], img.get_pixbuf(), type_])
- else:
- liststore.append([type_, img.get_pixbuf(), type_])
- else:
- liststore.append([type_, img, type_])
- if account:
- for service in self.agents[type_]:
- gajim.connections[account].request_gateway_prompt(service)
- self.protocol_combobox.set_active(0)
- self.auto_authorize_checkbutton.show()
- liststore = Gtk.ListStore(str)
- self.protocol_jid_combobox.set_model(liststore)
- if jid:
- self.jid_escaped = True
- type_ = gajim.get_transport_name_from_jid(jid)
- if not type_:
- type_ = 'jabber'
- if type_ == 'jabber':
- self.uid_entry.set_text(jid)
- else:
- uid, transport = gajim.get_name_and_server_from_jid(jid)
- self.uid_entry.set_text(uid.replace('%', '@', 1))
- # set protocol_combobox
- model = self.protocol_combobox.get_model()
- iter_ = model.get_iter_first()
- i = 0
- while iter_:
- if model[iter_][2] == type_:
- self.protocol_combobox.set_active(i)
- break
- iter_ = model.iter_next(iter_)
- i += 1
-
- # set protocol_jid_combobox
- self.protocol_jid_combobox.set_active(0)
- model = self.protocol_jid_combobox.get_model()
- iter_ = model.get_iter_first()
- i = 0
- while iter_:
- if model[iter_][0] == transport:
- self.protocol_jid_combobox.set_active(i)
- break
- iter_ = model.iter_next(iter_)
- i += 1
- if user_nick:
- self.nickname_entry.set_text(user_nick)
- self.nickname_entry.grab_focus()
- else:
- self.jid_escaped = False
- self.uid_entry.grab_focus()
- group_names = []
- for acct in accounts:
- for g in gajim.groups[acct].keys():
- if g not in helpers.special_groups and g not in group_names:
- group_names.append(g)
- group_names.sort()
- i = 0
- for g in group_names:
- self.group_comboboxentry.append_text(g)
- if group == g:
- self.group_comboboxentry.set_active(i)
- i += 1
-
- self.window.set_transient_for(gajim.interface.roster.window)
- self.window.show_all()
-
- if self.account:
- self.account_label.hide()
- self.account_hbox.hide()
- else:
- liststore = Gtk.ListStore(str, str)
- for acct in accounts:
- liststore.append([acct, acct])
- self.account_combobox.set_model(liststore)
- self.account_combobox.set_active(0)
-
- if self.account:
- message_buffer = self.message_textview.get_buffer()
- msg = helpers.from_one_line(helpers.get_subscription_request_msg(
- self.account))
- message_buffer.set_text(msg)
-
- gajim.ged.register_event_handler('gateway-prompt-received', ged.GUI1,
- self._nec_gateway_prompt_received)
- gajim.ged.register_event_handler('presence-received', ged.GUI1,
- self._nec_presence_received)
-
- def on_add_new_contact_window_destroy(self, widget):
- if self.account:
- location = gajim.interface.instances[self.account]
- else:
- location = gajim.interface.instances
- del location['add_contact']
- gajim.ged.remove_event_handler('presence-received', ged.GUI1,
- self._nec_presence_received)
- gajim.ged.remove_event_handler('gateway-prompt-received', ged.GUI1,
- self._nec_gateway_prompt_received)
-
- def on_register_button_clicked(self, widget):
- model = self.protocol_jid_combobox.get_model()
- row = self.protocol_jid_combobox.get_active()
- jid = model[row][0]
- gajim.connections[self.account].request_register_agent_info(jid)
-
- def on_add_new_contact_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape: # ESCAPE
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- """
- When Cancel button is clicked
- """
- self.window.destroy()
-
- def on_add_button_clicked(self, widget):
- """
- When Subscribe button is clicked
- """
- jid = self.uid_entry.get_text().strip()
- if not jid:
- return
-
- model = self.protocol_combobox.get_model()
- row = self.protocol_combobox.get_active_iter()
- type_ = model[row][2]
- if type_ != 'jabber':
- model = self.protocol_jid_combobox.get_model()
- row = self.protocol_jid_combobox.get_active()
- transport = model[row][0]
- if self.account and not self.jid_escaped:
- self.adding_jid = (jid, transport, type_)
- gajim.connections[self.account].request_gateway_prompt(
- transport, jid)
- else:
- jid = jid.replace('@', '%') + '@' + transport
- self._add_jid(jid, type_)
- else:
- self._add_jid(jid, type_)
-
- def _add_jid(self, jid, type_):
- # check if jid is conform to RFC and stringprep it
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as s:
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, str(s))
- return
-
- # No resource in jid
- if jid.find('/') >= 0:
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, _('The user ID must not contain a resource.'))
- return
-
- if jid == gajim.get_jid_from_account(self.account):
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, _('You cannot add yourself to your roster.'))
- return
-
- nickname = self.nickname_entry.get_text() or ''
- # get value of account combobox, if account was not specified
- if not self.account:
- model = self.account_combobox.get_model()
- index = self.account_combobox.get_active()
- self.account = model[index][1]
-
- # Check if jid is already in roster
- if jid in gajim.contacts.get_jid_list(self.account):
- c = gajim.contacts.get_first_contact_from_jid(self.account, jid)
- if _('Not in Roster') not in c.groups and c.sub in ('both', 'to'):
- ErrorDialog(_('Contact already in roster'),
- _('This contact is already listed in your roster.'))
- return
-
- if type_ == 'jabber':
- message_buffer = self.message_textview.get_buffer()
- start_iter = message_buffer.get_start_iter()
- end_iter = message_buffer.get_end_iter()
- message = message_buffer.get_text(start_iter, end_iter, True)
- if self.save_message_checkbutton.get_active():
- msg = helpers.to_one_line(message)
- gajim.config.set_per('accounts', self.account,
- 'subscription_request_msg', msg)
- else:
- message= ''
- group = self.group_comboboxentry.get_child().get_text()
- groups = []
- if group:
- groups = [group]
- auto_auth = self.auto_authorize_checkbutton.get_active()
- gajim.interface.roster.req_sub(self, jid, message, self.account,
- groups=groups, nickname=nickname, auto_auth=auto_auth)
- self.window.destroy()
-
- def on_account_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- account = model[iter_][0]
- message_buffer = self.message_textview.get_buffer()
- message_buffer.set_text(helpers.get_subscription_request_msg(account))
-
- def on_protocol_jid_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- if not iter_:
- return
- jid_ = model[iter_][0]
- model = self.protocol_combobox.get_model()
- iter_ = self.protocol_combobox.get_active_iter()
- type_ = model[iter_][2]
- desc = None
- if self.agents[type_] and jid_ in self.gateway_prompt:
- desc = self.gateway_prompt[jid_]['desc']
- if not desc:
- desc = self.default_desc
- self.xml.get_object('prompt_label').set_markup(desc)
-
- prompt = None
- if self.agents[type_] and jid_ in self.gateway_prompt:
- prompt = self.gateway_prompt[jid_]['prompt']
- if not prompt:
- if type_ in self.uid_labels:
- prompt = self.uid_labels[type_]
- else:
- prompt = _('User ID:')
- self.uid_label.set_text(prompt)
-
- def on_protocol_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- type_ = model[iter_][2]
- model = self.protocol_jid_combobox.get_model()
- model.clear()
- if len(self.agents[type_]):
- for jid_ in self.agents[type_]:
- model.append([jid_])
- self.protocol_jid_combobox.set_active(0)
- desc = None
- if self.agents[type_]:
- jid_ = self.agents[type_][0]
- if jid_ in self.gateway_prompt:
- desc = self.gateway_prompt[jid_]['desc']
- if not desc:
- desc = self.default_desc
- self.xml.get_object('prompt_label').set_markup(desc)
- if len(self.agents[type_]) > 1:
- self.protocol_jid_combobox.show()
- else:
- self.protocol_jid_combobox.hide()
- prompt = None
- if self.agents[type_]:
- jid_ = self.agents[type_][0]
- if jid_ in self.gateway_prompt:
- prompt = self.gateway_prompt[jid_]['prompt']
- if not prompt:
- if type_ in self.uid_labels:
- prompt = self.uid_labels[type_]
- else:
- prompt = _('User ID:')
- self.uid_label.set_text(prompt)
-
- if type_ == 'jabber':
- self.message_scrolledwindow.show()
- self.save_message_checkbutton.show()
- else:
- self.message_scrolledwindow.hide()
- self.save_message_checkbutton.hide()
- if type_ in self.available_types:
- self.register_hbox.show()
- self.auto_authorize_checkbutton.hide()
- self.connected_label.hide()
- self.subscription_table.hide()
- self.add_button.set_sensitive(False)
- else:
- self.register_hbox.hide()
- if type_ != 'jabber':
- model = self.protocol_jid_combobox.get_model()
- row = self.protocol_jid_combobox.get_active()
- jid = model[row][0]
- contact = gajim.contacts.get_first_contact_from_jid(
- self.account, jid)
- if contact.show in ('offline', 'error'):
- self.subscription_table.hide()
- self.connected_label.show()
- self.add_button.set_sensitive(False)
- self.auto_authorize_checkbutton.hide()
- return
- self.subscription_table.show()
- self.auto_authorize_checkbutton.show()
- self.connected_label.hide()
- self.add_button.set_sensitive(True)
-
- def transport_signed_in(self, jid):
- model = self.protocol_jid_combobox.get_model()
- row = self.protocol_jid_combobox.get_active()
- _jid = model[row][0]
- if _jid == jid:
- self.register_hbox.hide()
- self.connected_label.hide()
- self.subscription_table.show()
- self.auto_authorize_checkbutton.show()
- self.add_button.set_sensitive(True)
-
- def transport_signed_out(self, jid):
- model = self.protocol_jid_combobox.get_model()
- row = self.protocol_jid_combobox.get_active()
- _jid = model[row][0]
- if _jid == jid:
- self.subscription_table.hide()
- self.auto_authorize_checkbutton.hide()
- self.connected_label.show()
- self.add_button.set_sensitive(False)
-
- def _nec_presence_received(self, obj):
- if gajim.jid_is_transport(obj.jid):
- if obj.old_show == 0 and obj.new_show > 1:
- self.transport_signed_in(obj.jid)
- elif obj.old_show > 1 and obj.new_show == 0:
- self.transport_signed_out(obj.jid)
-
- def _nec_gateway_prompt_received(self, obj):
- if self.adding_jid:
- jid, transport, type_ = self.adding_jid
- if obj.stanza.getError():
- ErrorDialog(_('Error while adding transport contact'),
- _('This error occured while adding a contact for transport '
- '%s:\n\n%s') % (transport, obj.stanza.getErrorMsg()))
- return
- if obj.prompt_jid:
- self._add_jid(obj.prompt_jid, type_)
- else:
- jid = jid.replace('@', '%') + '@' + transport
- self._add_jid(jid, type_)
- elif obj.jid in self.gateway_prompt:
- if obj.desc:
- self.gateway_prompt[obj.jid]['desc'] = obj.desc
- if obj.prompt:
- self.gateway_prompt[obj.jid]['prompt'] = obj.prompt
-
-class AboutDialog(Gtk.AboutDialog):
- """
- Class for about dialog
- """
-
- def __init__(self):
- Gtk.AboutDialog.__init__(self)
- self.set_transient_for(gajim.interface.roster.window)
- self.set_name('Gajim')
- self.set_version(gajim.version)
- s = 'Copyright © 2003-2014 Gajim Team'
- self.set_copyright(s)
- copying_file_path = self.get_path('COPYING')
- if copying_file_path:
- with open(copying_file_path) as a_file:
- text = a_file.read()
- self.set_license(text)
-
- gtk_ver = '%i.%i.%i' % (Gtk.get_major_version(),
- Gtk.get_minor_version(), Gtk.get_micro_version())
- gobject_ver = self.tuple2str(GObject.pygobject_version)
- nbxmpp_ver = nbxmpp.__version__
- self.set_comments('%s\n%s %s\n%s %s\n%s %s' % (_('A GTK+ XMPP client'),
- _('GTK+ Version:'), gtk_ver, _('PyGobject Version:'), gobject_ver,
- _('python-nbxmpp Version:'), nbxmpp_ver))
- self.set_website('https://gajim.org/')
-
- authors_file_path = self.get_path('AUTHORS')
- if authors_file_path:
- authors = []
- with open(authors_file_path) as a_file:
- authors_file = a_file.read()
- authors_file = authors_file.split('\n')
- for author in authors_file:
- if author == 'CURRENT DEVELOPERS:':
- authors.append(_('Current Developers:'))
- elif author == 'PAST DEVELOPERS:':
- authors.append('\n' + _('Past Developers:'))
- elif author != '': # Real author line
- authors.append(author)
-
- thanks_file_path = self.get_path('THANKS')
- if thanks_file_path:
- authors.append('\n' + _('THANKS:'))
- with open(thanks_file_path) as a_file:
- text = a_file.read()
- text_splitted = text.split('\n')
- text = '\n'.join(text_splitted[:-2]) # remove one english sentence
- # and add it manually as translatable
- text += '\n%s\n' % _('Last but not least, we would like to '
- 'thank all the package maintainers.')
- authors.append(text)
-
- self.set_authors(authors)
-
- self.props.wrap_license = True
-
- pixbuf = gtkgui_helpers.get_icon_pixmap('org.gajim.Gajim', 128)
-
- self.set_logo(pixbuf)
- #here you write your name in the form Name FamilyName <someone@somewhere>
- self.set_translator_credits(_('translator-credits'))
-
- thanks_artists_file_path = self.get_path('THANKS.artists')
- if thanks_artists_file_path:
- with open(thanks_artists_file_path) as a_file:
- artists_text = a_file.read()
- artists = artists_text.split('\n')
- self.set_artists(artists)
-
- self.connect('response', self.on_response)
- self.show_all()
-
- def on_response(self, dialog, response_id):
- if response_id == Gtk.ResponseType.DELETE_EVENT:
- dialog.destroy()
-
- def tuple2str(self, tuple_):
- str_ = ''
- for num in tuple_:
- str_ += str(num) + '.'
- return str_[0:-1] # remove latest .
-
- def get_path(self, filename):
- """
- Where can we find this Credits file?
- """
- if os.path.isfile(os.path.join(defs.docdir, filename)):
- return os.path.join(defs.docdir, filename)
- elif os.path.isfile('../' + filename):
- return ('../' + filename)
- else:
- return None
-
-class Dialog(Gtk.Dialog):
- def __init__(self, parent, title, buttons, default=None,
- on_response_ok=None, on_response_cancel=None):
- GObject.GObject.__init__(self, title, parent,
- Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.NO_SEPARATOR)
-
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- self.set_border_width(6)
- self.vbox.set_spacing(12)
- self.set_resizable(False)
-
- for stock, response in buttons:
- b = self.add_button(stock, response)
-
- if default is not None:
- self.set_default_response(default)
- else:
- self.set_default_response(buttons[-1][1])
-
- self.connect('response', self.on_response)
-
- def on_response(self, widget, response_id):
- if response_id == Gtk.ResponseType.OK:
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.destroy()
- elif response_id == Gtk.ResponseType.CANCEL:
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_ok[1:])
- else:
- self.user_response_cancel()
- self.destroy()
-
- def just_destroy(self, widget):
- self.destroy()
-
- def get_button(self, index):
- buttons = self.action_area.get_children()
- return index < len(buttons) and buttons[index] or None
-
-
-class HigDialog(Gtk.MessageDialog):
- def __init__(self, parent, type_, buttons, pritext, sectext,
- on_response_ok=None, on_response_cancel=None, on_response_yes=None,
- on_response_no=None):
- self.call_cancel_on_destroy = True
- Gtk.MessageDialog.__init__(self, transient_for=parent,
- modal=True, destroy_with_parent=True,
- message_type=type_, buttons=buttons, text=pritext)
-
- self.format_secondary_markup(sectext)
-
- self.possible_responses = {Gtk.ResponseType.OK: on_response_ok,
- Gtk.ResponseType.CANCEL: on_response_cancel,
- Gtk.ResponseType.YES: on_response_yes,
- Gtk.ResponseType.NO: on_response_no}
-
- self.connect('response', self.on_response)
- self.connect('destroy', self.on_dialog_destroy)
-
- def on_response(self, dialog, response_id):
- if not response_id in self.possible_responses:
- return
- if not self.possible_responses[response_id]:
- self.destroy()
- elif isinstance(self.possible_responses[response_id], tuple):
- if len(self.possible_responses[response_id]) == 1:
- self.possible_responses[response_id][0](dialog)
- else:
- self.possible_responses[response_id][0](dialog,
- *self.possible_responses[response_id][1:])
- else:
- self.possible_responses[response_id](dialog)
-
-
- def on_dialog_destroy(self, widget):
- if not self.call_cancel_on_destroy:
- return
- cancel_handler = self.possible_responses[Gtk.ResponseType.CANCEL]
- if not cancel_handler:
- return False
- if isinstance(cancel_handler, tuple):
- cancel_handler[0](None, *cancel_handler[1:])
- else:
- cancel_handler(None)
-
- def popup(self):
- """
- Show dialog
- """
- vb = self.get_children()[0].get_children()[0] # Give focus to top vbox
-# vb.set_flags(Gtk.CAN_FOCUS)
- vb.grab_focus()
- self.show_all()
-
-class FileChooserDialog(Gtk.FileChooserDialog):
- """
- Non-blocking FileChooser Dialog around Gtk.FileChooserDialog
- """
- def __init__(self, title_text, action, buttons, default_response,
- select_multiple=False, current_folder=None, on_response_ok=None,
- on_response_cancel=None, transient_for=None):
-
- Gtk.FileChooserDialog.__init__(self, title=title_text,
- parent=transient_for, action=action)
- self.add_button(buttons[0],buttons[1])
- if len(buttons) ==4:
- self.add_button(buttons[2],buttons[3])
- self.set_default_response(default_response)
- self.set_select_multiple(select_multiple)
- if current_folder and os.path.isdir(current_folder):
- self.set_current_folder(current_folder)
- else:
- self.set_current_folder(os.path.expanduser('~'))
- self.response_ok, self.response_cancel = \
- on_response_ok, on_response_cancel
- # in gtk+-2.10 clicked signal on some of the buttons in a dialog
- # is emitted twice, so we cannot rely on 'clicked' signal
- self.connect('response', self.on_dialog_response)
- self.show_all()
-
- def on_dialog_response(self, dialog, response):
- if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.CLOSE):
- if self.response_cancel:
- if isinstance(self.response_cancel, tuple):
- self.response_cancel[0](dialog, *self.response_cancel[1:])
- else:
- self.response_cancel(dialog)
- else:
- self.just_destroy(dialog)
- elif response == Gtk.ResponseType.OK:
- if self.response_ok:
- if isinstance(self.response_ok, tuple):
- self.response_ok[0](dialog, *self.response_ok[1:])
- else:
- self.response_ok(dialog)
- else:
- self.just_destroy(dialog)
-
- def just_destroy(self, widget):
- self.destroy()
-
-class AspellDictError:
- def __init__(self, lang):
- ErrorDialog(
- _('Dictionary for lang %s not available') % lang,
- _('You have to install %s dictionary to use spellchecking, or '
- 'choose another language by setting the speller_language option.'
- '\n\nHighlighting misspelled words feature will not be used') % lang)
- gajim.config.set('use_speller', False)
-
-class ConfirmationDialog(HigDialog):
- """
- HIG compliant confirmation dialog
- """
-
- def __init__(self, pritext, sectext='', on_response_ok=None,
- on_response_cancel=None, transient_for=None):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- HigDialog.__init__(self, transient_for,
- Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, pritext, sectext,
- self.on_response_ok, self.on_response_cancel)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_ok[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
-class NonModalConfirmationDialog(HigDialog):
- """
- HIG compliant non modal confirmation dialog
- """
-
- def __init__(self, pritext, sectext='', on_response_ok=None,
- on_response_cancel=None):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
- if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
- self.set_modal(False)
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](*self.user_response_ok[1:])
- else:
- self.user_response_ok()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
-class WarningDialog(HigDialog):
- """
- HIG compliant warning dialog
- """
-
- def __init__(self, pritext, sectext='', transient_for=None):
- if not transient_for and hasattr(gajim.interface, 'roster') and \
- gajim.interface.roster:
- transient_for = gajim.interface.roster.window
- HigDialog.__init__(self, transient_for, Gtk.MessageType.WARNING,
- Gtk.ButtonsType.OK, pritext, sectext)
- self.set_modal(False)
- self.popup()
-
-class InformationDialog(HigDialog):
- """
- HIG compliant info dialog
- """
-
- def __init__(self, pritext, sectext='', transient_for=None):
- if transient_for:
- parent = transient_for
- elif hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
- pritext, sectext)
- self.set_modal(False)
- self.popup()
-
-class ErrorDialog(HigDialog):
- """
- HIG compliant error dialog
- """
-
- def __init__(self, pritext, sectext='', on_response_ok=None,
- on_response_cancel=None, transient_for=None):
- if transient_for:
- parent = transient_for
- elif hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK,
- pritext, sectext, on_response_ok=on_response_ok,
- on_response_cancel=on_response_cancel)
- self.popup()
-
-class YesNoDialog(HigDialog):
- """
- HIG compliant YesNo dialog
- """
-
- def __init__(self, pritext, sectext='', checktext='', text_label=None,
- on_response_yes=None, on_response_no=None, type_=Gtk.MessageType.QUESTION,
- transient_for=None):
- self.user_response_yes = on_response_yes
- self.user_response_no = on_response_no
- if transient_for:
- parent = transient_for
- elif hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, type_, Gtk.ButtonsType.YES_NO, pritext,
- sectext, on_response_yes=self.on_response_yes,
- on_response_no=self.on_response_no)
-
- if checktext:
- self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
- self.vbox.pack_start(self.checkbutton, False, True, 0)
- else:
- self.checkbutton = None
- if text_label:
- label = Gtk.Label(label=text_label)
- self.vbox.pack_start(label, False, True, 0)
- buff = Gtk.TextBuffer()
- self.textview = Gtk.TextView.new_with_buffer(buff)
- frame = Gtk.Frame()
- frame.set_shadow_type(Gtk.ShadowType.IN)
- frame.add(self.textview)
- self.vbox.pack_start(frame, False, True, 0)
- else:
- self.textview = None
- self.set_modal(False)
- self.popup()
-
- def on_response_yes(self, widget):
- if self.user_response_yes:
- if self.textview:
- buff = self.textview.get_buffer()
- start, end = buff.get_bounds()
- txt = self.textview.get_buffer().get_text(start, end, True)
-
- if isinstance(self.user_response_yes, tuple):
- if self.textview:
- self.user_response_yes[0](self.is_checked(), txt,
- *self.user_response_yes[1:])
- else:
- self.user_response_yes[0](self.is_checked(),
- *self.user_response_yes[1:])
- else:
- if self.textview:
- self.user_response_yes(self.is_checked(), txt)
- else:
- self.user_response_yes(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_no(self, widget):
- if self.user_response_no:
- if self.textview:
- buff = self.textview.get_buffer()
- start, end = buff.get_bounds()
- txt = self.textview.get_buffer().get_text(start, end, True)
-
- if isinstance(self.user_response_no, tuple):
- if self.textview:
- self.user_response_no[0](txt, *self.user_response_no[1:])
- else:
- self.user_response_no[0](*self.user_response_no[1:])
- else:
- if self.textview:
- self.user_response_no(txt)
- else:
- self.user_response_no()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- if not self.checkbutton:
- return False
- return self.checkbutton.get_active()
-
-class ConfirmationDialogCheck(ConfirmationDialog):
- """
- HIG compliant confirmation dialog with checkbutton
- """
-
- def __init__(self, pritext, sectext='', checktext='', on_response_ok=None,
- on_response_cancel=None, is_modal=True, transient_for=None):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- if transient_for:
- parent = transient_for
- elif hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(Gtk.ResponseType.OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
- self.vbox.pack_start(self.checkbutton, False, True, 0)
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](self.is_checked(),
- *self.user_response_cancel[1:])
- else:
- self.user_response_cancel(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- return self.checkbutton.get_active()
-
-class ConfirmationDialogDoubleCheck(ConfirmationDialog):
- """
- HIG compliant confirmation dialog with 2 checkbuttons
- """
-
- def __init__(self, pritext, sectext='', checktext1='', checktext2='',
- tooltip1='', tooltip2='', on_response_ok=None, on_response_cancel=None,
- is_modal=True):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(Gtk.ResponseType.OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- if checktext1:
- self.checkbutton1 = Gtk.CheckButton.new_with_mnemonic(checktext1)
- if tooltip1:
- self.checkbutton1.set_tooltip_text(tooltip1)
- self.vbox.pack_start(self.checkbutton1, False, True, 0)
- else:
- self.checkbutton1 = None
- if checktext2:
- self.checkbutton2 = Gtk.CheckButton.new_with_mnemonic(checktext2)
- if tooltip2:
- self.checkbutton2.set_tooltip_text(tooltip2)
- self.vbox.pack_start(self.checkbutton2, False, True, 0)
- else:
- self.checkbutton2 = None
-
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- ''' Get active state of the checkbutton '''
- if self.checkbutton1:
- is_checked_1 = self.checkbutton1.get_active()
- else:
- is_checked_1 = False
- if self.checkbutton2:
- is_checked_2 = self.checkbutton2.get_active()
- else:
- is_checked_2 = False
- return [is_checked_1, is_checked_2]
-
-class PlainConnectionDialog(ConfirmationDialogDoubleCheck):
- """
- Dialog that is shown when using an insecure connection
- """
- def __init__(self, account, on_ok, on_cancel):
- pritext = _('Insecure connection')
- sectext = _('You are about to connect to the account %(account)s '
- '(%(server)s) insecurely. This means conversations will not be '
- 'encrypted, and is strongly discouraged.\nAre you sure you want '
- 'to do that?') % {'account': account,
- 'server': gajim.get_hostname_from_account(account)}
- checktext1 = _('Yes, I really want to connect insecurely')
- tooltip1 = _('Gajim will NOT connect unless you check this box')
- checktext2 = _('_Do not ask me again')
- ConfirmationDialogDoubleCheck.__init__(self, pritext, sectext,
- checktext1, checktext2, tooltip1=tooltip1, on_response_ok=on_ok,
- on_response_cancel=on_cancel, is_modal=False)
- self.ok_button = self.action_area.get_children()[0] # right to left
- self.ok_button.set_sensitive(False)
- self.checkbutton1.connect('clicked', self.on_checkbutton_clicked)
- self.set_title(_('Insecure connection'))
-
- def on_checkbutton_clicked(self, widget):
- self.ok_button.set_sensitive(widget.get_active())
-
-class ConfirmationDialogDoubleRadio(ConfirmationDialog):
- """
- HIG compliant confirmation dialog with 2 radios
- """
-
- def __init__(self, pritext, sectext='', radiotext1='', radiotext2='',
- on_response_ok=None, on_response_cancel=None, is_modal=True):
- self.user_response_ok = on_response_ok
- self.user_response_cancel = on_response_cancel
-
- if hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.OK_CANCEL, pritext, sectext, self.on_response_ok,
- self.on_response_cancel)
-
- self.set_default_response(Gtk.ResponseType.OK)
-
- ok_button = self.action_area.get_children()[0] # right to left
- ok_button.grab_focus()
-
- self.radiobutton1 = Gtk.RadioButton(label=radiotext1)
- self.vbox.pack_start(self.radiobutton1, False, True, 0)
-
- self.radiobutton2 = Gtk.RadioButton(group=self.radiobutton1,
- label=radiotext2)
- self.vbox.pack_start(self.radiobutton2, False, True, 0)
-
- self.set_modal(is_modal)
- self.popup()
-
- def on_response_ok(self, widget):
- if self.user_response_ok:
- if isinstance(self.user_response_ok, tuple):
- self.user_response_ok[0](self.is_checked(),
- *self.user_response_ok[1:])
- else:
- self.user_response_ok(self.is_checked())
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def on_response_cancel(self, widget):
- if self.user_response_cancel:
- if isinstance(self.user_response_cancel, tuple):
- self.user_response_cancel[0](*self.user_response_cancel[1:])
- else:
- self.user_response_cancel()
- self.call_cancel_on_destroy = False
- self.destroy()
-
- def is_checked(self):
- ''' Get active state of the checkbutton '''
- if self.radiobutton1:
- is_checked_1 = self.radiobutton1.get_active()
- else:
- is_checked_1 = False
- if self.radiobutton2:
- is_checked_2 = self.radiobutton2.get_active()
- else:
- is_checked_2 = False
- return [is_checked_1, is_checked_2]
-
-class FTOverwriteConfirmationDialog(ConfirmationDialog):
- """
- HIG compliant confirmation dialog to overwrite or resume a file transfert
- """
-
- def __init__(self, pritext, sectext='', propose_resume=True,
- on_response=None, transient_for=None):
- if transient_for:
- parent = transient_for
- elif hasattr(gajim.interface, 'roster') and gajim.interface.roster:
- parent = gajim.interface.roster.window
- else:
- parent = None
- HigDialog.__init__(self, parent, Gtk.MessageType.QUESTION,
- Gtk.ButtonsType.CANCEL, pritext, sectext)
-
- self.on_response = on_response
-
- if propose_resume:
- b = Gtk.Button(label='', stock=Gtk.STOCK_REFRESH)
- align = b.get_children()[0]
- hbox = align.get_children()[0]
- label = hbox.get_children()[1]
- label.set_text(_('_Resume'))
- label.set_use_underline(True)
- self.add_action_widget(b, 100)
-
- b = Gtk.Button(label='', stock=Gtk.STOCK_SAVE_AS)
- align = b.get_children()[0]
- hbox = align.get_children()[0]
- label = hbox.get_children()[1]
- label.set_text(_('Re_place'))
- label.set_use_underline(True)
- self.add_action_widget(b, 200)
-
- self.connect('response', self.on_dialog_response)
- self.show_all()
-
- def on_dialog_response(self, dialog, response):
- if self.on_response:
- if isinstance(self.on_response, tuple):
- self.on_response[0](response, *self.on_response[1:])
- else:
- self.on_response(response)
- self.call_cancel_on_destroy = False
- self.destroy()
-
-class CommonInputDialog:
- """
- Common Class for Input dialogs
- """
-
- def __init__(self, title, label_str, is_modal, ok_handler, cancel_handler,
- transient_for=None):
- self.dialog = self.xml.get_object('input_dialog')
- label = self.xml.get_object('label')
- self.dialog.set_title(title)
- label.set_markup(label_str)
- self.cancel_handler = cancel_handler
- self.vbox = self.xml.get_object('vbox')
- if transient_for:
- self.dialog.set_transient_for(transient_for)
- else:
- self.dialog.set_transient_for(gajim.interface.roster.window)
-
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.xml.connect_signals(self)
- self.dialog.show_all()
-
- def on_input_dialog_destroy(self, widget):
- if self.cancel_handler:
- self.cancel_handler()
-
- def on_okbutton_clicked(self, widget):
- user_input = self.get_text()
- if user_input:
- user_input = user_input
- self.cancel_handler = None
- self.dialog.destroy()
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input, *self.ok_handler[1:])
- else:
- self.ok_handler(user_input)
-
- def on_cancelbutton_clicked(self, widget):
- self.dialog.destroy()
-
- def destroy(self):
- self.dialog.destroy()
-
-class InputDialog(CommonInputDialog):
- """
- Class for Input dialog
- """
-
- def __init__(self, title, label_str, input_str=None, is_modal=True,
- ok_handler=None, cancel_handler=None, transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
- CommonInputDialog.__init__(self, title, label_str, is_modal,
- ok_handler, cancel_handler,
- transient_for=transient_for)
- self.input_entry = self.xml.get_object('input_entry')
- if input_str:
- self.set_entry(input_str)
-
- def on_input_dialog_delete_event(self, widget, event):
- '''
- may be implemented by subclasses
- '''
- pass
-
- def set_entry(self, value):
- self.input_entry.set_text(value)
- self.input_entry.select_region(0, -1) # select all
-
- def get_text(self):
- return self.input_entry.get_text()
-
-class InputDialogCheck(InputDialog):
- """
- Class for Input dialog
- """
-
- def __init__(self, title, label_str, checktext='', input_str=None,
- is_modal=True, ok_handler=None, cancel_handler=None,
- transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_dialog.ui')
- InputDialog.__init__(self, title, label_str, input_str=input_str,
- is_modal=is_modal, ok_handler=ok_handler,
- cancel_handler=cancel_handler,
- transient_for=transient_for)
- self.input_entry = self.xml.get_object('input_entry')
- if input_str:
- self.input_entry.set_text(input_str)
- self.input_entry.select_region(0, -1) # select all
-
- if checktext:
- self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
- self.vbox.pack_start(self.checkbutton, False, True, 0)
- self.checkbutton.show()
-
- def on_okbutton_clicked(self, widget):
- user_input = self.get_text()
- if user_input:
- user_input = user_input
- self.cancel_handler = None
- self.dialog.destroy()
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input, self.is_checked(), *self.ok_handler[1:])
- else:
- self.ok_handler(user_input, self.is_checked())
-
- def get_text(self):
- return self.input_entry.get_text()
-
- def is_checked(self):
- """
- Get active state of the checkbutton
- """
- try:
- return self.checkbutton.get_active()
- except Exception:
- # There is no checkbutton
- return False
-
-class ChangeNickDialog(InputDialogCheck):
- """
- Class for changing room nickname in case of conflict
- """
-
- def __init__(self, account, room_jid, title, prompt, check_text=None,
- change_nick=False, transient_for=None):
- """
- change_nick must be set to True when we are already occupant of the room
- and we are changing our nick
- """
- InputDialogCheck.__init__(self, title, '', checktext=check_text,
- input_str='', is_modal=True, ok_handler=None,
- cancel_handler=None,
- transient_for=transient_for)
- self.room_queue = [(account, room_jid, prompt, change_nick)]
- self.check_next()
-
- def on_input_dialog_delete_event(self, widget, event):
- self.on_cancelbutton_clicked(widget)
- return True
-
- def setup_dialog(self):
- self.gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- self.room_jid, self.account)
- if not self.gc_control and \
- self.room_jid in gajim.interface.minimized_controls[self.account]:
- self.gc_control = \
- gajim.interface.minimized_controls[self.account][self.room_jid]
- if not self.gc_control:
- self.check_next()
- return
- label = self.xml.get_object('label')
- label.set_markup(self.prompt)
- self.set_entry(self.gc_control.nick + \
- gajim.config.get('gc_proposed_nick_char'))
-
- def check_next(self):
- if len(self.room_queue) == 0:
- self.cancel_handler = None
- self.dialog.destroy()
- if 'change_nick_dialog' in gajim.interface.instances:
- del gajim.interface.instances['change_nick_dialog']
- return
- self.account, self.room_jid, self.prompt, self.change_nick = \
- self.room_queue.pop(0)
- self.setup_dialog()
-
- if gajim.new_room_nick is not None and not gajim.gc_connected[
- self.account][self.room_jid] and self.gc_control.nick != \
- gajim.new_room_nick:
- self.dialog.hide()
- self.on_ok(gajim.new_room_nick, True)
- else:
- self.dialog.show()
-
- def on_okbutton_clicked(self, widget):
- nick = self.get_text()
- if nick:
- nick = nick
- # send presence to room
- try:
- nick = helpers.parse_resource(nick)
- except Exception:
- # invalid char
- ErrorDialog(_('Invalid nickname'),
- _('The nickname contains invalid characters.'))
- return
- self.on_ok(nick, self.is_checked())
-
- def on_ok(self, nick, is_checked):
- if is_checked:
- gajim.new_room_nick = nick
- gajim.connections[self.account].join_gc(nick, self.room_jid, None,
- change_nick=self.change_nick)
- if gajim.gc_connected[self.account][self.room_jid]:
- # We are changing nick, we will change self.nick when we receive
- # presence that inform that it works
- self.gc_control.new_nick = nick
- else:
- # We are connecting, we will not get a changed nick presence so
- # change it NOW. We don't already have a nick so it's harmless
- self.gc_control.nick = nick
- self.check_next()
-
- def on_cancelbutton_clicked(self, widget):
- self.gc_control.new_nick = ''
- self.check_next()
-
- def add_room(self, account, room_jid, prompt, change_nick=False):
- if (account, room_jid, prompt, change_nick) not in self.room_queue:
- self.room_queue.append((account, room_jid, prompt, change_nick))
-
-class InputTextDialog(CommonInputDialog):
- """
- Class for multilines Input dialog (more place than InputDialog)
- """
-
- def __init__(self, title, label_str, input_str=None, is_modal=True,
- ok_handler=None, cancel_handler=None, transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('input_text_dialog.ui')
- CommonInputDialog.__init__(self, title, label_str, is_modal,
- ok_handler, cancel_handler,
- transient_for=transient_for)
- self.input_buffer = self.xml.get_object('input_textview').get_buffer()
- if input_str:
- self.input_buffer.set_text(input_str)
- start_iter, end_iter = self.input_buffer.get_bounds()
- self.input_buffer.select_range(start_iter, end_iter) # select all
-
- def get_text(self):
- start_iter, end_iter = self.input_buffer.get_bounds()
- return self.input_buffer.get_text(start_iter, end_iter, True)
-
-class DoubleInputDialog:
- """
- Class for Double Input dialog
- """
-
- def __init__(self, title, label_str1, label_str2, input_str1=None,
- input_str2=None, is_modal=True, ok_handler=None, cancel_handler=None,
- transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('dubbleinput_dialog.ui')
- self.dialog = self.xml.get_object('dubbleinput_dialog')
- label1 = self.xml.get_object('label1')
- self.input_entry1 = self.xml.get_object('input_entry1')
- label2 = self.xml.get_object('label2')
- self.input_entry2 = self.xml.get_object('input_entry2')
- self.dialog.set_title(title)
- label1.set_markup(label_str1)
- label2.set_markup(label_str2)
- self.cancel_handler = cancel_handler
- if input_str1:
- self.input_entry1.set_text(input_str1)
- self.input_entry1.select_region(0, -1) # select all
- if input_str2:
- self.input_entry2.set_text(input_str2)
- self.input_entry2.select_region(0, -1) # select all
- if transient_for:
- self.dialog.set_transient_for(transient_for)
-
- self.dialog.set_modal(is_modal)
-
- self.ok_handler = ok_handler
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.xml.connect_signals(self)
- self.dialog.show_all()
-
- def on_dubbleinput_dialog_destroy(self, widget):
- if not self.cancel_handler:
- return False
- if isinstance(self.cancel_handler, tuple):
- self.cancel_handler[0](*self.cancel_handler[1:])
- else:
- self.cancel_handler()
-
- def on_okbutton_clicked(self, widget):
- user_input1 = self.input_entry1.get_text()
- user_input2 = self.input_entry2.get_text()
- self.cancel_handler = None
- self.dialog.destroy()
- if not self.ok_handler:
- return
- if isinstance(self.ok_handler, tuple):
- self.ok_handler[0](user_input1, user_input2, *self.ok_handler[1:])
- else:
- self.ok_handler(user_input1, user_input2)
-
- def on_cancelbutton_clicked(self, widget):
- self.dialog.destroy()
- if not self.cancel_handler:
- return
- if isinstance(self.cancel_handler, tuple):
- self.cancel_handler[0](*self.cancel_handler[1:])
- else:
- self.cancel_handler()
-
-class SubscriptionRequestWindow:
- def __init__(self, jid, text, account, user_nick=None):
- xml = gtkgui_helpers.get_gtk_builder('subscription_request_window.ui')
- self.window = xml.get_object('subscription_request_window')
- self.jid = jid
- self.account = account
- self.user_nick = user_nick
- if len(gajim.connections) >= 2:
- prompt_text = \
- _('Subscription request for account %(account)s from %(jid)s')\
- % {'account': account, 'jid': self.jid}
- else:
- prompt_text = _('Subscription request from %s') % self.jid
- xml.get_object('from_label').set_text(prompt_text)
- xml.get_object('message_textview').get_buffer().set_text(text)
- xml.connect_signals(self)
- self.window.show_all()
-
- def on_subscription_request_window_destroy(self, widget):
- """
- Close window
- """
- if self.jid in gajim.interface.instances[self.account]['sub_request']:
- # remove us from open windows
- del gajim.interface.instances[self.account]['sub_request'][self.jid]
-
- def prepare_popup_menu(self):
- xml = gtkgui_helpers.get_gtk_builder('subscription_request_popup_menu.ui')
- menu = xml.get_object('subscription_request_popup_menu')
- xml.connect_signals(self)
- return menu
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_authorize_button_clicked(self, widget):
- """
- Accept the request
- """
- gajim.connections[self.account].send_authorization(self.jid)
- self.window.destroy()
- contact = gajim.contacts.get_contact(self.account, self.jid)
- if not contact or _('Not in Roster') in contact.groups:
- AddNewContactWindow(self.account, self.jid, self.user_nick)
-
- def on_contact_info_activate(self, widget):
- """
- Ask vcard
- """
- if self.jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][self.jid].window.present()
- else:
- contact = gajim.contacts.create_contact(jid=self.jid, account=self.account)
- gajim.interface.instances[self.account]['infos'][self.jid] = \
- vcard.VcardWindow(contact, self.account)
- # Remove jabber page
- gajim.interface.instances[self.account]['infos'][self.jid].xml.\
- get_object('information_notebook').remove_page(0)
-
- def on_start_chat_activate(self, widget):
- """
- Open chat
- """
- gajim.interface.new_chat_from_jid(self.account, self.jid)
-
- def on_deny_button_clicked(self, widget):
- """
- Refuse the request
- """
- gajim.connections[self.account].refuse_authorization(self.jid)
- contact = gajim.contacts.get_contact(self.account, self.jid)
- if contact and _('Not in Roster') in contact.get_shown_groups():
- gajim.interface.roster.remove_contact(self.jid, self.account)
- self.window.destroy()
-
- def on_actions_button_clicked(self, widget):
- """
- Popup action menu
- """
- menu = self.prepare_popup_menu()
- menu.show_all()
- gtkgui_helpers.popup_emoticons_under_button(menu, widget,
- self.window.get_window())
-
-
-class JoinGroupchatWindow:
- def __init__(self, account=None, room_jid='', nick='', password='',
- automatic=False):
- """
- Automatic is a dict like {'invities': []}. If automatic is not empty,
- this means room must be automaticaly configured and when done, invities
- must be automatically invited
- """
- self.window_account = None
- if account:
- if room_jid != '' and room_jid in gajim.gc_connected[account] and \
- gajim.gc_connected[account][room_jid]:
- ErrorDialog(_('You are already in group chat %s') % room_jid)
- raise GajimGeneralException('You are already in this group chat')
- if nick == '':
- nick = gajim.nicks[account]
- if gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('You can not join a group chat unless you are connected.'))
- raise GajimGeneralException('You must be connected to join a groupchat')
- self.window_account = account
-
- self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui')
-
- account_label = self.xml.get_object('account_label')
- account_combobox = self.xml.get_object('account_combobox')
- account_label.set_no_show_all(False)
- account_combobox.set_no_show_all(False)
- liststore = Gtk.ListStore(str)
- account_combobox.set_model(liststore)
- cell = Gtk.CellRendererText()
- account_combobox.pack_start(cell, True)
- account_combobox.add_attribute(cell, 'text', 0)
- account_combobox.set_active(-1)
-
- # Add accounts, set current as active if it matches 'account'
- for acct in [a for a in gajim.connections if \
- gajim.account_is_connected(a)]:
- if gajim.connections[acct].is_zeroconf:
- continue
- liststore.append([acct])
- if account and account == acct:
- account_combobox.set_active(liststore.iter_n_children(None)-1)
-
- self.account = account
- self.automatic = automatic
- self._empty_required_widgets = []
-
- self.window = self.xml.get_object('join_groupchat_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- self._room_jid_entry = self.xml.get_object('room_jid_entry')
- self._nickname_entry = self.xml.get_object('nickname_entry')
- self._password_entry = self.xml.get_object('password_entry')
- self.server_comboboxtext = self.xml.get_object('server_comboboxtext')
-
- self._nickname_entry.set_text(nick)
- if password:
- self._password_entry.set_text(password)
- self.xml.connect_signals(self)
- title = None
- if account:
- # now add us to open windows
- gajim.interface.instances[account]['join_gc'] = self
- if len(gajim.connections) > 1:
- title = _('Join Group Chat with account %s') % account
- if title is None:
- title = _('Join Group Chat')
- self.window.set_title(title)
-
- self.browse_button = self.xml.get_object('browse_rooms_button')
- self.browse_button.set_sensitive(False)
-
- self.recently_combobox = self.xml.get_object('recently_combobox')
- liststore = Gtk.ListStore(str, str)
- self.recently_combobox.set_model(liststore)
- cell = Gtk.CellRendererText()
- self.recently_combobox.pack_start(cell, True)
- self.recently_combobox.add_attribute(cell, 'text', 0)
- self.recently_groupchat = gajim.config.get('recently_groupchat').split()
-
- server_list = []
- # get the muc server of our server
- if 'jabber' in gajim.connections[account].muc_jid:
- server_list.append(gajim.connections[account].muc_jid['jabber'])
- for g in self.recently_groupchat:
- r_jid = gajim.get_jid_without_resource(g)
- nick = gajim.get_resource_from_jid(g)
- if nick:
- show = '%(nick)s on %(room_jid)s' % {'nick': nick,
- 'room_jid': r_jid}
- else:
- show = r_jid
- liststore.append([show, g])
- server = gajim.get_server_from_jid(r_jid)
- if server not in server_list and not server.startswith('irc'):
- server_list.append(server)
-
- for s in server_list:
- self.server_comboboxtext.append_text(s)
-
-
- self._set_room_jid(room_jid)
-
- if len(self.recently_groupchat) == 0:
- self.recently_combobox.set_sensitive(False)
- elif room_jid == '':
- self.recently_combobox.set_active(0)
- self._room_jid_entry.select_region(0, -1)
- elif room_jid != '':
- self.xml.get_object('join_button').grab_focus()
-
- if not self._room_jid_entry.get_text():
- self._empty_required_widgets.append(self._room_jid_entry)
- if not self._nickname_entry.get_text():
- self._empty_required_widgets.append(self._nickname_entry)
- if len(self._empty_required_widgets):
- self.xml.get_object('join_button').set_sensitive(False)
-
- if account and not gajim.connections[account].private_storage_supported:
- self.xml.get_object('bookmark_checkbutton').set_sensitive(False)
-
- self.requested_jid = None
- gajim.ged.register_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received)
- gajim.ged.register_event_handler('agent-info-error-received', ged.GUI1,
- self._nec_agent_info_error_received)
-
- self.window.show_all()
-
- def on_join_groupchat_window_destroy(self, widget):
- """
- Close window
- """
- gajim.ged.remove_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received)
- gajim.ged.register_event_handler('agent-info-error-received', ged.GUI1,
- self._nec_agent_info_error_received)
- if self.window_account and 'join_gc' in gajim.interface.instances[
- self.window_account]:
- # remove us from open windows
- del gajim.interface.instances[self.window_account]['join_gc']
-
- def on_join_groupchat_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape: # ESCAPE
- widget.destroy()
-
- def on_required_entry_changed(self, widget):
- if not widget.get_text():
- self._empty_required_widgets.append(widget)
- self.xml.get_object('join_button').set_sensitive(False)
- else:
- if widget in self._empty_required_widgets:
- self._empty_required_widgets.remove(widget)
- if not self._empty_required_widgets and self.account:
- self.xml.get_object('join_button').set_sensitive(True)
- text = self._room_jid_entry.get_text()
- if widget == self._room_jid_entry and text.startswith('xmpp:'):
- text = text[5:]
- self._room_jid_entry.set_text(text)
- if widget == self._room_jid_entry and '@' in text:
- # Don't allow @ char in room entry
- room_jid, server = text.split('@', 1)
- self._room_jid_entry.set_text(room_jid)
- if server:
- if '?' in server:
- server = server.split('?')[0]
- self.server_comboboxtext.get_child().set_text(server)
- self.server_comboboxtext.grab_focus()
-
- def on_account_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- self.account = model[iter_][0]
- self.on_required_entry_changed(self._nickname_entry)
-
- def _set_room_jid(self, full_jid):
- room_jid, nick = gajim.get_room_and_nick_from_fjid(full_jid)
- room, server = gajim.get_name_and_server_from_jid(room_jid)
- self._room_jid_entry.set_text(room)
- model = self.server_comboboxtext.get_model()
- self.server_comboboxtext.get_child().set_text(server)
- if nick:
- self._nickname_entry.set_text(nick)
-
- def on_recently_combobox_changed(self, widget):
- model = widget.get_model()
- iter_ = widget.get_active_iter()
- full_jid = model[iter_][1]
- self._set_room_jid(full_jid)
-
- def on_browse_rooms_button_clicked(self, widget):
- server = self.server_comboboxtext.get_child().get_text()
- self.requested_jid = server
- gajim.connections[self.account].discoverInfo(server)
-
- def _nec_agent_info_error_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.jid != self.requested_jid:
- return
- self.requested_jid = None
- window = gajim.interface.instances[self.account]['join_gc'].window
- ErrorDialog(_('Wrong server'), _('%s is not a groupchat server') % \
- obj.jid, transient_for=window)
-
- def _nec_agent_info_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.jid != self.requested_jid:
- return
- self.requested_jid = None
- if nbxmpp.NS_MUC not in obj.features:
- window = gajim.interface.instances[self.account]['join_gc'].window
- ErrorDialog(_('Wrong server'), _('%s is not a groupchat server') % \
- obj.jid, transient_for=window)
- return
- if obj.jid in gajim.interface.instances[self.account]['disco']:
- gajim.interface.instances[self.account]['disco'][obj.jid].window.\
- present()
- else:
- try:
- # Object will add itself to the window dict
- import disco
- disco.ServiceDiscoveryWindow(self.account, obj.jid,
- initial_identities=[{'category': 'conference',
- 'type': 'text'}])
- except GajimGeneralException:
- pass
-
- def on_server_entry_changed(self, widget):
- if not widget.get_text():
- self.browse_button.set_sensitive(False)
- else:
- self.browse_button.set_sensitive(True)
-
- def on_cancel_button_clicked(self, widget):
- """
- When Cancel button is clicked
- """
- self.window.destroy()
-
- def on_bookmark_checkbutton_toggled(self, widget):
- auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton')
- if widget.get_active():
- auto_join_checkbutton.set_sensitive(True)
- else:
- auto_join_checkbutton.set_sensitive(False)
-
- def on_join_button_clicked(self, widget):
- """
- When Join button is clicked
- """
- if not self.account:
- ErrorDialog(_('Invalid Account'),
- _('You have to choose an account from which you want to join the '
- 'groupchat.'))
- return
- nickname = self._nickname_entry.get_text()
- server = self.server_comboboxtext.get_child().get_text()
- room = self._room_jid_entry.get_text().strip()
- room_jid = room + '@' + server
- password = self._password_entry.get_text()
- try:
- nickname = helpers.parse_resource(nickname)
- except Exception:
- ErrorDialog(_('Invalid Nickname'),
- _('The nickname contains invalid characters.'))
- return
- user, server, resource = helpers.decompose_jid(room_jid)
- if not user or not server or resource:
- ErrorDialog(_('Invalid group chat JID'),
- _('Please enter the group chat JID as room@server.'))
- return
- try:
- room_jid = helpers.parse_jid(room_jid)
- except Exception:
- ErrorDialog(_('Invalid group chat JID'),
- _('The group chat JID contains invalid characters.'))
- return
-
- if gajim.contacts.get_contact(self.account, room_jid) and \
- not gajim.contacts.get_contact(self.account, room_jid).is_groupchat():
- ErrorDialog(_('This is not a group chat'),
- _('%s is already in your roster. Please check if %s is a '
- 'correct group chat name. If it is, delete it from your roster '
- 'and try joining the group chat again.') % (room_jid, room_jid))
- return
-
- full_jid = room_jid + '/' + nickname
- if full_jid in self.recently_groupchat:
- self.recently_groupchat.remove(full_jid)
- self.recently_groupchat.insert(0, full_jid)
- if len(self.recently_groupchat) > 10:
- self.recently_groupchat = self.recently_groupchat[0:10]
- gajim.config.set('recently_groupchat',
- ' '.join(self.recently_groupchat))
-
- if self.xml.get_object('bookmark_checkbutton').get_active():
- if self.xml.get_object('auto_join_checkbutton').get_active():
- autojoin = '1'
- else:
- autojoin = '0'
- # Add as bookmark, with autojoin and not minimized
- name = gajim.get_nick_from_jid(room_jid)
- gajim.interface.add_gc_bookmark(self.account, name, room_jid,
- autojoin, autojoin, password, nickname)
-
- if self.automatic:
- gajim.automatic_rooms[self.account][room_jid] = self.automatic
-
- gajim.interface.join_gc_room(self.account, room_jid, nickname, password)
-
- self.window.destroy()
-
-class SynchroniseSelectAccountDialog:
- def __init__(self, account):
- # 'account' can be None if we are about to create our first one
- if not account or gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not synchronise your contacts.'))
- raise GajimGeneralException('You are not connected to the server')
- self.account = account
- self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui')
- self.dialog = self.xml.get_object('synchronise_select_account_dialog')
- self.dialog.set_transient_for(gajim.interface.instances['accounts'].window)
- self.accounts_treeview = self.xml.get_object('accounts_treeview')
- model = Gtk.ListStore(str, str, bool)
- self.accounts_treeview.set_model(model)
- # columns
- renderer = Gtk.CellRendererText()
- self.accounts_treeview.insert_column_with_attributes(-1, _('Name'),
- renderer, text=0)
- renderer = Gtk.CellRendererText()
- self.accounts_treeview.insert_column_with_attributes(-1, _('Server'),
- renderer, text=1)
-
- self.xml.connect_signals(self)
- self.init_accounts()
- self.dialog.show_all()
-
- def on_accounts_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def init_accounts(self):
- """
- Initialize listStore with existing accounts
- """
- model = self.accounts_treeview.get_model()
- model.clear()
- for remote_account in gajim.connections:
- if remote_account == self.account:
- # Do not show the account we're sync'ing
- continue
- iter_ = model.append()
- model.set(iter_, 0, remote_account, 1,
- gajim.get_hostname_from_account(remote_account))
-
- def on_cancel_button_clicked(self, widget):
- self.dialog.destroy()
-
- def on_ok_button_clicked(self, widget):
- sel = self.accounts_treeview.get_selection()
- (model, iter_) = sel.get_selected()
- if not iter_:
- return
- remote_account = model.get_value(iter_, 0)
-
- if gajim.connections[remote_account].connected < 2:
- ErrorDialog(_('This account is not connected to the server'),
- _('You cannot synchronize with an account unless it is connected.'))
- return
- else:
- try:
- SynchroniseSelectContactsDialog(self.account, remote_account)
- except GajimGeneralException:
- # if we showed ErrorDialog, there will not be dialog instance
- return
- self.dialog.destroy()
-
-class SynchroniseSelectContactsDialog:
- def __init__(self, account, remote_account):
- self.local_account = account
- self.remote_account = remote_account
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'synchronise_select_contacts_dialog.ui')
- self.dialog = self.xml.get_object('synchronise_select_contacts_dialog')
- self.contacts_treeview = self.xml.get_object('contacts_treeview')
- model = Gtk.ListStore(bool, str)
- self.contacts_treeview.set_model(model)
- # columns
- renderer1 = Gtk.CellRendererToggle()
- renderer1.set_property('activatable', True)
- renderer1.connect('toggled', self.toggled_callback)
- self.contacts_treeview.insert_column_with_attributes(-1,
- _('Synchronise'), renderer1, active=0)
- renderer2 = Gtk.CellRendererText()
- self.contacts_treeview.insert_column_with_attributes(-1, _('Name'),
- renderer2, text=1)
-
- self.xml.connect_signals(self)
- self.init_contacts()
- self.dialog.show_all()
-
- def toggled_callback(self, cell, path):
- model = self.contacts_treeview.get_model()
- iter_ = model.get_iter(path)
- model[iter_][0] = not cell.get_active()
-
- def on_contacts_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def init_contacts(self):
- """
- Initialize listStore with existing accounts
- """
- model = self.contacts_treeview.get_model()
- model.clear()
-
- # recover local contacts
- local_jid_list = gajim.contacts.get_contacts_jid_list(self.local_account)
-
- remote_jid_list = gajim.contacts.get_contacts_jid_list(
- self.remote_account)
- for remote_jid in remote_jid_list:
- if remote_jid not in local_jid_list:
- iter_ = model.append()
- model.set(iter_, 0, True, 1, remote_jid)
-
- def on_cancel_button_clicked(self, widget):
- self.dialog.destroy()
-
- def on_ok_button_clicked(self, widget):
- model = self.contacts_treeview.get_model()
- iter_ = model.get_iter_first()
- while iter_:
- if model[iter_][0]:
- # it is selected
- remote_jid = model[iter_][1]
- message = 'I\'m synchronizing my contacts from my %s account, could you please add this address to your contact list?' % \
- gajim.get_hostname_from_account(self.remote_account)
- remote_contact = gajim.contacts.get_first_contact_from_jid(
- self.remote_account, remote_jid)
- # keep same groups and same nickname
- gajim.interface.roster.req_sub(self, remote_jid, message,
- self.local_account, groups = remote_contact.groups,
- nickname = remote_contact.name, auto_auth = True)
- iter_ = model.iter_next(iter_)
- self.dialog.destroy()
-
-class NewChatDialog(InputDialog):
- def __init__(self, account):
- self.account = account
-
- if len(gajim.connections) > 1:
- title = _('Start Chat with account %s') % account
- else:
- title = _('Start Chat')
- prompt_text = _('Fill in the nickname or the JID of the contact you '
- 'would like\nto send a chat message to:')
- InputDialog.__init__(self, title, prompt_text, is_modal=False)
- self.input_entry.set_placeholder_text(_('Nickname / JID'))
-
- self.completion_dict = {}
- liststore = gtkgui_helpers.get_completion_liststore(self.input_entry)
- self.completion_dict = helpers.get_contact_dict_for_account(account)
- # add all contacts to the model
- keys = sorted(self.completion_dict.keys())
- for jid in keys:
- contact = self.completion_dict[jid]
- img = gajim.interface.jabber_state_images['16'][contact.show]
- liststore.append((img.get_pixbuf(), jid))
-
- self.ok_handler = self.new_chat_response
- okbutton = self.xml.get_object('okbutton')
- okbutton.connect('clicked', self.on_okbutton_clicked)
- cancelbutton = self.xml.get_object('cancelbutton')
- cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
- self.dialog.set_transient_for(gajim.interface.roster.window)
- self.dialog.show_all()
-
- def new_chat_response(self, jid):
- """
- Called when ok button is clicked
- """
- if gajim.connections[self.account].connected <= 1:
- #if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % self.account)
- return
-
- if jid in self.completion_dict:
- jid = self.completion_dict[jid].jid
- else:
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as e:
- ErrorDialog(_('Invalid JID'), str(e))
- return
- except:
- ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid)
- return
- gajim.interface.new_chat_from_jid(self.account, jid)
-
-class ChangePasswordDialog:
- def __init__(self, account, on_response, transient_for=None):
- # 'account' can be None if we are about to create our first one
- if not account or gajim.connections[account].connected < 2:
- ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not change your password.'))
- raise GajimGeneralException('You are not connected to the server')
- self.account = account
- self.on_response = on_response
- self.xml = gtkgui_helpers.get_gtk_builder('change_password_dialog.ui')
- self.dialog = self.xml.get_object('change_password_dialog')
- self.dialog.set_transient_for(transient_for)
- self.password1_entry = self.xml.get_object('password1_entry')
- self.password2_entry = self.xml.get_object('password2_entry')
- self.dialog.connect('response', self.on_dialog_response)
-
- self.dialog.show_all()
-
- def on_dialog_response(self, dialog, response):
- if response != Gtk.ResponseType.OK:
- dialog.destroy()
- self.on_response(None)
- return
- password1 = self.password1_entry.get_text()
- if not password1:
- ErrorDialog(_('Invalid password'), _('You must enter a password.'))
- return
- password2 = self.password2_entry.get_text()
- if password1 != password2:
- ErrorDialog(_('Passwords do not match'),
- _('The passwords typed in both fields must be identical.'))
- return
- dialog.destroy()
- self.on_response(password1)
-
-class PopupNotificationWindow:
- def __init__(self, event_type, jid, account, msg_type='',
- path_to_image=None, title=None, text=None, timeout=-1):
- self.account = account
- self.jid = jid
- self.msg_type = msg_type
- self.index = len(gajim.interface.roster.popup_notification_windows)
-
- xml = gtkgui_helpers.get_gtk_builder('popup_notification_window.ui')
- self.window = xml.get_object('popup_notification_window')
- self.window.set_type_hint(Gdk.WindowTypeHint.TOOLTIP)
- close_button = xml.get_object('close_button')
- event_type_label = xml.get_object('event_type_label')
- event_description_label = xml.get_object('event_description_label')
- eventbox = xml.get_object('eventbox')
- image = xml.get_object('notification_image')
-
- if not text:
- text = gajim.get_name_from_jid(account, jid) # default value of text
- if not title:
- title = ''
-
- event_type_label.set_markup(
- '<span foreground="black" weight="bold">%s</span>' %
- GLib.markup_escape_text(title))
-
- # set colors [ http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html ]
- color = Gdk.RGBA()
- Gdk.RGBA.parse(color, 'black')
- self.window.override_background_color(Gtk.StateType.NORMAL, color)
-
- # default image
- if not path_to_image:
- path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
-
- if event_type == _('Contact Signed In'):
- bg_color = gajim.config.get('notif_signin_color')
- elif event_type == _('Contact Signed Out'):
- bg_color = gajim.config.get('notif_signout_color')
- elif event_type in (_('New Message'), _('New Single Message'),
- _('New Private Message'), _('New E-mail')):
- bg_color = gajim.config.get('notif_message_color')
- elif event_type == _('File Transfer Request'):
- bg_color = gajim.config.get('notif_ftrequest_color')
- elif event_type == _('File Transfer Error'):
- bg_color = gajim.config.get('notif_fterror_color')
- elif event_type in (_('File Transfer Completed'),
- _('File Transfer Stopped')):
- bg_color = gajim.config.get('notif_ftcomplete_color')
- elif event_type == _('Groupchat Invitation'):
- bg_color = gajim.config.get('notif_invite_color')
- elif event_type == _('Contact Changed Status'):
- bg_color = gajim.config.get('notif_status_color')
- else: # Unknown event! Shouldn't happen but deal with it
- bg_color = gajim.config.get('notif_other_color')
- popup_bg_color = Gdk.RGBA()
- Gdk.RGBA.parse(popup_bg_color, bg_color)
- close_button.override_background_color(Gtk.StateType.NORMAL,
- popup_bg_color)
- eventbox.override_background_color(Gtk.StateType.NORMAL, popup_bg_color)
- event_description_label.set_markup('<span foreground="black">%s</span>' %
- GLib.markup_escape_text(text))
-
- # set the image
- image.set_from_file(path_to_image)
-
- # position the window to bottom-right of screen
- window_width, self.window_height = self.window.get_size()
- gajim.interface.roster.popups_notification_height += self.window_height
- pos_x = gajim.config.get('notification_position_x')
- if pos_x < 0:
- pos_x = Gdk.Screen.width() - window_width + pos_x + 1
- pos_y = gajim.config.get('notification_position_y')
- if pos_y < 0:
- pos_y = Gdk.Screen.height() - \
- gajim.interface.roster.popups_notification_height + pos_y + 1
- self.window.move(pos_x, pos_y)
-
- xml.connect_signals(self)
- self.window.show_all()
- if timeout > 0:
- GLib.timeout_add_seconds(timeout, self.on_timeout)
-
- def on_close_button_clicked(self, widget):
- self.adjust_height_and_move_popup_notification_windows()
-
- def on_timeout(self):
- self.adjust_height_and_move_popup_notification_windows()
-
- def adjust_height_and_move_popup_notification_windows(self):
- #remove
- gajim.interface.roster.popups_notification_height -= self.window_height
- self.window.destroy()
-
- if len(gajim.interface.roster.popup_notification_windows) > self.index:
- # we want to remove the destroyed window from the list
- gajim.interface.roster.popup_notification_windows.pop(self.index)
-
- # move the rest of popup windows
- gajim.interface.roster.popups_notification_height = 0
- current_index = 0
- for window_instance in gajim.interface.roster.popup_notification_windows:
- window_instance.index = current_index
- current_index += 1
- window_width, window_height = window_instance.window.get_size()
- gajim.interface.roster.popups_notification_height += window_height
- window_instance.window.move(Gdk.Screen.width() - window_width,
- Gdk.Screen.height() - \
- gajim.interface.roster.popups_notification_height)
-
- def on_popup_notification_window_button_press_event(self, widget, event):
- if event.button != 1:
- self.window.destroy()
- return
- gajim.interface.handle_event(self.account, self.jid, self.msg_type)
- self.adjust_height_and_move_popup_notification_windows()
-
-class SingleMessageWindow:
- """
- SingleMessageWindow can send or show a received singled message depending on
- action argument which can be 'send' or 'receive'
- """
- # Keep a reference on windows so garbage collector don't restroy them
- instances = []
- def __init__(self, account, to='', action='', from_whom='', subject='',
- message='', resource='', session=None, form_node=None):
- self.instances.append(self)
- self.account = account
- self.action = action
-
- self.subject = subject
- self.message = message
- self.to = to
- self.from_whom = from_whom
- self.resource = resource
- self.session = session
-
- self.xml = gtkgui_helpers.get_gtk_builder('single_message_window.ui')
- self.window = self.xml.get_object('single_message_window')
- self.count_chars_label = self.xml.get_object('count_chars_label')
- self.from_label = self.xml.get_object('from_label')
- self.from_entry = self.xml.get_object('from_entry')
- self.to_label = self.xml.get_object('to_label')
- self.to_entry = self.xml.get_object('to_entry')
- self.subject_entry = self.xml.get_object('subject_entry')
- self.message_scrolledwindow = self.xml.get_object(
- 'message_scrolledwindow')
- self.message_textview = self.xml.get_object('message_textview')
- self.message_tv_buffer = self.message_textview.get_buffer()
- self.conversation_scrolledwindow = self.xml.get_object(
- 'conversation_scrolledwindow')
- self.conversation_textview = conversation_textview.ConversationTextview(
- account, used_in_history_window=True)
- self.conversation_textview.tv.show()
- self.conversation_tv_buffer = self.conversation_textview.tv.get_buffer()
- self.xml.get_object('conversation_scrolledwindow').add(
- self.conversation_textview.tv)
-
- self.form_widget = None
- parent_box = self.xml.get_object('conversation_scrolledwindow').\
- get_parent()
- if form_node:
- dataform = dataforms.ExtendForm(node=form_node)
- self.form_widget = dataforms_widget.DataFormWidget(dataform)
- self.form_widget.show_all()
- parent_box.add(self.form_widget)
- parent_box.child_set_property(self.form_widget, 'position',
- parent_box.child_get_property(self.xml.get_object(
- 'conversation_scrolledwindow'), 'position'))
- self.action = 'form'
-
- self.send_button = self.xml.get_object('send_button')
- self.reply_button = self.xml.get_object('reply_button')
- self.send_and_close_button = self.xml.get_object('send_and_close_button')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.close_button = self.xml.get_object('close_button')
- self.message_tv_buffer.connect('changed', self.update_char_counter)
- if isinstance(to, list):
- jid = ', '.join( [i[0].get_full_jid() for i in to])
- self.to_entry.set_text(jid)
- self.to_entry.set_sensitive(False)
- else:
- self.to_entry.set_text(to)
-
- if gajim.config.get('use_speller') and HAS_GTK_SPELL and action == 'send':
- try:
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- gtkspell.Spell(self.conversation_textview.tv, lang)
- gtkspell.Spell(self.message_textview, lang)
- except (GObject.GError, TypeError, RuntimeError, OSError):
- AspellDictError(lang)
-
- self.prepare_widgets_for(self.action)
-
- # set_text(None) raises TypeError exception
- if self.subject is None:
- self.subject = ''
- self.subject_entry.set_text(self.subject)
-
-
- if to == '':
- liststore = gtkgui_helpers.get_completion_liststore(self.to_entry)
- self.completion_dict = helpers.get_contact_dict_for_account(account)
- keys = sorted(self.completion_dict.keys())
- for jid in keys:
- contact = self.completion_dict[jid]
- img = gajim.interface.jabber_state_images['16'][contact.show]
- liststore.append((img.get_pixbuf(), jid))
- else:
- self.completion_dict = {}
- self.xml.connect_signals(self)
-
- # get window position and size from config
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('single-msg-width'),
- gajim.config.get('single-msg-height'))
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('single-msg-x-position'),
- gajim.config.get('single-msg-y-position'))
-
- self.window.show_all()
-
- def on_single_message_window_destroy(self, widget):
- self.instances.remove(self)
- c = gajim.contacts.get_contact_with_highest_priority(self.account,
- self.from_whom)
- if not c:
- # Groupchat is maybe already destroyed
- return
- if c.is_groupchat() and not self.from_whom in \
- gajim.interface.minimized_controls[self.account] and self.action == \
- 'receive' and gajim.events.get_nb_roster_events(self.account,
- self.from_whom, types=['chat', 'normal']) == 0:
- gajim.interface.roster.remove_groupchat(self.from_whom, self.account)
-
- def set_cursor_to_end(self):
- end_iter = self.message_tv_buffer.get_end_iter()
- self.message_tv_buffer.place_cursor(end_iter)
-
- def save_pos(self):
- # save the window size and position
- x, y = self.window.get_position()
- gajim.config.set('single-msg-x-position', x)
- gajim.config.set('single-msg-y-position', y)
- width, height = self.window.get_size()
- gajim.config.set('single-msg-width', width)
- gajim.config.set('single-msg-height', height)
-
- def on_single_message_window_delete_event(self, window, ev):
- self.save_pos()
-
- def prepare_widgets_for(self, action):
- if len(gajim.connections) > 1:
- if action == 'send':
- title = _('Single Message using account %s') % self.account
- else:
- title = _('Single Message in account %s') % self.account
- else:
- title = _('Single Message')
-
- if action == 'send': # prepare UI for Sending
- title = _('Send %s') % title
- self.send_button.show()
- self.send_and_close_button.show()
- self.to_label.show()
- self.to_entry.show()
- self.reply_button.hide()
- self.from_label.hide()
- self.from_entry.hide()
- self.conversation_scrolledwindow.hide()
- self.message_scrolledwindow.show()
-
- if self.message: # we come from a reply?
- self.message_textview.grab_focus()
- self.cancel_button.hide()
- self.close_button.show()
- self.message_tv_buffer.set_text(self.message)
- GLib.idle_add(self.set_cursor_to_end)
- else: # we write a new message (not from reply)
- self.close_button.hide()
- if self.to: # do we already have jid?
- self.subject_entry.grab_focus()
-
- elif action == 'receive': # prepare UI for Receiving
- title = _('Received %s') % title
- self.reply_button.show()
- self.from_label.show()
- self.from_entry.show()
- self.send_button.hide()
- self.send_and_close_button.hide()
- self.to_label.hide()
- self.to_entry.hide()
- self.conversation_scrolledwindow.show()
- self.message_scrolledwindow.hide()
-
- if self.message:
- self.conversation_textview.print_real_text(self.message)
- fjid = self.from_whom
- if self.resource:
- fjid += '/' + self.resource # Full jid of sender (with resource)
- self.from_entry.set_text(fjid)
- self.from_entry.set_property('editable', False)
- self.subject_entry.set_property('editable', False)
- self.reply_button.grab_focus()
- self.cancel_button.hide()
- self.close_button.show()
- elif action == 'form': # prepare UI for Receiving
- title = _('Form %s') % title
- self.send_button.show()
- self.send_and_close_button.show()
- self.to_label.show()
- self.to_entry.show()
- self.reply_button.hide()
- self.from_label.hide()
- self.from_entry.hide()
- self.conversation_scrolledwindow.hide()
- self.message_scrolledwindow.hide()
-
- self.window.set_title(title)
-
- def on_cancel_button_clicked(self, widget):
- self.save_pos()
- self.window.destroy()
-
- def on_close_button_clicked(self, widget):
- self.save_pos()
- self.window.destroy()
-
- def update_char_counter(self, widget):
- characters_no = self.message_tv_buffer.get_char_count()
- self.count_chars_label.set_text(str(characters_no))
-
- def send_single_message(self):
- if gajim.connections[self.account].connected <= 1:
- # if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % self.account)
- return True
- if isinstance(self.to, list):
- sender_list = []
- for i in self.to:
- if i[0].resource:
- sender_list.append(i[0].jid + '/' + i[0].resource)
- else:
- sender_list.append(i[0].jid)
- else:
- sender_list = [j.strip() for j in self.to_entry.get_text().split(
- ',')]
-
- subject = self.subject_entry.get_text()
- begin, end = self.message_tv_buffer.get_bounds()
- message = self.message_tv_buffer.get_text(begin, end, True)
-
- if self.form_widget:
- form_node = self.form_widget.data_form
- else:
- form_node = None
-
- recipient_list = []
-
- for to_whom_jid in sender_list:
- if to_whom_jid in self.completion_dict:
- to_whom_jid = self.completion_dict[to_whom_jid].jid
- try:
- to_whom_jid = helpers.parse_jid(to_whom_jid)
- except helpers.InvalidFormat:
- ErrorDialog(_('Invalid JID'),
- _('It is not possible to send a message to %s, this JID is not '
- 'valid.') % to_whom_jid)
- return True
-
- if '/announce/' in to_whom_jid:
- gajim.connections[self.account].send_motd(to_whom_jid, subject,
- message)
- continue
-
- recipient_list.append(to_whom_jid)
-
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
- account=self.account, jid=recipient_list, message=message,
- type_='normal', subject=subject, form_node=form_node))
-
- self.subject_entry.set_text('') # we sent ok, clear the subject
- self.message_tv_buffer.set_text('') # we sent ok, clear the textview
-
- def on_send_button_clicked(self, widget):
- self.send_single_message()
-
- def on_reply_button_clicked(self, widget):
- # we create a new blank window to send and we preset RE: and to jid
- self.subject = _('RE: %s') % self.subject
- self.message = _('%s wrote:\n') % self.from_whom + self.message
- # add > at the begining of each line
- self.message = self.message.replace('\n', '\n> ') + '\n\n'
- self.window.destroy()
- SingleMessageWindow(self.account, to=self.from_whom, action='send',
- from_whom=self.from_whom, subject=self.subject, message=self.message,
- session=self.session)
-
- def on_send_and_close_button_clicked(self, widget):
- if self.send_single_message():
- return
- self.save_pos()
- self.window.destroy()
-
- def on_single_message_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape: # ESCAPE
- self.save_pos()
- self.window.destroy()
-
-class XMLConsoleWindow:
- def __init__(self, account):
- self.account = account
-
- self.xml = gtkgui_helpers.get_gtk_builder('xml_console_window.ui')
- self.window = self.xml.get_object('xml_console_window')
- self.input_textview = self.xml.get_object('input_textview')
- self.stanzas_log_textview = self.xml.get_object('stanzas_log_textview')
- self.input_tv_buffer = self.input_textview.get_buffer()
- self.parent = self.stanzas_log_textview.get_parent()
- buffer_ = self.stanzas_log_textview.get_buffer()
-
- self.tagIn = buffer_.create_tag('incoming')
- color = gajim.config.get('inmsgcolor')
- self.tagIn.set_property('foreground', color)
- self.tagInPresence = buffer_.create_tag('incoming_presence')
- self.tagInPresence.set_property('foreground', color)
- self.tagInMessage = buffer_.create_tag('incoming_message')
- self.tagInMessage.set_property('foreground', color)
- self.tagInIq = buffer_.create_tag('incoming_iq')
- self.tagInIq.set_property('foreground', color)
-
- self.tagOut = buffer_.create_tag('outgoing')
- color = gajim.config.get('outmsgcolor')
- self.tagOut.set_property('foreground', color)
- self.tagOutPresence = buffer_.create_tag('outgoing_presence')
- self.tagOutPresence.set_property('foreground', color)
- self.tagOutMessage = buffer_.create_tag('outgoing_message')
- self.tagOutMessage.set_property('foreground', color)
- self.tagOutIq = buffer_.create_tag('outgoing_iq')
- self.tagOutIq.set_property('foreground', color)
- buffer_.create_tag('') # Default tag
-
- self.enabled = True
- self.xml.get_object('enable_checkbutton').set_active(True)
-
- if len(gajim.connections) > 1:
- title = _('XML Console for %s') % self.account
- else:
- title = _('XML Console')
-
- self.window.set_title(title)
- self.window.show_all()
- gajim.ged.register_event_handler('stanza-received', ged.GUI1,
- self._nec_stanza_received)
- gajim.ged.register_event_handler('stanza-sent', ged.GUI1,
- self._nec_stanza_sent)
-
- self.xml.connect_signals(self)
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_xml_console_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['xml_console']
- gajim.ged.remove_event_handler('stanza-received', ged.GUI1,
- self._nec_stanza_received)
- gajim.ged.remove_event_handler('stanza-sent', ged.GUI1,
- self._nec_stanza_sent)
-
- def on_clear_button_clicked(self, widget):
- buffer_ = self.stanzas_log_textview.get_buffer()
- buffer_.set_text('')
-
- def on_enable_checkbutton_toggled(self, widget):
- self.enabled = widget.get_active()
-
- def on_in_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagIn.set_property('invisible', active)
- self.tagInPresence.set_property('invisible', active)
- self.tagInMessage.set_property('invisible', active)
- self.tagInIq.set_property('invisible', active)
-
- def on_presence_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInPresence.set_property('invisible', active)
- self.tagOutPresence.set_property('invisible', active)
-
- def on_out_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagOut.set_property('invisible', active)
- self.tagOutPresence.set_property('invisible', active)
- self.tagOutMessage.set_property('invisible', active)
- self.tagOutIq.set_property('invisible', active)
-
- def on_message_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInMessage.set_property('invisible', active)
- self.tagOutMessage.set_property('invisible', active)
-
- def on_iq_stanza_checkbutton_toggled(self, widget):
- active = widget.get_active()
- self.tagInIq.set_property('invisible', active)
- self.tagOutIq.set_property('invisible', active)
-
- def print_stanza(self, stanza, kind):
- # kind must be 'incoming' or 'outgoing'
- if not self.enabled:
- return
- if not stanza:
- return
-
- at_the_end = gtkgui_helpers.at_the_end(self.parent)
-
- buffer = self.stanzas_log_textview.get_buffer()
- end_iter = buffer.get_end_iter()
-
- type_ = ''
- if stanza[1:9] == 'presence':
- type_ = 'presence'
- elif stanza[1:8] == 'message':
- type_ = 'message'
- elif stanza[1:3] == 'iq':
- type_ = 'iq'
-
- if type_:
- type_ = kind + '_' + type_
- else:
- type_ = kind # 'incoming' or 'outgoing'
-
- if kind == 'incoming':
- buffer.insert_with_tags_by_name(end_iter, '<!-- In %s -->\n' % \
- time.strftime('%c'), type_)
- elif kind == 'outgoing':
- buffer.insert_with_tags_by_name(end_iter, '<!-- Out %s -->\n' % \
- time.strftime('%c'), type_)
- end_iter = buffer.get_end_iter()
- buffer.insert_with_tags_by_name(end_iter, stanza.replace('><', '>\n<') \
- + '\n\n', type_)
- if at_the_end:
- GLib.idle_add(gtkgui_helpers.scroll_to_end, self.parent)
-
- def _nec_stanza_received(self, obj):
- if obj.conn.name != self.account:
- return
- self.print_stanza(obj.stanza_str, 'incoming')
-
- def _nec_stanza_sent(self, obj):
- if obj.conn.name != self.account:
- return
- self.print_stanza(obj.stanza_str, 'outgoing')
-
- def on_send_button_clicked(self, widget):
- if gajim.connections[self.account].connected <= 1:
- # if offline or connecting
- ErrorDialog(_('Connection not available'),
- _('Please make sure you are connected with "%s".') % \
- self.account)
- return
- begin_iter, end_iter = self.input_tv_buffer.get_bounds()
- stanza = self.input_tv_buffer.get_text(begin_iter, end_iter, True)
- if stanza:
- gajim.connections[self.account].send_stanza(stanza)
- self.input_tv_buffer.set_text('') # we sent ok, clear the textview
-
- def on_presence_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<presence><show></show><status></status><priority></priority>'
- '</presence>')
-
- def on_iq_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<iq to="" type=""><query xmlns=""></query></iq>')
-
- def on_message_button_clicked(self, widget):
- self.input_tv_buffer.set_text(
- '<message to="" type=""><body></body></message>')
-
- def on_expander_activate(self, widget):
- if not widget.get_expanded(): # it's the opposite!
- # it's expanded!!
- self.input_textview.grab_focus()
-
-#Action that can be done with an incoming list of contacts
-TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'),
- 'remove': _('remove')}
-class RosterItemExchangeWindow:
- """
- Windows used when someone send you a exchange contact suggestion
- """
-
- def __init__(self, account, action, exchange_list, jid_from,
- message_body=None):
- self.account = account
- self.action = action
- self.exchange_list = exchange_list
- self.message_body = message_body
- self.jid_from = jid_from
-
- show_dialog = False
-
- # Connect to gtk builder
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'roster_item_exchange_window.ui')
- self.window = self.xml.get_object('roster_item_exchange_window')
-
- # Add Widgets.
- for widget_to_add in ['accept_button_label', 'type_label',
- 'body_scrolledwindow', 'body_textview', 'items_list_treeview']:
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- # Set labels
- # self.action can be 'add', 'modify' or 'remove'
- self.type_label.set_label(
- _('<b>%(jid)s</b> would like you to <b>%(action)s</b> some contacts '
- 'in your roster.') % {'jid': jid_from,
- 'action': TRANSLATED_ACTION[self.action]})
- if message_body:
- buffer_ = self.body_textview.get_buffer()
- buffer_.set_text(self.message_body)
- else:
- self.body_scrolledwindow.hide()
- # Treeview
- model = Gtk.ListStore(bool, str, str, str, str)
- self.items_list_treeview.set_model(model)
- # columns
- renderer1 = Gtk.CellRendererToggle()
- renderer1.set_property('activatable', True)
- renderer1.connect('toggled', self.toggled_callback)
- if self.action == 'add':
- title = _('Add')
- elif self.action == 'modify':
- title = _('Modify')
- elif self.action == 'delete':
- title = _('Delete')
- self.items_list_treeview.insert_column_with_attributes(-1, title,
- renderer1, active=0)
- renderer2 = Gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('JID'),
- renderer2, text=1)
- renderer3 = Gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('Name'),
- renderer3, text=2)
- renderer4 = Gtk.CellRendererText()
- self.items_list_treeview.insert_column_with_attributes(-1, _('Groups'),
- renderer4, text=3)
-
- # Init contacts
- model = self.items_list_treeview.get_model()
- model.clear()
-
- if action == 'add':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- if not contact or _('Not in Roster') in contact.groups:
- is_in_roster = False
- name = self.exchange_list[jid][0]
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if contact and not group in contact.groups:
- is_in_roster = False
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if not is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Add'))
- elif action == 'modify':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- is_right = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- name = self.exchange_list[jid][0]
- if not contact:
- is_in_roster = False
- is_right = False
- else:
- if name != contact.name:
- is_right = False
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if contact and not group in contact.groups:
- is_right = False
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if not is_right and is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Modify'))
- elif action == 'delete':
- for jid in self.exchange_list:
- groups = ''
- is_in_roster = True
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, jid)
- name = self.exchange_list[jid][0]
- if not contact:
- is_in_roster = False
- num_list = len(self.exchange_list[jid][1])
- current = 0
- for group in self.exchange_list[jid][1]:
- current += 1
- if current == num_list:
- groups = groups + group
- else:
- groups = groups + group + ', '
- if is_in_roster:
- show_dialog = True
- iter_ = model.append()
- model.set(iter_, 0, True, 1, jid, 2, name, 3, groups)
-
- # Change label for accept_button to action name instead of 'OK'.
- self.accept_button_label.set_label(_('Delete'))
-
- if show_dialog:
- self.window.show_all()
- self.xml.connect_signals(self)
-
- def toggled_callback(self, cell, path):
- model = self.items_list_treeview.get_model()
- iter_ = model.get_iter(path)
- model[iter_][0] = not cell.get_active()
-
- def on_accept_button_clicked(self, widget):
- model = self.items_list_treeview.get_model()
- iter_ = model.get_iter_first()
- if self.action == 'add':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- #remote_jid = model[iter_][1]
- message = _('%s suggested me to add you in my roster.'
- % self.jid_from)
- # keep same groups and same nickname
- groups = model[iter_][3].split(', ')
- if groups == ['']:
- groups = []
- jid = model[iter_][1]
- if gajim.jid_is_transport(self.jid_from):
- gajim.connections[self.account].automatically_added.append(
- jid)
- gajim.interface.roster.req_sub(self, jid, message,
- self.account, groups=groups, nickname=model[iter_][2],
- auto_auth=True)
- iter_ = model.iter_next(iter_)
- InformationDialog(i18n.ngettext('Added %d contact',
- 'Added %d contacts', a, a, a))
- elif self.action == 'modify':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- jid = model[iter_][1]
- # keep same groups and same nickname
- groups = model[iter_][3].split(', ')
- if groups == ['']:
- groups = []
- for u in gajim.contacts.get_contact(self.account, jid):
- u.name = model[iter_][2]
- gajim.connections[self.account].update_contact(jid,
- model[iter_][2], groups)
- self.draw_contact(jid, self.account)
- # Update opened chat
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
- if ctrl:
- ctrl.update_ui()
- win = gajim.interface.msg_win_mgr.get_window(jid,
- self.account)
- win.redraw_tab(ctrl)
- win.show_title()
- iter_ = model.iter_next(iter_)
- elif self.action == 'delete':
- a = 0
- while iter_:
- if model[iter_][0]:
- a+=1
- # it is selected
- jid = model[iter_][1]
- gajim.connections[self.account].unsubscribe(jid)
- gajim.interface.roster.remove_contact(jid, self.account)
- gajim.contacts.remove_jid(self.account, jid)
- iter_ = model.iter_next(iter_)
- InformationDialog(i18n.ngettext('Removed %d contact',
- 'Removed %d contacts', a, a, a))
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
-
-class ItemArchivingPreferencesWindow:
- otr_name = ('approve', 'concede', 'forbid', 'oppose', 'prefer', 'require')
- otr_index = dict([(j, i) for i, j in enumerate(otr_name)])
- save_name = ('body', 'false', 'message', 'stream')
- save_index = dict([(j, i) for i, j in enumerate(save_name)])
-
- def __init__(self, account, item):
- self.account = account
- self.item = item
- if self.item and self.item != 'Default':
- self.item_config = gajim.connections[self.account].items[self.item]
- else:
- self.item_config = gajim.connections[self.account].default
- self.waiting = None
-
- # Connect to gtk builder
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'item_archiving_preferences_window.ui')
- self.window = self.xml.get_object('item_archiving_preferences_window')
-
- # Add Widgets
- for widget_to_add in ('jid_entry', 'expire_entry', 'otr_combobox',
- 'save_combobox', 'cancel_button', 'ok_button', 'progressbar'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- if self.item:
- self.jid_entry.set_text(self.item)
- expire_value = self.item_config['expire'] or ''
- self.otr_combobox.set_active(self.otr_index[self.item_config['otr']])
- self.save_combobox.set_active(
- self.save_index[self.item_config['save']])
- self.expire_entry.set_text(expire_value)
-
- self.window.set_title(_('Archiving Preferences for %s') % self.account)
-
- self.window.show_all()
- self.progressbar.hide()
- self.xml.connect_signals(self)
-
- def update_progressbar(self):
- if self.waiting:
- self.progressbar.pulse()
- return True
- return False
-
- def on_otr_combobox_changed(self, widget):
- otr = self.otr_name[self.otr_combobox.get_active()]
- if otr == 'require':
- self.save_combobox.set_active(self.save_index['false'])
-
- def on_ok_button_clicked(self, widget):
- # Return directly if operation in progress
- if self.waiting:
- return
-
- item = self.jid_entry.get_text()
- otr = self.otr_name[self.otr_combobox.get_active()]
- save = self.save_name[self.save_combobox.get_active()]
- expire = self.expire_entry.get_text()
-
- if self.item != 'Default':
- try:
- item = helpers.parse_jid(item)
- except helpers.InvalidFormat as s:
- pritext = _('Invalid User ID')
- ErrorDialog(pritext, str(s))
- return
-
- if expire:
- try:
- if int(expire) < 0 or str(int(expire)) != expire:
- raise ValueError
- except ValueError:
- pritext = _('Invalid expire value')
- sectext = _('Expire must be a valid positive integer.')
- ErrorDialog(pritext, sectext)
- return
-
- if not (item == self.item and expire == self.item_config['expire'] and
- otr == self.item_config['otr'] and save == self.item_config['save']):
- if not self.item or self.item == item:
- if self.item == 'Default':
- self.waiting = 'default'
- gajim.connections[self.account].set_default(
- otr, save, expire)
- else:
- self.waiting = 'item'
- gajim.connections[self.account].append_or_update_item(
- item, otr, save, expire)
- else:
- self.waiting = 'item'
- gajim.connections[self.account].append_or_update_item(
- item, otr, save, expire)
- gajim.connections[self.account].remove_item(self.item)
- self.launch_progressbar()
- #self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_item_archiving_preferences_window_destroy(self, widget):
- if self.item:
- key_name = 'edit_item_archiving_preferences_%s' % self.item
- else:
- key_name = 'new_item_archiving_preferences'
- if key_name in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account][key_name]
-
- def launch_progressbar(self):
- self.progressbar.show()
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
-
- def response_arrived(self, data):
- if self.waiting:
- self.window.destroy()
-
- def error_arrived(self, error):
- if self.waiting:
- self.waiting = None
- self.progressbar.hide()
- pritext = _('There is an error with the form')
- sectext = error
- ErrorDialog(pritext, sectext)
-
-
-class ArchivingPreferencesWindow:
- auto_name = ('false', 'true')
- auto_index = dict([(j, i) for i, j in enumerate(auto_name)])
- method_foo_name = ('prefer', 'concede', 'forbid')
- method_foo_index = dict([(j, i) for i, j in enumerate(method_foo_name)])
-
- def __init__(self, account):
- self.account = account
- self.waiting = []
-
- # Connect to glade
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'archiving_preferences_window.ui')
- self.window = self.xml.get_object('archiving_preferences_window')
-
- # Add Widgets
- for widget_to_add in ('auto_combobox', 'method_auto_combobox',
- 'method_local_combobox', 'method_manual_combobox', 'close_button',
- 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox',
- 'expire_entry', 'remove_button', 'edit_button'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- self.auto_combobox.set_active(
- self.auto_index[gajim.connections[self.account].auto])
- self.method_auto_combobox.set_active(
- self.method_foo_index[gajim.connections[self.account].method_auto])
- self.method_local_combobox.set_active(
- self.method_foo_index[gajim.connections[self.account].method_local])
- self.method_manual_combobox.set_active(
- self.method_foo_index[gajim.connections[self.account].\
- method_manual])
-
- model = Gtk.ListStore(str, str, str, str)
- self.item_treeview.set_model(model)
- col = Gtk.TreeViewColumn('jid')
- self.item_treeview.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True, True, 0)
- col.set_attributes(renderer, text=0)
-
- col = Gtk.TreeViewColumn('expire')
- col.pack_start(renderer, True, True, 0)
- col.set_attributes(renderer, text=1)
- self.item_treeview.append_column(col)
-
- col = Gtk.TreeViewColumn('otr')
- col.pack_start(renderer, True, True, 0)
- col.set_attributes(renderer, text=2)
- self.item_treeview.append_column(col)
-
- col = Gtk.TreeViewColumn('save')
- col.pack_start(renderer, True, True, 0)
- col.set_attributes(renderer, text=3)
- self.item_treeview.append_column(col)
-
- self.fill_items()
-
- self.current_item = None
-
- def sort_items(model, iter1, iter2, data=None):
- item1 = model.get_value(iter1, 0)
- item2 = model.get_value(iter2, 0)
- if item1 == 'Default':
- return -1
- if item2 == 'Default':
- return 1
- if '@' in item1:
- if '@' not in item2:
- return 1
- elif '@' in item2:
- return -1
- if item1 < item2:
- return -1
- if item1 > item2:
- return 1
- # item1 == item2 ? WTF?
- return 0
-
- model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
- model.set_sort_func(0, sort_items)
-
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(False)
-
- self.window.set_title(_('Archiving Preferences for %s') % self.account)
-
- gajim.ged.register_event_handler(
- 'archiving-preferences-changed-received', ged.GUI1,
- self._nec_archiving_changed_received)
- gajim.ged.register_event_handler('archiving-error-received', ged.GUI1,
- self._nec_archiving_error)
-
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_add_item_button_clicked(self, widget):
- key_name = 'new_item_archiving_preferences'
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- ItemArchivingPreferencesWindow(self.account, '')
-
- def on_remove_item_button_clicked(self, widget):
- if not self.current_item:
- return
-
- self.waiting.append('itemremove')
- sel = self.item_treeview.get_selection()
- (model, iter_) = sel.get_selected()
- gajim.connections[self.account].remove_item(model[iter_][0])
- model.remove(iter_)
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(False)
-
- def on_edit_item_button_clicked(self, widget):
- if not self.current_item:
- return
-
- key_name = 'edit_item_archiving_preferences_%s' % self.current_item
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- ItemArchivingPreferencesWindow(self.account, self.current_item)
-
- def on_item_treeview_cursor_changed(self, widget):
- sel = self.item_treeview.get_selection()
- (model, iter_) = sel.get_selected()
- item = None
- if iter_:
- item = model[iter_][0]
- if self.current_item and self.current_item == item:
- return
-
- self.current_item = item
- if self.current_item == 'Default':
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(True)
- elif self.current_item:
- self.remove_button.set_sensitive(True)
- self.edit_button.set_sensitive(True)
- else:
- self.remove_button.set_sensitive(False)
- self.edit_button.set_sensitive(False)
-
- def on_auto_combobox_changed(self, widget):
- save = self.auto_name[widget.get_active()]
- gajim.connections[self.account].set_auto(save)
-
- def on_method_foo_combobox_changed(self, widget):
- # We retrieve method type from widget name
- # ('foo' in 'method_foo_combobox')
- method_type = widget.name.split('_')[1]
- use = self.method_foo_name[widget.get_active()]
- self.waiting.append('method_%s' % method_type)
- gajim.connections[self.account].set_method(method_type, use)
-
- def get_child_window(self):
- edit_key_name = 'edit_item_archiving_preferences_%s' % self.current_item
- new_key_name = 'new_item_archiving_preferences'
-
- if edit_key_name in gajim.interface.instances[self.account]:
- return gajim.interface.instances[self.account][edit_key_name]
-
- if new_key_name in gajim.interface.instances[self.account]:
- return gajim.interface.instances[self.account][new_key_name]
-
- def _nec_archiving_changed_received(self, obj):
- if obj.conn.name != self.account:
- return
- for key in ('auto', 'method_auto', 'method_local', 'method_manual'):
- if key in obj.conf and key in self.waiting:
- self.waiting.remove(key)
- if 'default' in obj.conf:
- key_name = 'edit_item_archiving_preferences_%s' % \
- self.current_item
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].\
- response_arrived(obj.conf['default'])
- self.fill_items(True)
- for jid, pref in obj.new_items.items():
- child = self.get_child_window()
- if child:
- is_new = not child.item
- child.response_arrived(pref)
- if is_new:
- model = self.item_treeview.get_model()
- model.append((jid, pref['expire'], pref['otr'],
- pref['save']))
- continue
- self.fill_items(True)
- if 'itemremove' in self.waiting and obj.removed_items:
- self.waiting.remove('itemremove')
- self.fill_items(True)
-
- def fill_items(self, clear=False):
- model = self.item_treeview.get_model()
- if clear:
- model.clear()
- default_config = gajim.connections[self.account].default
- expire_value = default_config['expire'] or ''
- model.append(('Default', expire_value,
- default_config['otr'], default_config['save']))
- for item, item_config in \
- gajim.connections[self.account].items.items():
- expire_value = item_config['expire'] or ''
- model.append((item, expire_value, item_config['otr'],
- item_config['save']))
-
- def _nec_archiving_error(self, obj):
- if obj.conn.name != self.account:
- return
- if self.waiting:
- pritext = _('There is an error')
- sectext = obj.error_msg
- ErrorDialog(pritext, sectext)
- self.waiting.pop()
- else:
- child = self.get_child_window()
- if child:
- child.error_arrived(obj.error_msg)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_archiving_preferences_window_destroy(self, widget):
- gajim.ged.remove_event_handler(
- 'archiving-preferences-changed-received', ged.GUI1,
- self._nec_archiving_changed_received)
- gajim.ged.remove_event_handler('archiving-error-received', ged.GUI1,
- self._nec_archiving_error)
- if 'archiving_preferences' in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account]['archiving_preferences']
-
-
-class Archiving313PreferencesWindow:
-
- default_dict = {'always': 0, 'roster': 1, 'never': 2}
- default_dict_cb = {0: 'always', 1: 'roster', 2: 'never'}
-
- def __init__(self, account):
- self.account = account
- self.idle_id = None
-
- # Connect to glade
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'archiving_313_preferences_window.ui')
- self.window = self.xml.get_object('archiving_313_pref')
-
- # Add Widgets
- for widget in ('archive_items_liststore', 'default_cb'):
- setattr(self, widget, self.xml.get_object(widget))
-
- self.window.set_title(_('Archiving Preferences for %s') % self.account)
-
- gajim.ged.register_event_handler(
- 'archiving-313-preferences-changed-received', ged.GUI1,
- self._nec_archiving_313_changed_received)
- gajim.ged.register_event_handler(
- 'archiving-error-received', ged.GUI1, self._nec_archiving_error)
-
- self.default_cb.set_active(0)
- self.set_widget_state(False)
- self.window.show_all()
- self.xml.connect_signals(self)
-
- self.idle_id = GLib.timeout_add_seconds(3, self._nec_archiving_error)
- gajim.connections[self.account].request_archive_preferences()
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def set_widget_state(self, state):
- for widget in ('default_cb', 'save_button', 'add_button',
- 'remove_button'):
- self.xml.get_object(widget).set_sensitive(state)
-
- def _nec_archiving_313_changed_received(self, obj):
- if obj.conn.name != self.account:
- return
- try:
- GLib.source_remove(self.idle_id)
- except Exception as e:
- log.debug(e)
- self.set_widget_state(True)
- if obj.answer:
- def on_ok(dialog):
- self.window.destroy()
- dialog = HigDialog(
- self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
- _('Success!'), _('Your Archiving Preferences have been saved!'),
- on_response_ok=on_ok, on_response_cancel=on_ok)
- dialog.popup()
- self.default_cb.set_active(self.default_dict[obj.default])
- self.archive_items_liststore.clear()
- for items in obj.items:
- self.archive_items_liststore.append(items)
-
- def _nec_archiving_error(self, obj=None):
- if obj and obj.conn.name != self.account:
- return
- try:
- GLib.source_remove(self.idle_id)
- except Exception as e:
- log.debug(e)
- if not obj:
- msg = _('We got no response from the Server')
- else:
- msg = _('We received an error: {}').format(self.error_msg)
-
- dialog = HigDialog(
- self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
- _('Error!'), msg)
- dialog.popup()
- self.set_widget_state(True)
- return
-
- def on_add_item_button_clicked(self, widget):
- key_name = 'item_archiving_preferences'
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- ItemArchiving313PreferencesWindow(
- self.account, self, self.window)
-
- def on_remove_item_button_clicked(self, widget):
- archive_view = self.xml.get_object('archive_view')
- mod, path = archive_view.get_selection().get_selected_rows()
- if path:
- iter_ = mod.get_iter(path)
- self.archive_items_liststore.remove(iter_)
-
- def on_save_button_clicked(self, widget):
- self.set_widget_state(False)
- items = []
- default = self.default_dict_cb[self.default_cb.get_active()]
- for item in self.archive_items_liststore:
- items.append((item[0].lower(), item[1].lower()))
- self.idle_id = GLib.timeout_add_seconds(3, self._nec_archiving_error)
- gajim.connections[self.account]. \
- set_archive_preferences(items, default)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_archiving_preferences_window_destroy(self, widget):
- gajim.ged.remove_event_handler(
- 'archiving-313-preferences-changed-received', ged.GUI1,
- self._nec_archiving_313_changed_received)
- gajim.ged.remove_event_handler(
- 'archiving-error-received', ged.GUI1, self._nec_archiving_error)
- if 'archiving_preferences' in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account]['archiving_preferences']
-
-
-class ItemArchiving313PreferencesWindow:
-
- def __init__(self, account, archive, transient):
-
- self.account = account
- self.archive = archive
-
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'archiving_313_preferences_item.ui')
- self.window = self.xml.get_object('item_dialog')
- self.window.set_transient_for(transient)
- # Add Widgets
- for widget in ('jid_entry', 'pref_cb'):
- setattr(self, widget, self.xml.get_object(widget))
-
- self.window.set_title(_('Add JID'))
- self.pref_cb.set_active(0)
- self.window.show_all()
- self.xml.connect_signals(self)
-
- def on_ok_button_clicked(self, widget):
- if self.pref_cb.get_active() == 0:
- pref = 'Always'
- else:
- pref = 'Never'
- text = self.jid_entry.get_text()
- if not text:
- self.window.destroy()
- return
- else:
- self.archive.archive_items_liststore.append((text, pref))
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_item_archiving_preferences_window_destroy(self, widget):
- key_name = 'item_archiving_preferences'
- if key_name in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account][key_name]
-
-
-class PrivacyListWindow:
- """
- Window that is used for creating NEW or EDITING already there privacy lists
- """
-
- def __init__(self, account, privacy_list_name, action):
- '''action is 'EDIT' or 'NEW' depending on if we create a new priv list
- or edit an already existing one'''
- self.account = account
- self.privacy_list_name = privacy_list_name
-
- # Dicts and Default Values
- self.active_rule = ''
- self.global_rules = {}
- self.list_of_groups = {}
-
- self.max_order = 0
-
- # Default Edit Values
- self.edit_rule_type = 'jid'
- self.allow_deny = 'allow'
-
- # Connect to gtk builder
- self.xml = gtkgui_helpers.get_gtk_builder('privacy_list_window.ui')
- self.window = self.xml.get_object('privacy_list_edit_window')
-
- # Add Widgets
-
- for widget_to_add in ('title_hbox', 'privacy_lists_title_label',
- 'list_of_rules_label', 'add_edit_rule_label', 'delete_open_buttons_hbox',
- 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton',
- 'list_of_rules_combobox', 'delete_open_buttons_hbox',
- 'delete_rule_button', 'open_rule_button', 'edit_allow_radiobutton',
- 'edit_deny_radiobutton', 'edit_type_jabberid_radiobutton',
- 'edit_type_jabberid_entry', 'edit_type_group_radiobutton',
- 'edit_type_group_combobox', 'edit_type_subscription_radiobutton',
- 'edit_type_subscription_combobox', 'edit_type_select_all_radiobutton',
- 'edit_queries_send_checkbutton', 'edit_send_messages_checkbutton',
- 'edit_view_status_checkbutton', 'edit_all_checkbutton',
- 'edit_order_spinbutton', 'new_rule_button', 'save_rule_button',
- 'privacy_list_refresh_button', 'privacy_list_close_button',
- 'edit_send_status_checkbutton', 'add_edit_vbox',
- 'privacy_list_active_checkbutton', 'privacy_list_default_checkbutton'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- self.privacy_lists_title_label.set_label(
- _('Privacy List <b><i>%s</i></b>') % \
- GLib.markup_escape_text(self.privacy_list_name))
-
- if len(gajim.connections) > 1:
- title = _('Privacy List for %s') % self.account
- else:
- title = _('Privacy List')
-
- self.delete_rule_button.set_sensitive(False)
- self.open_rule_button.set_sensitive(False)
- self.privacy_list_active_checkbutton.set_sensitive(False)
- self.privacy_list_default_checkbutton.set_sensitive(False)
- self.list_of_rules_combobox.set_sensitive(False)
-
- # set jabber id completion
- jids_list_store = Gtk.ListStore(GObject.TYPE_STRING)
- for jid in gajim.contacts.get_jid_list(self.account):
- jids_list_store.append([jid])
- jid_entry_completion = Gtk.EntryCompletion()
- jid_entry_completion.set_text_column(0)
- jid_entry_completion.set_model(jids_list_store)
- jid_entry_completion.set_popup_completion(True)
- self.edit_type_jabberid_entry.set_completion(jid_entry_completion)
- if action == 'EDIT':
- self.refresh_rules()
-
- model = self.edit_type_group_combobox.get_model()
- count = 0
- for group in gajim.groups[self.account]:
- self.list_of_groups[group] = count
- count += 1
- model.append([group])
- self.edit_type_group_combobox.set_active(0)
-
- self.window.set_title(title)
-
- gajim.ged.register_event_handler('privacy-list-received', ged.GUI1,
- self._nec_privacy_list_received)
- gajim.ged.register_event_handler('privacy-list-active-default',
- ged.GUI1, self._nec_privacy_list_active_default)
-
- self.window.show_all()
- self.add_edit_vbox.hide()
-
- self.xml.connect_signals(self)
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_privacy_list_edit_window_destroy(self, widget):
- key_name = 'privacy_list_%s' % self.privacy_list_name
- if key_name in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account][key_name]
- gajim.ged.remove_event_handler('privacy-list-received', ged.GUI1,
- self._nec_privacy_list_received)
- gajim.ged.remove_event_handler('privacy-list-active-default',
- ged.GUI1, self._nec_privacy_list_active_default)
-
- def _nec_privacy_list_active_default(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.active_list == self.privacy_list_name:
- self.privacy_list_active_checkbutton.set_active(True)
- else:
- self.privacy_list_active_checkbutton.set_active(False)
- if obj.default_list == self.privacy_list_name:
- self.privacy_list_default_checkbutton.set_active(True)
- else:
- self.privacy_list_default_checkbutton.set_active(False)
-
- def privacy_list_received(self, rules):
- model = self.list_of_rules_combobox.get_model()
- model.clear()
- self.global_rules = {}
- for rule in rules:
- if 'type' in rule:
- text_item = _('Order: %(order)s, action: %(action)s, type: %(type)s'
- ', value: %(value)s') % {'order': rule['order'],
- 'action': rule['action'], 'type': rule['type'],
- 'value': rule['value']}
- else:
- text_item = _('Order: %(order)s, action: %(action)s') % \
- {'order': rule['order'], 'action': rule['action']}
- if int(rule['order']) > self.max_order:
- self.max_order = int(rule['order'])
- self.global_rules[text_item] = rule
- model.append([text_item])
- if len(rules) == 0:
- self.title_hbox.set_sensitive(False)
- self.list_of_rules_combobox.set_sensitive(False)
- self.delete_rule_button.set_sensitive(False)
- self.open_rule_button.set_sensitive(False)
- self.privacy_list_active_checkbutton.set_sensitive(False)
- self.privacy_list_default_checkbutton.set_sensitive(False)
- else:
- self.list_of_rules_combobox.set_active(0)
- self.title_hbox.set_sensitive(True)
- self.list_of_rules_combobox.set_sensitive(True)
- self.delete_rule_button.set_sensitive(True)
- self.open_rule_button.set_sensitive(True)
- self.privacy_list_active_checkbutton.set_sensitive(True)
- self.privacy_list_default_checkbutton.set_sensitive(True)
- self.reset_fields()
- gajim.connections[self.account].get_active_default_lists()
-
- def _nec_privacy_list_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.list_name != self.privacy_list_name:
- return
- self.privacy_list_received(obj.rules)
-
- def refresh_rules(self):
- gajim.connections[self.account].get_privacy_list(self.privacy_list_name)
-
- def on_delete_rule_button_clicked(self, widget):
- model = self.list_of_rules_combobox.get_model()
- iter_ = self.list_of_rules_combobox.get_active_iter()
- _rule = model[iter_][0]
- tags = []
- for rule in self.global_rules:
- if rule != _rule:
- tags.append(self.global_rules[rule])
- gajim.connections[self.account].set_privacy_list(
- self.privacy_list_name, tags)
- self.privacy_list_received(tags)
- self.add_edit_vbox.hide()
- if not tags: # we removed latest rule
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- win = gajim.interface.instances[self.account]['privacy_lists']
- win.remove_privacy_list_from_combobox(self.privacy_list_name)
- win.draw_widgets()
-
- def on_open_rule_button_clicked(self, widget):
- self.add_edit_rule_label.set_label(
- _('<b>Edit a rule</b>'))
- active_num = self.list_of_rules_combobox.get_active()
- if active_num == -1:
- self.active_rule = ''
- else:
- model = self.list_of_rules_combobox.get_model()
- iter_ = self.list_of_rules_combobox.get_active_iter()
- self.active_rule = model[iter_][0]
- if self.active_rule != '':
- rule_info = self.global_rules[self.active_rule]
- self.edit_order_spinbutton.set_value(int(rule_info['order']))
- if 'type' in rule_info:
- if rule_info['type'] == 'jid':
- self.edit_type_jabberid_radiobutton.set_active(True)
- self.edit_type_jabberid_entry.set_text(rule_info['value'])
- elif rule_info['type'] == 'group':
- self.edit_type_group_radiobutton.set_active(True)
- if rule_info['value'] in self.list_of_groups:
- self.edit_type_group_combobox.set_active(
- self.list_of_groups[rule_info['value']])
- else:
- self.edit_type_group_combobox.set_active(0)
- elif rule_info['type'] == 'subscription':
- self.edit_type_subscription_radiobutton.set_active(True)
- sub_value = rule_info['value']
- if sub_value == 'none':
- self.edit_type_subscription_combobox.set_active(0)
- elif sub_value == 'both':
- self.edit_type_subscription_combobox.set_active(1)
- elif sub_value == 'from':
- self.edit_type_subscription_combobox.set_active(2)
- elif sub_value == 'to':
- self.edit_type_subscription_combobox.set_active(3)
- else:
- self.edit_type_select_all_radiobutton.set_active(True)
- else:
- self.edit_type_select_all_radiobutton.set_active(True)
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_all_checkbutton.set_active(False)
- if not rule_info['child']:
- self.edit_all_checkbutton.set_active(True)
- else:
- if 'presence-out' in rule_info['child']:
- self.edit_send_status_checkbutton.set_active(True)
- if 'presence-in' in rule_info['child']:
- self.edit_view_status_checkbutton.set_active(True)
- if 'iq' in rule_info['child']:
- self.edit_queries_send_checkbutton.set_active(True)
- if 'message' in rule_info['child']:
- self.edit_send_messages_checkbutton.set_active(True)
-
- if rule_info['action'] == 'allow':
- self.edit_allow_radiobutton.set_active(True)
- else:
- self.edit_deny_radiobutton.set_active(True)
- self.add_edit_vbox.show()
-
- def on_edit_all_checkbutton_toggled(self, widget):
- if widget.get_active():
- self.edit_send_messages_checkbutton.set_active(True)
- self.edit_queries_send_checkbutton.set_active(True)
- self.edit_view_status_checkbutton.set_active(True)
- self.edit_send_status_checkbutton.set_active(True)
- self.edit_send_messages_checkbutton.set_sensitive(False)
- self.edit_queries_send_checkbutton.set_sensitive(False)
- self.edit_view_status_checkbutton.set_sensitive(False)
- self.edit_send_status_checkbutton.set_sensitive(False)
- else:
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_send_messages_checkbutton.set_sensitive(True)
- self.edit_queries_send_checkbutton.set_sensitive(True)
- self.edit_view_status_checkbutton.set_sensitive(True)
- self.edit_send_status_checkbutton.set_sensitive(True)
-
- def on_privacy_list_active_checkbutton_toggled(self, widget):
- if widget.get_active():
- gajim.connections[self.account].set_active_list(
- self.privacy_list_name)
- else:
- gajim.connections[self.account].set_active_list(None)
-
- def on_privacy_list_default_checkbutton_toggled(self, widget):
- if widget.get_active():
- gajim.connections[self.account].set_default_list(
- self.privacy_list_name)
- else:
- gajim.connections[self.account].set_default_list(None)
-
- def on_new_rule_button_clicked(self, widget):
- self.reset_fields()
- self.add_edit_vbox.show()
-
- def reset_fields(self):
- self.edit_type_jabberid_entry.set_text('')
- self.edit_allow_radiobutton.set_active(True)
- self.edit_type_jabberid_radiobutton.set_active(True)
- self.active_rule = ''
- self.edit_send_messages_checkbutton.set_active(False)
- self.edit_queries_send_checkbutton.set_active(False)
- self.edit_view_status_checkbutton.set_active(False)
- self.edit_send_status_checkbutton.set_active(False)
- self.edit_all_checkbutton.set_active(False)
- self.edit_order_spinbutton.set_value(self.max_order + 1)
- self.edit_type_group_combobox.set_active(0)
- self.edit_type_subscription_combobox.set_active(0)
- self.add_edit_rule_label.set_label(
- _('<b>Add a rule</b>'))
-
- def get_current_tags(self):
- if self.edit_type_jabberid_radiobutton.get_active():
- edit_type = 'jid'
- edit_value = self.edit_type_jabberid_entry.get_text()
- elif self.edit_type_group_radiobutton.get_active():
- edit_type = 'group'
- model = self.edit_type_group_combobox.get_model()
- iter_ = self.edit_type_group_combobox.get_active_iter()
- edit_value = model[iter_][0]
- elif self.edit_type_subscription_radiobutton.get_active():
- edit_type = 'subscription'
- subs = ['none', 'both', 'from', 'to']
- edit_value = subs[self.edit_type_subscription_combobox.get_active()]
- elif self.edit_type_select_all_radiobutton.get_active():
- edit_type = ''
- edit_value = ''
- edit_order = str(self.edit_order_spinbutton.get_value_as_int())
- if self.edit_allow_radiobutton.get_active():
- edit_deny = 'allow'
- else:
- edit_deny = 'deny'
- child = []
- if not self.edit_all_checkbutton.get_active():
- if self.edit_send_messages_checkbutton.get_active():
- child.append('message')
- if self.edit_queries_send_checkbutton.get_active():
- child.append('iq')
- if self.edit_send_status_checkbutton.get_active():
- child.append('presence-out')
- if self.edit_view_status_checkbutton.get_active():
- child.append('presence-in')
- if edit_type != '':
- return {'order': edit_order, 'action': edit_deny,
- 'type': edit_type, 'value': edit_value, 'child': child}
- return {'order': edit_order, 'action': edit_deny, 'child': child}
-
- def on_save_rule_button_clicked(self, widget):
- tags=[]
- current_tags = self.get_current_tags()
- if int(current_tags['order']) > self.max_order:
- self.max_order = int(current_tags['order'])
- if self.active_rule == '':
- tags.append(current_tags)
-
- for rule in self.global_rules:
- if rule != self.active_rule:
- tags.append(self.global_rules[rule])
- else:
- tags.append(current_tags)
-
- gajim.connections[self.account].set_privacy_list(
- self.privacy_list_name, tags)
- self.refresh_rules()
- self.add_edit_vbox.hide()
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- win = gajim.interface.instances[self.account]['privacy_lists']
- win.add_privacy_list_to_combobox(self.privacy_list_name)
- win.draw_widgets()
-
- def on_list_of_rules_combobox_changed(self, widget):
- self.add_edit_vbox.hide()
-
- def on_edit_type_radiobutton_changed(self, widget, radiobutton):
- active_bool = widget.get_active()
- if active_bool:
- self.edit_rule_type = radiobutton
-
- def on_edit_allow_radiobutton_changed(self, widget, radiobutton):
- active_bool = widget.get_active()
- if active_bool:
- self.allow_deny = radiobutton
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
-class PrivacyListsWindow:
- """
- Window that is the main window for Privacy Lists; we can list there the
- privacy lists and ask to create a new one or edit an already there one
- """
- def __init__(self, account):
- self.account = account
- self.privacy_lists_save = []
-
- self.xml = gtkgui_helpers.get_gtk_builder('privacy_lists_window.ui')
-
- self.window = self.xml.get_object('privacy_lists_first_window')
- for widget_to_add in ('list_of_privacy_lists_combobox',
- 'delete_privacy_list_button', 'open_privacy_list_button',
- 'new_privacy_list_button', 'new_privacy_list_entry',
- 'privacy_lists_refresh_button', 'close_privacy_lists_window_button'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- self.draw_privacy_lists_in_combobox([])
- self.privacy_lists_refresh()
-
- self.enabled = True
-
- if len(gajim.connections) > 1:
- title = _('Privacy Lists for %s') % self.account
- else:
- title = _('Privacy Lists')
-
- self.window.set_title(title)
-
- gajim.ged.register_event_handler('privacy-lists-received', ged.GUI1,
- self._nec_privacy_lists_received)
- gajim.ged.register_event_handler('privacy-lists-removed', ged.GUI1,
- self._nec_privacy_lists_removed)
-
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_privacy_lists_first_window_destroy(self, widget):
- if 'privacy_lists' in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account]['privacy_lists']
- gajim.ged.remove_event_handler('privacy-lists-received', ged.GUI1,
- self._nec_privacy_lists_received)
- gajim.ged.remove_event_handler('privacy-lists-removed', ged.GUI1,
- self._nec_privacy_lists_removed)
-
- def remove_privacy_list_from_combobox(self, privacy_list):
- if privacy_list not in self.privacy_lists_save:
- return
- privacy_list_index = self.privacy_lists_save.index(privacy_list)
- self.list_of_privacy_lists_combobox.remove_text(privacy_list_index)
- self.privacy_lists_save.remove(privacy_list)
-
- def add_privacy_list_to_combobox(self, privacy_list):
- if privacy_list in self.privacy_lists_save:
- return
- model = self.list_of_privacy_lists_combobox.get_model()
- model.append([privacy_list])
- self.privacy_lists_save.append(privacy_list)
-
- def draw_privacy_lists_in_combobox(self, privacy_lists):
- self.list_of_privacy_lists_combobox.set_active(-1)
- self.list_of_privacy_lists_combobox.get_model().clear()
- self.privacy_lists_save = []
- for add_item in privacy_lists:
- self.add_privacy_list_to_combobox(add_item)
- self.draw_widgets()
-
- def draw_widgets(self):
- if len(self.privacy_lists_save) == 0:
- self.list_of_privacy_lists_combobox.set_sensitive(False)
- self.open_privacy_list_button.set_sensitive(False)
- self.delete_privacy_list_button.set_sensitive(False)
- else:
- self.list_of_privacy_lists_combobox.set_sensitive(True)
- self.list_of_privacy_lists_combobox.set_active(0)
- self.open_privacy_list_button.set_sensitive(True)
- self.delete_privacy_list_button.set_sensitive(True)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_delete_privacy_list_button_clicked(self, widget):
- active_list = self.privacy_lists_save[
- self.list_of_privacy_lists_combobox.get_active()]
- gajim.connections[self.account].del_privacy_list(active_list)
-
- def privacy_list_removed(self, active_list):
- self.privacy_lists_save.remove(active_list)
- self.privacy_lists_received({'lists': self.privacy_lists_save})
-
- def _nec_privacy_lists_removed(self, obj):
- if obj.conn.name != self.account:
- return
- self.privacy_list_removed(obj.lists_list)
-
- def privacy_lists_received(self, lists):
- if not lists:
- return
- privacy_lists = []
- for privacy_list in lists['lists']:
- privacy_lists.append(privacy_list)
- self.draw_privacy_lists_in_combobox(privacy_lists)
-
- def _nec_privacy_lists_received(self, obj):
- if obj.conn.name != self.account:
- return
- self.privacy_lists_received(obj.lists_list)
-
- def privacy_lists_refresh(self):
- gajim.connections[self.account].get_privacy_lists()
-
- def on_new_privacy_list_button_clicked(self, widget):
- name = self.new_privacy_list_entry.get_text()
- if not name:
- ErrorDialog(_('Invalid List Name'),
- _('You must enter a name to create a privacy list.'),
- transient_for=self.window)
- return
- key_name = 'privacy_list_%s' % name
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- PrivacyListWindow(self.account, name, 'NEW')
- self.new_privacy_list_entry.set_text('')
-
- def on_privacy_lists_refresh_button_clicked(self, widget):
- self.privacy_lists_refresh()
-
- def on_open_privacy_list_button_clicked(self, widget):
- name = self.privacy_lists_save[
- self.list_of_privacy_lists_combobox.get_active()]
- key_name = 'privacy_list_%s' % name
- if key_name in gajim.interface.instances[self.account]:
- gajim.interface.instances[self.account][key_name].window.present()
- else:
- gajim.interface.instances[self.account][key_name] = \
- PrivacyListWindow(self.account, name, 'EDIT')
-
-class InvitationReceivedDialog:
- def __init__(self, account, room_jid, contact_fjid, password=None,
- comment=None, is_continued=False):
-
- self.room_jid = room_jid
- self.account = account
- self.password = password
- self.is_continued = is_continued
- self.contact_fjid = contact_fjid
-
- jid = gajim.get_jid_without_resource(contact_fjid)
-
- pritext = _('''You are invited to a groupchat''')
- #Don't translate $Contact
- if is_continued:
- sectext = _('$Contact has invited you to join a discussion')
- else:
- sectext = _('$Contact has invited you to group chat %(room_jid)s')\
- % {'room_jid': room_jid}
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- contact_text = contact and contact.name or jid
- sectext = i18n.direction_mark + sectext.replace('$Contact',
- contact_text)
-
- if comment: # only if not None and not ''
- comment = GLib.markup_escape_text(comment)
- comment = _('Comment: %s') % comment
- sectext += '\n\n%s' % comment
- sectext += '\n\n' + _('Do you want to accept the invitation?')
-
- def on_yes(checked, text):
- try:
- if self.is_continued:
- gajim.interface.join_gc_room(self.account, self.room_jid,
- gajim.nicks[self.account], self.password,
- is_continued=True)
- else:
- JoinGroupchatWindow(self.account, self.room_jid,
- password=self.password)
- except GajimGeneralException:
- pass
-
- def on_no(text):
- gajim.connections[account].decline_invitation(self.room_jid,
- self.contact_fjid, text)
-
- dlg = YesNoDialog(pritext, sectext,
- text_label=_('Reason (if you decline):'), on_response_yes=on_yes,
- on_response_no=on_no)
- dlg.set_title(_('Groupchat Invitation'))
-
-class ProgressDialog:
- def __init__(self, title_text, during_text, messages_queue):
- """
- During text is what to show during the procedure, messages_queue has the
- message to show in the textview
- """
- self.xml = gtkgui_helpers.get_gtk_builder('progress_dialog.ui')
- self.dialog = self.xml.get_object('progress_dialog')
- self.label = self.xml.get_object('label')
- self.label.set_markup('<big>' + during_text + '</big>')
- self.progressbar = self.xml.get_object('progressbar')
- self.dialog.set_title(title_text)
- self.dialog.set_default_size(450, 250)
- self.window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
- self.dialog.show_all()
- self.xml.connect_signals(self)
-
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
-
- def update_progressbar(self):
- if self.dialog:
- self.progressbar.pulse()
- return True # loop forever
- return False
-
- def on_progress_dialog_delete_event(self, widget, event):
- return True # WM's X button or Escape key should not destroy the window
-
-
-class ClientCertChooserDialog(FileChooserDialog):
- def __init__(self, path_to_clientcert_file='', on_response_ok=None,
- on_response_cancel=None):
- '''
- optionally accepts path_to_clientcert_file so it has that as selected
- '''
- def on_ok(widget, callback):
- '''
- check if file exists and call callback
- '''
- path_to_clientcert_file = self.get_filename()
- if os.path.exists(path_to_clientcert_file):
- callback(widget, path_to_clientcert_file)
-
- FileChooserDialog.__init__(self,
- title_text=_('Choose Client Cert #PCKS12'),
- transient_for=gajim.interface.instances['accounts'].window,
- action=Gtk.FileChooserAction.OPEN,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- current_folder='',
- default_response=Gtk.ResponseType.OK,
- on_response_ok=(on_ok, on_response_ok),
- on_response_cancel=on_response_cancel)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('PKCS12 Files'))
- filter_.add_pattern('*.p12')
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- if path_to_clientcert_file:
- # set_filename accept only absolute path
- path_to_clientcert_file = os.path.abspath(path_to_clientcert_file)
- self.set_filename(path_to_clientcert_file)
-
-
-class SoundChooserDialog(FileChooserDialog):
- def __init__(self, path_to_snd_file='', on_response_ok=None,
- on_response_cancel=None, transient_for=None):
- """
- Optionally accepts path_to_snd_file so it has that as selected
- """
- def on_ok(widget, callback):
- """
- Check if file exists and call callback
- """
- path_to_snd_file = self.get_filename()
- if os.path.exists(path_to_snd_file):
- callback(widget, path_to_snd_file)
-
- FileChooserDialog.__init__(
- self, title_text=_('Choose Sound'),
- action=Gtk.FileChooserAction.OPEN,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=gajim.config.get('last_sounds_dir'),
- on_response_ok=(on_ok, on_response_ok),
- on_response_cancel=on_response_cancel,
- transient_for=transient_for)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('Wav Sounds'))
- filter_.add_pattern('*.wav')
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- path_to_snd_file = helpers.check_soundfile_path(path_to_snd_file)
- if path_to_snd_file:
- # set_filename accept only absolute path
- path_to_snd_file = os.path.abspath(path_to_snd_file)
- self.set_filename(path_to_snd_file)
-
-class ImageChooserDialog(FileChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None):
- """
- Optionally accepts path_to_snd_file so it has that as selected
- """
- def on_ok(widget, callback):
- '''check if file exists and call callback'''
- path_to_file = self.get_filename()
- if not path_to_file:
- return
- if os.path.exists(path_to_file):
- if isinstance(callback, tuple):
- callback[0](widget, path_to_file, *callback[1:])
- else:
- callback(widget, path_to_file)
-
- path = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES)
- FileChooserDialog.__init__(self,
- title_text = _('Choose Image'),
- action = Gtk.FileChooserAction.OPEN,
- buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- default_response = Gtk.ResponseType.OK,
- current_folder = path,
- on_response_ok = (on_ok, on_response_ok),
- on_response_cancel = on_response_cancel)
-
- if on_response_cancel:
- self.connect('destroy', on_response_cancel)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('Images'))
- filter_.add_mime_type('image/png')
- filter_.add_mime_type('image/jpeg')
- filter_.add_mime_type('image/gif')
- filter_.add_mime_type('image/tiff')
- filter_.add_mime_type('image/svg+xml')
- filter_.add_mime_type('image/x-xpixmap') # xpm
- self.add_filter(filter_)
- self.set_filter(filter_)
-
- if path_to_file:
- self.set_filename(path_to_file)
-
- self.set_use_preview_label(False)
- self.set_preview_widget(Gtk.Image())
- self.connect('selection-changed', self.update_preview)
-
- def update_preview(self, widget):
- path_to_file = widget.get_preview_filename()
- if path_to_file is None or os.path.isdir(path_to_file):
- # nothing to preview or directory
- # make sure you clean image do show nothing
- preview = widget.get_preview_widget()
- preview.clear()
- return
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path_to_file, 100, 100)
- except GObject.GError:
- return
- widget.get_preview_widget().set_from_pixbuf(pixbuf)
-
-class AvatarChooserDialog(ImageChooserDialog):
- def __init__(self, path_to_file='', on_response_ok=None,
- on_response_cancel=None, on_response_clear=None):
- ImageChooserDialog.__init__(self, path_to_file, on_response_ok,
- on_response_cancel)
- button = Gtk.Button(None, Gtk.STOCK_CLEAR)
- self.response_clear = on_response_clear
- if on_response_clear:
- button.connect('clicked', self.on_clear)
- button.show_all()
- self.action_area.pack_start(button, True, True, 0)
- self.action_area.reorder_child(button, 0)
-
- def on_clear(self, widget):
- if isinstance(self.response_clear, tuple):
- self.response_clear[0](widget, *self.response_clear[1:])
- else:
- self.response_clear(widget)
-
-
-class ArchiveChooserDialog(FileChooserDialog):
- def __init__(self, on_response_ok=None, on_response_cancel=None,
- transient_for=None):
-
- def on_ok(widget, callback):
- '''check if file exists and call callback'''
- path_to_file = self.get_filename()
- if not path_to_file:
- return
- if os.path.exists(path_to_file):
- if isinstance(callback, tuple):
- callback[0](path_to_file, *callback[1:])
- else:
- callback(path_to_file)
- self.destroy()
-
- path = os.path.expanduser('~')
-
- FileChooserDialog.__init__(self,
- title_text=_('Choose Archive'),
- action=Gtk.FileChooserAction.OPEN,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=path,
- on_response_ok=(on_ok, on_response_ok),
- on_response_cancel=on_response_cancel,
- transient_for=transient_for)
-
- if on_response_cancel:
- self.connect('destroy', on_response_cancel)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('All files'))
- filter_.add_pattern('*')
- self.add_filter(filter_)
-
- filter_ = Gtk.FileFilter()
- filter_.set_name(_('Zip files'))
- filter_.add_pattern('*.zip')
-
- self.add_filter(filter_)
- self.set_filter(filter_)
-
-
-class AddSpecialNotificationDialog:
- def __init__(self, jid):
- """
- jid is the jid for which we want to add special notification (sound and
- notification popups)
- """
- self.xml = gtkgui_helpers.get_gtk_builder(
- 'add_special_notification_window.ui')
- self.window = self.xml.get_object('add_special_notification_window')
- self.condition_combobox = self.xml.get_object('condition_combobox')
- self.condition_combobox.set_active(0)
- self.notification_popup_yes_no_combobox = self.xml.get_object(
- 'notification_popup_yes_no_combobox')
- self.notification_popup_yes_no_combobox.set_active(0)
- self.listen_sound_combobox = self.xml.get_object('listen_sound_combobox')
- self.listen_sound_combobox.set_active(0)
-
- self.jid = jid
- self.xml.get_object('when_foo_becomes_label').set_text(
- _('When %s becomes:') % self.jid)
-
- self.window.set_title(_('Adding Special Notification for %s') % jid)
- self.window.show_all()
- self.xml.connect_signals(self)
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def on_add_special_notification_window_delete_event(self, widget, event):
- self.window.destroy()
-
- def on_listen_sound_combobox_changed(self, widget):
- active = widget.get_active()
- if active == 1: # user selected 'choose sound'
- def on_ok(widget, path_to_snd_file):
- pass
-
- def on_cancel(widget):
- widget.set_active(0) # go back to No Sound
-
- self.dialog = SoundChooserDialog(on_response_ok=on_ok,
- on_response_cancel=on_cancel)
-
- def on_ok_button_clicked(self, widget):
- conditions = ('online', 'chat', 'online_and_chat',
- 'away', 'xa', 'away_and_xa', 'dnd', 'xa_and_dnd', 'offline')
- active = self.condition_combobox.get_active()
-
- active_iter = self.listen_sound_combobox.get_active_iter()
- listen_sound_model = self.listen_sound_combobox.get_model()
-
-class TransformChatToMUC:
- # Keep a reference on windows so garbage collector don't restroy them
- instances = []
- def __init__(self, account, jids, preselected=None):
- """
- This window is used to trasform a one-to-one chat to a MUC. We do 2
- things: first select the server and then make a guests list
- """
-
- self.instances.append(self)
- self.account = account
- self.auto_jids = jids
- self.preselected_jids = preselected
-
- self.xml = gtkgui_helpers.get_gtk_builder('chat_to_muc_window.ui')
- self.window = self.xml.get_object('chat_to_muc_window')
-
- for widget_to_add in ('invite_button', 'cancel_button',
- 'server_list_comboboxentry', 'guests_treeview',
- 'server_and_guests_hseparator', 'server_select_label'):
- self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
-
- server_list = []
- self.servers = Gtk.ListStore(str)
- self.server_list_comboboxentry.set_model(self.servers)
- cell = Gtk.CellRendererText()
- self.server_list_comboboxentry.pack_start(cell, True)
- self.server_list_comboboxentry.add_attribute(cell, 'text', 0)
-
- # get the muc server of our server
- if 'jabber' in gajim.connections[account].muc_jid:
- server_list.append(gajim.connections[account].muc_jid['jabber'])
- # add servers or recently joined groupchats
- recently_groupchat = gajim.config.get('recently_groupchat').split()
- for g in recently_groupchat:
- server = gajim.get_server_from_jid(g)
- if server not in server_list and not server.startswith('irc'):
- server_list.append(server)
- # add a default server
- if not server_list:
- server_list.append('conference.jabber.org')
-
- for s in server_list:
- self.servers.append([s])
-
- self.server_list_comboboxentry.set_active(0)
-
- # set treeview
- # name, jid
- self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
- self.store.set_sort_column_id(1, Gtk.SortType.ASCENDING)
- self.guests_treeview.set_model(self.store)
-
- renderer1 = Gtk.CellRendererText()
- renderer2 = Gtk.CellRendererPixbuf()
- column = Gtk.TreeViewColumn('Status', renderer2, pixbuf=0)
- self.guests_treeview.append_column(column)
- column = Gtk.TreeViewColumn('Name', renderer1, text=1)
- self.guests_treeview.append_column(column)
-
- self.guests_treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
-
- # All contacts beside the following can be invited:
- # transports, zeroconf contacts, minimized groupchats
- def invitable(contact, contact_transport=None):
- return (contact.jid not in self.auto_jids and
- contact.jid != gajim.get_jid_from_account(self.account) and
- contact.jid not in gajim.interface.minimized_controls[account] and
- not contact.is_transport() and
- contact_transport in ('jabber', None))
-
- # set jabber id and pseudos
- for account in gajim.contacts.get_accounts():
- if gajim.connections[account].is_zeroconf:
- continue
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- contact_transport = gajim.get_transport_name_from_jid(jid)
- # Add contact if it can be invited
- if invitable(contact, contact_transport) and \
- contact.show not in ('offline', 'error'):
- img = gajim.interface.jabber_state_images['16'][contact.show]
- name = contact.name
- if name == '':
- name = jid.split('@')[0]
- iter_ = self.store.append([img.get_pixbuf(), name, jid])
- # preselect treeview rows
- if self.preselected_jids and jid in self.preselected_jids:
- path = self.store.get_path(iter_)
- self.guests_treeview.get_selection().select_path(path)
-
- gajim.ged.register_event_handler('unique-room-id-supported', ged.GUI1,
- self._nec_unique_room_id_supported)
- gajim.ged.register_event_handler('unique-room-id-not-supported',
- ged.GUI1, self._nec_unique_room_id_not_supported)
-
- # show all
- self.window.show_all()
-
- self.xml.connect_signals(self)
-
- def on_chat_to_muc_window_destroy(self, widget):
- gajim.ged.remove_event_handler('unique-room-id-supported', ged.GUI1,
- self._nec_unique_room_id_supported)
- gajim.ged.remove_event_handler('unique-room-id-not-supported', ged.GUI1,
- self._nec_unique_room_id_not_supported)
- self.instances.remove(self)
-
- def on_chat_to_muc_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape: # ESCAPE
- self.window.destroy()
-
- def on_invite_button_clicked(self, widget):
- row = self.server_list_comboboxentry.get_child().get_displayed_row()
- model = self.server_list_comboboxentry.get_model()
- server = model[row][0].strip()
- if server == '':
- return
- gajim.connections[self.account].check_unique_room_id_support(server, self)
-
- def _nec_unique_room_id_supported(self, obj):
- if obj.instance != self:
- return
- guest_list = []
- guests = self.guests_treeview.get_selection().get_selected_rows()
- for guest in guests[1]:
- iter_ = self.store.get_iter(guest)
- guest_list.append(self.store[iter_][2])
- for guest in self.auto_jids:
- guest_list.append(guest)
- room_jid = obj.room_id + '@' + obj.server
- gajim.automatic_rooms[self.account][room_jid] = {}
- gajim.automatic_rooms[self.account][room_jid]['invities'] = guest_list
- gajim.automatic_rooms[self.account][room_jid]['continue_tag'] = True
- gajim.interface.join_gc_room(self.account, room_jid,
- gajim.nicks[self.account], None, is_continued=True)
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
-
- def _nec_unique_room_id_not_supported(self, obj):
- if obj.instance != self:
- return
- obj.room_id = gajim.nicks[self.account].lower().replace(' ', '') + \
- str(randrange(9999999))
- self._nec_unique_room_id_supported(obj)
-
-class DataFormWindow(Dialog):
- def __init__(self, form, on_response_ok):
- self.df_response_ok = on_response_ok
- Dialog.__init__(self, None, 'test', [(Gtk.STOCK_CANCEL,
- Gtk.ResponseType.REJECT), (Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)],
- on_response_ok=self.on_ok)
- self.set_resizable(True)
- gtkgui_helpers.resize_window(self, 600, 400)
- self.dataform_widget = dataforms_widget.DataFormWidget()
- self.dataform = dataforms.ExtendForm(node=form)
- self.dataform_widget.set_sensitive(True)
- self.dataform_widget.data_form = self.dataform
- self.dataform_widget.show_all()
- self.vbox.pack_start(self.dataform_widget, True, True, 0)
-
- def on_ok(self):
- form = self.dataform_widget.data_form
- if isinstance(self.df_response_ok, tuple):
- self.df_response_ok[0](form, *self.df_response_ok[1:])
- else:
- self.df_response_ok(form)
- self.destroy()
-
-
-class ResourceConflictDialog(TimeoutDialog, InputDialog):
- def __init__(self, title, text, resource, ok_handler):
- TimeoutDialog.__init__(self, 15, self.on_timeout)
- InputDialog.__init__(self, title, text, input_str=resource,
- is_modal=False, ok_handler=ok_handler)
- self.title_text = title
- self.run_timeout()
-
- def on_timeout(self):
- self.on_okbutton_clicked(None)
-
-
-
-class VoIPCallReceivedDialog(object):
- instances = {}
- def __init__(self, account, contact_jid, sid, content_types):
- self.instances[(contact_jid, sid)] = self
- self.account = account
- self.fjid = contact_jid
- self.sid = sid
- self.content_types = content_types
-
- xml = gtkgui_helpers.get_gtk_builder('voip_call_received_dialog.ui')
- xml.connect_signals(self)
-
- jid = gajim.get_jid_without_resource(self.fjid)
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if contact and contact.name:
- self.contact_text = '%s (%s)' % (contact.name, jid)
- else:
- self.contact_text = contact_jid
-
- self.dialog = xml.get_object('voip_call_received_messagedialog')
- self.set_secondary_text()
-
- self.dialog.show_all()
-
- @classmethod
- def get_dialog(cls, jid, sid):
- if (jid, sid) in cls.instances:
- return cls.instances[(jid, sid)]
- else:
- return None
-
- def set_secondary_text(self):
- if 'audio' in self.content_types and 'video' in self.content_types:
- types_text = _('an audio and video')
- elif 'audio' in self.content_types:
- types_text = _('an audio')
- elif 'video' in self.content_types:
- types_text = _('a video')
-
- # do the substitution
- self.dialog.set_property('secondary-text',
- _('%(contact)s wants to start %(type)s session with you. Do you want '
- 'to answer the call?') % {'contact': self.contact_text,
- 'type': types_text})
-
- def add_contents(self, content_types):
- for type_ in content_types:
- if type_ not in self.content_types:
- self.content_types.add(type_)
- self.set_secondary_text()
-
- def remove_contents(self, content_types):
- for type_ in content_types:
- if type_ in self.content_types:
- self.content_types.remove(type_)
- if not self.content_types:
- self.dialog.destroy()
- else:
- self.set_secondary_text()
-
- def on_voip_call_received_messagedialog_destroy(self, dialog):
- if (self.fjid, self.sid) in self.instances:
- del self.instances[(self.fjid, self.sid)]
-
- def on_voip_call_received_messagedialog_close(self, dialog):
- return self.on_voip_call_received_messagedialog_response(dialog,
- Gtk.ResponseType.NO)
-
- def on_voip_call_received_messagedialog_response(self, dialog, response):
- # we've got response from user, either stop connecting or accept the call
- session = gajim.connections[self.account].get_jingle_session(self.fjid,
- self.sid)
- if not session:
- dialog.destroy()
- return
- if response == Gtk.ResponseType.YES:
- #TODO: Ensure that ctrl.contact.resource == resource
- jid = gajim.get_jid_without_resource(self.fjid)
- resource = gajim.get_resource_from_jid(self.fjid)
- ctrl = (gajim.interface.msg_win_mgr.get_control(self.fjid, self.account)
- or gajim.interface.msg_win_mgr.get_control(jid, self.account)
- or gajim.interface.new_chat_from_jid(self.account, jid))
-
- # Chat control opened, update content's status
- audio = session.get_content('audio')
- video = session.get_content('video')
- if audio and not audio.negotiated:
- ctrl.set_audio_state('connecting', self.sid)
- if video and not video.negotiated:
- video_hbox = ctrl.xml.get_object('video_hbox')
- video_hbox.set_no_show_all(False)
- if gajim.config.get('video_see_self'):
- fixed = ctrl.xml.get_object('outgoing_fixed')
- fixed.set_no_show_all(False)
- video_hbox.show_all()
- ctrl.xml.get_object('incoming_drawingarea').realize()
- if os.name == 'nt':
- in_xid = ctrl.xml.get_object('incoming_drawingarea').\
- get_window().handle
- else:
- in_xid = ctrl.xml.get_object('incoming_drawingarea').\
- get_property('window').get_xid()
- content = session.get_content('video')
- # move outgoing stream to chat window
- if gajim.config.get('video_see_self'):
- ctrl.xml.get_object('outgoing_drawingarea').realize()
- if os.name == 'nt':
- out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
- get_window().handle
- else:
- out_xid = ctrl.xml.get_object('outgoing_drawingarea').\
- get_property('window').get_xid()
- b = content.src_bin
- found = False
- for e in b.children:
- if e.get_name().startswith('autovideosink'):
- found = True
- break
- if found:
- for f in e.children:
- if f.get_name().startswith('autovideosink'):
- f.set_window_handle(out_xid)
- content.out_xid = out_xid
- break
- content.in_xid = in_xid
- ctrl.set_video_state('connecting', self.sid)
- # Now, accept the content/sessions.
- # This should be done after the chat control is running
- if not session.accepted:
- session.approve_session()
- for content in self.content_types:
- session.approve_content(content)
- else: # response==Gtk.ResponseType.NO
- if not session.accepted:
- session.decline_session()
- else:
- for content in self.content_types:
- session.reject_content(content)
-
- dialog.destroy()
-
-class CertificatDialog(InformationDialog):
- def __init__(self, parent, account, cert):
- issuer = cert.get_issuer()
- subject = cert.get_subject()
- InformationDialog.__init__(self,
- _('Certificate for account %s') % account, _('''<b>Issued to:</b>
-Common Name (CN): %(scn)s
-Organization (O): %(sorg)s
-Organizationl Unit (OU): %(sou)s
-Serial Number: %(sn)s
-
-<b>Issued by:</b>
-Common Name (CN): %(icn)s
-Organization (O): %(iorg)s
-Organizationl Unit (OU): %(iou)s
-
-<b>Validity:</b>
-Issued on: %(io)s
-Expires on: %(eo)s
-
-<b>Fingerprint</b>
-SHA-1 Fingerprint: %(sha1)s
-
-SHA256 Fingerprint: %(sha256)s
-''') % {
- 'scn': subject.commonName, 'sorg': subject.organizationName,
- 'sou': subject.organizationalUnitName,
- 'sn': cert.get_serial_number(), 'icn': issuer.commonName,
- 'iorg': issuer.organizationName,
- 'iou': issuer.organizationalUnitName,
- 'io': cert.get_notBefore().decode('utf-8'),
- 'eo': cert.get_notAfter().decode('utf-8'),
- 'sha1': cert.digest('sha1').decode('utf-8'),
- 'sha256': cert.digest('sha256').decode('utf-8')})
- pix = gtkgui_helpers.get_icon_pixmap('application-certificate', size=32,
- quiet=True)
- if pix:
- img = Gtk.Image.new_from_pixbuf(pix)
- img.show_all()
- self.set_image(img)
- self.set_transient_for(parent)
- self.set_title(_('Certificate for account %s') % account)
-
-
-class CheckFingerprintDialog(YesNoDialog):
- def __init__(self, pritext='', sectext='', checktext='',
- on_response_yes=None, on_response_no=None, account=None, certificate=None):
- self.account = account
- self.cert = certificate
- YesNoDialog.__init__(self, pritext, sectext=sectext,
- checktext=checktext, on_response_yes=on_response_yes,
- on_response_no=on_response_no)
- self.set_title(_('SSL Certificate Verification for %s') % account)
- b = Gtk.Button(label=_('View cert…'))
- b.connect('clicked', self.on_cert_clicked)
- b.show_all()
- area = self.get_action_area()
- area.pack_start(b, True, True, 0)
-
- def on_cert_clicked(self, button):
- CertificatDialog(self, self.account, self.cert)
-
-class SSLErrorDialog(ConfirmationDialogDoubleCheck):
- def __init__(self, account, certificate, pritext, sectext, checktext1,
- checktext2, on_response_ok=None, on_response_cancel=None):
- self.account = account
- self.cert = certificate
- ConfirmationDialogDoubleCheck.__init__(self, pritext, sectext,
- checktext1, checktext2, on_response_ok=on_response_ok,
- on_response_cancel=on_response_cancel, is_modal=False)
- b = Gtk.Button(_('View cert…'))
- b.connect('clicked', self.on_cert_clicked)
- b.show_all()
- area = self.get_action_area()
- area.pack_start(b, True, True, 0)
-
- def on_cert_clicked(self, button):
- d = CertificatDialog(self, self.account, self.cert)
-
-
-class BigAvatarWindow(Gtk.Window):
- def __init__(self, avatar, pos_x, pos_y, width, height, callback):
- super(BigAvatarWindow, self).__init__(type=Gtk.WindowType.POPUP)
- self.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
- self.avatar = avatar
- self.callback = callback
- self.screen = self.get_screen()
- self.visual = self.screen.get_rgba_visual()
- if self.visual is not None and self.screen.is_composited():
- self.set_visual(self.visual)
- self.set_app_paintable(True)
- self.set_size_request(width, height)
- self.move(pos_x, pos_y)
- # we should hide the window
- self.connect('leave_notify_event', self._on_window_avatar_leave_notify)
- self.connect('motion-notify-event', self._on_window_motion_notify)
- self.realize()
- # make the cursor invisible so we can see the image
- self.get_window().set_cursor(
- Gdk.Cursor.new(Gdk.CursorType.BLANK_CURSOR))
- self.add(Gtk.Image.new_from_pixbuf(self.avatar))
- self.show_all()
-
- def _on_window_avatar_leave_notify(self, widget, event):
- """
- Just left the popup window that holds avatar
- """
- self.destroy()
- self.bigger_avatar_window = None
- # Re-show the small avatar
- self.callback()
-
- def _on_window_motion_notify(self, widget, event):
- """
- Just moved the mouse so show the cursor
- """
- cursor = gtkgui_helpers.get_cursor('LEFT_PTR')
- self.get_window().set_cursor(cursor)
diff --git a/src/disco.py b/src/disco.py
deleted file mode 100644
index 0832e286e..000000000
--- a/src/disco.py
+++ /dev/null
@@ -1,2223 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/disco.py
-##
-## Copyright (C) 2005-2006 Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-# The appearance of the treeview, and parts of the dialog, are controlled by
-# AgentBrowser (sub-)classes. Methods that probably should be overridden when
-# subclassing are: (look at the docstrings and source for additional info)
-# - def cleanup(self) *
-# - def _create_treemodel(self) *
-# - def _add_actions(self)
-# - def _clean_actions(self)
-# - def update_theme(self) *
-# - def update_actions(self)
-# - def default_action(self)
-# - def _find_item(self, jid, node)
-# - def _add_item(self, jid, node, parent_node, item, force)
-# - def _update_item(self, iter_, jid, node, item)
-# - def _update_info(self, iter_, jid, node, identities, features, data)
-# - def _update_error(self, iter_, jid, node)
-#
-# * Should call the super class for this method.
-# All others do not have to call back to the super class. (but can if they want
-# the functionality)
-# There are more methods, of course, but this is a basic set.
-
-import types
-import weakref
-from gi.repository import GLib
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import Pango
-
-import dialogs
-import gtkgui_helpers
-import groups
-import adhoc_commands
-import search_window
-import gui_menu_builder
-
-from common import gajim
-import nbxmpp
-from common.exceptions import GajimGeneralException
-from common import helpers
-from common import ged
-
-LABELS = {
- 1: _('This service has not yet responded with detailed information'),
- 2: _('This service could not respond with detailed information.\n'
- 'It is most likely legacy or broken'),
-}
-
-# Dictionary mapping category, type pairs to browser class, image pairs.
-# This is a function, so we can call it after the classes are declared.
-# For the browser class, None means that the service will only be browsable
-# when it advertises disco as it's feature, False means it's never browsable.
-def _gen_agent_type_info():
- return {
- # Defaults
- (0, 0): (None, None),
-
- # Jabber server
- ('server', 'im'): (ToplevelAgentBrowser, 'jabber'),
- ('services', 'jabber'): (ToplevelAgentBrowser, 'jabber'),
- ('hierarchy', 'branch'): (AgentBrowser, 'jabber'),
-
- # Services
- ('conference', 'text'): (MucBrowser, 'conference'),
- ('headline', 'rss'): (AgentBrowser, 'rss'),
- ('headline', 'weather'): (False, 'weather'),
- ('gateway', 'weather'): (False, 'weather'),
- ('_jid', 'weather'): (False, 'weather'),
- ('gateway', 'sip'): (False, 'sip'),
- ('directory', 'user'): (None, 'jud'),
- ('pubsub', 'generic'): (PubSubBrowser, 'pubsub'),
- ('pubsub', 'service'): (PubSubBrowser, 'pubsub'),
- ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy
- ('headline', 'newmail'): (ToplevelAgentBrowser, 'mail'),
-
- # Transports
- ('conference', 'irc'): (ToplevelAgentBrowser, 'irc'),
- ('_jid', 'irc'): (False, 'irc'),
- ('gateway', 'irc'): (False, 'irc'),
- ('gateway', 'aim'): (False, 'aim'),
- ('_jid', 'aim'): (False, 'aim'),
- ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'),
- ('_jid', 'gadugadu'): (False, 'gadu-gadu'),
- ('gateway', 'http-ws'): (False, 'http-ws'),
- ('gateway', 'icq'): (False, 'icq'),
- ('_jid', 'icq'): (False, 'icq'),
- ('gateway', 'msn'): (False, 'msn'),
- ('_jid', 'msn'): (False, 'msn'),
- ('gateway', 'sms'): (False, 'sms'),
- ('_jid', 'sms'): (False, 'sms'),
- ('gateway', 'smtp'): (False, 'mail'),
- ('gateway', 'yahoo'): (False, 'yahoo'),
- ('_jid', 'yahoo'): (False, 'yahoo'),
- ('gateway', 'mrim'): (False, 'mrim'),
- ('_jid', 'mrim'): (False, 'mrim'),
- ('gateway', 'facebook'): (False, 'facebook'),
- ('_jid', 'facebook'): (False, 'facebook'),
- ('gateway', 'tv'): (False, 'tv'),
- ('gateway', 'twitter'): (False, 'twitter'),
- }
-
-# Category type to "human-readable" description string
-_cat_to_descr = {
- 'other': _('Others'),
- 'gateway': _('Transports'),
- '_jid': _('Transports'),
- #conference is a category for listing mostly groupchats in service discovery
- 'conference': _('Conference'),
-}
-
-
-class CacheDictionary:
- """
- A dictionary that keeps items around for only a specific time. Lifetime is
- in minutes. Getrefresh specifies whether to refresh when an item is merely
- accessed instead of set aswell
- """
-
- def __init__(self, lifetime, getrefresh = True):
- self.lifetime = lifetime * 1000 * 60
- self.getrefresh = getrefresh
- self.cache = {}
-
- class CacheItem:
- """
- An object to store cache items and their timeouts
- """
- def __init__(self, value):
- self.value = value
- self.source = None
-
- def __call__(self):
- return self.value
-
- def cleanup(self):
- for key in list(self.cache.keys()):
- item = self.cache[key]
- if item.source:
- GLib.source_remove(item.source)
- del self.cache[key]
-
- def _expire_timeout(self, key):
- """
- The timeout has expired, remove the object
- """
- if key in self.cache:
- del self.cache[key]
- return False
-
- def _refresh_timeout(self, key):
- """
- The object was accessed, refresh the timeout
- """
- item = self.cache[key]
- if item.source:
- GLib.source_remove(item.source)
- if self.lifetime:
- source = GLib.timeout_add_seconds(int(self.lifetime/1000), self._expire_timeout, key)
- item.source = source
-
- def __getitem__(self, key):
- item = self.cache[key]
- if self.getrefresh:
- self._refresh_timeout(key)
- return item()
-
- def __setitem__(self, key, value):
- item = self.CacheItem(value)
- self.cache[key] = item
- self._refresh_timeout(key)
-
- def __delitem__(self, key):
- item = self.cache[key]
- if item.source:
- GLib.source_remove(item.source)
- del self.cache[key]
-
- def __contains__(self, key):
- return key in self.cache
-
-_icon_cache = CacheDictionary(15)
-
-def get_agent_address(jid, node = None):
- """
- Get an agent's address for displaying in the GUI
- """
- if node:
- return '%s@%s' % (node, str(jid))
- else:
- return str(jid)
-
-class Closure(object):
- """
- A weak reference to a callback with arguments as an object
-
- Weak references to methods immediatly die, even if the object is still
- alive. Besides a handy way to store a callback, this provides a workaround
- that keeps a reference to the object instead.
-
- Userargs and removeargs must be tuples.
- """
-
- def __init__(self, cb, userargs = (), remove = None, removeargs = ()):
- self.userargs = userargs
- self.remove = remove
- self.removeargs = removeargs
- if isinstance(cb, types.MethodType):
- self.meth_self = weakref.ref(cb.__self__, self._remove)
- self.meth_name = cb.__name__
- elif callable(cb):
- self.meth_self = None
- self.cb = weakref.ref(cb, self._remove)
- else:
- raise TypeError('Object is not callable')
-
- def _remove(self, ref):
- if self.remove:
- self.remove(self, *self.removeargs)
-
- def __call__(self, *args, **kwargs):
- if self.meth_self:
- obj = self.meth_self()
- cb = getattr(obj, self.meth_name)
- else:
- cb = self.cb()
- args = args + self.userargs
- return cb(*args, **kwargs)
-
-
-class ServicesCache:
- """
- Class that caches our query results. Each connection will have it's own
- ServiceCache instance
- """
-
- def __init__(self, account):
- self.account = account
- self._items = CacheDictionary(0, getrefresh = False)
- self._info = CacheDictionary(0, getrefresh = False)
- self._subscriptions = CacheDictionary(5, getrefresh=False)
- self._cbs = {}
- gajim.ged.register_event_handler('agent-items-received', ged.GUI1,
- self._nec_agent_items_received)
- gajim.ged.register_event_handler('agent-items-error-received', ged.GUI1,
- self._nec_agent_items_error_received)
- gajim.ged.register_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received)
- gajim.ged.register_event_handler('agent-info-error-received', ged.GUI1,
- self._nec_agent_info_error_received)
-
- def __del__(self):
- gajim.ged.remove_event_handler('agent-items-received', ged.GUI1,
- self._nec_agent_items_received)
- gajim.ged.remove_event_handler('agent-items-error-received', ged.GUI1,
- self._nec_agent_items_error_received)
- gajim.ged.remove_event_handler('agent-info-received', ged.GUI1,
- self._nec_agent_info_received)
- gajim.ged.remove_event_handler('agent-info-error-received', ged.GUI1,
- self._nec_agent_info_error_received)
-
- def cleanup(self):
- self._items.cleanup()
- self._info.cleanup()
-
- def _clean_closure(self, cb, type_, addr):
- # A closure died, clean up
- cbkey = (type_, addr)
- try:
- self._cbs[cbkey].remove(cb)
- except KeyError:
- return
- except ValueError:
- return
- # Clean an empty list
- if not self._cbs[cbkey]:
- del self._cbs[cbkey]
-
- def get_icon(self, identities=None, addr=''):
- """
- Return the icon for an agent
- """
- if identities is None:
- identities = []
- # Grab the first identity with an icon
- quiet = False
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- filename = info[1]
- if filename:
- break
- else:
- # Loop fell through, default to unknown
- filename = addr.split('.')[0]
- quiet = True
- # Use the cache if possible
- if filename in _icon_cache:
- return _icon_cache[filename]
- # Or load it
- pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-' + filename, size=32,
- quiet=quiet)
- if pix:
- # Store in cache
- _icon_cache[filename] = pix
- return pix
- if 'jabber' in _icon_cache:
- return _icon_cache['jabber']
- pix = gtkgui_helpers.get_icon_pixmap('gajim-agent-jabber', size=32)
- _icon_cache['jabber'] = pix
- return pix
-
- def get_browser(self, identities=None, features=None):
- """
- Return the browser class for an agent
- """
- if identities is None:
- identities = []
- if features is None:
- features = []
- # First pass, we try to find a ToplevelAgentBrowser
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- browser = info[0]
- if browser and browser == ToplevelAgentBrowser:
- return browser
-
- # second pass, we haven't found a ToplevelAgentBrowser
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- info = _agent_type_info[(cat, type_)]
- except KeyError:
- continue
- browser = info[0]
- if browser:
- return browser
- # NS_BROWSE is deprecated, but we check for it anyways.
- # Some services list it in features and respond to
- # NS_DISCO_ITEMS anyways.
- # Allow browsing for unknown types aswell.
- if (not features and not identities) or \
- nbxmpp.NS_DISCO_ITEMS in features or nbxmpp.NS_BROWSE in features:
- return ToplevelAgentBrowser
- return None
-
- def get_info(self, jid, node, cb, force=False, nofetch=False, args=()):
- """
- Get info for an agent
- """
- addr = get_agent_address(jid, node)
- # Check the cache
- if addr in self._info and not force:
- args = self._info[addr] + args
- cb(jid, node, *args)
- return
- if nofetch:
- return
-
- # Create a closure object
- cbkey = ('info', addr)
- cb = Closure(cb, userargs=args, remove=self._clean_closure,
- removeargs=cbkey)
- # Are we already fetching this?
- if cbkey in self._cbs:
- self._cbs[cbkey].append(cb)
- else:
- self._cbs[cbkey] = [cb]
- gajim.connections[self.account].discoverInfo(jid, node)
-
- def get_items(self, jid, node, cb, force=False, nofetch=False, args=()):
- """
- Get a list of items in an agent
- """
- addr = get_agent_address(jid, node)
- # Check the cache
- if addr in self._items and not force:
- args = (self._items[addr],) + args
- cb(jid, node, *args)
- return
- if nofetch:
- return
-
- # Create a closure object
- cbkey = ('items', addr)
- cb = Closure(cb, userargs=args, remove=self._clean_closure,
- removeargs=cbkey)
- # Are we already fetching this?
- if cbkey in self._cbs:
- self._cbs[cbkey].append(cb)
- else:
- self._cbs[cbkey] = [cb]
- gajim.connections[self.account].discoverItems(jid, node)
-
- def _nec_agent_info_received(self, obj):
- """
- Callback for when we receive an agent's info
- array is (agent, node, identities, features, data)
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- self._on_agent_info(obj.fjid, obj.node, obj.identities, obj.features,
- obj.data)
-
- def _on_agent_info(self, fjid, node, identities, features, data):
- addr = get_agent_address(fjid, node)
-
- # Store in cache
- self._info[addr] = (identities, features, data)
-
- # Call callbacks
- cbkey = ('info', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(fjid, node, identities, features, data)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def _nec_agent_items_received(self, obj):
- """
- Callback for when we receive an agent's items
- array is (agent, node, items)
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
-
- addr = get_agent_address(obj.fjid, obj.node)
-
- # Store in cache
- self._items[addr] = obj.items
-
- # Call callbacks
- cbkey = ('items', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(obj.fjid, obj.node, obj.items)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def _nec_agent_info_error_received(self, obj):
- """
- Callback for when a query fails. Even after the browse and agents
- namespaces
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- addr = get_agent_address(obj.fjid)
-
- # Call callbacks
- cbkey = ('info', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(obj.fjid, '', 0, 0, 0)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
- def _nec_agent_items_error_received(self, obj):
- """
- Callback for when a query fails. Even after the browse and agents
- namespaces
- """
- # We receive events from all accounts from GED
- if obj.conn.name != self.account:
- return
- addr = get_agent_address(obj.fjid)
-
- # Call callbacks
- cbkey = ('items', addr)
- if cbkey in self._cbs:
- for cb in self._cbs[cbkey]:
- cb(obj.fjid, '', 0)
- # clean_closure may have beaten us to it
- if cbkey in self._cbs:
- del self._cbs[cbkey]
-
-# object is needed so that @property works
-class ServiceDiscoveryWindow(object):
- """
- Class that represents the Services Discovery window
- """
-
- def __init__(self, account, jid='', node='', address_entry=False,
- parent=None, initial_identities=None):
- self._account = account
- self.parent = parent
- if not jid:
- jid = gajim.config.get_per('accounts', account, 'hostname')
- node = ''
-
- self.jid = None
- self.browser = None
- self.children = []
- self.dying = False
- self.node = None
- self.reloading = False
-
- # Check connection
- if gajim.connections[account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
-_('Without a connection, you can not browse available services'))
- raise RuntimeError('You must be connected to browse services')
-
- # Get a ServicesCache object.
- try:
- self.cache = gajim.connections[account].services_cache
- except AttributeError:
- self.cache = ServicesCache(account)
- gajim.connections[account].services_cache = self.cache
-
- if initial_identities:
- self.cache._on_agent_info(jid, node, initial_identities, [], None)
- self.xml = gtkgui_helpers.get_gtk_builder('service_discovery_window.ui')
- self.window = self.xml.get_object('service_discovery_window')
- self.services_treeview = self.xml.get_object('services_treeview')
- self.model = None
- # This is more reliable than the cursor-changed signal.
- selection = self.services_treeview.get_selection()
- selection.connect_after('changed',
- self.on_services_treeview_selection_changed)
- self.services_scrollwin = self.xml.get_object('services_scrollwin')
- self.progressbar = self.xml.get_object('services_progressbar')
- self.banner = self.xml.get_object('banner_agent_label')
- self.banner_icon = self.xml.get_object('banner_agent_icon')
- self.banner_eventbox = self.xml.get_object('banner_agent_eventbox')
- self.style_event_id = 0
- self.banner.realize()
- self.action_buttonbox = self.xml.get_object('action_buttonbox')
-
- # Address combobox
- self.address_comboboxtext = None
- address_table = self.xml.get_object('address_table')
- if address_entry:
- self.address_comboboxtext = self.xml.get_object(
- 'address_comboboxtext')
- self.address_comboboxtext_entry = self.xml.get_object(
- 'address_entry')
-
- self.latest_addresses = gajim.config.get(
- 'latest_disco_addresses').split()
- if jid in self.latest_addresses:
- self.latest_addresses.remove(jid)
- self.latest_addresses.insert(0, jid)
- if len(self.latest_addresses) > 10:
- self.latest_addresses = self.latest_addresses[0:10]
- for j in self.latest_addresses:
- self.address_comboboxtext.append_text(j)
- self.address_comboboxtext.get_child().set_text(jid)
- else:
- # Don't show it at all if we didn't ask for it
- address_table.set_no_show_all(True)
- address_table.hide()
-
- accel_group = Gtk.AccelGroup()
- keyval, mod = Gtk.accelerator_parse('<Control>r')
- accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE,
- self.accel_group_func)
- self.window.add_accel_group(accel_group)
-
- self._initial_state()
- self.xml.connect_signals(self)
- self.travel(jid, node)
- self.window.show_all()
-
- @property
- def account(self):
- return self._account
-
- @account.setter
- def account(self, value):
- self._account = value
- self.cache.account = value
- if self.browser:
- self.browser.account = value
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def accel_group_func(self, accel_group, acceleratable, keyval, modifier):
- if (modifier & Gdk.ModifierType.CONTROL_MASK) and (keyval == Gdk.KEY_r):
- self.reload()
-
- def _initial_state(self):
- """
- Set some initial state on the window. Separated in a method because it's
- handy to use within browser's cleanup method
- """
- self.progressbar.hide()
- title_text = _('Service Discovery using account %s') % self.account
- self.window.set_title(title_text)
- self._set_window_banner_text(_('Service Discovery'))
- self.banner_icon.clear()
- self.banner_icon.hide() # Just clearing it doesn't work
-
- def _set_window_banner_text(self, text, text_after = None):
- theme = gajim.config.get('roster_theme')
- bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
- bannerfontattrs = gajim.config.get_per('themes', theme,
- 'bannerfontattrs')
-
- if bannerfont:
- font = Pango.FontDescription(bannerfont)
- else:
- font = Pango.FontDescription('Normal')
- if bannerfontattrs:
- # B is attribute set by default
- if 'B' in bannerfontattrs:
- font.set_weight(Pango.Weight.HEAVY)
- if 'I' in bannerfontattrs:
- font.set_style(Pango.Style.ITALIC)
-
- font_attrs = 'font_desc="%s"' % font.to_string()
- font_size = font.get_size()
-
- # in case there is no font specified we use x-large font size
- if font_size == 0:
- font_attrs = '%s size="large"' % font_attrs
- markup = '<span %s>%s</span>' % (font_attrs, text)
- if text_after:
- font.set_weight(Pango.Weight.NORMAL)
- markup = '%s\n<span font_desc="%s" size="small">%s</span>' % \
- (markup, font.to_string(), text_after)
- self.banner.set_markup(markup)
-
- def destroy(self, chain = False):
- """
- Close the browser. This can optionally close its children and propagate
- to the parent. This should happen on actions like register, or join to
- kill off the entire browser chain
- """
- if self.dying:
- return
- self.dying = True
-
- # self.browser._get_agent_address() would break when no browser.
- addr = get_agent_address(self.jid, self.node)
- if addr in gajim.interface.instances[self.account]['disco']:
- del gajim.interface.instances[self.account]['disco'][addr]
-
- if self.browser:
- self.window.hide()
- self.browser.cleanup()
- self.browser = None
- self.window.destroy()
-
- for child in self.children[:]:
- child.parent = None
- if chain:
- child.destroy(chain = chain)
- self.children.remove(child)
- if self.parent:
- if self in self.parent.children:
- self.parent.children.remove(self)
- if chain and not self.parent.children:
- self.parent.destroy(chain = chain)
- self.parent = None
- else:
- self.cache.cleanup()
-
- def reload(self):
- if not self.jid:
- return
- self.reloading = True
- self.travel(self.jid, self.node)
-
- def travel(self, jid, node):
- """
- Travel to an agent within the current services window
- """
- if self.browser:
- self.browser.cleanup()
- self.browser = None
- # Update the window list
- if self.jid:
- old_addr = get_agent_address(self.jid, self.node)
- if old_addr in gajim.interface.instances[self.account]['disco']:
- del gajim.interface.instances[self.account]['disco'][old_addr]
- addr = get_agent_address(jid, node)
- gajim.interface.instances[self.account]['disco'][addr] = self
- # We need to store these, self.browser is not always available.
- self.jid = jid
- self.node = node
- self.cache.get_info(jid, node, self._travel, force=self.reloading)
-
- def _travel(self, jid, node, identities, features, data):
- """
- Continuation of travel
- """
- if self.dying or jid != self.jid or node != self.node:
- return
- if not identities:
- if not self.address_comboboxtext:
- # We can't travel anywhere else.
- self.destroy()
- dialogs.ErrorDialog(_('The service could not be found'),
- _('There is no service at the address you entered, or it is '
- 'not responding. Check the address and try again.'),
- transient_for=self.window)
- return
- klass = self.cache.get_browser(identities, features)
- if not klass:
- dialogs.ErrorDialog(_('The service is not browsable'),
- _('This type of service does not contain any items to browse.'),
- transient_for=self.window)
- return
- elif klass is None:
- klass = AgentBrowser
- self.browser = klass(self.account, jid, node)
- self.browser.prepare_window(self)
- self.browser.browse(force=self.reloading)
- self.reloading = False
-
- def open(self, jid, node):
- """
- Open an agent. By default, this happens in a new window
- """
- try:
- win = gajim.interface.instances[self.account]['disco']\
- [get_agent_address(jid, node)]
- win.window.present()
- return
- except KeyError:
- pass
- try:
- win = ServiceDiscoveryWindow(self.account, jid, node, parent=self)
- except RuntimeError:
- # Disconnected, perhaps
- return
- self.children.append(win)
-
- def on_service_discovery_window_destroy(self, widget):
- self.destroy()
-
- def on_close_button_clicked(self, widget):
- self.destroy()
-
- def on_address_comboboxtext_changed(self, widget):
- if self.address_comboboxtext.get_active() != -1:
- # user selected one of the entries so do auto-visit
- jid = self.address_comboboxtext_entry.get_text()
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as s:
- pritext = _('Invalid Server Name')
- dialogs.ErrorDialog(pritext, str(s))
- return
- self.travel(jid, '')
-
- def on_go_button_clicked(self, widget):
- jid = self.address_comboboxtext_entry.get_text()
- try:
- jid = helpers.parse_jid(jid)
- except helpers.InvalidFormat as s:
- pritext = _('Invalid Server Name')
- dialogs.ErrorDialog(pritext, str(s),
- transient_for=self.window)
- return
- if jid == self.jid: # jid has not changed
- return
- if jid in self.latest_addresses:
- self.latest_addresses.remove(jid)
- self.latest_addresses.insert(0, jid)
- if len(self.latest_addresses) > 10:
- self.latest_addresses = self.latest_addresses[0:10]
- self.address_comboboxtext.get_model().clear()
- for j in self.latest_addresses:
- self.address_comboboxtext.append_text(j)
- gajim.config.set('latest_disco_addresses',
- ' '.join(self.latest_addresses))
- self.travel(jid, '')
-
- def on_services_treeview_row_activated(self, widget, path, col = 0):
- if self.browser:
- self.browser.default_action()
-
- def on_services_treeview_selection_changed(self, widget):
- if self.browser:
- self.browser.update_actions()
-
- def _on_entry_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter:
- self.on_go_button_clicked(widget)
-
-
-class AgentBrowser:
- """
- Class that deals with browsing agents and appearance of the browser window.
- This class and subclasses should basically be treated as "part" of the
- ServiceDiscoveryWindow class, but had to be separated because this part is
- dynamic
- """
-
- def __init__(self, account, jid, node):
- self.account = account
- self.jid = jid
- self.node = node
- self._total_items = 0
- self.browse_button = None
- # This is for some timeout callbacks
- self.active = False
-
- def _get_agent_address(self):
- """
- Get the agent's address for displaying in the GUI
- """
- return get_agent_address(self.jid, self.node)
-
- def _set_initial_title(self):
- """
- Set the initial window title based on agent address
- """
- self.window.window.set_title(_('Browsing %(address)s using account '
- '%(account)s') % {'address': self._get_agent_address(),
- 'account': self.account})
- self.window._set_window_banner_text(self._get_agent_address())
-
- def _create_treemodel(self):
- """
- Create the treemodel for the services treeview. When subclassing, note
- that the first two columns should ALWAYS be of type string and contain
- the JID and node of the item respectively
- """
- # JID, node, name, address
- self.model = Gtk.ListStore(str, str, str, str)
- self.model.set_sort_column_id(3, Gtk.SortType.ASCENDING)
- self.window.services_treeview.set_model(self.model)
- # Name column
- col = Gtk.TreeViewColumn(_('Name'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 2)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Address column
- col = Gtk.TreeViewColumn(_('JID'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 3)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- self.window.services_treeview.set_headers_visible(True)
-
- def _clean_treemodel(self):
- self.model.clear()
- for col in self.window.services_treeview.get_columns():
- self.window.services_treeview.remove_column(col)
- self.window.services_treeview.set_headers_visible(False)
-
- def _add_actions(self):
- """
- Add the action buttons to the buttonbox for actions the browser can
- perform
- """
- self.browse_button = Gtk.Button()
- self.browse_button.connect('clicked', self.on_browse_button_clicked)
- self.window.action_buttonbox.add(self.browse_button)
- image = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.BUTTON)
- self.browse_button.set_image(image)
- label = _('Browse')
- self.browse_button.set_label(label)
- self.browse_button.show_all()
-
- def _clean_actions(self):
- """
- Remove the action buttons specific to this browser
- """
- if self.browse_button:
- self.browse_button.destroy()
- self.browse_button = None
-
- def _set_title(self, jid, node, identities, features, data):
- """
- Set the window title based on agent info
- """
- # Set the banner and window title
- name = ''
- if len(identities) > 1:
- # Check if an identity with server category is present
- for i, _identity in enumerate(identities):
- if _identity['category'] == 'server' and 'name' in _identity:
- name = _identity['name']
- break
- elif 'name' in identities[0]:
- name = identities[0]['name']
-
- if name:
- self.window._set_window_banner_text(self._get_agent_address(), name)
-
- # Add an icon to the banner.
- pix = self.cache.get_icon(identities, addr=self._get_agent_address())
- self.window.banner_icon.set_from_pixbuf(pix)
- self.window.banner_icon.show()
-
- def _clean_title(self):
- # Everything done here is done in window._initial_state
- # This is for subclasses.
- pass
-
- def prepare_window(self, window):
- """
- Prepare the service discovery window. Called when a browser is hooked up
- with a ServiceDiscoveryWindow instance
- """
- self.window = window
- self.cache = window.cache
-
- self._set_initial_title()
- self._create_treemodel()
- self._add_actions()
-
- # This is a hack. The buttonbox apparently doesn't care about pack_start
- # or pack_end, so we repack the close button here to make sure it's last
- close_button = self.window.xml.get_object('close_button')
- self.window.action_buttonbox.remove(close_button)
- self.window.action_buttonbox.pack_end(close_button, True, True, 0)
- close_button.show_all()
-
- self.update_actions()
-
- self.active = True
- self.cache.get_info(self.jid, self.node, self._set_title)
-
- def cleanup(self):
- """
- Cleanup when the window intends to switch browsers
- """
- self.active = False
-
- self._clean_actions()
- self._clean_treemodel()
- self._clean_title()
-
- self.window._initial_state()
-
- def update_theme(self):
- """
- Called when the default theme is changed
- """
- pass
-
- def on_browse_button_clicked(self, widget = None):
- """
- When we want to browse an agent: open a new services window with a
- browser for the agent type
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0]
- if jid:
- node = model[iter_][1]
- self.window.open(jid, node)
-
- def update_actions(self):
- """
- When we select a row: activate action buttons based on the agent's info
- """
- if self.browse_button:
- self.browse_button.set_sensitive(False)
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0]
- node = model[iter_][1]
- if jid:
- self.cache.get_info(jid, node, self._update_actions, nofetch = True)
-
- def _update_actions(self, jid, node, identities, features, data):
- """
- Continuation of update_actions
- """
- if not identities or not self.browse_button:
- return
- klass = self.cache.get_browser(identities, features)
- if klass:
- self.browse_button.set_sensitive(True)
-
- def default_action(self):
- """
- When we double-click a row: perform the default action on the selected
- item
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0]
- node = model[iter_][1]
- if jid:
- self.cache.get_info(jid, node, self._default_action, nofetch = True)
-
- def _default_action(self, jid, node, identities, features, data):
- """
- Continuation of default_action
- """
- if self.cache.get_browser(identities, features):
- # Browse if we can
- self.on_browse_button_clicked()
- return True
- return False
-
- def browse(self, force=False):
- """
- Fill the treeview with agents, fetching the info if necessary
- """
- self.model.clear()
- self._total_items = self._progress = 0
- self.window.progressbar.show()
- self._pulse_timeout = GLib.timeout_add(250, self._pulse_timeout_cb)
- self.cache.get_items(self.jid, self.node, self._agent_items,
- force=force, args=(force,))
-
- def _pulse_timeout_cb(self, *args):
- """
- Simple callback to keep the progressbar pulsing
- """
- if not self.active:
- return False
- self.window.progressbar.pulse()
- return True
-
- def _find_item(self, jid, node):
- """
- Check if an item is already in the treeview. Return an iter to it if so,
- None otherwise
- """
- iter_ = self.model.get_iter_first()
- while iter_:
- cjid = self.model.get_value(iter_, 0)
- cnode = self.model.get_value(iter_, 1)
- if jid == cjid and node == cnode:
- break
- iter_ = self.model.iter_next(iter_)
- if iter_:
- return iter_
- return None
-
- def add_self_line(self):
- pass
-
- def _agent_items(self, jid, node, items, force):
- """
- Callback for when we receive a list of agent items
- """
- self.model.clear()
- self.add_self_line()
- self._total_items = 0
- GLib.source_remove(self._pulse_timeout)
- self.window.progressbar.hide()
- # The server returned an error
- if items == 0:
- if not self.window.address_comboboxtext:
- # We can't travel anywhere else.
- self.window.destroy()
- dialogs.ErrorDialog(_('The service is not browsable'),
- _('This service does not contain any items to browse.'),
- transient_for=self.window.window)
- return
- # We got a list of items
- def fill_partial_rows(items):
- '''Generator to fill the listmodel of a treeview progressively.'''
- self.window.services_treeview.freeze_child_notify()
- for item in items:
- if self.window.dying:
- yield False
- jid_ = item['jid']
- node_ = item.get('node', '')
- # If such an item is already here: don't add it
- if self._find_item(jid_, node_):
- continue
- self._total_items += 1
- self._add_item(jid_, node_, node, item, force)
- if (self._total_items % 10) == 0:
- self.window.services_treeview.thaw_child_notify()
- yield True
- self.window.services_treeview.freeze_child_notify()
- self.window.services_treeview.thaw_child_notify()
- #stop idle_add()
- yield False
- loader = fill_partial_rows(items)
- GLib.idle_add(next, loader)
-
- def _agent_info(self, jid, node, identities, features, data):
- """
- Callback for when we receive info about an agent's item
- """
- iter_ = self._find_item(jid, node)
- if not iter_:
- # Not in the treeview, stop
- return
- if identities == 0:
- # The server returned an error
- self._update_error(iter_, jid, node)
- else:
- # We got our info
- self._update_info(iter_, jid, node, identities, features, data)
- self.update_actions()
-
- def _add_item(self, jid, node, parent_node, item, force):
- """
- Called when an item should be added to the model. The result of a
- disco#items query
- """
- self.model.append((jid, node, item.get('name', ''),
- get_agent_address(jid, node)))
- self.cache.get_info(jid, node, self._agent_info, force = force)
-
- def _update_item(self, iter_, jid, node, item):
- """
- Called when an item should be updated in the model. The result of a
- disco#items query
- """
- if 'name' in item:
- self.model[iter_][2] = item['name']
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- """
- Called when an item should be updated in the model with further info.
- The result of a disco#info query
- """
- name = identities[0].get('name', '')
- if name:
- self.model[iter_][2] = name
-
- def _update_error(self, iter_, jid, node):
- '''Called when a disco#info query failed for an item.'''
- pass
-
-
-class ToplevelAgentBrowser(AgentBrowser):
- """
- This browser is used at the top level of a jabber server to browse services
- such as transports, conference servers, etc
- """
-
- def __init__(self, *args):
- AgentBrowser.__init__(self, *args)
- self._progressbar_sourceid = None
- self._renderer = None
- self._progress = 0
- self.register_button = None
- self.join_button = None
- self.execute_button = None
- self.search_button = None
- # Keep track of our treeview signals
- self._view_signals = []
- self._scroll_signal = None
-
- def add_self_line(self):
- addr = get_agent_address(self.jid, self.node)
- descr = "<b>%s</b>" % addr
- # Guess which kind of service this is
- identities = []
- type_ = gajim.get_transport_name_from_jid(self.jid,
- use_config_setting=False)
- if type_:
- identity = {'category': '_jid', 'type': type_}
- identities.append(identity)
- # Set the pixmap for the row
- pix = self.cache.get_icon(identities, addr=addr)
- self.model.append(None, (self.jid, self.node, pix, descr, LABELS[1]))
- # Grab info on the service
- self.cache.get_info(self.jid, self.node, self._agent_info, force=False)
-
- def _pixbuf_renderer_data_func(self, col, cell, model, iter_, data=None):
- """
- Callback for setting the pixbuf renderer's properties
- """
- jid = model.get_value(iter_, 0)
- if jid:
- pix = model.get_value(iter_, 2)
- cell.set_property('visible', True)
- cell.set_property('pixbuf', pix)
- else:
- cell.set_property('visible', False)
-
- def _text_renderer_data_func(self, col, cell, model, iter_, data=None):
- """
- Callback for setting the text renderer's properties
- """
- jid = model.get_value(iter_, 0)
- markup = model.get_value(iter_, 3)
- state = model.get_value(iter_, 4)
- cell.set_property('markup', markup)
- if jid:
- cell.set_property('cell_background_set', False)
- if state is not None:
- # fetching or error
- cell.set_property('foreground_set', True)
- else:
- # Normal/succes
- cell.set_property('foreground_set', False)
- else:
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if bgcolor:
- cell.set_property('cell_background_set', True)
- cell.set_property('foreground_set', False)
-
- def _treemodel_sort_func(self, model, iter1, iter2, data=None):
- """
- Sort function for our treemode
- """
- # Compare state
- state1 = model.get_value(iter1, 4)
- state2 = model.get_value(iter2, 4)
- if state1 is not None:
- return 1
- if state2 is not None:
- return -1
- descr1 = model.get_value(iter1, 3)
- descr2 = model.get_value(iter2, 3)
- # Compare strings
- if descr1 > descr2:
- return 1
- if descr1 < descr2:
- return -1
- return 0
-
- def _create_treemodel(self):
- # JID, node, icon, description, state
- # state is None on sucess or has a string
- # from LABELS on error or while fetching
- view = self.window.services_treeview
- self.model = Gtk.TreeStore(str, str, GdkPixbuf.Pixbuf, str, str)
- self.model.set_sort_func(4, self._treemodel_sort_func)
- self.model.set_sort_column_id(4, Gtk.SortType.ASCENDING)
- view.set_model(self.model)
-
- col = Gtk.TreeViewColumn()
- # Icon Renderer
- renderer = Gtk.CellRendererPixbuf()
- renderer.set_property('xpad', 6)
- col.pack_start(renderer, False)
- col.set_cell_data_func(renderer, self._pixbuf_renderer_data_func)
- # Text Renderer
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.set_cell_data_func(renderer, self._text_renderer_data_func)
- renderer.set_property('foreground', 'dark gray')
- # Save this so we can go along with theme changes
- self._renderer = renderer
- self.update_theme()
-
- view.set_tooltip_column(4)
- view.insert_column(col, -1)
- col.set_resizable(True)
-
- def _clean_treemodel(self):
- # Disconnect signals
- view = self.window.services_treeview
- for sig in self._view_signals:
- view.disconnect(sig)
- self._view_signals = []
- if self._scroll_signal:
- scrollwin = self.window.services_scrollwin
- scrollwin.disconnect(self._scroll_signal)
- self._scroll_signal = None
- AgentBrowser._clean_treemodel(self)
-
- def _add_actions(self):
- AgentBrowser._add_actions(self)
- self.execute_button = Gtk.Button()
- self.execute_button.connect('clicked', self.on_execute_button_clicked)
- self.window.action_buttonbox.add(self.execute_button)
- image = Gtk.Image.new_from_stock(Gtk.STOCK_EXECUTE, Gtk.IconSize.BUTTON)
- self.execute_button.set_image(image)
- label = _('Execute Command')
- self.execute_button.set_label(label)
- self.execute_button.show_all()
-
- self.register_button = Gtk.Button(label=_("Re_gister"),
- use_underline=True)
- self.register_button.connect('clicked', self.on_register_button_clicked)
- self.window.action_buttonbox.add(self.register_button)
- self.register_button.show_all()
-
- self.join_button = Gtk.Button()
- self.join_button.connect('clicked', self.on_join_button_clicked)
- self.window.action_buttonbox.add(self.join_button)
- image = Gtk.Image.new_from_stock(Gtk.STOCK_CONNECT, Gtk.IconSize.BUTTON)
- self.join_button.set_image(image)
- label = _('Join')
- self.join_button.set_label(label)
- self.join_button.show_all()
-
- self.search_button = Gtk.Button()
- self.search_button.connect('clicked', self.on_search_button_clicked)
- self.window.action_buttonbox.add(self.search_button)
- image = Gtk.Image.new_from_stock(Gtk.STOCK_FIND, Gtk.IconSize.BUTTON)
- self.search_button.set_image(image)
- label = _('Search')
- self.search_button.set_label(label)
- self.search_button.show_all()
-
- def _clean_actions(self):
- if self.execute_button:
- self.execute_button.destroy()
- self.execute_button = None
- if self.register_button:
- self.register_button.destroy()
- self.register_button = None
- if self.join_button:
- self.join_button.destroy()
- self.join_button = None
- if self.search_button:
- self.search_button.destroy()
- self.search_button = None
- AgentBrowser._clean_actions(self)
-
- def on_search_button_clicked(self, widget = None):
- """
- When we want to search something: open search window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0]
- if service in gajim.interface.instances[self.account]['search']:
- gajim.interface.instances[self.account]['search'][service].window.\
- present()
- else:
- gajim.interface.instances[self.account]['search'][service] = \
- search_window.SearchWindow(self.account, service)
-
- def cleanup(self):
- AgentBrowser.cleanup(self)
-
- def update_theme(self):
- theme = gajim.config.get('roster_theme')
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- if bgcolor:
- self._renderer.set_property('cell-background', bgcolor)
- self.window.services_treeview.queue_draw()
-
- def on_execute_button_clicked(self, widget=None):
- """
- When we want to execute a command: open adhoc command window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0]
- node = model[iter_][1]
- adhoc_commands.CommandWindow(self.account, service, commandnode=node)
-
- def on_register_button_clicked(self, widget = None):
- """
- When we want to register an agent: request information about registering
- with the agent and close the window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][0]
- if jid:
- gajim.connections[self.account].request_register_agent_info(jid)
- self.window.destroy(chain = True)
-
- def on_join_button_clicked(self, widget):
- """
- When we want to join an IRC room or create a new MUC room: Opens the
- join_groupchat_window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0]
- if 'join_gc' not in gajim.interface.instances[self.account]:
- try:
- dialogs.JoinGroupchatWindow(self.account, service)
- except GajimGeneralException:
- pass
- else:
- gajim.interface.instances[self.account]['join_gc'].window.present()
-
- def update_actions(self):
- if self.execute_button:
- self.execute_button.set_sensitive(False)
- if self.register_button:
- self.register_button.set_sensitive(False)
- if self.browse_button:
- self.browse_button.set_sensitive(False)
- if self.join_button:
- self.join_button.set_sensitive(False)
- if self.search_button:
- self.search_button.set_sensitive(False)
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- if not model[iter_][0]:
- # We're on a category row
- return
- if model[iter_][4] is not None:
- # We don't have the info (yet)
- # It's either unknown or a transport, register button should be active
- if self.register_button:
- self.register_button.set_sensitive(True)
- # Guess what kind of service we're dealing with
- if self.browse_button:
- jid = model[iter_][0]
- type_ = gajim.get_transport_name_from_jid(jid,
- use_config_setting = False)
- if type_:
- identity = {'category': '_jid', 'type': type_}
- klass = self.cache.get_browser([identity])
- if klass:
- self.browse_button.set_sensitive(True)
- else:
- # We couldn't guess
- self.browse_button.set_sensitive(True)
- else:
- # Normal case, we have info
- AgentBrowser.update_actions(self)
-
- def _update_actions(self, jid, node, identities, features, data):
- AgentBrowser._update_actions(self, jid, node, identities, features, data)
- if self.execute_button and nbxmpp.NS_COMMANDS in features:
- self.execute_button.set_sensitive(True)
- if self.search_button and nbxmpp.NS_SEARCH in features:
- self.search_button.set_sensitive(True)
- # Don't autorize to register with a server via disco
- if self.register_button and nbxmpp.NS_REGISTER in features and \
- jid != self.jid:
- # We can register this agent
- registered_transports = []
- jid_list = gajim.contacts.get_jid_list(self.account)
- for jid_ in jid_list:
- contact = gajim.contacts.get_first_contact_from_jid(
- self.account, jid_)
- if _('Transports') in contact.groups:
- registered_transports.append(jid_)
- registered_transports.append(self.jid)
- if jid in registered_transports:
- self.register_button.set_label(_('_Edit'))
- else:
- self.register_button.set_label(_('Re_gister'))
- self.register_button.set_sensitive(True)
- if self.join_button and nbxmpp.NS_MUC in features:
- self.join_button.set_sensitive(True)
-
- def _default_action(self, jid, node, identities, features, data):
- if AgentBrowser._default_action(self, jid, node, identities, features, data):
- return True
- if nbxmpp.NS_REGISTER in features:
- # Register if we can't browse
- self.on_register_button_clicked()
- return True
- return False
-
- def browse(self, force=False):
- self._progress = 0
- AgentBrowser.browse(self, force = force)
-
- def _expand_all(self):
- """
- Expand all items in the treeview
- """
- # GTK apparently screws up here occasionally. :/
- #def expand_all(*args):
- # self.window.services_treeview.expand_all()
- # self.expanding = False
- # return False
- #self.expanding = True
- #GLib.idle_add(expand_all)
- self.window.services_treeview.expand_all()
-
- def _update_progressbar(self):
- """
- Update the progressbar
- """
- # Refresh this every update
- if self._progressbar_sourceid:
- GLib.source_remove(self._progressbar_sourceid)
-
- fraction = 0
- if self._total_items:
- self.window.progressbar.set_text(_("Scanning %(current)d / %(total)d.."
- ) % {'current': self._progress, 'total': self._total_items})
- fraction = float(self._progress) / float(self._total_items)
- if self._progress >= self._total_items:
- # We show the progressbar for just a bit before hiding it.
- id_ = GLib.timeout_add_seconds(2, self._hide_progressbar_cb)
- self._progressbar_sourceid = id_
- else:
- self.window.progressbar.show()
- # Hide the progressbar if we're timing out anyways. (20 secs)
- id_ = GLib.timeout_add_seconds(20, self._hide_progressbar_cb)
- self._progressbar_sourceid = id_
- self.window.progressbar.set_fraction(fraction)
-
- def _hide_progressbar_cb(self, *args):
- """
- Simple callback to hide the progressbar a second after we finish
- """
- if self.active:
- self.window.progressbar.hide()
- return False
-
- def _friendly_category(self, category, type_=None):
- """
- Get the friendly category name
- """
- cat = None
- if type_:
- # Try type-specific override
- try:
- cat = _cat_to_descr[(category, type_)]
- except KeyError:
- pass
- if not cat:
- try:
- cat = _cat_to_descr[category]
- except KeyError:
- cat = _cat_to_descr['other']
- return cat
-
- def _create_category(self, cat, type_=None):
- """
- Creates a category row
- """
- cat = self._friendly_category(cat, type_)
- return self.model.append(None, ('', '', None, cat, None))
-
- def _find_category(self, cat, type_=None):
- """
- Looks up a category row and returns the iterator to it, or None
- """
- cat = self._friendly_category(cat, type_)
- iter_ = self.model.get_iter_first()
- while iter_:
- if self.model.get_value(iter_, 3) == cat:
- break
- iter_ = self.model.iter_next(iter_)
- if iter_:
- return iter_
- return None
-
- def _find_item(self, jid, node):
- iter_ = None
- cat_iter = self.model.get_iter_first()
- while cat_iter and not iter_:
- cjid = self.model.get_value(cat_iter, 0)
- cnode = self.model.get_value(cat_iter, 1)
- if jid == cjid and node == cnode:
- iter_ = cat_iter
- break
- iter_ = self.model.iter_children(cat_iter)
- while iter_:
- cjid = self.model.get_value(iter_, 0)
- cnode = self.model.get_value(iter_, 1)
- if jid == cjid and node == cnode:
- break
- iter_ = self.model.iter_next(iter_)
- cat_iter = self.model.iter_next(cat_iter)
- if iter_:
- return iter_
- return None
-
- def _add_item(self, jid, node, parent_node, item, force):
- # Row text
- addr = get_agent_address(jid, node)
- if 'name' in item:
- descr = "<b>%s</b>\n%s" % (item['name'], addr)
- else:
- descr = "<b>%s</b>" % addr
- # Guess which kind of service this is
- identities = []
- type_ = gajim.get_transport_name_from_jid(jid,
- use_config_setting = False)
- if type_:
- identity = {'category': '_jid', 'type': type_}
- identities.append(identity)
- cat_args = ('_jid', type_)
- else:
- # Put it in the 'other' category for now
- cat_args = ('other',)
- # Set the pixmap for the row
- pix = self.cache.get_icon(identities, addr=addr)
- # Put it in the right category
- cat = self._find_category(*cat_args)
- if not cat:
- cat = self._create_category(*cat_args)
- self.model.append(cat, (jid, node, pix, descr, LABELS[1]))
- GLib.idle_add(self._expand_all)
- # Grab info on the service
- self.cache.get_info(jid, node, self._agent_info, force=force)
- self._update_progressbar()
-
- def _update_item(self, iter_, jid, node, item):
- addr = get_agent_address(jid, node)
- if 'name' in item:
- descr = "<b>%s</b>\n%s" % (item['name'], addr)
- else:
- descr = "<b>%s</b>" % addr
- self.model[iter_][3] = descr
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- addr = get_agent_address(jid, node)
- name = identities[0].get('name', '')
- if name:
- descr = "<b>%s</b>\n%s" % (name, addr)
- else:
- descr = "<b>%s</b>" % addr
-
- # Update progress
- self._progress += 1
- self._update_progressbar()
-
- # Search for an icon and category we can display
- pix = self.cache.get_icon(identities, addr=addr)
- cat, type_ = None, None
- for identity in identities:
- try:
- cat, type_ = identity['category'], identity['type']
- except KeyError:
- continue
- break
-
- # Check if we have to move categories
- old_cat_iter = self.model.iter_parent(iter_)
- if not old_cat_iter or self.model.get_value(old_cat_iter, 3) == cat:
- # Already in the right category, just update
- self.model[iter_][2] = pix
- self.model[iter_][3] = descr
- self.model[iter_][4] = None
- return
- # Not in the right category, move it.
- self.model.remove(iter_)
-
- old_cat = self.model.get_value(old_cat_iter, 3)
- # Check if the old category is empty
- if not self.model.iter_is_valid(old_cat_iter):
- old_cat_iter = self._find_category(old_cat)
- if not self.model.iter_children(old_cat_iter):
- self.model.remove(old_cat_iter)
-
- cat_iter = self._find_category(cat, type_)
- if not cat_iter:
- cat_iter = self._create_category(cat, type_)
- self.model.append(cat_iter, (jid, node, pix, descr, None))
- self._expand_all()
-
- def _update_error(self, iter_, jid, node):
- self.model[iter_][4] = LABELS[2]
- self._progress += 1
- self._update_progressbar()
-
-
-class MucBrowser(AgentBrowser):
- def __init__(self, *args, **kwargs):
- AgentBrowser.__init__(self, *args, **kwargs)
- self.join_button = None
- self.bookmark_button = None
-
- def _create_treemodel(self):
- # JID, node, name, users_int, users_str, description, fetched
- # This is rather long, I'd rather not use a data_func here though.
- # Users is a string, because want to be able to leave it empty.
- self.model = Gtk.ListStore(str, str, str, int, str, str, bool)
- self.model.set_sort_column_id(2, Gtk.SortType.ASCENDING)
- self.window.services_treeview.set_model(self.model)
- # Name column
- col = Gtk.TreeViewColumn(_('Name'))
- col.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
- col.set_fixed_width(100)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 2)
- col.set_sort_column_id(2)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Users column
- col = Gtk.TreeViewColumn(_('Users'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 4)
- col.set_sort_column_id(3)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Description column
- col = Gtk.TreeViewColumn(_('Description'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 5)
- col.set_sort_column_id(4)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- # Id column
- col = Gtk.TreeViewColumn(_('Id'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 0)
- col.set_sort_column_id(0)
- self.window.services_treeview.insert_column(col, -1)
- col.set_resizable(True)
- self.window.services_treeview.set_headers_visible(True)
- self.window.services_treeview.set_headers_clickable(True)
- # Source id for idle callback used to start disco#info queries.
- self._fetch_source = None
- # Query failure counter
- self._broken = 0
- # Connect to scrollwindow scrolling
- self.vadj = self.window.services_scrollwin.get_property('vadjustment')
- self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll)
- # And to size changes
- self.size_cbid = self.window.services_scrollwin.connect(
- 'size-allocate', self.on_scroll)
-
- def _clean_treemodel(self):
- if self.size_cbid:
- self.window.services_scrollwin.disconnect(self.size_cbid)
- self.size_cbid = None
- if self.vadj_cbid:
- self.vadj.disconnect(self.vadj_cbid)
- self.vadj_cbid = None
- AgentBrowser._clean_treemodel(self)
-
- def _add_actions(self):
- self.bookmark_button = Gtk.Button(label=_('_Bookmark'), use_underline=True)
- self.bookmark_button.connect('clicked', self.on_bookmark_button_clicked)
- self.window.action_buttonbox.add(self.bookmark_button)
- self.bookmark_button.show_all()
- self.join_button = Gtk.Button(label=_('_Join'), use_underline=True)
- self.join_button.connect('clicked', self.on_join_button_clicked)
- self.window.action_buttonbox.add(self.join_button)
- self.join_button.show_all()
-
- def _clean_actions(self):
- if self.bookmark_button:
- self.bookmark_button.destroy()
- self.bookmark_button = None
- if self.join_button:
- self.join_button.destroy()
- self.join_button = None
-
- def on_bookmark_button_clicked(self, *args):
- model, iter = self.window.services_treeview.get_selection().get_selected()
- if not iter:
- return
- name = gajim.config.get_per('accounts', self.account, 'name')
- room_jid = model[iter][0]
- bm = {
- 'name': room_jid.split('@')[0],
- 'jid': room_jid,
- 'autojoin': '0',
- 'minimize': '0',
- 'password': '',
- 'nick': name
- }
-
- for bookmark in gajim.connections[self.account].bookmarks:
- if bookmark['jid'] == bm['jid']:
- dialogs.ErrorDialog( _('Bookmark already set'),
- _('Group Chat "%s" is already in your bookmarks.') % bm['jid'],
- transient_for=self.window.window)
- return
-
- gajim.connections[self.account].bookmarks.append(bm)
- gajim.connections[self.account].store_bookmarks()
-
- gui_menu_builder.build_bookmark_menu(self.account)
-
- dialogs.InformationDialog(
- _('Bookmark has been added successfully'),
- _('You can manage your bookmarks via Actions menu in your roster.'),
- transient_for=self.window.window)
-
- def on_join_button_clicked(self, *args):
- """
- When we want to join a conference: ask specific informations about the
- selected agent and close the window
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_:
- return
- service = model[iter_][0]
- if 'join_gc' not in gajim.interface.instances[self.account]:
- try:
- dialogs.JoinGroupchatWindow(self.account, service)
- except GajimGeneralException:
- pass
- else:
- gajim.interface.instances[self.account]['join_gc']._set_room_jid(
- service)
- gajim.interface.instances[self.account]['join_gc'].window.present()
-
- def update_actions(self):
- sens = self.window.services_treeview.get_selection().count_selected_rows()
- if self.bookmark_button:
- self.bookmark_button.set_sensitive(sens > 0)
- if self.join_button:
- self.join_button.set_sensitive(sens > 0)
-
- def default_action(self):
- self.on_join_button_clicked()
-
- def _start_info_query(self):
- """
- Idle callback to start checking for visible rows
- """
- self._fetch_source = None
- self._query_visible()
- return False
-
- def on_scroll(self, *args):
- """
- Scrollwindow callback to trigger new queries on scolling
- """
- # This apparently happens when inactive sometimes
- self._query_visible()
-
- def _query_visible(self):
- """
- Query the next visible row for info
- """
- if self._fetch_source:
- # We're already fetching
- return
- view = self.window.services_treeview
- if not view.get_realized():
- # Prevent a silly warning, try again in a bit.
- self._fetch_source = GLib.timeout_add(100, self._start_info_query)
- return
- range_ = view.get_visible_range()
- if not range_:
- return
- start, end = range_
- iter_ = self.model.get_iter(start)
- while iter_:
- if not self.model.get_value(iter_, 6):
- jid = self.model.get_value(iter_, 0)
- node = self.model.get_value(iter_, 1)
- self.cache.get_info(jid, node, self._agent_info)
- self._fetch_source = True
- return
- if self.model.get_path(iter_) == end:
- break
- iter_ = self.model.iter_next(iter_)
- self._fetch_source = None
-
- def _channel_altinfo(self, jid, node, items, name = None):
- """
- Callback for the alternate disco#items query. We try to atleast get the
- amount of users in the room if the service does not support MUC dataforms
- """
- if items == 0:
- # The server returned an error
- self._broken += 1
- if self._broken >= 3:
- # Disable queries completely after 3 failures
- if self.size_cbid:
- self.window.services_scrollwin.disconnect(self.size_cbid)
- self.size_cbid = None
- if self.vadj_cbid:
- self.vadj.disconnect(self.vadj_cbid)
- self.vadj_cbid = None
- self._fetch_source = None
- return
- else:
- iter_ = self._find_item(jid, node)
- if iter_:
- if name:
- self.model[iter_][2] = name
- self.model[iter_][3] = len(items) # The number of users
- self.model[iter_][4] = str(len(items)) # The number of users
- self.model[iter_][6] = True
- self._fetch_source = None
- self._query_visible()
-
- def _add_item(self, jid, node, parent_node, item, force):
- self.model.append((jid, node, item.get('name', ''), -1, '', '', False))
- if not self._fetch_source:
- self._fetch_source = GLib.idle_add(self._start_info_query)
-
- def _update_info(self, iter_, jid, node, identities, features, data):
- name = identities[0].get('name', '')
- for form in data:
- typefield = form.getField('FORM_TYPE')
- if typefield and typefield.getValue() == \
- 'http://jabber.org/protocol/muc#roominfo':
- # Fill model row from the form's fields
- users = form.getField('muc#roominfo_occupants')
- descr = form.getField('muc#roominfo_description')
- if users:
- self.model[iter_][3] = int(users.getValue())
- self.model[iter_][4] = users.getValue()
- if descr and descr.getValue():
- self.model[iter_][5] = descr.getValue()
- # Only set these when we find a form with additional info
- # Some servers don't support forms and put extra info in
- # the name attribute, so we preserve it in that case.
- self.model[iter_][2] = name
- self.model[iter_][6] = True
- break
- else:
- # We didn't find a form, switch to alternate query mode
- self.cache.get_items(jid, node, self._channel_altinfo, args = (name,))
- return
- # Continue with the next
- self._fetch_source = None
- self._query_visible()
-
- def _update_error(self, iter_, jid, node):
- # switch to alternate query mode
- self.cache.get_items(jid, node, self._channel_altinfo)
-
-def PubSubBrowser(account, jid, node):
- """
- Return an AgentBrowser subclass that will display service discovery for
- particular pubsub service. Different pubsub services may need to present
- different data during browsing
- """
- # for now, only discussion groups are supported...
- # TODO: check if it has appropriate features to be such kind of service
- return DiscussionGroupsBrowser(account, jid, node)
-
-class DiscussionGroupsBrowser(AgentBrowser):
- """
- For browsing pubsub-based discussion groups service
- """
-
- def __init__(self, account, jid, node):
- AgentBrowser.__init__(self, account, jid, node)
-
- # this will become set object when we get subscriptions; None means
- # we don't know yet which groups are subscribed
- self.subscriptions = None
-
- # this will become our action widgets when we create them; None means
- # we don't have them yet (needed for check in callback)
- self.subscribe_button = None
- self.unsubscribe_button = None
-
- gajim.connections[account].send_pb_subscription_query(jid,
- self._on_pep_subscriptions)
-
- def _create_treemodel(self):
- """
- Create treemodel for the window
- """
- # JID, node, name (with description) - pango markup, dont have info?, subscribed?
- self.model = Gtk.TreeStore(str, str, str, bool, bool)
- # sort by name
- self.model.set_sort_column_id(2, Gtk.SortType.ASCENDING)
- self.window.services_treeview.set_model(self.model)
-
- # Name column
- # Pango markup for name and description, description printed with
- # <small/> font
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Name'))
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'markup', 2)
- col.set_resizable(True)
- self.window.services_treeview.insert_column(col, -1)
- self.window.services_treeview.set_headers_visible(True)
-
- # Subscription state
- renderer = Gtk.CellRendererToggle()
- col = Gtk.TreeViewColumn(_('Subscribed'))
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'inconsistent', 3)
- col.add_attribute(renderer, 'active', 4)
- col.set_resizable(False)
- self.window.services_treeview.insert_column(col, -1)
-
- # Node Column
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Node'))
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'markup', 1)
- col.set_resizable(True)
- self.window.services_treeview.insert_column(col, -1)
-
- def _add_items(self, jid, node, items, force):
- for item in items:
- jid_ = item['jid']
- node_ = item.get('node', '')
- self._total_items += 1
- self._add_item(jid_, node_, node, item, force)
-
- def _in_list_foreach(self, model, path, iter_, node):
- if model[path][1] == node:
- self.in_list = True
-
- def _in_list(self, node):
- self.in_list = False
- self.model.foreach(self._in_list_foreach, node)
- return self.in_list
-
- def _add_item(self, jid, node, parent_node, item, force):
- """
- Called when we got basic information about new node from query. Show the
- item
- """
- name = item.get('name', '')
-
- if self.subscriptions is not None:
- dunno = False
- subscribed = node in self.subscriptions
- else:
- dunno = True
- subscribed = False
-
- name = GLib.markup_escape_text(name)
- name = '<b>%s</b>' % name
-
- if parent_node:
- parent_iter = self._get_iter(parent_node)
- else:
- parent_iter = None
- if not node or not self._in_list(node):
- self.model.append(parent_iter, (jid, node, name, dunno, subscribed))
- self.cache.get_items(jid, node, self._add_items, force = force,
- args = (force,))
-
- def _get_child_iter(self, parent_iter, node):
- child_iter = self.model.iter_children(parent_iter)
- while child_iter:
- if self.model[child_iter][1] == node:
- return child_iter
- child_iter = self.model.iter_next(child_iter)
- return None
-
- def _get_iter(self, node):
- ''' Look for an iter with the given node '''
- self.found_iter = None
- def is_node(model, path, iter, node):
- if model[iter][1] == node:
- self.found_iter = iter
- return True
- self.model.foreach(is_node, node)
- return self.found_iter
-
- def _add_actions(self):
- self.post_button = Gtk.Button(label=_('New post'), use_underline=True)
- self.post_button.set_sensitive(False)
- self.post_button.connect('clicked', self.on_post_button_clicked)
- self.window.action_buttonbox.add(self.post_button)
- self.post_button.show_all()
-
- self.subscribe_button = Gtk.Button(label=_('_Subscribe'), use_underline=True)
- self.subscribe_button.set_sensitive(False)
- self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked)
- self.window.action_buttonbox.add(self.subscribe_button)
- self.subscribe_button.show_all()
-
- self.unsubscribe_button = Gtk.Button(label=_('_Unsubscribe'), use_underline=True)
- self.unsubscribe_button.set_sensitive(False)
- self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked)
- self.window.action_buttonbox.add(self.unsubscribe_button)
- self.unsubscribe_button.show_all()
-
- def _clean_actions(self):
- if self.post_button is not None:
- self.post_button.destroy()
- self.post_button = None
-
- if self.subscribe_button is not None:
- self.subscribe_button.destroy()
- self.subscribe_button = None
-
- if self.unsubscribe_button is not None:
- self.unsubscribe_button.destroy()
- self.unsubscribe_button = None
-
- def update_actions(self):
- """
- Called when user selected a row. Make subscribe/unsubscribe buttons
- sensitive appropriatelly
- """
- # we have nothing to do if we don't have buttons...
- if self.subscribe_button is None: return
-
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if not iter_ or self.subscriptions is None:
- # no item selected or no subscriptions info, all buttons are insensitive
- self.post_button.set_sensitive(False)
- self.subscribe_button.set_sensitive(False)
- self.unsubscribe_button.set_sensitive(False)
- else:
- subscribed = model.get_value(iter_, 4) # 4 = subscribed?
- self.post_button.set_sensitive(subscribed)
- self.subscribe_button.set_sensitive(not subscribed)
- self.unsubscribe_button.set_sensitive(subscribed)
-
- def on_post_button_clicked(self, widget):
- """
- Called when 'post' button is pressed. Open window to create post
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- groups.GroupsPostWindow(self.account, self.jid, groupnode)
-
- def on_subscribe_button_clicked(self, widget):
- """
- Called when 'subscribe' button is pressed. Send subscribtion request
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode,
- self._on_pep_subscribe, groupnode)
-
- def on_unsubscribe_button_clicked(self, widget):
- """
- Called when 'unsubscribe' button is pressed. Send unsubscription request
- """
- model, iter_ = self.window.services_treeview.get_selection().get_selected()
- if iter_ is None: return
-
- groupnode = model.get_value(iter_, 1) # 1 = groupnode
-
- gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode,
- self._on_pep_unsubscribe, groupnode)
-
- def _on_pep_subscriptions(self, conn, request):
- """
- We got the subscribed groups list stanza. Now, if we already have items
- on the list, we should actualize them
- """
- try:
- subscriptions = request.getTag('pubsub').getTag('subscriptions')
- except Exception:
- return
-
- groups = set()
- for child in subscriptions.getTags('subscription'):
- groups.add(child['node'])
-
- self.subscriptions = groups
-
- # try to setup existing items in model
- model = self.window.services_treeview.get_model()
- for row in model:
- # 1 = group node
- # 3 = insensitive checkbox for subscribed
- # 4 = subscribed?
- groupnode = row[1]
- row[3] = False
- row[4] = groupnode in groups
-
- # we now know subscriptions, update button states
- self.update_actions()
-
- raise nbxmpp.NodeProcessed
-
- def _on_pep_subscribe(self, conn, request, groupnode):
- """
- We have just subscribed to a node. Update UI
- """
- self.subscriptions.add(groupnode)
-
- model = self.window.services_treeview.get_model()
- for row in model:
- if row[1] == groupnode: # 1 = groupnode
- row[4] = True
- break
-
- self.update_actions()
-
- raise nbxmpp.NodeProcessed
-
- def _on_pep_unsubscribe(self, conn, request, groupnode):
- """
- We have just unsubscribed from a node. Update UI
- """
- self.subscriptions.remove(groupnode)
-
- model = self.window.services_treeview.get_model()
- for row in model:
- if row[1] == groupnode: # 1 = groupnode
- row[4]=False
- break
-
- self.update_actions()
-
- raise nbxmpp.NodeProcessed
-
-# Fill the global agent type info dictionary
-_agent_type_info = _gen_agent_type_info()
diff --git a/src/features_window.py b/src/features_window.py
deleted file mode 100644
index 552e6699b..000000000
--- a/src/features_window.py
+++ /dev/null
@@ -1,238 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/features_window.py
-##
-## Copyright (C) 2007 Jean-Marie Traissard <jim AT lapin.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import gi
-from gi.repository import Gtk, Gdk
-import gtkgui_helpers
-
-from common import gajim
-from common.i18n import Q_
-
-class FeaturesWindow:
- """
- Class for features window
- """
-
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('features_window.ui')
- self.window = self.xml.get_object('features_window')
- self.window.set_transient_for(gajim.interface.roster.window)
- treeview = self.xml.get_object('features_treeview')
- self.desc_label = self.xml.get_object('feature_desc_label')
-
- # {name: (available_function, unix_text, windows_text)}
- self.features = {
- _('SSL certificate validation'): (self.pyopenssl_available,
- _('A library used to validate server certificates to ensure a secure connection.'),
- _('Requires python-pyopenssl > 0.12 and pyasn1.'),
- _('Requires python-pyopenssl > 0.12 and pyasn1.')),
- _('Bonjour / Zeroconf'): (self.zeroconf_available,
- _('Serverless chatting with autodetected clients in a local network.'),
- _('Requires python-avahi.'),
- _('Requires pybonjour and bonjour SDK running (http://developer.apple.com/opensource/).')),
- _('Command line'): (self.dbus_available,
- _('A script to control Gajim via commandline.'),
- _('Requires python-dbus.'),
- _('Feature not available under Windows.')),
- _('OpenPGP message encryption'): (self.gpg_available,
- _('Ability to encrypting chat messages with OpenPGP.'),
- _('Requires gpg and python-gnupg (http://code.google.com/p/python-gnupg/).'),
- _('Requires gpg.exe in PATH.')),
- _('Network-Watcher'): (self.network_watcher_available,
- _('Autodetection of network status.'),
- _('Requires gnome-network-manager'),
- _('Feature not available under Windows.')),
- _('Password encryption'): (self.some_keyring_available,
- _('Passwords can be stored securely and not just in plaintext.'),
- _('Requires libsecret and a provider (such as GNOME Keyring and KSecretService).'),
- _('On Windows the Windows Credential Vault is used.')),
- _('Spell Checker'): (self.speller_available,
- _('Spellchecking of composed messages.'),
- _('Requires libgtkspell.'),
- _('Requires libgtkspell and libenchant.')),
- _('Notification'): (self.notification_available,
- _('Passive popups notifying for new events.'),
- _('Requires python-notify or instead python-dbus in conjunction with notification-daemon.'),
- _('Feature not available under Windows.')),
- _('Automatic status'): (self.idle_available,
- _('Ability to measure idle time, in order to set auto status.'),
- _('Requires libxss library.'),
- _('Requires python2.5.')),
- _('End to End message encryption'): (self.pycrypto_available,
- _('Encrypting chat messages.'),
- _('Requires python-crypto.'),
- _('Requires python-crypto.')),
- _('RST Generator'): (self.docutils_available,
- _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'),
- _('Requires python-docutils.'),
- _('Requires python-docutils.')),
- _('Audio / Video'): (self.farstream_available,
- _('Ability to start audio and video chat.'),
- _('Requires gir1.2-farstream-0.2, gir1.2-gstreamer-1.0, gstreamer1.0-libav and gstreamer1.0-plugins-ugly.'),
- _('Feature not available under Windows.')),
- _('UPnP-IGD'): (self.gupnp_igd_available,
- _('Ability to request your router to forward port for file transfer.'),
- _('Requires gir1.2-gupnpigd-1.0.'),
- _('Feature not available under Windows.')),
- _('UPower'): (self.upower_available,
- _('Ability to disconnect properly just before suspending the machine.'),
- _('Requires upower and python-dbus.'),
- _('Feature not available under Windows.')),
- }
-
- # name, supported
- self.model = Gtk.ListStore(str, bool)
- treeview.set_model(self.model)
-
- col = Gtk.TreeViewColumn(Q_('?features:Available'))
- treeview.append_column(col)
- cell = Gtk.CellRendererToggle()
- cell.set_property('radio', True)
- col.pack_start(cell, True)
- col.add_attribute(cell, 'active', 1)
-
- col = Gtk.TreeViewColumn(_('Feature'))
- treeview.append_column(col)
- cell = Gtk.CellRendererText()
- col.pack_start(cell, True)
- col.add_attribute(cell, 'text', 0)
-
- # Fill model
- for feature in self.features:
- func = self.features[feature][0]
- rep = func()
- self.model.append([feature, rep])
-
- self.model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
-
- self.xml.connect_signals(self)
- self.window.show_all()
- self.xml.get_object('close_button').grab_focus()
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
- def on_features_treeview_cursor_changed(self, widget):
- selection = widget.get_selection()
- if not selection:
- return
- rows = selection.get_selected_rows()[1]
- if not rows:
- return
- path = rows[0]
- feature = self.model[path][0]
- text = self.features[feature][1] + '\n'
- if os.name == 'nt':
- text = text + self.features[feature][3]
- else:
- text = text + self.features[feature][2]
- self.desc_label.set_text(text)
-
- def pyopenssl_available(self):
- try:
- import OpenSSL.SSL
- import OpenSSL.crypto
- ver = OpenSSL.__version__
- ver_l = [int(i) for i in ver.split('.')]
- if ver_l < [0, 12]:
- raise ImportError
- import pyasn1
- except Exception:
- return False
- return True
-
- def zeroconf_available(self):
- return gajim.HAVE_ZEROCONF
-
- def dbus_available(self):
- from common import dbus_support
- return dbus_support.supported
-
- def gpg_available(self):
- return gajim.HAVE_GPG
-
- def network_watcher_available(self):
- import network_watcher
- return network_watcher.supported
-
- def some_keyring_available(self):
- if os.name == 'nt':
- return True
- try:
- gi.require_version('Secret', '1')
- from gi.repository import Secret
- except (ValueError, ImportError):
- return False
- return True
-
- def speller_available(self):
- try:
- __import__('gtkspell')
- except ValueError:
- return False
- return True
-
- def notification_available(self):
- if os.name == 'nt':
- return False
- from common import dbus_support
- if self.dbus_available() and dbus_support.get_notifications_interface():
- return True
- try:
- __import__('pynotify')
- except Exception:
- return False
- return True
-
- def idle_available(self):
- from common import sleepy
- return sleepy.SUPPORTED
-
- def pycrypto_available(self):
- return gajim.HAVE_PYCRYPTO
-
- def docutils_available(self):
- try:
- __import__('docutils')
- except Exception:
- return False
- return True
-
- def farstream_available(self):
- return gajim.HAVE_FARSTREAM
-
- def gupnp_igd_available(self):
- return gajim.HAVE_UPNP_IGD
-
- def upower_available(self):
- if os.name == 'nt':
- return False
- import upower_listener
- return upower_listener.supported
diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py
deleted file mode 100644
index 7d1ab5483..000000000
--- a/src/filetransfers_window.py
+++ /dev/null
@@ -1,1089 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/filetransfers_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Travis Shirk <travis AT pobox.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/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GLib
-from gi.repository import Pango
-import os
-import time
-
-from enum import IntEnum, unique
-
-import gtkgui_helpers
-import tooltips
-import dialogs
-
-from common import gajim
-from common import helpers
-from common.file_props import FilesProp
-from common.protocol.bytestream import (is_transfer_active, is_transfer_paused,
- is_transfer_stopped)
-from nbxmpp.protocol import NS_JINGLE_FILE_TRANSFER_5
-import logging
-log = logging.getLogger('gajim.filetransfer_window')
-
-@unique
-class Column(IntEnum):
- IMAGE = 0
- LABELS = 1
- FILE = 2
- TIME = 3
- PROGRESS = 4
- PERCENT = 5
- PULSE = 6
- SID = 7
-
-
-class FileTransfersWindow:
- def __init__(self):
- self.files_props = {'r' : {}, 's': {}}
- self.height_diff = 0
- self.xml = gtkgui_helpers.get_gtk_builder('filetransfers.ui')
- self.window = self.xml.get_object('file_transfers_window')
- self.tree = self.xml.get_object('transfers_list')
- self.cancel_button = self.xml.get_object('cancel_button')
- self.pause_button = self.xml.get_object('pause_restore_button')
- self.cleanup_button = self.xml.get_object('cleanup_button')
- self.notify_ft_checkbox = self.xml.get_object(
- 'notify_ft_complete_checkbox')
-
- shall_notify = gajim.config.get('notify_on_file_complete')
- self.notify_ft_checkbox.set_active(shall_notify)
- self.model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str, str, int,
- int, str)
- self.tree.set_model(self.model)
- col = Gtk.TreeViewColumn()
-
- render_pixbuf = Gtk.CellRendererPixbuf()
-
- col.pack_start(render_pixbuf, True)
- render_pixbuf.set_property('xpad', 3)
- render_pixbuf.set_property('ypad', 3)
- render_pixbuf.set_property('yalign', .0)
- col.add_attribute(render_pixbuf, 'pixbuf', 0)
- self.tree.append_column(col)
-
- col = Gtk.TreeViewColumn(_('File'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, False)
- col.add_attribute(renderer, 'markup', Column.LABELS)
- renderer.set_property('yalign', 0.)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'markup', Column.FILE)
- renderer.set_property('xalign', 0.)
- renderer.set_property('yalign', 0.)
- renderer.set_property('ellipsize', Pango.EllipsizeMode.END)
- col.set_resizable(True)
- col.set_expand(True)
- self.tree.append_column(col)
-
- col = Gtk.TreeViewColumn(_('Time'))
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, False)
- col.add_attribute(renderer, 'markup', Column.TIME)
- renderer.set_property('yalign', 0.5)
- renderer.set_property('xalign', 0.5)
- renderer = Gtk.CellRendererText()
- renderer.set_property('ellipsize', Pango.EllipsizeMode.END)
- col.set_resizable(True)
- col.set_expand(False)
- self.tree.append_column(col)
-
- col = Gtk.TreeViewColumn(_('Progress'))
- renderer = Gtk.CellRendererProgress()
- renderer.set_property('yalign', 0.5)
- renderer.set_property('xalign', 0.5)
- col.pack_start(renderer, False)
- col.add_attribute(renderer, 'text', Column.PROGRESS)
- col.add_attribute(renderer, 'value', Column.PERCENT)
- col.add_attribute(renderer, 'pulse', Column.PULSE)
- col.set_resizable(True)
- col.set_expand(False)
- self.tree.append_column(col)
-
- self.images = {}
- self.icons = {
- 'upload': 'go-up',
- 'download': 'go-down',
- 'stop': 'window-close',
- 'waiting': 'view-refresh',
- 'pause': 'media-playback-pause',
- 'continue': 'media-playback-start',
- 'ok': 'emblem-ok-symbolic',
- 'computing': 'system-run',
- 'hash_error': 'network-error-symbolic',
- }
-
- self.tree.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
- self.tree.get_selection().connect('changed', self.selection_changed)
- self.tooltip = tooltips.FileTransfersTooltip()
- self.file_transfers_menu = self.xml.get_object('file_transfers_menu')
- self.open_folder_menuitem = self.xml.get_object('open_folder_menuitem')
- self.cancel_menuitem = self.xml.get_object('cancel_menuitem')
- self.pause_menuitem = self.xml.get_object('pause_menuitem')
- self.continue_menuitem = self.xml.get_object('continue_menuitem')
- self.remove_menuitem = self.xml.get_object('remove_menuitem')
- self.xml.connect_signals(self)
-
- def find_transfer_by_jid(self, account, jid):
- """
- Find all transfers with peer 'jid' that belong to 'account'
- """
- active_transfers = [[], []] # ['senders', 'receivers']
- allfp = FilesProp.getAllFileProp()
- for file_props in allfp:
- if file_props.type_ == 's' and file_props.tt_account == account:
- # 'account' is the sender
- receiver_jid = file_props.receiver.split('/')[0]
- if jid == receiver_jid and not is_transfer_stopped(file_props):
- active_transfers[0].append(file_props)
- elif file_props.type_ == 'r' and file_props.tt_account == account:
- # 'account' is the recipient
- sender_jid = file_props.sender.split('/')[0]
- if jid == sender_jid and not is_transfer_stopped(file_props):
- active_transfers[1].append(file_props)
- else:
- raise Exception('file_props has no type')
- return active_transfers
-
- def show_completed(self, jid, file_props):
- """
- Show a dialog saying that file (file_props) has been transferred
- """
- def on_open(widget, file_props):
- dialog.destroy()
- if not file_props.file_name:
- return
- path = os.path.split(file_props.file_name)[0]
- if os.path.exists(path) and os.path.isdir(path):
- helpers.launch_file_manager(path)
- self.tree.get_selection().unselect_all()
-
- if file_props.type_ == 'r':
- # file path is used below in 'Save in'
- (file_path, file_name) = os.path.split(file_props.file_name)
- else:
- file_name = file_props.name
- sectext = '\t' + _('Filename: %s') % GLib.markup_escape_text(file_name)
- sectext += '\n\t' + _('Size: %s') % \
- helpers.convert_bytes(file_props.size)
- if file_props.type_ == 'r':
- jid = file_props.sender.split('/')[0]
- sender_name = gajim.contacts.get_first_contact_from_jid(
- file_props.tt_account, jid).get_shown_name()
- sender = sender_name
- else:
- #You is a reply of who sent a file
- sender = _('You')
- sectext += '\n\t' + _('Sender: %s') % sender
- sectext += '\n\t' + _('Recipient: ')
- if file_props.type_ == 's':
- jid = file_props.receiver.split('/')[0]
- receiver_name = gajim.contacts.get_first_contact_from_jid(
- file_props.tt_account, jid).get_shown_name()
- recipient = receiver_name
- else:
- #You is a reply of who received a file
- recipient = _('You')
- sectext += recipient
- if file_props.type_ == 'r':
- sectext += '\n\t' + _('Saved in: %s') % file_path
- dialog = dialogs.HigDialog(None, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE,
- _('File transfer completed'), sectext)
- if file_props.type_ == 'r':
- button = Gtk.Button.new_with_mnemonic(_('Open _Containing Folder'))
- button.connect('clicked', on_open, file_props)
- dialog.action_area.pack_start(button, True, True, 0)
- ok_button = dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
- def on_ok(widget):
- dialog.destroy()
- ok_button.connect('clicked', on_ok)
- dialog.show_all()
-
- def show_request_error(self, file_props):
- """
- Show error dialog to the recipient saying that transfer has been canceled
- """
- dialogs.InformationDialog(_('File transfer cancelled'), _('Connection with peer cannot be established.'))
- self.tree.get_selection().unselect_all()
-
- def show_send_error(self, file_props):
- """
- Show error dialog to the sender saying that transfer has been canceled
- """
- dialogs.InformationDialog(_('File transfer cancelled'),
- _('Connection with peer cannot be established.'))
- self.tree.get_selection().unselect_all()
-
- def show_stopped(self, jid, file_props, error_msg=''):
- if file_props.type_ == 'r':
- file_name = os.path.basename(file_props.file_name)
- else:
- file_name = file_props.name
- sectext = '\t' + _('Filename: %s') % GLib.markup_escape_text(file_name)
- sectext += '\n\t' + _('Recipient: %s') % jid
- if error_msg:
- sectext += '\n\t' + _('Error message: %s') % error_msg
- dialogs.ErrorDialog(_('File transfer stopped'), sectext)
- self.tree.get_selection().unselect_all()
-
- def show_hash_error(self, jid, file_props, account):
-
- def on_yes(dummy, fjid, file_props, account):
- # Delete old file
- os.remove(file_props.file_name)
- jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
- if resource:
- contact = gajim.contacts.get_contact(account, jid, resource)
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- fjid = contact.get_full_jid()
- # Request the file to the sender
- sid = helpers.get_random_string_16()
- new_file_props = FilesProp.getNewFileProp(account, sid)
- new_file_props.file_name = file_props.file_name
- new_file_props.name = file_props.name
- new_file_props.desc = file_props.desc
- new_file_props.size = file_props.size
- new_file_props.date = file_props.date
- new_file_props.hash_ = file_props.hash_
- new_file_props.type_ = 'r'
- tsid = gajim.connections[account].start_file_transfer(fjid,
- new_file_props,
- True)
- new_file_props.transport_sid = tsid
- self.add_transfer(account, contact, new_file_props)
-
- if file_props.type_ == 'r':
- file_name = os.path.basename(file_props.file_name)
- else:
- file_name = file_props.name
- dialogs.YesNoDialog(('File transfer error'),
- _('The file %(file)s has been received, but it seems to have '
- 'been damaged along the way.\nDo you want to download it again?') % \
- {'file': file_name}, on_response_yes=(on_yes, jid, file_props,
- account), type_=Gtk.MessageType.ERROR)
-
- def show_file_send_request(self, account, contact):
- win = Gtk.ScrolledWindow()
- win.set_shadow_type(Gtk.ShadowType.IN)
- win.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER)
-
- from message_textview import MessageTextView
- desc_entry = MessageTextView()
- win.add(desc_entry)
-
- def on_ok(widget):
- file_dir = None
- files_path_list = dialog.get_filenames()
- text_buffer = desc_entry.get_buffer()
- desc = text_buffer.get_text(text_buffer.get_start_iter(),
- text_buffer.get_end_iter(), True)
- for file_path in files_path_list:
- if self.send_file(account, contact, file_path, desc) \
- and file_dir is None:
- file_dir = os.path.dirname(file_path)
- if file_dir:
- gajim.config.set('last_send_dir', file_dir)
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(_('Choose File to Send…'),
- Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL),
- Gtk.ResponseType.OK,
- True, # select multiple true as we can select many files to send
- gajim.config.get('last_send_dir'),
- on_response_ok=on_ok,
- on_response_cancel=lambda e:dialog.destroy(),
- transient_for=gajim.interface.roster.window
- )
-
- btn = Gtk.Button.new_with_mnemonic(_('_Send'))
- btn.set_property('can-default', True)
- # FIXME: add send icon to this button (JUMP_TO)
- dialog.add_action_widget(btn, Gtk.ResponseType.OK)
- dialog.set_default_response(Gtk.ResponseType.OK)
-
- desc_hbox = Gtk.HBox(homogeneous=False, spacing=5)
- desc_hbox.pack_start(Gtk.Label.new(_('Description: ')), False, False, 0)
- desc_hbox.pack_start(win, True, True, 0)
-
- dialog.vbox.pack_start(desc_hbox, False, False, 0)
-
- btn.show()
- desc_hbox.show_all()
-
- def send_file(self, account, contact, file_path, file_desc=''):
- """
- Start the real transfer(upload) of the file
- """
- if gtkgui_helpers.file_is_locked(file_path):
- pritext = _('Gajim can not read this file')
- sextext = _('Another process is using this file.')
- dialogs.ErrorDialog(pritext, sextext)
- return
-
- if isinstance(contact, str):
- if contact.find('/') == -1:
- return
- (jid, resource) = contact.split('/', 1)
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- resource=resource)
- file_name = os.path.split(file_path)[1]
- file_props = self.get_send_file_props(account, contact,
- file_path, file_name, file_desc)
- if file_props is None:
- return False
- if contact.supports(NS_JINGLE_FILE_TRANSFER_5):
- log.info("contact %s supports jingle file transfer"%(contact.get_full_jid()))
- gajim.connections[account].start_file_transfer(contact.get_full_jid(),
- file_props)
- self.add_transfer(account, contact, file_props)
- else:
- log.info("contact does not support jingle file transfer")
- gajim.connections[account].send_file_request(file_props)
- self.add_transfer(account, contact, file_props)
- return True
-
- def _start_receive(self, file_path, account, contact, file_props):
- file_dir = os.path.dirname(file_path)
- if file_dir:
- gajim.config.set('last_save_dir', file_dir)
- file_props.file_name = file_path
- file_props.type_ = 'r'
- self.add_transfer(account, contact, file_props)
- gajim.connections[account].send_file_approval(file_props)
-
- def on_file_request_accepted(self, account, contact, file_props):
- def on_ok(widget, account, contact, file_props):
- file_path = dialog2.get_filename()
- if os.path.exists(file_path):
- # check if we have write permissions
- if not os.access(file_path, os.W_OK):
- file_name = GLib.markup_escape_text(os.path.basename(
- file_path))
- dialogs.ErrorDialog(
- _('Cannot overwrite existing file "%s"' % file_name),
- _('A file with this name already exists and you do not '
- 'have permission to overwrite it.'))
- return
- stat = os.stat(file_path)
- dl_size = stat.st_size
- file_size = file_props.size
- dl_finished = dl_size >= file_size
-
- def on_response(response):
- if response < 0:
- return
- elif response == 100:
- file_props.offset = dl_size
- dialog2.destroy()
- self._start_receive(file_path, account, contact, file_props)
-
- dialog = dialogs.FTOverwriteConfirmationDialog(
- _('This file already exists'), _('What do you want to do?'),
- propose_resume=not dl_finished, on_response=on_response,
- transient_for=dialog2)
- dialog.set_destroy_with_parent(True)
- return
- else:
- dirname = os.path.dirname(file_path)
- if not os.access(dirname, os.W_OK) and os.name != 'nt':
- # read-only bit is used to mark special folder under
- # windows, not to mark that a folder is read-only.
- # See ticket #3587
- dialogs.ErrorDialog(_('Directory "%s" is not writable') % \
- dirname, _('You do not have permission to create files '
- 'in this directory.'))
- return
- dialog2.destroy()
- self._start_receive(file_path, account, contact, file_props)
-
- def on_cancel(widget, account, contact, file_props):
- dialog2.destroy()
- gajim.connections[account].send_file_rejection(file_props)
-
- dialog2 = dialogs.FileChooserDialog(
- title_text=_('Save File as…'),
- action=Gtk.FileChooserAction.SAVE,
- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=gajim.config.get('last_save_dir'),
- on_response_ok=(on_ok, account, contact, file_props),
- on_response_cancel=(on_cancel, account, contact, file_props))
-
- dialog2.set_current_name(file_props.name)
- dialog2.connect('delete-event', lambda widget, event:
- on_cancel(widget, account, contact, file_props))
-
- def show_file_request(self, account, contact, file_props):
- """
- Show dialog asking for comfirmation and store location of new file
- requested by a contact
- """
- if not file_props or not file_props.name:
- return
- sec_text = '\t' + _('File: %s') % GLib.markup_escape_text(
- file_props.name)
- if file_props.size:
- sec_text += '\n\t' + _('Size: %s') % \
- helpers.convert_bytes(file_props.size)
- if file_props.mime_type:
- sec_text += '\n\t' + _('Type: %s') % file_props.mime_type
- if file_props.desc:
- sec_text += '\n\t' + _('Description: %s') % file_props.desc
- prim_text = _('%s wants to send you a file:') % contact.jid
- dialog = None
-
- def on_response_ok(account, contact, file_props):
- self.on_file_request_accepted(account, contact, file_props)
-
- def on_response_cancel(account, file_props):
- gajim.connections[account].send_file_rejection(file_props)
-
- dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
- on_response_ok=(on_response_ok, account, contact, file_props),
- on_response_cancel=(on_response_cancel, account, file_props))
- dialog.connect('delete-event', lambda widget, event:
- on_response_cancel(account, file_props))
- dialog.popup()
-
- def get_icon(self, ident):
- return self.images.setdefault(ident,
- gtkgui_helpers.get_icon_pixmap(self.icons[ident], 24))
-
- def set_status(self,file_props, status):
- """
- Change the status of a transfer to state 'status'
- """
- iter_ = self.get_iter_by_sid(file_props.type_, file_props.sid)
- if iter_ is None:
- return
- self.model[iter_][Column.SID]
- if status == 'stop':
- file_props.stopped = True
- elif status == 'ok':
- file_props.completed = True
- text = self._format_percent(100)
- received_size = int(file_props.received_len)
- full_size = file_props.size
- text += helpers.convert_bytes(received_size) + '/' + \
- helpers.convert_bytes(full_size)
- self.model.set(iter_, Column.PROGRESS, text)
- self.model.set(iter_, Column.PULSE, GLib.MAXINT32)
- elif status == 'computing':
- self.model.set(iter_, Column.PULSE, 1)
- text = _('Checking file…') + '\n'
- received_size = int(file_props.received_len)
- full_size = file_props.size
- text += helpers.convert_bytes(received_size) + '/' + \
- helpers.convert_bytes(full_size)
- self.model.set(iter_, Column.PROGRESS, text)
- def pulse():
- p = self.model.get(iter_, Column.PULSE)[0]
- if p == GLib.MAXINT32:
- return False
- self.model.set(iter_, Column.PULSE, p + 1)
- return True
- GLib.timeout_add(100, pulse)
- elif status == 'hash_error':
- text = _('File error') + '\n'
- received_size = int(file_props.received_len)
- full_size = file_props.size
- text += helpers.convert_bytes(received_size) + '/' + \
- helpers.convert_bytes(full_size)
- self.model.set(iter_, Column.PROGRESS, text)
- self.model.set(iter_, Column.PULSE, GLib.MAXINT32)
- self.model.set(iter_, Column.IMAGE, self.get_icon(status))
- path = self.model.get_path(iter_)
- self.select_func(path)
-
- def _format_percent(self, percent):
- """
- Add extra spaces from both sides of the percent, so that progress string
- has always a fixed size
- """
- _str = ' '
- if percent != 100.:
- _str += ' '
- if percent < 10:
- _str += ' '
- _str += str(percent) + '% \n'
- return _str
-
- def _format_time(self, _time):
- times = { 'hours': 0, 'minutes': 0, 'seconds': 0 }
- _time = int(_time)
- times['seconds'] = _time % 60
- if _time >= 60:
- _time /= 60
- times['minutes'] = _time % 60
- if _time >= 60:
- times['hours'] = _time / 60
-
- #Print remaining time in format 00:00:00
- #You can change the places of (hours), (minutes), (seconds) -
- #they are not translatable.
- return _('%(hours)02.d:%(minutes)02.d:%(seconds)02.d') % times
-
- def _get_eta_and_speed(self, full_size, transfered_size, file_props):
- if len(file_props.transfered_size) == 0:
- return 0., 0.
- elif len(file_props.transfered_size) == 1:
- speed = round(float(transfered_size) / file_props.elapsed_time)
- else:
- # first and last are (time, transfered_size)
- first = file_props.transfered_size[0]
- last = file_props.transfered_size[-1]
- transfered = last[1] - first[1]
- tim = last[0] - first[0]
- if tim == 0:
- return 0., 0.
- speed = round(float(transfered) / tim)
- if speed == 0.:
- return 0., 0.
- remaining_size = full_size - transfered_size
- eta = remaining_size / speed
- return eta, speed
-
- def _remove_transfer(self, iter_, sid, file_props):
- self.model.remove(iter_)
- if not file_props:
- return
- if file_props.tt_account:
- # file transfer is set
- account = file_props.tt_account
- if account in gajim.connections:
- # there is a connection to the account
- gajim.connections[account].remove_transfer(file_props)
- if file_props.type_ == 'r': # we receive a file
- other = file_props.sender
- else: # we send a file
- other = file_props.receiver
- if isinstance(other, str):
- jid = gajim.get_jid_without_resource(other)
- else: # It's a Contact instance
- jid = other.jid
- for ev_type in ('file-error', 'file-completed', 'file-request-error',
- 'file-send-error', 'file-stopped'):
- for event in gajim.events.get_events(account, jid, [ev_type]):
- if event.file_props.sid == file_props.sid:
- gajim.events.remove_events(account, jid, event)
- gajim.interface.roster.draw_contact(jid, account)
- gajim.interface.roster.show_title()
- FilesProp.deleteFileProp(file_props)
- del(file_props)
-
- def set_progress(self, typ, sid, transfered_size, iter_=None):
- """
- Change the progress of a transfer with new transfered size
- """
- file_props = FilesProp.getFilePropByType(typ, sid)
- full_size = file_props.size
- if full_size == 0:
- percent = 0
- else:
- percent = round(float(transfered_size) / full_size * 100, 1)
- if iter_ is None:
- iter_ = self.get_iter_by_sid(typ, sid)
- if iter_ is not None:
- just_began = False
- if self.model[iter_][Column.PERCENT] == 0 and int(percent > 0):
- just_began = True
- text = self._format_percent(percent)
- if transfered_size == 0:
- text += '0'
- else:
- text += helpers.convert_bytes(transfered_size)
- text += '/' + helpers.convert_bytes(full_size)
- # Kb/s
-
- # remaining time
- if file_props.offset:
- transfered_size -= file_props.offset
- full_size -= file_props.offset
-
- if file_props.elapsed_time > 0:
- file_props.transfered_size.append((file_props.last_time, transfered_size))
- if len(file_props.transfered_size) > 6:
- file_props.transfered_size.pop(0)
- eta, speed = self._get_eta_and_speed(full_size, transfered_size,
- file_props)
-
- self.model.set(iter_, Column.PROGRESS, text)
- self.model.set(iter_, Column.PERCENT, int(percent))
- text = self._format_time(eta)
- text += '\n'
- #This should make the string Kb/s,
- #where 'Kb' part is taken from %s.
- #Only the 's' after / (which means second) should be translated.
- text += _('(%(filesize_unit)s/s)') % {'filesize_unit':
- helpers.convert_bytes(speed)}
- self.model.set(iter_, Column.TIME, text)
-
- # try to guess what should be the status image
- if file_props.type_ == 'r':
- status = 'download'
- else:
- status = 'upload'
- if file_props.paused == True:
- status = 'pause'
- elif file_props.stalled == True:
- status = 'waiting'
- if file_props.connected == False:
- status = 'stop'
- self.model.set(iter_, 0, self.get_icon(status))
- if transfered_size == full_size:
- # If we are receiver and this is a jingle session
- if file_props.type_ == 'r' and \
- file_props.session_type == 'jingle' and file_props.hash_:
- # Show that we are computing the hash
- self.set_status(file_props, 'computing')
- else:
- self.set_status(file_props, 'ok')
- elif just_began:
- path = self.model.get_path(iter_)
- self.select_func(path)
-
- def get_iter_by_sid(self, typ, sid):
- """
- Return iter to the row, which holds file transfer, identified by the
- session id
- """
- iter_ = self.model.get_iter_first()
- while iter_:
- if typ + sid == self.model[iter_][Column.SID]:
- return iter_
- iter_ = self.model.iter_next(iter_)
-
- def __convert_date(self, epoch):
- # Converts date-time from seconds from epoch to iso 8601
- import time, datetime
- ts = time.gmtime(epoch)
- dt = datetime.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour,
- ts.tm_min, ts.tm_sec)
- return dt.isoformat()
-
- def get_send_file_props(self, account, contact, file_path, file_name,
- file_desc=''):
- """
- Create new file_props object and set initial file transfer
- properties in it
- """
- if os.path.isfile(file_path):
- stat = os.stat(file_path)
- else:
- dialogs.ErrorDialog(_('Invalid File'), _('File: ') + file_path)
- return None
- if stat[6] == 0:
- dialogs.ErrorDialog(_('Invalid File'),
- _('It is not possible to send empty files'))
- return None
- file_props = FilesProp.getNewFileProp(account,
- sid=helpers.get_random_string_16())
- mod_date = os.path.getmtime(file_path)
- file_props.file_name = file_path
- file_props.name = file_name
- file_props.date = self.__convert_date(mod_date)
- file_props.type_ = 's'
- file_props.desc = file_desc
- file_props.elapsed_time = 0
- file_props.size = stat[6]
- file_props.sender = account
- file_props.receiver = contact
- file_props.tt_account = account
- return file_props
-
- def add_transfer(self, account, contact, file_props):
- """
- Add new transfer to FT window and show the FT window
- """
- self.on_transfers_list_leave_notify_event(None)
- if file_props is None:
- return
- file_props.elapsed_time = 0
- iter_ = self.model.prepend()
- text_labels = '<b>' + _('Name: ') + '</b>\n'
- if file_props.type_ == 'r':
- text_labels += '<b>' + _('Sender: ') + '</b>'
- else:
- text_labels += '<b>' + _('Recipient: ') + '</b>'
-
- if file_props.type_ == 'r':
- file_name = os.path.split(file_props.file_name)[1]
- else:
- file_name = file_props.name
- text_props = GLib.markup_escape_text(file_name) + '\n'
- text_props += contact.get_shown_name()
- self.model.set(iter_, 1, text_labels, 2, text_props, Column.PULSE, -1, Column.SID,
- file_props.type_ + file_props.sid)
- self.set_progress(file_props.type_, file_props.sid, 0, iter_)
- if file_props.started is False:
- status = 'waiting'
- elif file_props.type_ == 'r':
- status = 'download'
- else:
- status = 'upload'
- file_props.tt_account = account
- self.set_status(file_props, status)
- self.set_cleanup_sensitivity()
- self.window.show_all()
-
- def on_transfers_list_motion_notify_event(self, widget, event):
- w = self.tree.get_window()
- device = w.get_display().get_device_manager().get_client_pointer()
- pointer = w.get_device_position(device)
- props = widget.get_path_at_pos(int(event.x), int(event.y))
- self.height_diff = pointer[2] - int(event.y)
- if self.tooltip.timeout > 0 or self.tooltip.shown:
- if not props or self.tooltip.id != props[0]:
- self.tooltip.hide_tooltip()
- if props:
- row = props[0]
- iter_ = None
- try:
- iter_ = self.model.get_iter(row)
- except Exception:
- self.tooltip.hide_tooltip()
- return
- sid = self.model[iter_][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- if file_props is not None:
- if self.tooltip.timeout == 0 or self.tooltip.id != props[0]:
- self.tooltip.id = row
- self.tooltip.timeout = GLib.timeout_add(500,
- self.show_tooltip, widget)
-
- def on_transfers_list_leave_notify_event(self, widget=None, event=None):
- if event is not None:
- self.height_diff = int(event.y)
- elif self.height_diff is 0:
- return
- w = self.tree.get_window()
- device = w.get_display().get_device_manager().get_client_pointer()
- pointer = w.get_device_position(device)
- props = self.tree.get_path_at_pos(pointer[1],
- pointer[2] - self.height_diff)
- if self.tooltip.timeout > 0 or self.tooltip.shown:
- if not props or self.tooltip.id == props[0]:
- self.tooltip.hide_tooltip()
-
- def on_transfers_list_row_activated(self, widget, path, col):
- # try to open the containing folder
- self.on_open_folder_menuitem_activate(widget)
-
- def set_cleanup_sensitivity(self):
- """
- Check if there are transfer rows and set cleanup_button sensitive, or
- insensitive if model is empty
- """
- if len(self.model) == 0:
- self.cleanup_button.set_sensitive(False)
- else:
- self.cleanup_button.set_sensitive(True)
-
- def set_all_insensitive(self):
- """
- Make all buttons/menuitems insensitive
- """
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- self.remove_menuitem.set_sensitive(False)
- self.cancel_button.set_sensitive(False)
- self.cancel_menuitem.set_sensitive(False)
- self.open_folder_menuitem.set_sensitive(False)
- self.set_cleanup_sensitivity()
-
- def set_buttons_sensitive(self, path, is_row_selected):
- """
- Make buttons/menuitems sensitive as appropriate to the state of file
- transfer located at path 'path'
- """
- if path is None:
- self.set_all_insensitive()
- return
- current_iter = self.model.get_iter(path)
- sid = self.model[current_iter][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- self.remove_menuitem.set_sensitive(is_row_selected)
- self.open_folder_menuitem.set_sensitive(is_row_selected)
- is_stopped = False
- if is_transfer_stopped(file_props):
- is_stopped = True
- self.cancel_button.set_sensitive(not is_stopped)
- self.cancel_menuitem.set_sensitive(not is_stopped)
- if not is_row_selected:
- # no selection, disable the buttons
- self.set_all_insensitive()
- elif not is_stopped and file_props.continue_cb:
- if is_transfer_active(file_props):
- # file transfer is active
- self.toggle_pause_continue(True)
- self.pause_button.set_sensitive(True)
- elif is_transfer_paused(file_props):
- # file transfer is paused
- self.toggle_pause_continue(False)
- self.pause_button.set_sensitive(True)
- else:
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- else:
- self.pause_button.set_sensitive(False)
- self.pause_menuitem.set_sensitive(False)
- self.continue_menuitem.set_sensitive(False)
- return True
-
- def selection_changed(self, args):
- """
- Selection has changed - change the sensitivity of the buttons/menuitems
- """
- selection = args
- selected = selection.get_selected_rows()
- if selected[1] != []:
- selected_path = selected[1][0]
- self.select_func(selected_path)
- else:
- self.set_all_insensitive()
-
- def select_func(self, path):
- is_selected = False
- selected = self.tree.get_selection().get_selected_rows()
- if selected[1] != []:
- selected_path = selected[1][0]
- if selected_path == path:
- is_selected = True
- self.set_buttons_sensitive(path, is_selected)
- self.set_cleanup_sensitivity()
- return True
-
- def on_cleanup_button_clicked(self, widget):
- i = len(self.model) - 1
- while i >= 0:
- iter_ = self.model.get_iter((i))
- sid = self.model[iter_][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- if is_transfer_stopped(file_props):
- self._remove_transfer(iter_, sid, file_props)
- i -= 1
- self.tree.get_selection().unselect_all()
- self.set_all_insensitive()
-
- def toggle_pause_continue(self, status):
- if status:
- label = _('Pause')
- self.pause_button.set_label(label)
- self.pause_button.set_image(Gtk.Image.new_from_stock(
- Gtk.STOCK_MEDIA_PAUSE, Gtk.IconSize.MENU))
-
- self.pause_menuitem.set_sensitive(True)
- self.pause_menuitem.set_no_show_all(False)
- self.continue_menuitem.hide()
- self.continue_menuitem.set_no_show_all(True)
-
- else:
- label = _('_Continue')
- self.pause_button.set_label(label)
- self.pause_button.set_image(Gtk.Image.new_from_stock(
- Gtk.STOCK_MEDIA_PLAY, Gtk.IconSize.MENU))
- self.pause_menuitem.hide()
- self.pause_menuitem.set_no_show_all(True)
- self.continue_menuitem.set_sensitive(True)
- self.continue_menuitem.set_no_show_all(False)
-
- def on_pause_restore_button_clicked(self, widget):
- selected = self.tree.get_selection().get_selected()
- if selected is None or selected[1] is None:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- if is_transfer_paused(file_props):
- file_props.last_time = time.time()
- file_props.paused = False
- types = {'r' : 'download', 's' : 'upload'}
- self.set_status(file_props, types[sid[0]])
- self.toggle_pause_continue(True)
- if file_props.continue_cb:
- file_props.continue_cb()
- elif is_transfer_active(file_props):
- file_props.paused = True
- self.set_status(file_props, 'pause')
- # reset that to compute speed only when we resume
- file_props.transfered_size = []
- self.toggle_pause_continue(False)
-
- def on_cancel_button_clicked(self, widget):
- selected = self.tree.get_selection().get_selected()
- if selected is None or selected[1] is None:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- account = file_props.tt_account
- if account not in gajim.connections:
- return
- con = gajim.connections[account]
- # Check if we are in a IBB transfer
- if file_props.direction:
- con.CloseIBBStream(file_props)
- con.disconnect_transfer(file_props)
- self.set_status(file_props, 'stop')
-
- def show_tooltip(self, widget):
- self.tooltip.timeout = 0
- if self.height_diff == 0:
- self.tooltip.hide_tooltip()
- return
- w = self.tree.get_window()
- device = w.get_display().get_device_manager().get_client_pointer()
- pointer = w.get_device_position(device)
- props = self.tree.get_path_at_pos(pointer[1],
- pointer[2] - self.height_diff)
- # check if the current pointer is at the same path
- # as it was before setting the timeout
- if props and self.tooltip.id == props[0]:
- iter_ = self.model.get_iter(props[0])
- sid = self.model[iter_][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- # bounding rectangle of coordinates for the cell within the treeview
- rect = self.tree.get_cell_area(props[0], props[1])
- # position of the treeview on the screen
- position = widget.get_window().get_origin()[1:]
- self.tooltip.show_tooltip(file_props, rect.height,
- position[1] + rect.y + self.height_diff)
- else:
- self.tooltip.hide_tooltip()
-
- def on_notify_ft_complete_checkbox_toggled(self, widget):
- gajim.config.set('notify_on_file_complete',
- widget.get_active())
-
- def on_file_transfers_dialog_delete_event(self, widget, event):
- self.on_transfers_list_leave_notify_event(widget, None)
- self.window.hide()
- return True # do NOT destory window
-
- def on_close_button_clicked(self, widget):
- self.window.hide()
-
- def show_context_menu(self, event, iter_):
- # change the sensitive propery of the buttons and menuitems
- if iter_:
- path = self.model.get_path(iter_)
- self.set_buttons_sensitive(path, True)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
- self.file_transfers_menu.show_all()
- self.file_transfers_menu.popup(None, self.tree, None, None,
- event_button, event.time)
-
- def on_transfers_list_key_press_event(self, widget, event):
- """
- When a key is pressed in the treeviews
- """
- self.tooltip.hide_tooltip()
- iter_ = None
- try:
- iter_ = self.tree.get_selection().get_selected()[1]
- except TypeError:
- self.tree.get_selection().unselect_all()
-
- if iter_ is not None:
- path = self.model.get_path(iter_)
- self.tree.get_selection().select_path(path)
-
- if event.keyval == Gdk.KEY_Menu:
- self.show_context_menu(event, iter_)
- return True
-
-
- def on_transfers_list_button_release_event(self, widget, event):
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- path = None
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- self.tree.get_selection().unselect_all()
- if path is None:
- self.set_all_insensitive()
- else:
- self.select_func(path)
-
- def on_transfers_list_button_press_event(self, widget, event):
- # hide tooltip, no matter the button is pressed
- self.tooltip.hide_tooltip()
- path, iter_ = None, None
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- self.tree.get_selection().unselect_all()
- if event.button == 3: # Right click
- if path:
- self.tree.get_selection().select_path(path)
- iter_ = self.model.get_iter(path)
- self.show_context_menu(event, iter_)
- if path:
- return True
-
- def on_open_folder_menuitem_activate(self, widget):
- selected = self.tree.get_selection().get_selected()
- if not selected or not selected[1]:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- if not file_props.file_name:
- return
- path = os.path.split(file_props.file_name)[0]
- if os.path.exists(path) and os.path.isdir(path):
- helpers.launch_file_manager(path)
-
- def on_cancel_menuitem_activate(self, widget):
- self.on_cancel_button_clicked(widget)
-
- def on_continue_menuitem_activate(self, widget):
- self.on_pause_restore_button_clicked(widget)
-
- def on_pause_menuitem_activate(self, widget):
- self.on_pause_restore_button_clicked(widget)
-
- def on_remove_menuitem_activate(self, widget):
- selected = self.tree.get_selection().get_selected()
- if not selected or not selected[1]:
- return
- s_iter = selected[1]
- sid = self.model[s_iter][Column.SID]
- file_props = FilesProp.getFilePropByType(sid[0], sid[1:])
- self._remove_transfer(s_iter, sid, file_props)
- self.set_all_insensitive()
-
- def on_file_transfers_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape: # ESCAPE
- self.window.hide()
diff --git a/src/gajim-remote.py b/src/gajim-remote.py
deleted file mode 100644
index d48adb228..000000000
--- a/src/gajim-remote.py
+++ /dev/null
@@ -1,609 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gajim-remote.py
-##
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Junglecow <junglecow AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-# gajim-remote help will show you the D-BUS API of Gajim
-
-import sys
-import locale
-import urllib
-import signal
-signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
-
-
-from common import exceptions
-from common import i18n # This installs _() function
-from common.i18n import Q_
-
-try:
- PREFERRED_ENCODING = locale.getpreferredencoding()
-except Exception:
- PREFERRED_ENCODING = 'UTF-8'
-
-def send_error(error_message):
- '''Writes error message to stderr and exits'''
- print(error_message, file=sys.stderr)
- sys.exit(1)
-
-try:
- import dbus
- import dbus.service
-# import dbus.glib
- # test if dbus-x11 is installed
- bus = dbus.SessionBus()
-except Exception:
- print(_('D-Bus is not present on this machine or python module is missing'))
- sys.exit(1)
-
-OBJ_PATH = '/org/gajim/dbus/RemoteObject'
-INTERFACE = 'org.gajim.dbus.RemoteInterface'
-SERVICE = 'org.gajim.dbus'
-BASENAME = 'gajim-remote'
-
-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': [
- _('Lists all contacts in roster, one for each line'),
- [
- (Q_('?CLI:account'), _('show only contacts of the given account'),
- False)
- ]
-
- ],
- 'list_accounts': [
- _('Prints a list of registered accounts'),
- []
- ],
- 'change_status': [
- _('Changes the status of account(s)'),
- [
-#offline, online, chat, away, xa, dnd, invisible should not be translated
- (Q_('?CLI:status'), _('one of: offline, online, chat, away, xa, dnd, invisible. If not set, use account\'s previous status'), False),
- (Q_('?CLI:message'), _('status message'), False),
- (Q_('?CLI: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)
- ]
- ],
- 'set_priority': [
- _('Changes the priority of account(s)'),
- [
- (Q_('?CLI:priority'), _('priority you want to give to the account'),
- True),
- (Q_('?CLI:account'), _('change the priority of the given account. '
- 'If not specified, 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),
- (Q_('?CLI:account'), _('if specified, contact is taken from the '
- 'contact list of this account'), False),
- (Q_('?CLI:message'),
- _('message content. The account must be specified or ""'),
- 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),
- (Q_('?CLI:message'), _('message contents'), True),
- (_('PGP key'), _('if specified, the message will be encrypted '
- 'using this public key'), False),
- (Q_('?CLI:account'), _('if specified, the message will be sent '
- 'using this account'), False),
- ]
- ],
- 'send_single_message': [
- _('Sends a chat message to someone on your roster. '
- 'Optionally with OpenPGP key and account. If you want '
- 'to only set the latter, set OpenPGP key to "".'),
- [
- ('jid', _('JID of the contact that will receive the message'), True),
- (_('subject'), _('message subject'), True),
- (Q_('?CLI:message'), _('message contents'), True),
- (_('PGP key'), _('if specified, the message will be encrypted '
- 'using this public key'), False),
- (Q_('?CLI: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),
- (Q_('?CLI:message'), _('message contents'), True),
- (Q_('?CLI: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),
- (Q_('?CLI: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 what 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),
- (Q_('?CLI: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),
- (Q_('?CLI:account'), _('Adds new contact to this account'), False)
- ]
- ],
-
- 'get_status': [
- _('Returns current status (the global one unless account is specified)'),
- [
- (Q_('?CLI:account'), '', False)
- ]
- ],
-
- 'get_status_message': [
- _('Returns current status message (the global one unless account is specified)'),
- [
- (Q_('?CLI:account'), '', False)
- ]
- ],
-
- 'get_unread_msgs_number': [
- _('Returns number of unread messages'),
- [ ]
- ],
- 'start_chat': [
- _('Opens \'Start Chat\' dialog'),
- [
- (Q_('?CLI:account'), _('Starts chat, using this account'), True)
- ]
- ],
- 'send_xml': [
- _('Sends custom XML'),
- [
- ('xml', _('XML to send'), True),
- ('account', _('Account to which the XML will be sent; '
- 'if not specified, XML will be sent to all accounts'),
- False)
- ]
- ],
- 'change_avatar': [
- _('Change the avatar'),
- [
- ('picture', _('Picture to use'), True),
- ('account', _('Account in which the avatar will be set; '
- 'if not specified, the avatar will be set for all accounts'),
- False)
- ]
- ],
- 'handle_uri': [
- _('Handle a xmpp:/ URI'),
- [
- (Q_('?CLI:uri'), _('URI to handle'), True),
- (Q_('?CLI:account'), _('Account in which you want to handle it'),
- False),
- (Q_('?CLI:message'), _('Message content'), False)
- ]
- ],
- 'join_room': [
- _('Join a MUC room'),
- [
- (Q_('?CLI:room'), _('Room JID'), True),
- (Q_('?CLI:nick'), _('Nickname to use'), False),
- (Q_('?CLI:password'), _('Password to enter the room'), False),
- (Q_('?CLI:account'), _('Account from which you want to enter the '
- 'room'), False)
- ]
- ],
- 'check_gajim_running': [
- _('Check if Gajim is running'),
- []
- ],
- '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:
- 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 = sorted(res.keys())
- for pref_key in pref_keys:
- result = '%s = %s' % (pref_key, res[pref_key])
- print(result)
- elif self.command == 'contact_info':
- print(self.print_info(0, res, True))
- elif res:
- print(res)
-
- def check_gajim_running(self):
- if not self.sbus:
- try:
- self.sbus = dbus.SessionBus()
- except Exception:
- 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 Exception:
- raise exceptions.SessionBusNotPresent
-
- if not self.check_gajim_running():
- #Do not translate "gajim-remote"
- 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 '[', ']'
- """
- s = ''
- for arg in args:
- if arg[2]:
- s += ' <' + arg[0] + '>'
- else:
- s += ' [' + arg[0] + ']'
- return s
-
- 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: %(basename)s %(command)s %(arguments)s \n\t %(help)s')\
- % {'basename': BASENAME, 'command': command,
- 'arguments': arguments_str, 'help': 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
- """
- s = _('Usage:\n %s command [arguments]\n\nCommand is one of:\n' ) % (
- BASENAME)
- for command in sorted(self.commands):
- s += ' ' + command
- for arg in self.commands[command][1]:
- if arg[2]:
- s += ' <' + arg[0] + '>'
- else:
- s += ' [' + arg[0] + ']'
- s += '\n'
- return s
-
- 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):
- 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, (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 Exception:
- 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 "%(basename)s help %(command)s" for more info') % {
- 'basename': BASENAME, 'command': self.command})
- if len(args) > argv_len:
- if args[argv_len][2]:
- send_error(_('Argument "%(arg)s" is not specified. \n'
- 'Type "%(basename)s help %(command)s" for more info') %
- {'arg': args[argv_len][0], 'basename': BASENAME,
- 'command': 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 len(sys.argv) < 3:
- send_error(_('No URI given'))
- if not sys.argv[2].startswith('xmpp:'):
- send_error(_('Wrong URI'))
- sys.argv[2] = sys.argv[2][5:]
- uri = sys.argv[2]
- if not '?' in uri:
- self.command = sys.argv[1] = 'open_chat'
- return
- jid, args = uri.split('?', 1)
- try:
- jid = urllib.parse.unquote(jid)
- except UnicodeDecodeError:
- pass
- args = args.split(';')
- action = None
- options = {}
- if args:
- action = args[0]
- for arg in args[1:]:
- opt = arg.split('=', 1)
- if len(opt) != 2:
- continue
- options[opt[0]] = opt[1]
-
- if action == 'message':
- self.command = sys.argv[1] = 'open_chat'
- sys.argv[2] = jid
- if 'body' in options:
- # Open chat window and paste the text in the input message
- # dialog
- message = options['body']
- try:
- message = urllib.parse.unquote(message)
- except UnicodeDecodeError:
- pass
- if len(sys.argv) == 4:
- # jid in the sys.argv
- sys.argv.append(message)
- else:
- sys.argv.append('')
- sys.argv.append(message)
- sys.argv[3] = ''
- sys.argv[4] = message
- return
- 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
- if action == 'roster':
- # Add contact to roster
- self.command = sys.argv[1] = 'add_contact'
- 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()
diff --git a/src/gajim.py b/src/gajim.py
deleted file mode 100644
index 1e85ad1ba..000000000
--- a/src/gajim.py
+++ /dev/null
@@ -1,398 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gajim.py
-##
-## Copyright (C) 2003-2017 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
-## Norman Rasmussen <norman AT rasmussen.co.za>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## James Newton <redshodan AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-## Copyright (C) 2016-2017 Emmanuel Gil Peyrot <linkmauve AT linkmauve.fr>
-## Philipp Hörist <philipp AT hoerist.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import sys
-import os
-import logging
-import signal
-import locale
-import gi
-gi.require_version('GLib', '2.0')
-gi.require_version('Gio', '2.0')
-gi.require_version('Gtk', '3.0')
-gi.require_version('Gdk', '3.0')
-gi.require_version('GObject', '2.0')
-gi.require_version('Pango', '1.0')
-from gi.repository import GLib, Gio, Gtk
-from common import i18n
-from common import logging_helpers
-from common import crypto
-try:
- PYOPENSSL_PRNG_PRESENT = True
- import OpenSSL.rand
-except ImportError:
- print('PyOpenSSL not available, impossible to generate entropy', file=sys.stderr)
- PYOPENSSL_PRNG_PRESENT = False
-
-MIN_NBXMPP_VER = "0.5.6"
-
-
-class GajimApplication(Gtk.Application):
- '''Main class handling activation and command line.'''
-
- def __init__(self):
- Gtk.Application.__init__(self, application_id='org.gajim.Gajim',
- flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
-
- self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE,
- GLib.OptionArg.NONE,
- _('Show the application\'s version'))
- self.add_main_option('quiet', ord('q'), GLib.OptionFlags.NONE,
- GLib.OptionArg.NONE,
- _('Show only critical errors'))
- self.add_main_option('separate', ord('s'), GLib.OptionFlags.NONE,
- GLib.OptionArg.NONE,
- _('Separate profile files completely (even '
- 'history db and plugins)'))
- self.add_main_option('verbose', ord('v'), GLib.OptionFlags.NONE,
- GLib.OptionArg.NONE,
- _('Print XML stanzas and other debug '
- 'information'))
- self.add_main_option('profile', ord('p'), GLib.OptionFlags.NONE,
- GLib.OptionArg.STRING,
- _('Use defined profile in configuration '
- 'directory'), 'NAME')
- self.add_main_option('config-path', ord('c'), GLib.OptionFlags.NONE,
- GLib.OptionArg.STRING,
- _('Set configuration directory'), 'PATH')
- self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
- GLib.OptionArg.STRING,
- _('Configure logging system'), 'LEVEL')
- self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
- GLib.OptionArg.NONE,
- _('Show all warnings'))
-
- self.profile = ''
- self.config_path = None
- self.profile_separation = False
- self.interface = None
- self.rng_seed = None
-
- GLib.set_prgname('gajim')
- GLib.set_application_name('Gajim')
-
- def do_startup(self):
- Gtk.Application.do_startup(self)
-
- import gtkexcepthook
- gtkexcepthook.init()
-
- try:
- import nbxmpp
- except ImportError:
- print('Gajim needs python-nbxmpp to run. Quitting…')
- sys.exit(1)
-
- from distutils.version import LooseVersion as V
- if V(nbxmpp.__version__) < V(MIN_NBXMPP_VER):
- print('Gajim needs python-nbxmpp >= %s to run. '
- 'Quitting...' % MIN_NBXMPP_VER)
- sys.exit(1)
-
- # Create and initialize Application Paths & Databases
- from common import configpaths
- configpaths.gajimpaths.init(
- self.config_path, self.profile, self.profile_separation)
-
- from common import gajim
- from common import check_paths
- from common import exceptions
- from common import logger
- from common import caps_cache
- try:
- gajim.logger = logger.Logger()
- caps_cache.initialize(gajim.logger)
- check_paths.check_and_possibly_create_paths()
- except exceptions.DatabaseMalformed as error:
- dlg = Gtk.MessageDialog(
- None,
- Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
- Gtk.MessageType.ERROR,
- Gtk.ButtonsType.OK,
- _('Database Error'))
- dlg.format_secondary_text(str(error))
- dlg.run()
- dlg.destroy()
- sys.exit()
-
- if os.name == 'nt':
- import gettext
- # needed for docutils
- sys.path.append('.')
- APP = 'gajim'
- DIR = '../po'
- lang, enc = locale.getdefaultlocale()
- os.environ['LANG'] = lang
- gettext.bindtextdomain(APP, DIR)
- gettext.textdomain(APP)
- gettext.install(APP, DIR)
-
- # This is for Windows translation which is currently not
- # working on GTK 3.18.9
- # locale.setlocale(locale.LC_ALL, '')
- # import ctypes
- # import ctypes.util
- # libintl_path = ctypes.util.find_library('intl')
- # if libintl_path == None:
- # local_intl = os.path.join('gtk', 'bin', 'intl.dll')
- # if os.path.exists(local_intl):
- # libintl_path = local_intl
- # if libintl_path == None:
- # raise ImportError('intl.dll library not found')
- # libintl = ctypes.cdll.LoadLibrary(libintl_path)
- # libintl.bindtextdomain(APP, DIR)
- # libintl.bind_textdomain_codeset(APP, 'UTF-8')
- # plugins_locale_dir = os.path.join(common.configpaths.gajimpaths[
- # 'PLUGINS_USER'], 'locale').encode(locale.getpreferredencoding())
- # libintl.bindtextdomain('gajim_plugins', plugins_locale_dir)
- # libintl.bind_textdomain_codeset('gajim_plugins', 'UTF-8')
-
- if Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL:
- i18n.direction_mark = '\u200F'
-
- from ctypes import CDLL
- from ctypes.util import find_library
- import platform
-
- sysname = platform.system()
- if sysname in ('Linux', 'FreeBSD', 'OpenBSD', 'NetBSD'):
- libc = CDLL(find_library('c'))
-
- # The constant defined in <linux/prctl.h> which is used to set the name
- # of the process.
- PR_SET_NAME = 15
-
- if sysname == 'Linux':
- libc.prctl(PR_SET_NAME, 'gajim')
- elif sysname in ('FreeBSD', 'OpenBSD', 'NetBSD'):
- libc.setproctitle('gajim')
-
- # Seed the OpenSSL pseudo random number generator from file and initialize
- if PYOPENSSL_PRNG_PRESENT:
- self.rng_seed = gajim.gajimpaths['RNG_SEED']
- # Seed from file
- try:
- OpenSSL.rand.load_file(self.rng_seed)
- except TypeError:
- OpenSSL.rand.load_file(self.rng_seed.encode('utf-8'))
- crypto.add_entropy_sources_OpenSSL()
- try:
- OpenSSL.rand.write_file(self.rng_seed)
- except TypeError:
- OpenSSL.rand.write_file(self.rng_seed.encode('utf-8'))
-
- def sigint_cb(num, stack):
- print('SIGINT/SIGTERM received')
- self.quit()
- # ^C exits the application normally
- signal.signal(signal.SIGINT, sigint_cb)
- signal.signal(signal.SIGTERM, sigint_cb)
-
- print("Encodings: d:{}, fs:{}, p:{}".format(sys.getdefaultencoding(),
- sys.getfilesystemencoding(), locale.getpreferredencoding()))
-
- # Set Application Menu
- gajim.app = self
- path = os.path.join(configpaths.get('GUI'), 'application_menu.ui')
- builder = Gtk.Builder()
- builder.set_translation_domain(i18n.APP)
- builder.add_from_file(path)
- self.set_menubar(builder.get_object("menubar"))
- self.set_app_menu(builder.get_object("appmenu"))
-
- def do_activate(self):
- Gtk.Application.do_activate(self)
- from gui_interface import Interface
- import gtkgui_helpers
- self.interface = Interface()
- gtkgui_helpers.load_css()
- self.interface.run(self)
- self.add_actions()
- import gui_menu_builder
- gui_menu_builder.build_accounts_menu()
-
- def do_shutdown(self, *args):
- Gtk.Application.do_shutdown(self)
- # Save the entropy from OpenSSL PRNG
- if PYOPENSSL_PRNG_PRESENT and self.rng_seed:
- try:
- OpenSSL.rand.write_file(self.rng_seed)
- except TypeError:
- OpenSSL.rand.write_file(self.rng_seed.encode('utf-8'))
- # Shutdown GUI and save config
- if hasattr(self.interface, 'roster') and self.interface.roster:
- self.interface.roster.prepare_quit()
-
- def do_handle_local_options(self, options: GLib.VariantDict) -> int:
-
- logging_helpers.init()
-
- if options.contains('profile'):
- # Incorporate profile name into application id
- # to have a single app instance for each profile.
- profile = options.lookup_value('profile').get_string()
- app_id = '%s.%s' % (self.get_application_id(), profile)
- self.set_application_id(app_id)
- self.profile = profile
- if options.contains('separate'):
- self.profile_separation = True
- if options.contains('config-path'):
- self.config_path = options.lookup_value('config-path').get_string()
- if options.contains('version'):
- from common.defs import version
- print(version)
- return 0
- if options.contains('quiet'):
- logging_helpers.set_quiet()
- if options.contains('verbose'):
- logging_helpers.set_verbose()
- if options.contains('loglevel'):
- loglevel = options.lookup_value('loglevel').get_string()
- logging_helpers.set_loglevels(loglevel)
- if options.contains('warnings'):
- self.show_warnings()
- return -1
-
- def do_command_line(self, command_line: Gio.ApplicationCommandLine) -> int:
- Gtk.Application.do_command_line(self, command_line)
- if not command_line.get_is_remote():
- self.activate()
- return 0
-
- def show_warnings(self):
- import traceback
- import warnings
-
- def warn_with_traceback(message, category, filename, lineno,
- file=None, line=None):
- traceback.print_stack(file=sys.stderr)
- sys.stderr.write(warnings.formatwarning(message, category,
- filename, lineno, line))
-
- warnings.showwarning = warn_with_traceback
- warnings.filterwarnings(action="always")
-
- def add_actions(self):
- ''' Build Application Actions '''
- from app_actions import AppActions
- action = AppActions(self)
-
- self.account_actions = [
- ('-start-single-chat', action.on_single_message, 'online', 's'),
- ('-start-chat', action.on_new_chat, 'online', 's'),
- ('-join-groupchat', action.on_join_gc, 'online', 's'),
- ('-add-contact', action.on_add_contact, 'online', 's'),
- ('-services', action.on_service_disco, 'online', 's'),
- ('-profile', action.on_profile, 'feature', 's'),
- ('-xml-console', action.on_xml_console, 'always', 's'),
- ('-archive', action.on_archiving_preferences, 'feature', 's'),
- ('-privacylists', action.on_privacy_lists, 'feature', 's'),
- ('-send-server-message',
- action.on_send_server_message, 'online', 's'),
- ('-set-motd', action.on_set_motd, 'online', 's'),
- ('-update-motd', action.on_update_motd, 'online', 's'),
- ('-delete-motd', action.on_delete_motd, 'online', 's'),
- ('-activate-bookmark',
- action.on_activate_bookmark, 'online', 'a{sv}')
- ]
-
- self.general_actions = [
- ('quit', action.on_quit),
- ('accounts', action.on_accounts),
- ('bookmarks', action.on_manage_bookmarks),
- ('history-manager', action.on_history_manager),
- ('preferences', action.on_preferences),
- ('plugins', action.on_plugins),
- ('file-transfer', action.on_file_transfers),
- ('history', action.on_history),
- ('shortcuts', action.on_keyboard_shortcuts),
- ('features', action.on_features),
- ('content', action.on_contents),
- ('about', action.on_about),
- ('faq', action.on_faq)
- ]
-
- for action in self.general_actions:
- action_name, func = action
- act = Gio.SimpleAction.new(action_name, None)
- act.connect("activate", func)
- self.add_action(act)
-
- from common import gajim
- accounts_list = sorted(gajim.contacts.get_accounts())
- if not accounts_list:
- return
- if len(accounts_list) > 1:
- for acc in accounts_list:
- self.add_account_actions(acc)
- else:
- self.add_account_actions(accounts_list[0])
-
- def add_account_actions(self, account):
- for action in self.account_actions:
- action_name, func, state, type_ = action
- action_name = account + action_name
- if self.lookup_action(action_name):
- # We already added this action
- continue
- act = Gio.SimpleAction.new(
- action_name, GLib.VariantType.new(type_))
- act.connect("activate", func)
- if state != 'always':
- act.set_enabled(False)
- self.add_action(act)
-
- def remove_account_actions(self, account):
- for action in self.account_actions:
- action_name = account + action[0]
- self.remove_action(action_name)
-
- def set_account_actions_state(self, account, new_state=False):
- for action in self.account_actions:
- action_name, _, state, _ = action
- if not new_state and state in ('online', 'feature'):
- # We go offline
- self.lookup_action(account + action_name).set_enabled(False)
- elif new_state and state == 'online':
- # We go online
- self.lookup_action(account + action_name).set_enabled(True)
-
-
-app = GajimApplication()
-app.run(sys.argv)
diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py
deleted file mode 100644
index b8b40afa6..000000000
--- a/src/gajim_themes_window.py
+++ /dev/null
@@ -1,409 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gajim_themes_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import Pango
-import dialogs
-import gtkgui_helpers
-
-from common import gajim
-
-class GajimThemesWindow:
-
- def __init__(self):
- self.xml = gtkgui_helpers.get_gtk_builder('gajim_themes_window.ui')
- self.window = self.xml.get_object('gajim_themes_window')
- self.window.set_transient_for(gajim.interface.instances[
- 'preferences'].window)
-
- self.options = ['account', 'group', 'contact', 'banner']
- self.options_combobox = self.xml.get_object('options_combobox')
- self.textcolor_checkbutton = self.xml.get_object('textcolor_checkbutton')
- self.background_checkbutton = self.xml.get_object('background_checkbutton')
- self.textfont_checkbutton = self.xml.get_object('textfont_checkbutton')
- self.text_colorbutton = self.xml.get_object('text_colorbutton')
- self.background_colorbutton = self.xml.get_object('background_colorbutton')
- self.text_fontbutton = self.xml.get_object('text_fontbutton')
- self.bold_togglebutton = self.xml.get_object('bold_togglebutton')
- self.italic_togglebutton = self.xml.get_object('italic_togglebutton')
- self.themes_tree = self.xml.get_object('themes_treeview')
- self.theme_options_vbox = self.xml.get_object('theme_options_vbox')
- self.theme_options_table = self.xml.get_object('theme_options_table')
- self.colorbuttons = {}
- for chatstate in ('inactive', 'composing', 'paused', 'gone',
- 'muc_msg', 'muc_directed_msg'):
- self.colorbuttons[chatstate] = self.xml.get_object(chatstate + \
- '_colorbutton')
- model = Gtk.ListStore(str)
- self.themes_tree.set_model(model)
- col = Gtk.TreeViewColumn(_('Theme'))
- self.themes_tree.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', 0)
- renderer.connect('edited', self.on_theme_cell_edited)
- renderer.set_property('editable', True)
- self.current_theme = gajim.config.get('roster_theme')
- self.no_update = False
- self.fill_themes_treeview()
- self.select_active_theme()
- self.current_option = self.options[0]
- self.set_theme_options(self.current_theme, self.current_option)
-
- self.xml.connect_signals(self)
- self.window.connect('delete-event', self.on_themese_window_delete_event)
- self.themes_tree.get_selection().connect('changed',
- self.selection_changed)
- self.window.show_all()
-
- def on_themese_window_delete_event(self, widget, event):
- self.window.hide()
- return True # do NOT destroy the window
-
- def on_close_button_clicked(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].update_theme_list()
- self.window.hide()
-
- def on_theme_cell_edited(self, cell, row, new_name):
- model = self.themes_tree.get_model()
- iter_ = model.get_iter_from_string(row)
- old_name = model.get_value(iter_, 0)
- if old_name == new_name:
- return
- if old_name == 'default':
- dialogs.ErrorDialog(
- _('You cannot make changes to the default theme'),
- _('Please create a new clean theme.'))
- return
- new_config_name = new_name.replace(' ', '_')
- if new_config_name in gajim.config.get_per('themes'):
- return
- gajim.config.add_per('themes', new_config_name)
- # Copy old theme values
- old_config_name = old_name.replace(' ', '_')
- properties = ['textcolor', 'bgcolor', 'font', 'fontattrs']
- gajim.config.add_per('themes', new_config_name)
- for option in self.options:
- for property_ in properties:
- option_name = option + property_
- gajim.config.set_per('themes', new_config_name, option_name,
- gajim.config.get_per('themes', old_config_name, option_name))
- gajim.config.del_per('themes', old_config_name)
- if old_config_name == gajim.config.get('roster_theme'):
- gajim.config.set('roster_theme', new_config_name)
- model.set_value(iter_, 0, new_name)
- self.current_theme = new_name
-
- def fill_themes_treeview(self):
- model = self.themes_tree.get_model()
- model.clear()
- for config_theme in gajim.config.get_per('themes'):
- theme = config_theme.replace('_', ' ')
- model.append([theme])
-
- def select_active_theme(self):
- model = self.themes_tree.get_model()
- iter_ = model.get_iter_first()
- active_theme = gajim.config.get('roster_theme').replace('_', ' ')
- while iter_:
- theme = model[iter_][0]
- if theme == active_theme:
- self.themes_tree.get_selection().select_iter(iter_)
- if active_theme == 'default':
- self.xml.get_object('remove_button').set_sensitive(False)
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- else:
- self.xml.get_object('remove_button').set_sensitive(True)
- self.theme_options_vbox.set_sensitive(True)
- self.theme_options_table.set_sensitive(True)
- break
- iter_ = model.iter_next(iter_)
-
- def selection_changed(self, widget = None):
- (model, iter_) = self.themes_tree.get_selection().get_selected()
- selected = self.themes_tree.get_selection().get_selected_rows()
- if not iter_ or selected[1] == []:
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- return
- self.current_theme = model.get_value(iter_, 0)
- self.current_theme = self.current_theme.replace(' ', '_')
- self.set_theme_options(self.current_theme)
- if self.current_theme == 'default':
- self.xml.get_object('remove_button').set_sensitive(False)
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- else:
- self.xml.get_object('remove_button').set_sensitive(True)
- self.theme_options_vbox.set_sensitive(True)
- self.theme_options_table.set_sensitive(True)
-
- def on_add_button_clicked(self, widget):
- model = self.themes_tree.get_model()
- iter_ = model.append()
- i = 0
- # don't confuse translators
- theme_name = _('theme name')
- theme_name_ns = theme_name.replace(' ', '_')
- while theme_name_ns + str(i) in gajim.config.get_per('themes'):
- i += 1
- model.set_value(iter_, 0, theme_name + str(i))
- gajim.config.add_per('themes', theme_name_ns + str(i))
- self.themes_tree.get_selection().select_iter(iter_)
- col = self.themes_tree.get_column(0)
- path = model.get_path(iter_)
- self.themes_tree.set_cursor(path, col, True)
-
- def on_remove_button_clicked(self, widget):
- (model, iter_) = self.themes_tree.get_selection().get_selected()
- if not iter_:
- return
- if self.current_theme == gajim.config.get('roster_theme'):
- dialogs.ErrorDialog(
- _('You cannot delete your current theme'),
- _('Pick another theme to use first.'))
- return
- self.theme_options_vbox.set_sensitive(False)
- self.theme_options_table.set_sensitive(False)
- self.xml.get_object('remove_button').set_sensitive(False)
- gajim.config.del_per('themes', self.current_theme)
- model.remove(iter_)
-
- def set_theme_options(self, theme, option = 'account'):
- self.no_update = True
- self.options_combobox.set_active(self.options.index(option))
- textcolor = gajim.config.get_per('themes', theme, option + 'textcolor')
- if textcolor:
- state = True
- rgba = Gdk.RGBA()
- rgba.parse(textcolor)
- self.text_colorbutton.set_rgba(rgba)
- else:
- state = False
- self.textcolor_checkbutton.set_active(state)
- self.text_colorbutton.set_sensitive(state)
- bgcolor = gajim.config.get_per('themes', theme, option + 'bgcolor')
- if bgcolor:
- state = True
- rgba = Gdk.RGBA()
- rgba.parse(bgcolor)
- self.background_colorbutton.set_rgba(rgba)
- else:
- state = False
- self.background_checkbutton.set_active(state)
- self.background_colorbutton.set_sensitive(state)
-
- # get the font name before we set widgets and it will not be overriden
- font_name = gajim.config.get_per('themes', theme, option + 'font')
- font_attrs = gajim.config.get_per('themes', theme, option + 'fontattrs')
- self._set_font_widgets(font_attrs)
- if font_name:
- state = True
- self.text_fontbutton.set_font_name(font_name)
- else:
- state = False
- self.textfont_checkbutton.set_active(state)
- self.text_fontbutton.set_sensitive(state)
- self.no_update = False
- gajim.interface.roster.change_roster_style(None)
-
- for chatstate in ('inactive', 'composing', 'paused', 'gone',
- 'muc_msg', 'muc_directed_msg'):
- color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \
- '_color')
- rgba = Gdk.RGBA()
- rgba.parse(color)
- self.colorbuttons[chatstate].set_rgba(rgba)
-
- def on_textcolor_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.text_colorbutton.set_sensitive(state)
- self._set_color(state, self.text_colorbutton,
- 'textcolor')
-
- def on_background_checkbutton_toggled(self, widget):
- state = widget.get_active()
- self.background_colorbutton.set_sensitive(state)
- self._set_color(state, self.background_colorbutton,
- 'bgcolor')
-
- def on_textfont_checkbutton_toggled(self, widget):
- self.text_fontbutton.set_sensitive(widget.get_active())
- self._set_font()
-
- def on_text_colorbutton_color_set(self, widget):
- self._set_color(True, widget, 'textcolor')
-
- def on_background_colorbutton_color_set(self, widget):
- self._set_color(True, widget, 'bgcolor')
-
- def on_text_fontbutton_font_set(self, widget):
- self._set_font()
-
- def on_options_combobox_changed(self, widget):
- index = self.options_combobox.get_active()
- if index == -1:
- return
- self.current_option = self.options[index]
- self.set_theme_options(self.current_theme,
- self.current_option)
-
- def on_bold_togglebutton_toggled(self, widget):
- if not self.no_update:
- self._set_font()
-
- def on_italic_togglebutton_toggled(self, widget):
- if not self.no_update:
- self._set_font()
-
- def _set_color(self, state, widget, option):
- """
- Set color value in prefs and update the UI
- """
- if state:
- color = widget.get_color()
- color_string = gtkgui_helpers.make_color_string(color)
- else:
- color_string = ''
- begin_option = ''
- if not option.startswith('state'):
- begin_option = self.current_option
- gajim.config.set_per('themes', self.current_theme,
- begin_option + option, color_string)
- # use faster functions for this
- if self.current_option == 'banner':
- gajim.interface.roster.repaint_themed_widgets()
- return
- if self.no_update:
- return
- gajim.interface.roster.change_roster_style(self.current_option)
-
- def _set_font(self):
- """
- Set font value in prefs and update the UI
- """
- state = self.textfont_checkbutton.get_active()
- if state:
- font_string = self.text_fontbutton.get_font_name()
- else:
- font_string = ''
- gajim.config.set_per('themes', self.current_theme,
- self.current_option + 'font', font_string)
- font_attrs = self._get_font_attrs()
- gajim.config.set_per('themes', self.current_theme,
- self.current_option + 'fontattrs', font_attrs)
- # use faster functions for this
- if self.current_option == 'banner':
- gajim.interface.roster.repaint_themed_widgets()
- if self.no_update:
- return
- gajim.interface.roster.change_roster_style(self.current_option)
-
- def _toggle_font_widgets(self, font_props):
- """
- Toggle font buttons with the bool values of font_props tuple
- """
- self.bold_togglebutton.set_active(font_props[0])
- self.italic_togglebutton.set_active(font_props[1])
-
- def _get_font_description(self):
- """
- Return a FontDescription from togglebuttons states
- """
- fd = Pango.FontDescription()
- if self.bold_togglebutton.get_active():
- fd.set_weight(Pango.Weight.BOLD)
- if self.italic_togglebutton.get_active():
- fd.set_style(Pango.Style.ITALIC)
- return fd
-
- def _set_font_widgets(self, font_attrs):
- """
- Set the correct toggle state of font style buttons by a font string of
- type 'BI'
- """
- font_props = [False, False, False]
- if font_attrs:
- if font_attrs.find('B') != -1:
- font_props[0] = True
- if font_attrs.find('I') != -1:
- font_props[1] = True
- self._toggle_font_widgets(font_props)
-
- def _get_font_attrs(self):
- """
- Get a string with letters of font attribures: 'BI'
- """
- attrs = ''
- if self.bold_togglebutton.get_active():
- attrs += 'B'
- if self.italic_togglebutton.get_active():
- attrs += 'I'
- return attrs
-
-
- def _get_font_props(self, font_name):
- """
- Get tuple of font properties: weight, style
- """
- font_props = [False, False, False]
- font_description = Pango.FontDescription(font_name)
- if font_description.get_weight() != Pango.Weight.NORMAL:
- font_props[0] = True
- if font_description.get_style() != Pango.Style.ITALIC:
- font_props[1] = True
- return font_props
-
- def on_inactive_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_inactive_color')
- self.no_update = False
-
- def on_composing_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_composing_color')
- self.no_update = False
-
- def on_paused_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_paused_color')
- self.no_update = False
-
- def on_gone_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_gone_color')
- self.no_update = False
-
- def on_muc_msg_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_muc_msg_color')
- self.no_update = False
-
- def on_muc_directed_msg_colorbutton_color_set(self, widget):
- self.no_update = True
- self._set_color(True, widget, 'state_muc_directed_msg_color')
- self.no_update = False
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
deleted file mode 100644
index 0baa07490..000000000
--- a/src/groupchat_control.py
+++ /dev/null
@@ -1,2947 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/groupchat_control.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2007-2008 Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import time
-import locale
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import Pango
-from gi.repository import GLib
-import gtkgui_helpers
-import gui_menu_builder
-import message_control
-import tooltips
-import dialogs
-import config
-import vcard
-import cell_renderer_image
-import dataforms_widget
-import nbxmpp
-
-from enum import IntEnum, unique
-
-from common import events
-from common import gajim
-from common import helpers
-from common import dataforms
-from common import ged
-from common import i18n
-
-from chat_control import ChatControl
-from chat_control_base import ChatControlBase
-
-from command_system.implementation.hosts import PrivateChatCommands
-from command_system.implementation.hosts import GroupChatCommands
-from common.connection_handlers_events import GcMessageOutgoingEvent
-
-import logging
-log = logging.getLogger('gajim.groupchat_control')
-
-@unique
-class Column(IntEnum):
- IMG = 0 # image to show state (online, new message etc)
- NICK = 1 # contact nickame or ROLE name
- TYPE = 2 # type of the row ('contact' or 'role')
- TEXT = 3 # text shown in the cellrenderer
- AVATAR = 4 # avatar of the contact
-
-empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
-empty_pixbuf.fill(0xffffff00)
-
-def tree_cell_data_func(column, renderer, model, iter_, tv=None):
- # cell data func is global, because we don't want it to keep
- # reference to GroupchatControl instance (self)
- theme = gajim.config.get('roster_theme')
- # allocate space for avatar only if needed
- parent_iter = model.iter_parent(iter_)
- if isinstance(renderer, Gtk.CellRendererPixbuf):
- avatar_position = gajim.config.get('avatar_position_in_roster')
- if avatar_position == 'right':
- renderer.set_property('xalign', 1) # align pixbuf to the right
- else:
- renderer.set_property('xalign', 0.5)
- if parent_iter and (model[iter_][Column.AVATAR] or avatar_position == \
- 'left'):
- renderer.set_property('visible', True)
- renderer.set_property('width', gajim.config.get(
- 'roster_avatar_width'))
- else:
- renderer.set_property('visible', False)
- if parent_iter:
- bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
- if bgcolor:
- renderer.set_property('cell-background', bgcolor)
- else:
- renderer.set_property('cell-background', None)
- if isinstance(renderer, Gtk.CellRendererText):
- # foreground property is only with CellRendererText
- color = gajim.config.get_per('themes', theme, 'contacttextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- renderer.set_property('foreground', None)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
- else: # it is root (eg. group)
- bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
- renderer.set_property('cell-background', bgcolor or None)
- if isinstance(renderer, Gtk.CellRendererText):
- # foreground property is only with CellRendererText
- color = gajim.config.get_per('themes', theme, 'grouptextcolor')
- renderer.set_property('foreground', color or None)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
-
-
-class PrivateChatControl(ChatControl):
- TYPE_ID = message_control.TYPE_PM
-
- # Set a command host to bound to. Every command given through a private chat
- # will be processed with this command host.
- COMMAND_HOST = PrivateChatCommands
-
- def __init__(self, parent_win, gc_contact, contact, account, session):
- room_jid = gc_contact.room_jid
- self.room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if room_jid in gajim.interface.minimized_controls[account]:
- self.room_ctrl = gajim.interface.minimized_controls[account][room_jid]
- if self.room_ctrl:
- self.room_name = self.room_ctrl.name
- else:
- self.room_name = room_jid
- self.gc_contact = gc_contact
- ChatControl.__init__(self, parent_win, contact, account, session)
- self.TYPE_ID = 'pm'
- gajim.ged.register_event_handler('caps-received', ged.GUI1,
- self._nec_caps_received_pm)
- gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
- self._nec_gc_presence_received)
-
- def get_our_nick(self):
- return self.room_ctrl.nick
-
- def shutdown(self):
- super(PrivateChatControl, self).shutdown()
- gajim.ged.remove_event_handler('caps-received', ged.GUI1,
- self._nec_caps_received_pm)
- gajim.ged.remove_event_handler('gc-presence-received', ged.GUI1,
- self._nec_gc_presence_received)
-
- def _nec_caps_received_pm(self, obj):
- if obj.conn.name != self.account or \
- obj.fjid != self.gc_contact.get_full_jid():
- return
- self.update_contact()
-
- def _nec_gc_presence_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.fjid != self.full_jid:
- return
- if '303' in obj.status_code:
- self.print_conversation(_('%(nick)s is now known as '
- '%(new_nick)s') % {'nick': obj.nick, 'new_nick': obj.new_nick},
- 'status')
- gc_c = gajim.contacts.get_gc_contact(obj.conn.name, obj.room_jid,
- obj.new_nick)
- c = gc_c.as_contact()
- self.gc_contact = gc_c
- self.contact = c
- if self.session:
- # stop e2e
- if self.session.enable_encryption:
- thread_id = self.session.thread_id
- self.session.terminate_e2e()
- obj.conn.delete_session(obj.fjid, thread_id)
- self.no_autonegotiation = False
- self.draw_banner()
- old_jid = obj.room_jid + '/' + obj.nick
- new_jid = obj.room_jid + '/' + obj.new_nick
- gajim.interface.msg_win_mgr.change_key(old_jid, new_jid,
- obj.conn.name)
- else:
- self.contact.show = obj.show
- self.contact.status = obj.status
- self.gc_contact.show = obj.show
- self.gc_contact.status = obj.status
- uf_show = helpers.get_uf_show(obj.show)
- self.print_conversation(_('%(nick)s is now %(status)s') % {
- 'nick': obj.nick, 'status': uf_show}, 'status')
- if obj.status:
- self.print_conversation(' (', 'status', simple=True)
- self.print_conversation('%s' % (obj.status), 'status',
- simple=True)
- self.print_conversation(')', 'status', simple=True)
- self.parent_win.redraw_tab(self)
- self.update_ui()
-
- def send_message(self, message, xhtml=None, process_commands=True,
- attention=False):
- """
- Call this method to send the message
- """
- message = helpers.remove_invalid_xml_chars(message)
- if not message:
- return
-
- # We need to make sure that we can still send through the room and that
- # the recipient did not go away
- contact = gajim.contacts.get_first_contact_from_jid(self.account,
- self.contact.jid)
- if not contact:
- # contact was from pm in MUC
- room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
- gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
- if not gc_contact:
- dialogs.ErrorDialog(
- _('Sending private message failed'),
- #in second %s code replaces with nickname
- _('You are no longer in group chat "%(room)s" or '
- '"%(nick)s" has left.') % {'room': '\u200E' + room,
- 'nick': nick}, transient_for=self.parent_win.window)
- return
-
- ChatControl.send_message(self, message, xhtml=xhtml,
- process_commands=process_commands, attention=attention)
-
- def update_ui(self):
- if self.contact.show == 'offline':
- self.got_disconnected()
- else:
- self.got_connected()
- ChatControl.update_ui(self)
-
- def update_contact(self):
- self.contact = self.gc_contact.as_contact()
-
- def begin_e2e_negotiation(self):
- self.no_autonegotiation = True
-
- if not self.session:
- fjid = self.gc_contact.get_full_jid()
- new_sess = gajim.connections[self.account].make_new_session(fjid,
- type_=self.type_id)
- self.set_session(new_sess)
-
- self.session.negotiate_e2e(False)
-
- def prepare_context_menu(self, hide_buttonbar_items=False):
- """
- Set compact view menuitem active state sets active and sensitivity state
- for history_menuitem (False for tranasports) and file_transfer_menuitem
- and hide()/show() for add_to_roster_menuitem
- """
- menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
- use_multiple_contacts=False, show_start_chat=False,
- show_encryption=True, control=self,
- show_buttonbar_items=not hide_buttonbar_items,
- gc_contact=self.gc_contact,
- is_anonymous=self.room_ctrl.is_anonymous)
- return menu
-
- def got_disconnected(self):
- ChatControl.got_disconnected(self)
-
-class GroupchatControl(ChatControlBase):
- TYPE_ID = message_control.TYPE_GC
-
- # Set a command host to bound to. Every command given through a group chat
- # will be processed with this command host.
- COMMAND_HOST = GroupChatCommands
-
- def __init__(self, parent_win, contact, acct, is_continued=False):
- ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
- 'groupchat_control', contact, acct)
-
- self.force_non_minimizable = False
- self.is_continued = is_continued
- self.is_anonymous = True
-
- # Controls the state of autorejoin.
- # None - autorejoin is neutral.
- # False - autorejoin is to be prevented (gets reset to initial state in
- # got_connected()).
- # int - autorejoin is being active and working (gets reset to initial
- # state in got_connected()).
- self.autorejoin = None
-
- # Keep error dialog instance to be sure to have only once at a time
- self.error_dialog = None
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(False)
-
- self.actions_button = self.xml.get_object('muc_window_actions_button')
- id_ = self.actions_button.connect('clicked',
- self.on_actions_button_clicked)
- self.handlers[id_] = self.actions_button
-
- widget = self.xml.get_object('change_nick_button')
- widget.set_sensitive(False)
- id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
- self.handlers[id_] = widget
-
- widget = self.xml.get_object('change_subject_button')
- widget.set_sensitive(False)
- id_ = widget.connect('clicked',
- self._on_change_subject_menuitem_activate)
- self.handlers[id_] = widget
-
- formattings_button = self.xml.get_object('formattings_button')
- formattings_button.set_sensitive(False)
-
- widget = self.xml.get_object('bookmark_button')
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.contact.jid:
- widget.hide()
- break
- else:
- id_ = widget.connect('clicked',
- self._on_bookmark_room_menuitem_activate)
- self.handlers[id_] = widget
-
- if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
- img = self.xml.get_object('image7')
- img.set_from_icon_name('bookmark-new', Gtk.IconSize.MENU)
- widget.set_sensitive(
- gajim.connections[self.account].private_storage_supported or \
- (gajim.connections[self.account].pubsub_supported and \
- gajim.connections[self.account].pubsub_publish_options_supported))
- widget.show()
-
- if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
- img = self.xml.get_object('history_image')
- img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
-
- self.current_tooltip = None
- if parent_win is not None:
- # On AutoJoin with minimize Groupchats are created without parent
- # Tooltip Window has to be created with parent
- self.set_tooltip()
-
- widget = self.xml.get_object('list_treeview')
- id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
- self.handlers[id_] = widget
-
- id_ = widget.connect('row_collapsed',
- self.on_list_treeview_row_collapsed)
- self.handlers[id_] = widget
-
- id_ = widget.connect('row_activated',
- self.on_list_treeview_row_activated)
- self.handlers[id_] = widget
-
- id_ = widget.connect('button_press_event',
- self.on_list_treeview_button_press_event)
- self.handlers[id_] = widget
-
- id_ = widget.connect('key_press_event',
- self.on_list_treeview_key_press_event)
- self.handlers[id_] = widget
-
- self.room_jid = self.contact.jid
- self.nick = contact.name
- self.new_nick = ''
- self.name = ''
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.room_jid:
- self.name = bm['name']
- break
- if not self.name:
- self.name = self.room_jid.split('@')[0]
-
- compact_view = gajim.config.get('compact_view')
- self.chat_buttons_set_visible(compact_view)
- self.widget_set_visible(self.xml.get_object('banner_eventbox'),
- gajim.config.get('hide_groupchat_banner'))
- self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
- gajim.config.get('hide_groupchat_occupants_list'))
-
- self._last_selected_contact = None # None or holds jid, account tuple
-
- # muc attention flag (when we are mentioned in a muc)
- # if True, the room has mentioned us
- self.attention_flag = False
-
- # sorted list of nicks who mentioned us (last at the end)
- self.attention_list = []
- self.room_creation = int(time.time()) # Use int to reduce mem usage
- self.nick_hits = []
- self.last_key_tabs = False
-
- self.subject = ''
-
- # nickname coloring
- self.gc_count_nicknames_colors = -1
- self.gc_custom_colors = {}
- self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
- split(':'))
-
- self.name_label = self.xml.get_object('banner_name_label')
- self.event_box = self.xml.get_object('banner_eventbox')
-
- self.list_treeview = self.xml.get_object('list_treeview')
- selection = self.list_treeview.get_selection()
- id_ = selection.connect('changed',
- self.on_list_treeview_selection_changed)
- self.handlers[id_] = selection
- id_ = self.list_treeview.connect('style-set',
- self.on_list_treeview_style_set)
- self.handlers[id_] = self.list_treeview
- self.resize_from_another_muc = False
- # we want to know when the the widget resizes, because that is
- # an indication that the hpaned has moved...
- self.hpaned = self.xml.get_object('hpaned')
- id_ = self.hpaned.connect('notify', self.on_hpaned_notify)
- self.handlers[id_] = self.hpaned
-
- # set the position of the current hpaned
- hpaned_position = gajim.config.get('gc-hpaned-position')
- self.hpaned.set_position(hpaned_position)
-
- #status_image, shown_nick, type, nickname, avatar
- self.columns = [Gtk.Image, str, str, str, GdkPixbuf.Pixbuf]
- self.model = Gtk.TreeStore(*self.columns)
- self.model.set_sort_func(Column.NICK, self.tree_compare_iters)
- self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING)
-
- # columns
- column = Gtk.TreeViewColumn()
- # list of renderers with attributes / properties in the form:
- # (name, renderer_object, expand?, attribute_name, attribute_value,
- # cell_data_func, func_arg)
- self.renderers_list = []
- # Number of renderers plugins added
- self.nb_ext_renderers = 0
- self.renderers_propertys = {}
- renderer_image = cell_renderer_image.CellRendererImage(0, 0)
- self.renderers_propertys[renderer_image] = ('width', 26)
- renderer_text = Gtk.CellRendererText()
- self.renderers_propertys[renderer_text] = ('ellipsize',
- Pango.EllipsizeMode.END)
-
- self.renderers_list += (
- # status img
- ('icon', renderer_image, False,
- 'image', Column.IMG, tree_cell_data_func, self.list_treeview),
- # contact name
- ('name', renderer_text, True,
- 'markup', Column.TEXT, tree_cell_data_func, self.list_treeview))
-
- # avatar img
- avater_renderer = ('avatar', Gtk.CellRendererPixbuf(),
- False, 'pixbuf', Column.AVATAR,
- tree_cell_data_func, self.list_treeview)
-
- if gajim.config.get('avatar_position_in_roster') == 'right':
- self.renderers_list.append(avater_renderer)
- else:
- self.renderers_list.insert(0, avater_renderer)
-
- self.fill_column(column)
- self.list_treeview.append_column(column)
-
- # workaround to avoid gtk arrows to be shown
- column = Gtk.TreeViewColumn() # 2nd COLUMN
- renderer = Gtk.CellRendererPixbuf()
- column.pack_start(renderer, False)
- self.list_treeview.append_column(column)
- column.set_visible(False)
- self.list_treeview.set_expander_column(column)
-
- self.setup_seclabel(self.xml.get_object('label_selector'))
-
- self.form_widget = None
-
- # Encryption
- self.lock_image = self.xml.get_object('lock_image')
- self.authentication_button = self.xml.get_object(
- 'authentication_button')
- id_ = self.authentication_button.connect('clicked',
- self._on_authentication_button_clicked)
- self.handlers[id_] = self.authentication_button
- self.set_lock_image()
-
- self.encryption_menu = self.xml.get_object('encryption_menu')
- self.encryption_menu.set_menu_model(
- gui_menu_builder.get_encryption_menu(self.contact, self.type_id))
- self.set_encryption_menu_icon()
-
- gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
- self._nec_gc_presence_received)
- gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
- self._nec_gc_message_received)
- gajim.ged.register_event_handler('vcard-published', ged.GUI1,
- self._nec_vcard_published)
- gajim.ged.register_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
- self._nec_gc_subject_received)
- gajim.ged.register_event_handler('gc-config-changed-received', ged.GUI1,
- self._nec_gc_config_changed_received)
- gajim.ged.register_event_handler('signed-in', ged.GUI1,
- self._nec_signed_in)
- gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
- self._nec_decrypted_message_received)
- gajim.gc_connected[self.account][self.room_jid] = False
- # disable win, we are not connected yet
- ChatControlBase.got_disconnected(self)
-
- self.update_ui()
- self.widget.show_all()
-
- # PluginSystem: adding GUI extension point for this GroupchatControl
- # instance object
- gajim.plugin_manager.gui_extension_point('groupchat_control', self)
-
- def on_groupchat_maximize(self):
- self.set_tooltip()
- self.add_window_actions()
- self.set_lock_image()
-
- def set_tooltip(self):
- widget = self.xml.get_object('list_treeview')
- if widget.get_tooltip_window():
- return
- widget.set_has_tooltip(True)
- widget.set_tooltip_window(tooltips.GCTooltip(self.parent_win.window))
- id_ = widget.connect('query-tooltip', self.query_tooltip)
- self.handlers[id_] = widget
-
- def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
- try:
- row = self.list_treeview.get_path_at_pos(x_pos, y_pos)[0]
- except TypeError:
- return False
- if not row:
- return False
-
- iter_ = None
- try:
- iter_ = self.model.get_iter(row)
- except Exception:
- return False
-
- typ = self.model[iter_][Column.TYPE]
- nick = self.model[iter_][Column.NICK]
-
- if typ != 'contact':
- return False
-
- if self.current_tooltip != row:
- # If the row changes we hide the current tooltip
- self.current_tooltip = row
- return False
-
- tooltip = widget.get_tooltip_window()
-
- if tooltip.row == row:
- # We already populated the window with the row data
- return True
- tooltip.row = row
-
- contact = gajim.contacts.get_gc_contact(
- self.account, self.room_jid, nick)
- if not contact:
- return False
-
- tooltip.populate(contact)
- return True
-
- def fill_column(self, col):
- for rend in self.renderers_list:
- col.pack_start(rend[1], rend[2])
- col.add_attribute(rend[1], rend[3], rend[4])
- col.set_cell_data_func(rend[1], rend[5], rend[6])
- # set renderers propertys
- for renderer in self.renderers_propertys.keys():
- renderer.set_property(self.renderers_propertys[renderer][0],
- self.renderers_propertys[renderer][1])
-
- def tree_compare_iters(self, model, iter1, iter2, data=None):
- """
- Compare two iters to sort them
- """
- type1 = model[iter1][Column.TYPE]
- type2 = model[iter2][Column.TYPE]
- if not type1 or not type2:
- return 0
- nick1 = model[iter1][Column.NICK]
- nick2 = model[iter2][Column.NICK]
- if not nick1 or not nick2:
- return 0
- if type1 == 'role':
- return locale.strcoll(nick1, nick2)
- if type1 == 'contact':
- gc_contact1 = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick1)
- if not gc_contact1:
- return 0
- if type2 == 'contact':
- gc_contact2 = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick2)
- if not gc_contact2:
- return 0
- if type1 == 'contact' and type2 == 'contact' and \
- gajim.config.get('sort_by_show_in_muc'):
- cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
- 'invisible': 5, 'offline': 6, 'error': 7}
- show1 = cshow[gc_contact1.show]
- show2 = cshow[gc_contact2.show]
- if show1 < show2:
- return -1
- elif show1 > show2:
- return 1
- # We compare names
- name1 = gc_contact1.get_shown_name()
- name2 = gc_contact2.get_shown_name()
- return locale.strcoll(name1.lower(), name2.lower())
-
- def on_msg_textview_populate_popup(self, textview, menu):
- """
- Override the default context menu and we prepend Clear
- and the ability to insert a nick
- """
- ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
- item = Gtk.SeparatorMenuItem.new()
- menu.prepend(item)
-
- item = Gtk.MenuItem.new_with_label(_('Insert Nickname'))
- menu.prepend(item)
- submenu = Gtk.Menu()
- item.set_submenu(submenu)
-
- for nick in sorted(gajim.contacts.get_nick_list(self.account,
- self.room_jid)):
- item = Gtk.MenuItem.new_with_label(nick)
- item.set_use_underline(False)
- submenu.append(item)
- id_ = item.connect('activate', self.append_nick_in_msg_textview,
- nick)
- self.handlers[id_] = item
-
- menu.show_all()
-
- def resize_occupant_treeview(self, position):
- self.resize_from_another_muc = True
- self.hpaned.set_position(position)
- def reset_flag():
- self.resize_from_another_muc = False
- # Reset the flag when everything will be redrawn, and in particular when
- # on_treeview_size_allocate will have been called.
- GLib.idle_add(reset_flag)
-
- def on_hpaned_notify(self, pane, gparamspec):
- """
- The MUC treeview has resized. Move the hpaned in all tabs to match
- """
- # print pane, dir(pane)
- #if gparamspec.name != 'position':
- #return
- if self.resize_from_another_muc:
- # Don't send the event to other MUC
- return
-
- hpaned_position = self.hpaned.get_position()
- gajim.config.set('gc-hpaned-position', hpaned_position)
- for account in gajim.gc_connected:
- for room_jid in [i for i in gajim.gc_connected[account] if \
- gajim.gc_connected[account][i] and i != self.room_jid]:
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- account)
- if not ctrl and room_jid in \
- gajim.interface.minimized_controls[account]:
- ctrl = gajim.interface.minimized_controls[account][room_jid]
- if ctrl and gajim.config.get('one_message_window') != 'never':
- ctrl.resize_occupant_treeview(hpaned_position)
-
- def iter_contact_rows(self):
- """
- Iterate over all contact rows in the tree model
- """
- role_iter = self.model.get_iter_first()
- while role_iter:
- contact_iter = self.model.iter_children(role_iter)
- while contact_iter:
- yield self.model[contact_iter]
- contact_iter = self.model.iter_next(contact_iter)
- role_iter = self.model.iter_next(role_iter)
-
- def on_list_treeview_style_set(self, treeview, style):
- """
- When style (theme) changes, redraw all contacts
- """
- # Get the room_jid from treeview
- for contact in self.iter_contact_rows():
- nick = contact[Column.NICK]
- self.draw_contact(nick)
-
- def on_list_treeview_selection_changed(self, selection):
- model, selected_iter = selection.get_selected()
- self.draw_contact(self.nick)
- if self._last_selected_contact is not None:
- self.draw_contact(self._last_selected_contact)
- if selected_iter is None:
- self._last_selected_contact = None
- return
- contact = model[selected_iter]
- nick = contact[Column.NICK]
- self._last_selected_contact = nick
- if contact[Column.TYPE] != 'contact':
- return
- self.draw_contact(nick, selected=True, focus=True)
-
- def get_tab_label(self, chatstate):
- """
- Markup the label if necessary. Returns a tuple such as: (new_label_str,
- color) either of which can be None if chatstate is given that means we
- have HE SENT US a chatstate
- """
-
- has_focus = self.parent_win.window.get_property('has-toplevel-focus')
- current_tab = self.parent_win.get_active_control() == self
- color = None
- if chatstate == 'attention' and (not has_focus or not current_tab):
- self.attention_flag = True
- color = 'state_muc_directed_msg_color'
- elif chatstate == 'active' or (current_tab and has_focus):
- self.attention_flag = False
- # get active color from gtk
- color = 'active'
- elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
- and not self.attention_flag:
- color = 'state_muc_msg_color'
-
- if self.is_continued:
- # if this is a continued conversation
- label_str = self.get_continued_conversation_name()
- else:
- label_str = self.name
-
- # count waiting highlighted messages
- unread = ''
- num_unread = self.get_nb_unread()
- if num_unread == 1:
- unread = '*'
- elif num_unread > 1:
- unread = '[' + str(num_unread) + ']'
- label_str = unread + label_str
- return (label_str, color)
-
- def get_tab_image(self, count_unread=True):
- # Set tab image (always 16x16)
- tab_image = None
- if gajim.gc_connected[self.account][self.room_jid]:
- tab_image = gtkgui_helpers.load_icon('muc_active')
- else:
- tab_image = gtkgui_helpers.load_icon('muc_inactive')
- return tab_image
-
- def update_ui(self):
- ChatControlBase.update_ui(self)
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- self.draw_contact(nick)
-
- def set_lock_image(self):
- encryption_state = {'visible': self.encryption is not None,
- 'enc_type': self.encryption,
- 'authenticated': False}
-
- if self.encryption:
- gajim.plugin_manager.extension_point(
- 'encryption_state' + self.encryption, self, encryption_state)
-
- self._show_lock_image(**encryption_state)
-
- def _show_lock_image(self, visible, enc_type='',
- authenticated=False):
- """
- Set lock icon visibility and create tooltip
- """
- if authenticated:
- authenticated_string = _('and authenticated')
- img_path = gtkgui_helpers.get_icon_path('security-high')
- else:
- authenticated_string = _('and NOT authenticated')
- img_path = gtkgui_helpers.get_icon_path('security-low')
- self.lock_image.set_from_file(img_path)
-
- tooltip = _('%(type)s encryption is active %(authenticated)s.') % {
- 'type': enc_type, 'authenticated': authenticated_string}
-
- self.authentication_button.set_tooltip_text(tooltip)
- self.widget_set_visible(self.authentication_button, not visible)
- self.lock_image.set_sensitive(visible)
-
- def _on_authentication_button_clicked(self, widget):
- gajim.plugin_manager.extension_point(
- 'encryption_dialog' + self.encryption, self)
-
- def _change_style(self, model, path, iter_, option):
- model[iter_][Column.NICK] = model[iter_][Column.NICK]
-
- def change_roster_style(self):
- self.model.foreach(self._change_style, None)
-
- def repaint_themed_widgets(self):
- ChatControlBase.repaint_themed_widgets(self)
- self.change_roster_style()
-
- def _update_banner_state_image(self):
- banner_status_img = self.xml.get_object('gc_banner_status_image')
- images = gajim.interface.jabber_state_images
- if self.room_jid in gajim.gc_connected[self.account] and \
- gajim.gc_connected[self.account][self.room_jid]:
- image = 'muc_active'
- else:
- image = 'muc_inactive'
- if '32' in images and image in images['32']:
- muc_icon = images['32'][image]
- if muc_icon.get_storage_type() != Gtk.ImageType.EMPTY:
- pix = muc_icon.get_pixbuf()
- banner_status_img.set_from_pixbuf(pix)
- return
- # we need to scale 16x16 to 32x32
- muc_icon = images['16'][image]
- pix = muc_icon.get_pixbuf()
- scaled_pix = pix.scale_simple(32, 32, GdkPixbuf.InterpType.BILINEAR)
- banner_status_img.set_from_pixbuf(scaled_pix)
-
- def get_continued_conversation_name(self):
- """
- Get the name of a continued conversation. Will return Continued
- Conversation if there isn't any other contact in the room
- """
- nicks = []
- for nick in gajim.contacts.get_nick_list(self.account,
- self.room_jid):
- if nick != self.nick:
- nicks.append(nick)
- if nicks != []:
- title = ', '
- title = _('Conversation with ') + title.join(nicks)
- else:
- title = _('Continued conversation')
- return title
-
- def draw_banner_text(self):
- """
- Draw the text in the fat line at the top of the window that houses the
- room jid, subject
- """
- self.name_label.set_ellipsize(Pango.EllipsizeMode.END)
- self.banner_status_label.set_ellipsize(Pango.EllipsizeMode.END)
- font_attrs, font_attrs_small = self.get_font_attrs()
- if self.is_continued:
- name = self.get_continued_conversation_name()
- else:
- name = self.room_jid
- text = '<span %s>%s</span>' % (font_attrs, '\u200E' + name)
- self.name_label.set_markup(text)
-
- if self.subject:
- subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
- subject = GLib.markup_escape_text(subject)
- subject_text = self.urlfinder.sub(self.make_href, subject)
- subject_text = '<span %s>%s</span>' % (font_attrs_small,
- subject_text)
-
- # tooltip must always hold ALL the subject
- self.event_box.set_tooltip_text(self.subject)
- self.banner_status_label.set_no_show_all(False)
- self.banner_status_label.show()
- else:
- subject_text = ''
- self.event_box.set_has_tooltip(False)
- self.banner_status_label.hide()
- self.banner_status_label.set_no_show_all(True)
-
- self.banner_status_label.set_markup(subject_text)
-
- def prepare_context_menu(self, hide_buttonbar_items=False):
- """
- Set sensitivity state for configure_room
- """
- xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
- menu = xml.get_object('gc_control_popup_menu')
-
- bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
- change_nick_menuitem = xml.get_object('change_nick_menuitem')
- configure_room_menuitem = xml.get_object('configure_room_menuitem')
- destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
- change_subject_menuitem = xml.get_object('change_subject_menuitem')
- history_menuitem = xml.get_object('history_menuitem')
- disconnect_menuitem = xml.get_object('disconnect_menuitem')
- minimize_menuitem = xml.get_object('minimize_menuitem')
- request_voice_menuitem = xml.get_object('request_voice_menuitem')
- bookmark_separator = xml.get_object('bookmark_separator')
- separatormenuitem2 = xml.get_object('separatormenuitem2')
- request_voice_separator = xml.get_object('request_voice_separator')
-
- if hide_buttonbar_items:
- change_nick_menuitem.hide()
- change_subject_menuitem.hide()
- bookmark_room_menuitem.hide()
- history_menuitem.hide()
- bookmark_separator.hide()
- separatormenuitem2.hide()
- else:
- change_nick_menuitem.show()
- change_subject_menuitem.show()
- bookmark_room_menuitem.show()
- history_menuitem.show()
- bookmark_separator.show()
- separatormenuitem2.show()
- for bm in gajim.connections[self.account].bookmarks:
- if bm['jid'] == self.room_jid:
- bookmark_room_menuitem.hide()
- bookmark_separator.hide()
- break
-
- ag = Gtk.accel_groups_from_object(self.parent_win.window)[0]
- change_nick_menuitem.add_accelerator('activate', ag, Gdk.KEY_n,
- Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE)
- change_subject_menuitem.add_accelerator('activate', ag,
- Gdk.KEY_t, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.VISIBLE)
- bookmark_room_menuitem.add_accelerator('activate', ag, Gdk.KEY_b,
- Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
- history_menuitem.add_accelerator('activate', ag, Gdk.KEY_h,
- Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE)
-
- if self.contact.jid not in gajim.config.get_per('accounts', self.account,
- 'non_minimized_gc').split(' '):
- minimize_menuitem.set_active(True)
- conn = gajim.connections[self.account]
- if not conn.private_storage_supported and (not conn.pubsub_supported or \
- not conn.pubsub_publish_options_supported):
- bookmark_room_menuitem.set_sensitive(False)
- if gajim.gc_connected[self.account][self.room_jid]:
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- self.nick)
- if c.affiliation not in ('owner', 'admin'):
- configure_room_menuitem.set_sensitive(False)
- else:
- configure_room_menuitem.set_sensitive(True)
- if c.affiliation != 'owner':
- destroy_room_menuitem.set_sensitive(False)
- else:
- destroy_room_menuitem.set_sensitive(True)
- change_subject_menuitem.set_sensitive(True)
- change_nick_menuitem.set_sensitive(True)
- if c.role == 'visitor':
- request_voice_menuitem.set_sensitive(True)
- else:
- request_voice_menuitem.set_sensitive(False)
- else:
- # We are not connected to this groupchat, disable unusable menuitems
- configure_room_menuitem.set_sensitive(False)
- destroy_room_menuitem.set_sensitive(False)
- change_subject_menuitem.set_sensitive(False)
- change_nick_menuitem.set_sensitive(False)
- request_voice_menuitem.set_sensitive(False)
-
- # connect the menuitems to their respective functions
- id_ = bookmark_room_menuitem.connect('activate',
- self._on_bookmark_room_menuitem_activate)
- self.handlers[id_] = bookmark_room_menuitem
-
- id_ = change_nick_menuitem.connect('activate',
- self._on_change_nick_menuitem_activate)
- self.handlers[id_] = change_nick_menuitem
-
- id_ = configure_room_menuitem.connect('activate',
- self._on_configure_room_menuitem_activate)
- self.handlers[id_] = configure_room_menuitem
-
- id_ = destroy_room_menuitem.connect('activate',
- self._on_destroy_room_menuitem_activate)
- self.handlers[id_] = destroy_room_menuitem
-
- id_ = change_subject_menuitem.connect('activate',
- self._on_change_subject_menuitem_activate)
- self.handlers[id_] = change_subject_menuitem
-
- id_ = history_menuitem.connect('activate',
- self._on_history_menuitem_activate)
- self.handlers[id_] = history_menuitem
-
- id_ = disconnect_menuitem.connect('activate',
- self._on_disconnect_menuitem_activate)
- self.handlers[id_] = disconnect_menuitem
-
- id_ = request_voice_menuitem.connect('activate',
- self._on_request_voice_menuitem_activate)
- self.handlers[id_] = request_voice_menuitem
-
- id_ = minimize_menuitem.connect('toggled',
- self.on_minimize_menuitem_toggled)
- self.handlers[id_] = minimize_menuitem
-
- menu.connect('selection-done', self.destroy_menu,
- change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_menuitem)
- return menu
-
- def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
- bookmark_room_menuitem, history_menuitem):
- # destroy accelerators
- ag = Gtk.accel_groups_from_object(self.parent_win.window)[0]
- change_nick_menuitem.remove_accelerator(ag, Gdk.KEY_n,
- Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK)
- change_subject_menuitem.remove_accelerator(ag, Gdk.KEY_t,
- Gdk.ModifierType.MOD1_MASK)
- bookmark_room_menuitem.remove_accelerator(ag, Gdk.KEY_b,
- Gdk.ModifierType.CONTROL_MASK)
- history_menuitem.remove_accelerator(ag, Gdk.KEY_h,
- Gdk.ModifierType.CONTROL_MASK)
- # destroy menu
- menu.destroy()
-
- def _nec_vcard_published(self, obj):
- if obj.conn.name != self.account:
- return
- show = gajim.SHOW_LIST[obj.conn.connected]
- status = obj.conn.status
- obj.conn.send_gc_status(self.nick, self.room_jid, show, status)
-
- def _nec_vcard_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.jid != self.room_jid:
- return
- self.draw_avatar(obj.resource)
-
- def _nec_gc_message_received(self, obj):
- if obj.room_jid != self.room_jid or obj.conn.name != self.account:
- return
- if obj.captcha_form:
- if self.form_widget:
- self.form_widget.hide()
- self.form_widget.destroy()
- self.btn_box.destroy()
- dataform = dataforms.ExtendForm(node=obj.captcha_form)
- self.form_widget = dataforms_widget.DataFormWidget(dataform)
-
- def on_send_dataform_clicked(widget):
- if not self.form_widget:
- return
- form_node = self.form_widget.data_form.get_purged()
- form_node.type_ = 'submit'
- obj.conn.send_captcha(self.room_jid, form_node)
- self.form_widget.hide()
- self.form_widget.destroy()
- self.btn_box.destroy()
- self.form_widget = None
- del self.btn_box
-
- self.form_widget.connect('validated', on_send_dataform_clicked)
- self.form_widget.show_all()
- vbox = self.xml.get_object('gc_textviews_vbox')
- vbox.pack_start(self.form_widget, False, True, 0)
-
- valid_button = Gtk.Button(stock=Gtk.STOCK_OK)
- valid_button.connect('clicked', on_send_dataform_clicked)
- self.btn_box = Gtk.HButtonBox()
- self.btn_box.set_layout(Gtk.ButtonBoxStyle.END)
- self.btn_box.pack_start(valid_button, True, True, 0)
- self.btn_box.show_all()
- vbox.pack_start(self.btn_box, False, False, 0)
- if self.parent_win:
- self.parent_win.redraw_tab(self, 'attention')
- else:
- self.attention_flag = True
- if '100' in obj.status_code:
- # Room is not anonymous
- self.is_anonymous = False
- if not obj.nick:
- # message from server
- self.print_conversation(obj.msgtxt, tim=obj.timestamp,
- xhtml=obj.xhtml_msgtxt, displaymarking=obj.displaymarking)
- else:
- # message from someone
- if obj.has_timestamp:
- # don't print xhtml if it's an old message.
- # Like that xhtml messages are grayed too.
- self.print_old_conversation(obj.msgtxt, contact=obj.nick,
- tim=obj.timestamp, xhtml=None, encrypted=obj.encrypted,
- displaymarking=obj.displaymarking, msg_stanza_id=obj.id_)
- else:
- if obj.nick == self.nick:
- self.last_sent_txt = obj.msgtxt
- self.print_conversation(obj.msgtxt, contact=obj.nick,
- tim=obj.timestamp, xhtml=obj.xhtml_msgtxt,
- displaymarking=obj.displaymarking, encrypted=obj.encrypted,
- correct_id=obj.correct_id, msg_stanza_id=obj.id_)
- obj.needs_highlight = self.needs_visual_notification(obj.msgtxt)
-
- def on_private_message(self, nick, msg, tim, xhtml, session, msg_log_id=None,
- encrypted=False, displaymarking=None):
- # Do we have a queue?
- fjid = self.room_jid + '/' + nick
- no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
-
- event = events.PmEvent(msg, '', 'incoming', tim, encrypted, '',
- msg_log_id, xhtml=xhtml, session=session, form_node=None,
- displaymarking=displaymarking, sent_forwarded=False)
- gajim.events.add_event(self.account, fjid, event)
-
- autopopup = gajim.config.get('autopopup')
- autopopupaway = gajim.config.get('autopopupaway')
- iter_ = self.get_contact_iter(nick)
- path = self.model.get_path(iter_)
- if not autopopup or (not autopopupaway and \
- gajim.connections[self.account].connected > 2):
- if no_queue: # We didn't have a queue: we change icons
- state_images = \
- gajim.interface.roster.get_appropriate_state_images(
- self.room_jid, icon_name='event')
- image = state_images['event']
- self.model[iter_][Column.IMG] = image
- if self.parent_win:
- self.parent_win.show_title()
- self.parent_win.redraw_tab(self)
- else:
- self._start_private_message(nick)
- # Scroll to line
- path_ = path
- path_.up()
- self.list_treeview.expand_row(path_, False)
- self.list_treeview.scroll_to_cell(path)
- self.list_treeview.set_cursor(path)
- contact = gajim.contacts.get_contact_with_highest_priority(
- self.account, self.room_jid)
- if contact:
- gajim.interface.roster.draw_contact(self.room_jid, self.account)
-
- def get_contact_iter(self, nick):
- role_iter = self.model.get_iter_first()
- while role_iter:
- user_iter = self.model.iter_children(role_iter)
- while user_iter:
- if nick == self.model[user_iter][Column.NICK]:
- return user_iter
- else:
- user_iter = self.model.iter_next(user_iter)
- role_iter = self.model.iter_next(role_iter)
- return None
-
- def print_old_conversation(self, text, contact='', tim=None, xhtml = None,
- displaymarking=None, msg_stanza_id=None, encrypted=None):
- if contact:
- if contact == self.nick: # it's us
- kind = 'outgoing'
- else:
- kind = 'incoming'
- else:
- kind = 'status'
- if gajim.config.get('restored_messages_small'):
- small_attr = ['small']
- else:
- small_attr = []
-
- ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
- small_attr, small_attr + ['restored_message'],
- small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml,
- displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
- encrypted=encrypted)
-
- def print_conversation(self, text, contact='', tim=None, xhtml=None,
- graphics=True, displaymarking=None, correct_id=None, msg_stanza_id=None,
- encrypted=None):
- """
- Print a line in the conversation
-
- If contact is set: it's a message from someone or an info message
- (contact = 'info' in such a case).
- If contact is not set: it's a message from the server or help.
- """
- other_tags_for_name = []
- other_tags_for_text = []
- if contact:
- if contact == self.nick: # it's us
- kind = 'outgoing'
- elif contact == 'info':
- kind = 'info'
- contact = None
- else:
- kind = 'incoming'
- # muc-specific chatstate
- if self.parent_win:
- self.parent_win.redraw_tab(self, 'newmsg')
- else:
- kind = 'status'
-
- if kind == 'incoming': # it's a message NOT from us
- # highlighting and sounds
- (highlight, sound) = self.highlighting_for_message(text, tim)
- if contact in self.gc_custom_colors:
- other_tags_for_name.append('gc_nickname_color_' + \
- str(self.gc_custom_colors[contact]))
- else:
- self.gc_count_nicknames_colors += 1
- if self.gc_count_nicknames_colors == self.number_of_colors:
- self.gc_count_nicknames_colors = 0
- self.gc_custom_colors[contact] = \
- self.gc_count_nicknames_colors
- other_tags_for_name.append('gc_nickname_color_' + \
- str(self.gc_count_nicknames_colors))
- if highlight:
- # muc-specific chatstate
- if self.parent_win:
- self.parent_win.redraw_tab(self, 'attention')
- else:
- self.attention_flag = True
- other_tags_for_name.append('bold')
- other_tags_for_text.append('marked')
-
- if contact in self.attention_list:
- self.attention_list.remove(contact)
- elif len(self.attention_list) > 6:
- self.attention_list.pop(0) # remove older
- self.attention_list.append(contact)
-
- if text.startswith('/me ') or text.startswith('/me\n'):
- other_tags_for_text.append('gc_nickname_color_' + \
- str(self.gc_custom_colors[contact]))
-
- self.check_and_possibly_add_focus_out_line()
-
- ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
- other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
- graphics=graphics, displaymarking=displaymarking,
- correct_id=correct_id, msg_stanza_id=msg_stanza_id, encrypted=encrypted)
-
- def get_nb_unread(self):
- type_events = ['printed_marked_gc_msg']
- if gajim.config.get('notify_on_all_muc_messages'):
- type_events.append('printed_gc_msg')
- nb = len(gajim.events.get_events(self.account, self.room_jid,
- type_events))
- nb += self.get_nb_unread_pm()
- return nb
-
- def get_nb_unread_pm(self):
- nb = 0
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- nb += len(gajim.events.get_events(self.account, self.room_jid + \
- '/' + nick, ['pm']))
- return nb
-
- def highlighting_for_message(self, text, tim):
- """
- Returns a 2-Tuple. The first says whether or not to highlight the text,
- the second, what sound to play
- """
- highlight, sound = (None, None)
-
- # Are any of the defined highlighting words in the text?
- if self.needs_visual_notification(text):
- highlight = True
- if gajim.config.get_per('soundevents', 'muc_message_highlight',
- 'enabled'):
- sound = 'highlight'
-
- # Do we play a sound on every muc message?
- elif gajim.config.get_per('soundevents', 'muc_message_received', \
- 'enabled'):
- sound = 'received'
-
- # Is it a history message? Don't want sound-floods when we join.
- if tim != time.localtime():
- sound = None
-
- return (highlight, sound)
-
- def check_and_possibly_add_focus_out_line(self):
- """
- Check and possibly add focus out line for room_jid if it needs it and
- does not already have it as last event. If it goes to add this line
- - remove previous line first
- """
- win = gajim.interface.msg_win_mgr.get_window(self.room_jid,
- self.account)
- if win and self.room_jid == win.get_active_jid() and\
- win.window.get_property('has-toplevel-focus') and\
- self.parent_win.get_active_control() == self:
- # it's the current room and it's the focused window.
- # we have full focus (we are reading it!)
- return
-
- at_the_end = self.conv_textview.at_the_end()
- self.conv_textview.show_focus_out_line(scroll=at_the_end)
-
- def needs_visual_notification(self, text):
- """
- Check text to see whether any of the words in (muc_highlight_words and
- nick) appear
- """
- special_words = gajim.config.get('muc_highlight_words').split(';')
- special_words.append(self.nick)
- # Strip empties: ''.split(';') == [''] and would highlight everything.
- # Also lowercase everything for case insensitive compare.
- special_words = [word.lower() for word in special_words if word]
- text = text.lower()
-
- for special_word in special_words:
- found_here = text.find(special_word)
- while(found_here > -1):
- end_here = found_here + len(special_word)
- if (found_here == 0 or not text[found_here - 1].isalpha()) and \
- (end_here == len(text) or not text[end_here].isalpha()):
- # It is beginning of text or char before is not alpha AND
- # it is end of text or char after is not alpha
- return True
- # continue searching
- start = found_here + 1
- found_here = text.find(special_word, start)
- return False
-
- def set_subject(self, subject):
- self.subject = subject
- self.draw_banner_text()
-
- def _nec_gc_subject_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.room_jid != self.room_jid:
- return
- self.set_subject(obj.subject)
- text = _('%(nick)s has set the subject to %(subject)s') % {
- 'nick': obj.nickname, 'subject': obj.subject}
- if obj.has_timestamp:
- self.print_old_conversation(text)
- else:
- self.print_conversation(text)
-
- def _nec_gc_config_changed_received(self, obj):
- # statuscode is a list
- # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
- # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes...
- # -init
- if obj.room_jid != self.room_jid or obj.conn.name != self.account:
- return
-
- changes = []
- if '100' in obj.status_code:
- # Can be a presence (see chg_contact_status in groupchat_control.py)
- changes.append(_('Any occupant is allowed to see your full JID'))
- self.is_anonymous = False
- if '102' in obj.status_code:
- changes.append(_('Room now shows unavailable members'))
- if '103' in obj.status_code:
- changes.append(_('Room now does not show unavailable members'))
- if '104' in obj.status_code:
- changes.append(_('A setting not related to privacy has been '
- 'changed'))
- if '170' in obj.status_code:
- # Can be a presence (see chg_contact_status in groupchat_control.py)
- changes.append(_('Room logging is now enabled'))
- if '171' in obj.status_code:
- changes.append(_('Room logging is now disabled'))
- if '172' in obj.status_code:
- changes.append(_('Room is now non-anonymous'))
- self.is_anonymous = False
- if '173' in obj.status_code:
- changes.append(_('Room is now semi-anonymous'))
- self.is_anonymous = True
- if '174' in obj.status_code:
- changes.append(_('Room is now fully anonymous'))
- self.is_anonymous = True
-
- for change in changes:
- self.print_conversation(change)
-
- def _nec_signed_in(self, obj):
- if obj.conn.name != self.account:
- return
- password = gajim.gc_passwords.get(self.room_jid, '')
- obj.conn.join_gc(self.nick, self.room_jid, password, rejoin=True)
-
- def _nec_decrypted_message_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.gc_control == self and obj.resource:
- # We got a pm from this room
- nick = obj.resource
- if obj.session.control:
- # print if a control is open
- obj.session.control.print_conversation(obj.msgtxt,
- tim=obj.timestamp, xhtml=obj.xhtml, encrypted=obj.encrypted,
- displaymarking=obj.displaymarking, msg_stanza_id=obj.id_,
- correct_id=obj.correct_id)
- else:
- # otherwise pass it off to the control to be queued
- self.on_private_message(nick, obj.msgtxt, obj.timestamp,
- obj.xhtml, self.session, msg_log_id=obj.msg_log_id,
- encrypted=obj.encrypted, displaymarking=obj.displaymarking)
-
- def _nec_ping_reply(self, obj):
- if obj.control:
- if obj.control != self:
- return
- else:
- if self.contact != obj.contact:
- return
- self.print_conversation(_('Pong! (%s s.)') % obj.seconds)
-
- def got_connected(self):
- # Make autorejoin stop.
- if self.autorejoin:
- GLib.source_remove(self.autorejoin)
- self.autorejoin = None
-
- gajim.gc_connected[self.account][self.room_jid] = True
- ChatControlBase.got_connected(self)
- self.list_treeview.set_model(self.model)
- self.list_treeview.expand_all()
- # We don't redraw the whole banner here, because only icon change
- self._update_banner_state_image()
- if self.parent_win:
- self.parent_win.redraw_tab(self)
-
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(True)
- emoticons_button = self.xml.get_object('emoticons_button')
- emoticons_button.set_sensitive(True)
- formattings_button = self.xml.get_object('formattings_button')
- formattings_button.set_sensitive(True)
- change_nick_button = self.xml.get_object('change_nick_button')
- change_nick_button.set_sensitive(True)
- change_subject_button = self.xml.get_object('change_subject_button')
- change_subject_button.set_sensitive(True)
-
- def got_disconnected(self):
- send_button = self.xml.get_object('send_button')
- send_button.set_sensitive(False)
- emoticons_button = self.xml.get_object('emoticons_button')
- emoticons_button.set_sensitive(False)
- formattings_button = self.xml.get_object('formattings_button')
- formattings_button.set_sensitive(False)
- change_nick_button = self.xml.get_object('change_nick_button')
- change_nick_button.set_sensitive(False)
- change_subject_button = self.xml.get_object('change_subject_button')
- change_subject_button.set_sensitive(False)
- self.list_treeview.set_model(None)
- self.model.clear()
- nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
- for nick in nick_list:
- # Update pm chat window
- fjid = self.room_jid + '/' + nick
- gc_contact = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick)
-
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
- if ctrl:
- gc_contact.show = 'offline'
- gc_contact.status = ''
- ctrl.update_ui()
- if ctrl.parent_win:
- ctrl.parent_win.redraw_tab(ctrl)
-
- gajim.contacts.remove_gc_contact(self.account, gc_contact)
- gajim.gc_connected[self.account][self.room_jid] = False
- ChatControlBase.got_disconnected(self)
- # Tell connection to note the date we disconnect to avoid duplicate logs
- gajim.connections[self.account].gc_got_disconnected(self.room_jid)
- # We don't redraw the whole banner here, because only icon change
- self._update_banner_state_image()
- if self.parent_win:
- self.parent_win.redraw_tab(self)
-
- # Autorejoin stuff goes here.
- # Notice that we don't need to activate autorejoin if connection is lost
- # or in progress.
- if self.autorejoin is None and gajim.account_is_connected(self.account):
- ar_to = gajim.config.get('muc_autorejoin_timeout')
- if ar_to:
- self.autorejoin = GLib.timeout_add_seconds(ar_to, self.rejoin)
-
- def rejoin(self):
- if not self.autorejoin:
- return False
- password = gajim.gc_passwords.get(self.room_jid, '')
- gajim.connections[self.account].join_gc(self.nick, self.room_jid,
- password, rejoin=True)
- return True
-
- def draw_roster(self):
- self.model.clear()
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- gc_contact = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick)
- self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role,
- gc_contact.affiliation, gc_contact.status, gc_contact.jid)
- self.draw_all_roles()
- # Recalculate column width for ellipsizin
- self.list_treeview.columns_autosize()
-
- def on_send_pm(self, widget=None, model=None, iter_=None, nick=None,
- msg=None):
- """
- Open a chat window and if msg is not None - send private message to a
- contact in a room
- """
- if nick is None:
- nick = model[iter_][Column.NICK]
-
- ctrl = self._start_private_message(nick)
- if ctrl and msg:
- ctrl.send_message(msg)
-
- def on_send_file(self, widget, gc_contact):
- """
- Send a file to a contact in the room
- """
- self._on_send_file(gc_contact)
-
- def draw_contact(self, nick, selected=False, focus=False):
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- state_images = gajim.interface.jabber_state_images['16']
- if len(gajim.events.get_events(self.account, self.room_jid + '/' + \
- nick)):
- image = state_images['event']
- else:
- image = state_images[gc_contact.show]
-
- name = GLib.markup_escape_text(gc_contact.name)
-
- # Strike name if blocked
- fjid = self.room_jid + '/' + nick
- if helpers.jid_is_blocked(self.account, fjid):
- name = '<span strikethrough="true">%s</span>' % name
-
- status = gc_contact.status
- # add status msg, if not empty, under contact name in the treeview
- if status and gajim.config.get('show_status_msgs_in_roster'):
- status = status.strip()
- if status != '':
- status = helpers.reduce_chars_newlines(status, max_lines=1)
- # escape markup entities and make them small italic and fg color
- color = gtkgui_helpers.get_fade_color(self.list_treeview,
- selected, focus)
- colorstring = "#%04x%04x%04x" % (int(color.red * 65535),
- int(color.green * 65535), int(color.blue * 65535))
- name += ('\n<span size="small" style="italic" foreground="%s">'
- '%s</span>') % (colorstring, GLib.markup_escape_text(
- status))
-
- if image.get_storage_type() == Gtk.ImageType.PIXBUF and \
- gc_contact.affiliation != 'none' and gajim.config.get(
- 'show_affiliation_in_groupchat'):
- pixbuf1 = image.get_pixbuf().copy()
- pixbuf2 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 4, 4)
- if gc_contact.affiliation == 'owner':
- pixbuf2.fill(0xff0000ff) # Red
- elif gc_contact.affiliation == 'admin':
- pixbuf2.fill(0xffb200ff) # Oragne
- elif gc_contact.affiliation == 'member':
- pixbuf2.fill(0x00ff00ff) # Green
- pixbuf2.composite(pixbuf1, 12, 12, pixbuf2.get_property('width'),
- pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
- GdkPixbuf.InterpType.HYPER, 127)
- image = Gtk.Image.new_from_pixbuf(pixbuf1)
- self.model[iter_][Column.IMG] = image
- self.model[iter_][Column.TEXT] = name
-
- def draw_avatar(self, nick):
- if not gajim.config.get('show_avatars_in_roster'):
- return
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- fake_jid = self.room_jid + '/' + nick
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
- if pixbuf in ('ask', None):
- scaled_pixbuf = empty_pixbuf
- else:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
- if not scaled_pixbuf:
- scaled_pixbuf = empty_pixbuf
- self.model[iter_][Column.AVATAR] = scaled_pixbuf
-
- def draw_role(self, role):
- role_iter = self.get_role_iter(role)
- if not role_iter:
- return
- role_name = helpers.get_uf_role(role, plural=True)
- if gajim.config.get('show_contacts_number'):
- nbr_role, nbr_total = gajim.contacts.get_nb_role_total_gc_contacts(
- self.account, self.room_jid, role)
- role_name += ' (%s/%s)' % (repr(nbr_role), repr(nbr_total))
- self.model[role_iter][Column.TEXT] = role_name
-
- def draw_all_roles(self):
- for role in ('visitor', 'participant', 'moderator'):
- self.draw_role(role)
-
- def _nec_gc_presence_received(self, obj):
- if obj.room_jid != self.room_jid or obj.conn.name != self.account:
- return
- if obj.ptype == 'error':
- return
-
- role = obj.role
- if not role:
- role = 'visitor'
-
- affiliation = obj.affiliation
- if not affiliation:
- affiliation = 'none'
-
- newly_created = False
- nick = i18n.direction_mark + obj.nick
- nick_jid = nick + i18n.direction_mark
-
- # Set to true if role or affiliation have changed
- right_changed = False
-
- if obj.real_jid:
- # delete ressource
- simple_jid = gajim.get_jid_without_resource(obj.real_jid)
- nick_jid += ' (%s)' % simple_jid
-
- # status_code
- # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-\
- # init
- if obj.status_code:
- if '110' in obj.status_code:
- # We just join the room
- if self.room_jid in gajim.automatic_rooms[self.account] and \
- gajim.automatic_rooms[self.account][self.room_jid]['invities']:
- if self.room_jid not in gajim.interface.instances[
- self.account]['gc_config']:
- if obj.affiliation == 'owner':
- # We need to configure the room if it's a new one.
- # We cannot know it's a new one. Status 201 is not
- # sent by all servers.
- gajim.connections[self.account].request_gc_config(
- self.room_jid)
- elif 'continue_tag' in gajim.automatic_rooms[
- self.account][self.room_jid]:
- # We just need to invite contacts
- for jid in gajim.automatic_rooms[self.account][
- self.room_jid]['invities']:
- obj.conn.send_invite(self.room_jid, jid)
- self.print_conversation(_('%(jid)s has been '
- 'invited in this room') % {'jid': jid},
- graphics=False)
- if '100' in obj.status_code:
- # Can be a message (see handle_event_gc_config_change in
- # gajim.py)
- self.print_conversation(
- _('Any occupant is allowed to see your full JID'))
- self.is_anonymous = False
- if '170' in obj.status_code:
- # Can be a message (see handle_event_gc_config_change in
- # gajim.py)
- self.print_conversation(_('Room logging is enabled'))
- if '201' in obj.status_code:
- self.print_conversation(_('A new room has been created'))
- if '210' in obj.status_code:
- self.print_conversation(\
- _('The server has assigned or modified your roomnick'))
-
- if obj.show in ('offline', 'error'):
- if obj.status_code:
- if '307' in obj.status_code:
- if obj.actor is None: # do not print 'kicked by None'
- s = _('%(nick)s has been kicked: %(reason)s') % {
- 'nick': nick, 'reason': obj.reason}
- else:
- s = _('%(nick)s has been kicked by %(who)s: '
- '%(reason)s') % {'nick': nick, 'who': obj.actor,
- 'reason': obj.reason}
- self.print_conversation(s, 'info', graphics=False)
- if obj.nick == self.nick and not gajim.config.get(
- 'muc_autorejoin_on_kick'):
- self.autorejoin = False
- elif '301' in obj.status_code:
- if obj.actor is None: # do not print 'banned by None'
- s = _('%(nick)s has been banned: %(reason)s') % {
- 'nick': nick, 'reason': obj.reason}
- else:
- s = _('%(nick)s has been banned by %(who)s: '
- '%(reason)s') % {'nick': nick, 'who': obj.actor,
- 'reason': obj.reason}
- self.print_conversation(s, 'info', graphics=False)
- if obj.nick == self.nick:
- self.autorejoin = False
- elif '303' in obj.status_code: # Someone changed his or her nick
- if obj.new_nick == self.new_nick or obj.nick == self.nick:
- # We changed our nick
- self.nick = obj.new_nick
- self.new_nick = ''
- s = _('You are now known as %s') % self.nick
- # Stop all E2E sessions
- nick_list = gajim.contacts.get_nick_list(self.account,
- self.room_jid)
- for nick_ in nick_list:
- fjid_ = self.room_jid + '/' + nick_
- ctrl = gajim.interface.msg_win_mgr.get_control(
- fjid_, self.account)
- if ctrl and ctrl.session and \
- ctrl.session.enable_encryption:
- thread_id = ctrl.session.thread_id
- ctrl.session.terminate_e2e()
- gajim.connections[self.account].delete_session(
- fjid_, thread_id)
- ctrl.no_autonegotiation = False
- else:
- s = _('%(nick)s is now known as %(new_nick)s') % {
- 'nick': nick, 'new_nick': obj.new_nick}
- tv = self.conv_textview
- if obj.nick in tv.last_received_message_marks:
- tv.last_received_message_marks[obj.new_nick] = \
- tv.last_received_message_marks[obj.nick]
- del tv.last_received_message_marks[obj.nick]
- if obj.nick in self.last_received_txt:
- self.last_received_txt[obj.new_nick] = \
- self.last_received_txt[obj.nick]
- del self.last_received_txt[obj.nick]
- self.last_received_id[obj.new_nick] = \
- self.last_received_id[obj.nick]
- del self.last_received_id[obj.nick]
- # We add new nick to muc roster here, so we don't see
- # that "new_nick has joined the room" when he just changed
- # nick.
- # add_contact_to_roster will be called a second time
- # after that, but that doesn't hurt
- self.add_contact_to_roster(obj.new_nick, obj.show, role,
- affiliation, obj.status, obj.real_jid)
- if obj.nick in self.attention_list:
- self.attention_list.remove(obj.nick)
- # keep nickname color
- if obj.nick in self.gc_custom_colors:
- self.gc_custom_colors[obj.new_nick] = \
- self.gc_custom_colors[obj.nick]
- # rename vcard / avatar
- puny_jid = helpers.sanitize_filename(self.room_jid)
- puny_nick = helpers.sanitize_filename(obj.nick)
- puny_new_nick = helpers.sanitize_filename(obj.new_nick)
- old_path = os.path.join(gajim.VCARD_PATH, puny_jid,
- puny_nick)
- new_path = os.path.join(gajim.VCARD_PATH, puny_jid,
- puny_new_nick)
- files = {old_path: new_path}
- path = os.path.join(gajim.AVATAR_PATH, puny_jid)
- # possible extensions
- for ext in ('.png', '.jpeg', '_notif_size_bw.png',
- '_notif_size_colored.png'):
- files[os.path.join(path, puny_nick + ext)] = \
- os.path.join(path, puny_new_nick + ext)
- for old_file in files:
- if os.path.exists(old_file) and old_file != \
- files[old_file]:
- if os.path.exists(files[old_file]) and \
- helpers.windowsify(old_file) != helpers.windowsify(
- files[old_file]):
- # Windows require this, but os.remove('test')
- # will also remove 'TEST'
- os.remove(files[old_file])
- os.rename(old_file, files[old_file])
- self.print_conversation(s, 'info', graphics=False)
- elif '321' in obj.status_code:
- s = _('%(nick)s has been removed from the room '
- '(%(reason)s)') % { 'nick': nick,
- 'reason': _('affiliation changed') }
- self.print_conversation(s, 'info', graphics=False)
- elif '322' in obj.status_code:
- s = _('%(nick)s has been removed from the room '
- '(%(reason)s)') % { 'nick': nick,
- 'reason': _('room configuration changed to '
- 'members-only') }
- self.print_conversation(s, 'info', graphics=False)
- elif '332' in obj.status_code:
- s = _('%(nick)s has been removed from the room '
- '(%(reason)s)') % {'nick': nick,
- 'reason': _('system shutdown') }
- self.print_conversation(s, 'info', graphics=False)
- # Room has been destroyed.
- elif 'destroyed' in obj.status_code:
- self.autorejoin = False
- self.print_conversation(obj.reason, 'info', graphics=False)
-
- if len(gajim.events.get_events(self.account, jid=obj.fjid,
- types=['pm'])) == 0:
- self.remove_contact(obj.nick)
- self.draw_all_roles()
- else:
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- obj.nick)
- c.show = obj.show
- c.status = obj.status
- if obj.nick == self.nick and (not obj.status_code or \
- '303' not in obj.status_code): # We became offline
- self.got_disconnected()
- contact = gajim.contacts.\
- get_contact_with_highest_priority(self.account,
- self.room_jid)
- if contact:
- gajim.interface.roster.draw_contact(self.room_jid,
- self.account)
- if self.parent_win:
- self.parent_win.redraw_tab(self)
- else:
- iter_ = self.get_contact_iter(obj.nick)
- if not iter_:
- if '210' in obj.status_code:
- # Server changed our nick
- self.nick = obj.nick
- s = _('You are now known as %s') % nick
- self.print_conversation(s, 'info', graphics=False)
- iter_ = self.add_contact_to_roster(obj.nick, obj.show, role,
- affiliation, obj.status, obj.real_jid)
- newly_created = True
- self.draw_all_roles()
- if obj.status_code and '201' in obj.status_code:
- # We just created the room
- gajim.connections[self.account].request_gc_config(
- self.room_jid)
- else:
- gc_c = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, obj.nick)
- if not gc_c:
- log.error('%s has an iter, but no gc_contact instance' % \
- obj.nick)
- return
- # Re-get vcard if avatar has changed
- # We do that here because we may request it to the real JID if
- # we knows it. connections.py doesn't know it.
- con = gajim.connections[self.account]
- if gc_c and gc_c.jid:
- real_jid = gc_c.jid
- else:
- real_jid = obj.fjid
- if obj.fjid in obj.conn.vcard_shas:
- if obj.avatar_sha != obj.conn.vcard_shas[obj.fjid]:
- server = gajim.get_server_from_jid(self.room_jid)
- if not server.startswith('irc'):
- obj.conn.request_vcard(real_jid, obj.fjid)
- else:
- cached_vcard = obj.conn.get_cached_vcard(obj.fjid, True)
- if cached_vcard and 'PHOTO' in cached_vcard and \
- 'SHA' in cached_vcard['PHOTO']:
- cached_sha = cached_vcard['PHOTO']['SHA']
- else:
- cached_sha = ''
- if cached_sha != obj.avatar_sha:
- # avatar has been updated
- # sha in mem will be updated later
- server = gajim.get_server_from_jid(self.room_jid)
- if not server.startswith('irc'):
- obj.conn.request_vcard(real_jid, obj.fjid)
- else:
- # save sha in mem NOW
- obj.conn.vcard_shas[obj.fjid] = obj.avatar_sha
-
- actual_affiliation = gc_c.affiliation
- if affiliation != actual_affiliation:
- if obj.actor:
- st = _('** Affiliation of %(nick)s has been set to '
- '%(affiliation)s by %(actor)s') % {'nick': nick_jid,
- 'affiliation': affiliation, 'actor': obj.actor}
- else:
- st = _('** Affiliation of %(nick)s has been set to '
- '%(affiliation)s') % {'nick': nick_jid,
- 'affiliation': affiliation}
- if obj.reason:
- st += ' (%s)' % obj.reason
- self.print_conversation(st, graphics=False)
- right_changed = True
- actual_role = self.get_role(obj.nick)
- if role != actual_role:
- self.remove_contact(obj.nick)
- self.add_contact_to_roster(obj.nick, obj.show, role,
- affiliation, obj.status, obj.real_jid)
- self.draw_role(actual_role)
- self.draw_role(role)
- if obj.actor:
- st = _('** Role of %(nick)s has been set to %(role)s '
- 'by %(actor)s') % {'nick': nick_jid, 'role': role,
- 'actor': obj.actor}
- else:
- st = _('** Role of %(nick)s has been set to '
- '%(role)s') % {'nick': nick_jid, 'role': role}
- if obj.reason:
- st += ' (%s)' % obj.reason
- self.print_conversation(st, graphics=False)
- right_changed = True
- else:
- if gc_c.show == obj.show and gc_c.status == obj.status and \
- gc_c.affiliation == affiliation: # no change
- return
- gc_c.show = obj.show
- gc_c.affiliation = affiliation
- gc_c.status = obj.status
- self.draw_contact(obj.nick)
- if (time.time() - self.room_creation) > 30 and obj.nick != self.nick \
- and (not obj.status_code or '303' not in obj.status_code) and not \
- right_changed:
- st = ''
- print_status = None
- for bookmark in gajim.connections[self.account].bookmarks:
- if bookmark['jid'] == self.room_jid:
- print_status = bookmark.get('print_status', None)
- break
- if not print_status:
- print_status = gajim.config.get('print_status_in_muc')
- if obj.show == 'offline':
- if obj.nick in self.attention_list:
- self.attention_list.remove(obj.nick)
- if obj.show == 'offline' and print_status in ('all', 'in_and_out') \
- and (not obj.status_code or '307' not in obj.status_code):
- st = _('%s has left') % nick_jid
- if obj.reason:
- st += ' [%s]' % obj.reason
- else:
- if newly_created and print_status in ('all', 'in_and_out'):
- st = _('%s has joined the group chat') % nick_jid
- elif print_status == 'all':
- st = _('%(nick)s is now %(status)s') % {'nick': nick_jid,
- 'status': helpers.get_uf_show(obj.show)}
- if st:
- if obj.status:
- st += ' (' + obj.status + ')'
- self.print_conversation(st, graphics=False)
-
- def add_contact_to_roster(self, nick, show, role, affiliation, status,
- jid=''):
- role_name = helpers.get_uf_role(role, plural=True)
-
- resource = ''
- if jid:
- jids = jid.split('/', 1)
- j = jids[0]
- if len(jids) > 1:
- resource = jids[1]
- else:
- j = ''
-
- name = nick
-
- role_iter = self.get_role_iter(role)
- if not role_iter:
- role_iter = self.model.append(None,
- [gajim.interface.jabber_state_images['16']['closed'], role,
- 'role', role_name, None] + [None] * self.nb_ext_renderers)
- self.draw_all_roles()
- iter_ = self.model.append(role_iter, [None, nick, 'contact', name, None] + \
- [None] * self.nb_ext_renderers)
- if not nick in gajim.contacts.get_nick_list(self.account,
- self.room_jid):
- gc_contact = gajim.contacts.create_gc_contact(
- room_jid=self.room_jid, account=self.account,
- name=nick, show=show, status=status, role=role,
- affiliation=affiliation, jid=j, resource=resource)
- gajim.contacts.add_gc_contact(self.account, gc_contact)
- self.draw_contact(nick)
- self.draw_avatar(nick)
- # Do not ask avatar to irc rooms as irc transports reply with messages
- server = gajim.get_server_from_jid(self.room_jid)
- if gajim.config.get('ask_avatars_on_startup') and \
- not server.startswith('irc'):
- fake_jid = self.room_jid + '/' + nick
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(fake_jid)
- if pixbuf == 'ask':
- if j and not self.is_anonymous:
- gajim.connections[self.account].request_vcard(j, fake_jid)
- else:
- gajim.connections[self.account].request_vcard(fake_jid,
- fake_jid)
- if nick == self.nick: # we became online
- self.got_connected()
- if self.list_treeview.get_model():
- self.list_treeview.expand_row((self.model.get_path(role_iter)), False)
- if self.is_continued:
- self.draw_banner_text()
- return iter_
-
- def get_role_iter(self, role):
- role_iter = self.model.get_iter_first()
- while role_iter:
- role_name = self.model[role_iter][Column.NICK]
- if role == role_name:
- return role_iter
- role_iter = self.model.iter_next(role_iter)
- return None
-
- def remove_contact(self, nick):
- """
- Remove a user from the contacts_list
- """
- iter_ = self.get_contact_iter(nick)
- if not iter_:
- return
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- if gc_contact:
- gajim.contacts.remove_gc_contact(self.account, gc_contact)
- parent_iter = self.model.iter_parent(iter_)
- self.model.remove(iter_)
- if self.model.iter_n_children(parent_iter) == 0:
- self.model.remove(parent_iter)
-
- def send_message(self, message, xhtml=None, process_commands=True):
- """
- Call this function to send our message
- """
- if not message:
- return
-
- if self.encryption:
- self.sendmessage = True
- gajim.plugin_manager.extension_point(
- 'send_message' + self.encryption, self)
- if not self.sendmessage:
- return
-
- if process_commands and self.process_as_command(message):
- return
-
- message = helpers.remove_invalid_xml_chars(message)
-
- if not message:
- return
-
- label = self.get_seclabel()
- if message != '' or message != '\n':
- self.save_message(message, 'sent')
-
- def _cb(obj):
- # we'll save sent message text when we'll receive it in
- # _nec_gc_message_received
- self.last_sent_msg = obj.msg_id
- if self.correcting:
- self.correcting = False
- gtkgui_helpers.remove_css_class(
- self.msg_textview, 'msgcorrectingcolor')
-
- if self.correcting and self.last_sent_msg:
- correct_id = self.last_sent_msg
- else:
- correct_id = None
- # Send the message
- gajim.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
- account=self.account, jid=self.room_jid, message=message,
- xhtml=xhtml, label=label, callback=_cb, correct_id=correct_id,
- automatic_message=False))
- self.msg_textview.get_buffer().set_text('')
- self.msg_textview.grab_focus()
-
- def get_role(self, nick):
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- if gc_contact:
- return gc_contact.role
- else:
- return 'visitor'
-
- def minimizable(self):
- if self.force_non_minimizable:
- return False
- if self.contact.jid not in gajim.config.get_per('accounts', self.account,
- 'non_minimized_gc').split(' '):
- return True
- return False
-
- def minimize(self, status='offline'):
- # Minimize it
- win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
- self.account)
- ctrl = win.get_control(self.contact.jid, self.account)
-
- ctrl_page = win.notebook.page_num(ctrl.widget)
- control = win.notebook.get_nth_page(ctrl_page)
-
- win.notebook.remove_page(ctrl_page)
- control.unparent()
- ctrl.parent_win = None
-
- gajim.interface.roster.add_groupchat(self.contact.jid, self.account,
- status = self.subject)
-
- del win._controls[self.account][self.contact.jid]
-
- def send_chatstate(self, state, contact):
- """
- Send OUR chatstate as STANDLONE chat state message (eg. no body)
- to contact only if new chatstate is different from the previous one
- if jid is not specified, send to active tab
- """
- # JEP 85 does not allow resending the same chatstate
- # this function checks for that and just returns so it's safe to call it
- # with same state.
-
- # This functions also checks for violation in state transitions
- # and raises RuntimeException with appropriate message
- # more on that http://xmpp.org/extensions/xep-0085.html#statechart
-
- # do not send if we have chat state notifications disabled
- # that means we won't reply to the <active/> from other peer
- # so we do not broadcast jep85 capabalities
- chatstate_setting = gajim.config.get('outgoing_chat_state_notifications')
- if chatstate_setting == 'disabled':
- return
-
- elif chatstate_setting == 'composing_only' and state != 'active' and\
- state != 'composing':
- return
-
- # if the new state we wanna send (state) equals
- # the current state (contact.our_chatstate) then return
- if contact.our_chatstate == state:
- return
-
- # if wel're inactive prevent composing (XEP violation)
- if contact.our_chatstate == 'inactive' and state == 'composing':
- # go active before
- gajim.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, chatstate='active',
- control=self))
- contact.our_chatstate = 'active'
- self.reset_kbd_mouse_timeout_vars()
-
- gajim.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
- account=self.account, jid=self.contact.jid, chatstate=state,
- control=self))
-
- contact.our_chatstate = state
- if state == 'active':
- self.reset_kbd_mouse_timeout_vars()
-
- 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()
- # PluginSystem: removing GUI extension points connected with
- # GrouphatControl instance object
- gajim.plugin_manager.remove_gui_extension_point('groupchat_control',
- self)
-
- # Preventing autorejoin from being activated
- self.autorejoin = False
-
- gajim.ged.remove_event_handler('gc-presence-received', ged.GUI1,
- self._nec_gc_presence_received)
- gajim.ged.remove_event_handler('gc-message-received', ged.GUI1,
- self._nec_gc_message_received)
- gajim.ged.remove_event_handler('vcard-published', ged.GUI1,
- self._nec_vcard_published)
- gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- gajim.ged.remove_event_handler('gc-subject-received', ged.GUI1,
- self._nec_gc_subject_received)
- gajim.ged.remove_event_handler('gc-config-changed-received', ged.GUI1,
- self._nec_gc_config_changed_received)
- gajim.ged.remove_event_handler('signed-in', ged.GUI1,
- self._nec_signed_in)
- gajim.ged.remove_event_handler('decrypted-message-received', ged.GUI2,
- self._nec_decrypted_message_received)
-
- if self.room_jid in gajim.gc_connected[self.account] and \
- gajim.gc_connected[self.account][self.room_jid]:
- # Tell connection to note the date we disconnect to avoid duplicate
- # logs. We do it only when connected because if connection was lost
- # there may be new messages since disconnection.
- gajim.connections[self.account].gc_got_disconnected(self.room_jid)
- gajim.connections[self.account].send_gc_status(self.nick,
- self.room_jid, show='offline', status=status)
- nick_list = gajim.contacts.get_nick_list(self.account, self.room_jid)
- for nick in nick_list:
- # Update pm chat window
- fjid = self.room_jid + '/' + nick
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(fjid,
- self.account)
- if ctrl:
- contact = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, nick)
- contact.show = 'offline'
- contact.status = ''
- ctrl.update_ui()
- ctrl.parent_win.redraw_tab(ctrl)
- for sess in gajim.connections[self.account].get_sessions(fjid):
- if sess.control:
- sess.control.no_autonegotiation = False
- if sess.enable_encryption:
- sess.terminate_e2e()
- gajim.connections[self.account].delete_session(fjid,
- sess.thread_id)
- # They can already be removed by the destroy function
- if self.room_jid in gajim.contacts.get_gc_list(self.account):
- gajim.contacts.remove_room(self.account, self.room_jid)
- del gajim.gc_connected[self.account][self.room_jid]
- # Save hpaned position
- gajim.config.set('gc-hpaned-position', self.hpaned.get_position())
- # remove all register handlers on wigets, created by self.xml
- # to prevent circular references among objects
- for i in list(self.handlers.keys()):
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- # Remove unread events from systray
- gajim.events.remove_events(self.account, self.room_jid)
-
- def safe_shutdown(self):
- if self.minimizable():
- return True
- includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
- excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
- # whether to ask for comfirmation before closing muc
- if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\
- and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \
- not in excludes:
- return False
- return True
-
- def allow_shutdown(self, method, on_yes, on_no, on_minimize):
- if self.minimizable():
- on_minimize(self)
- return
- if method == self.parent_win.CLOSE_ESC:
- iter_ = self.list_treeview.get_selection().get_selected()[1]
- if iter_:
- self.list_treeview.get_selection().unselect_all()
- on_no(self)
- return
- includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
- excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
- # whether to ask for comfirmation before closing muc
- if (gajim.config.get('confirm_close_muc') or self.room_jid in includes)\
- and gajim.gc_connected[self.account][self.room_jid] and self.room_jid \
- not in excludes:
-
- def on_ok(clicked):
- if clicked:
- # user does not want to be asked again
- gajim.config.set('confirm_close_muc', False)
- on_yes(self)
-
- def on_cancel(clicked):
- if clicked:
- # user does not want to be asked again
- gajim.config.set('confirm_close_muc', False)
- on_no(self)
-
- pritext = _('Are you sure you want to leave group chat "%s"?')\
- % self.name
- sectext = _('If you close this window, you will be disconnected '
- 'from this group chat.')
-
- dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('_Do not ask me again'), on_response_ok=on_ok,
- on_response_cancel=on_cancel,
- transient_for=self.parent_win.window)
- return
-
- on_yes(self)
-
- def set_control_active(self, state):
- self.conv_textview.allow_focus_out_line = True
- self.attention_flag = False
- ChatControlBase.set_control_active(self, state)
- if not state:
- # add the focus-out line to the tab we are leaving
- self.check_and_possibly_add_focus_out_line()
- # Sending active to undo unread state
- self.parent_win.redraw_tab(self, 'active')
-
- def get_specific_unread(self):
- # returns the number of the number of unread msgs
- # for room_jid & number of unread private msgs with each contact
- # that we have
- nb = 0
- for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
- fjid = self.room_jid + '/' + nick
- nb += len(gajim.events.get_events(self.account, fjid))
- # gc can only have messages as event
- return nb
-
- def _on_change_subject_menuitem_activate(self, widget):
- def on_ok(subject):
- # Note, we don't update self.subject since we don't know whether it
- # will work yet
- gajim.connections[self.account].send_gc_subject(self.room_jid,
- subject)
-
- dialogs.InputTextDialog(_('Changing Subject'),
- _('Please specify the new subject:'), input_str=self.subject,
- ok_handler=on_ok, transient_for=self.parent_win.window)
-
- def _on_disconnect_menuitem_activate(self, widget):
- self.force_non_minimizable = True
- self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND)
- self.force_non_minimizable = False
-
- def _on_change_nick_menuitem_activate(self, widget):
- if 'change_nick_dialog' in gajim.interface.instances:
- gajim.interface.instances['change_nick_dialog'].dialog.present()
- else:
- title = _('Changing Nickname')
- prompt = _('Please specify the new nickname you want to use:')
- gajim.interface.instances['change_nick_dialog'] = \
- dialogs.ChangeNickDialog(self.account, self.room_jid, title,
- prompt, change_nick=True, transient_for=self.parent_win.window)
-
- def _on_configure_room_menuitem_activate(self, widget):
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- self.nick)
- if c.affiliation == 'owner':
- gajim.connections[self.account].request_gc_config(self.room_jid)
- elif c.affiliation == 'admin':
- if self.room_jid not in gajim.interface.instances[self.account][
- 'gc_config']:
- gajim.interface.instances[self.account]['gc_config'][
- self.room_jid] = config.GroupchatConfigWindow(self.account,
- self.room_jid)
-
- def _on_destroy_room_menuitem_activate(self, widget):
- def on_ok(reason, jid):
- if jid:
- # Test jid
- try:
- jid = helpers.parse_jid(jid)
- except Exception:
- dialogs.ErrorDialog(_('Invalid group chat JID'),
- _('The group chat JID has not allowed characters.'))
- return
- gajim.connections[self.account].destroy_gc_room(self.room_jid,
- reason, jid)
-
- # Ask for a reason
- dialogs.DoubleInputDialog(_('Destroying %s') % '\u200E' + \
- self.room_jid, _('You are going to remove this room permanently.'
- '\nYou may specify a reason below:'),
- _('You may also enter an alternate venue:'), ok_handler=on_ok,
- transient_for=self.parent_win.window)
-
- def _on_bookmark_room_menuitem_activate(self, widget):
- """
- Bookmark the room, without autojoin and not minimized
- """
- password = gajim.gc_passwords.get(self.room_jid, '')
- gajim.interface.add_gc_bookmark(self.account, self.name, self.room_jid,\
- '0', '0', password, self.nick)
-
- def _on_request_voice_menuitem_activate(self, widget):
- """
- Request voice in the current room
- """
- gajim.connections[self.account].request_voice(self.room_jid)
-
- def _on_drag_data_received(self, widget, context, x, y, selection,
- target_type, timestamp):
- # Invite contact to groupchat
- treeview = gajim.interface.roster.tree
- model = treeview.get_model()
- if not selection.get_data() or target_type == 80:
- # target_type = 80 means a file is dropped
- return
- data = selection.get_data()
- path = treeview.get_selection().get_selected_rows()[1][0]
- iter_ = model.get_iter(path)
- type_ = model[iter_][2]
- if type_ != 'contact': # source is not a contact
- return
- contact_jid = data
- gajim.connections[self.account].send_invite(self.room_jid, contact_jid)
- self.print_conversation(_('%(jid)s has been invited in this room') % {
- 'jid': contact_jid}, graphics=False)
-
- def _on_message_textview_key_press_event(self, widget, event):
- res = ChatControlBase._on_message_textview_key_press_event(self, widget,
- event)
- if res:
- return True
-
- if event.keyval == Gdk.KEY_Tab: # TAB
- message_buffer = widget.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
- cursor_position = message_buffer.get_insert()
- end_iter = message_buffer.get_iter_at_mark(cursor_position)
- text = message_buffer.get_text(start_iter, end_iter, False)
-
- splitted_text = text.split()
-
- # nick completion
- # check if tab is pressed with empty message
- if len(splitted_text): # if there are any words
- begin = splitted_text[-1] # last word we typed
- else:
- begin = ''
-
- gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
- with_refer_to_nick_char = False
- after_nick_len = 1 # the space that is printed after we type [Tab]
-
- # first part of this if : works fine even if refer_to_nick_char
- if gc_refer_to_nick_char and text.endswith(
- gc_refer_to_nick_char + ' '):
- with_refer_to_nick_char = True
- after_nick_len = len(gc_refer_to_nick_char + ' ')
- if len(self.nick_hits) and self.last_key_tabs and \
- text[:-after_nick_len].endswith(self.nick_hits[0]):
- # we should cycle
- # Previous nick in list may had a space inside, so we check text
- # and not splitted_text and store it into 'begin' var
- self.nick_hits.append(self.nick_hits[0])
- begin = self.nick_hits.pop(0)
- else:
- self.nick_hits = [] # clear the hit list
- list_nick = gajim.contacts.get_nick_list(self.account,
- self.room_jid)
- list_nick.sort(key=str.lower) # case-insensitive sort
- if begin == '':
- # empty message, show lasts nicks that highlighted us first
- for nick in self.attention_list:
- if nick in list_nick:
- list_nick.remove(nick)
- list_nick.insert(0, nick)
-
- list_nick.remove(self.nick) # Skip self
- for nick in list_nick:
- fjid = self.room_jid + '/' + nick
- if nick.lower().startswith(begin.lower()) and not \
- helpers.jid_is_blocked(self.account, fjid):
- # the word is the begining of a nick
- self.nick_hits.append(nick)
- if len(self.nick_hits):
- if len(splitted_text) < 2 or with_refer_to_nick_char:
- # This is the 1st word of the line or no word or we are cycling
- # at the beginning, possibly with a space in one nick
- add = gc_refer_to_nick_char + ' '
- else:
- add = ' '
- start_iter = end_iter.copy()
- if self.last_key_tabs and with_refer_to_nick_char or (text and \
- text[-1] == ' '):
- # have to accomodate for the added space from last
- # completion
- # gc_refer_to_nick_char may be more than one char!
- start_iter.backward_chars(len(begin) + len(add))
- elif self.last_key_tabs and not gajim.config.get(
- 'shell_like_completion'):
- # have to accomodate for the added space from last
- # completion
- start_iter.backward_chars(len(begin) + \
- len(gc_refer_to_nick_char))
- else:
- start_iter.backward_chars(len(begin))
-
- message_buffer.delete(start_iter, end_iter)
- # get a shell-like completion
- # if there's more than one nick for this completion, complete
- # only the part that all these nicks have in common
- if gajim.config.get('shell_like_completion') and \
- len(self.nick_hits) > 1:
- end = False
- completion = ''
- add = "" # if nick is not complete, don't add anything
- while not end and len(completion) < len(self.nick_hits[0]):
- completion = self.nick_hits[0][:len(completion)+1]
- for nick in self.nick_hits:
- if completion.lower() not in nick.lower():
- end = True
- completion = completion[:-1]
- break
- # if the current nick matches a COMPLETE existing nick,
- # and if the user tab TWICE, complete that nick (with the
- # "add")
- if self.last_key_tabs:
- for nick in self.nick_hits:
- if nick == completion:
- # The user seems to want this nick, so
- # complete it as if it were the only nick
- # available
- add = gc_refer_to_nick_char + ' '
- else:
- completion = self.nick_hits[0]
- message_buffer.insert_at_cursor(completion + add)
- self.last_key_tabs = True
- return True
- self.last_key_tabs = False
-
- def on_list_treeview_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- selection = widget.get_selection()
- iter_ = selection.get_selected()[1]
- if iter_:
- widget.get_selection().unselect_all()
- return True
-
- def on_list_treeview_row_expanded(self, widget, iter_, path):
- """
- When a row is expanded: change the icon of the arrow
- """
- model = widget.get_model()
- image = gajim.interface.jabber_state_images['16']['opened']
- model[iter_][Column.IMG] = image
-
- def on_list_treeview_row_collapsed(self, widget, iter_, path):
- """
- When a row is collapsed: change the icon of the arrow
- """
- model = widget.get_model()
- image = gajim.interface.jabber_state_images['16']['closed']
- model[iter_][Column.IMG] = image
-
- def kick(self, widget, nick):
- """
- Kick a user
- """
- def on_ok(reason):
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'none', reason)
-
- # ask for reason
- dialogs.InputDialog(_('Kicking %s') % nick,
- _('You may specify a reason below:'), ok_handler=on_ok,
- transient_for=self.parent_win.window)
-
- def mk_menu(self, event, iter_):
- """
- Make contact's popup menu
- """
- nick = self.model[iter_][Column.NICK]
- c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- fjid = self.room_jid + '/' + nick
- jid = c.jid
- target_affiliation = c.affiliation
- target_role = c.role
-
- # looking for user's affiliation and role
- user_nick = self.nick
- user_affiliation = gajim.contacts.get_gc_contact(self.account,
- self.room_jid, user_nick).affiliation
- user_role = self.get_role(user_nick)
-
- # making menu from gtk builder
- xml = gtkgui_helpers.get_gtk_builder('gc_occupants_menu.ui')
-
- # these conditions were taken from JEP 0045
- item = xml.get_object('kick_menuitem')
- if user_role != 'moderator' or \
- (user_affiliation == 'admin' and target_affiliation == 'owner') or \
- (user_affiliation == 'member' and target_affiliation in ('admin',
- 'owner')) or (user_affiliation == 'none' and target_affiliation != \
- 'none'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.kick, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('voice_checkmenuitem')
- item.set_active(target_role != 'visitor')
- if user_role != 'moderator' or \
- user_affiliation == 'none' or \
- (user_affiliation=='member' and target_affiliation!='none') or \
- target_affiliation in ('admin', 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_voice_checkmenuitem_activate,
- nick)
- self.handlers[id_] = item
-
- item = xml.get_object('moderator_checkmenuitem')
- item.set_active(target_role == 'moderator')
- if not user_affiliation in ('admin', 'owner') or \
- target_affiliation in ('admin', 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate,
- nick)
- self.handlers[id_] = item
-
- item = xml.get_object('ban_menuitem')
- if not user_affiliation in ('admin', 'owner') or \
- (target_affiliation in ('admin', 'owner') and\
- user_affiliation != 'owner'):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.ban, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('member_checkmenuitem')
- item.set_active(target_affiliation != 'none')
- if not user_affiliation in ('admin', 'owner') or \
- (user_affiliation != 'owner' and target_affiliation in ('admin',
- 'owner')):
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_member_checkmenuitem_activate,
- jid)
- self.handlers[id_] = item
-
- item = xml.get_object('admin_checkmenuitem')
- item.set_active(target_affiliation in ('admin', 'owner'))
- if not user_affiliation == 'owner':
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_admin_checkmenuitem_activate,
- jid)
- self.handlers[id_] = item
-
- item = xml.get_object('owner_checkmenuitem')
- item.set_active(target_affiliation == 'owner')
- if not user_affiliation == 'owner':
- item.set_sensitive(False)
- id_ = item.connect('activate', self.on_owner_checkmenuitem_activate,
- jid)
- self.handlers[id_] = item
-
- item = xml.get_object('invite_menuitem')
- if jid and c.name != self.nick:
- bookmarked = False
- contact = gajim.contacts.get_contact(self.account, jid, c.resource)
- if contact and contact.supports(nbxmpp.NS_CONFERENCE):
- bookmarked=True
- gui_menu_builder.build_invite_submenu(item, ((c, self.account),),
- ignore_rooms=[self.room_jid], show_bookmarked=bookmarked)
- else:
- item.set_sensitive(False)
-
- item = xml.get_object('information_menuitem')
- id_ = item.connect('activate', self.on_info, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('history_menuitem')
- id_ = item.connect('activate', self.on_history, nick)
- self.handlers[id_] = item
-
- item = xml.get_object('add_to_roster_menuitem')
- our_jid = gajim.get_jid_from_account(self.account)
- if not jid or jid == our_jid or not gajim.connections[self.account].\
- roster_supported:
- item.set_sensitive(False)
- else:
- id_ = item.connect('activate', self.on_add_to_roster, jid)
- self.handlers[id_] = item
-
- item = xml.get_object('block_menuitem')
- item2 = xml.get_object('unblock_menuitem')
- if helpers.jid_is_blocked(self.account, fjid):
- item.set_no_show_all(True)
- item.hide()
- id_ = item2.connect('activate', self.on_unblock, nick)
- self.handlers[id_] = item2
- else:
- id_ = item.connect('activate', self.on_block, nick)
- self.handlers[id_] = item
- item2.set_no_show_all(True)
- item2.hide()
-
- item = xml.get_object('send_private_message_menuitem')
- id_ = item.connect('activate', self.on_send_pm, self.model, iter_)
- self.handlers[id_] = item
-
- item = xml.get_object('send_file_menuitem')
- if not c.resource:
- item.set_sensitive(False)
- else:
- id_ = item.connect('activate', self.on_send_file, c)
- self.handlers[id_] = item
-
- # show the popup now!
- menu = xml.get_object('gc_occupants_menu')
- menu.show_all()
- menu.attach_to_widget(gajim.interface.roster.window, None)
- menu.popup(None, None, None, None, event.button, event.time)
-
- def _start_private_message(self, nick):
- gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
- nick_jid = gc_c.get_full_jid()
-
- ctrl = gajim.interface.msg_win_mgr.get_control(nick_jid, self.account)
- if not ctrl:
- ctrl = gajim.interface.new_private_chat(gc_c, self.account)
-
- if ctrl:
- ctrl.parent_win.set_active_tab(ctrl)
-
- return ctrl
-
- def on_row_activated(self, widget, path):
- """
- When an iter is activated (dubblick or single click if gnome is set this
- way
- """
- if path.get_depth() == 1: # It's a group
- if (widget.row_expanded(path)):
- widget.collapse_row(path)
- else:
- widget.expand_row(path, False)
- else: # We want to send a private message
- nick = self.model[path][Column.NICK]
- self._start_private_message(nick)
-
- def on_list_treeview_row_activated(self, widget, path, col=0):
- """
- When an iter is double clicked: open the chat window
- """
- if not gajim.single_click:
- self.on_row_activated(widget, path)
-
- def on_list_treeview_button_press_event(self, widget, event):
- """
- Popup user's group's or agent menu
- """
- try:
- pos = widget.get_path_at_pos(int(event.x), int(event.y))
- path, x = pos[0], pos[2]
- except TypeError:
- widget.get_selection().unselect_all()
- return
- if event.button == 3: # right click
- widget.get_selection().select_path(path)
- iter_ = self.model.get_iter(path)
- if path.get_depth() == 2:
- self.mk_menu(event, iter_)
- return True
-
- elif event.button == 2: # middle click
- widget.get_selection().select_path(path)
- iter_ = self.model.get_iter(path)
- if path.get_depth() == 2:
- nick = self.model[iter_][Column.NICK]
- self._start_private_message(nick)
- return True
-
- elif event.button == 1: # left click
- if gajim.single_click and not event.get_state() & Gdk.ModifierType.SHIFT_MASK:
- self.on_row_activated(widget, path)
- return True
- else:
- iter_ = self.model.get_iter(path)
- nick = self.model[iter_][Column.NICK]
- if not nick in gajim.contacts.get_nick_list(self.account,
- self.room_jid):
- # it's a group
- if x < 27:
- if (widget.row_expanded(path)):
- widget.collapse_row(path)
- else:
- widget.expand_row(path, False)
- elif event.get_state() & Gdk.ModifierType.SHIFT_MASK:
- self.append_nick_in_msg_textview(self.msg_textview, nick)
- self.msg_textview.grab_focus()
- return True
-
- def append_nick_in_msg_textview(self, widget, nick):
- message_buffer = self.msg_textview.get_buffer()
- start_iter, end_iter = message_buffer.get_bounds()
- cursor_position = message_buffer.get_insert()
- end_iter = message_buffer.get_iter_at_mark(cursor_position)
- text = message_buffer.get_text(start_iter, end_iter, False)
- start = ''
- if text: # Cursor is not at first position
- if not text[-1] in (' ', '\n', '\t'):
- start = ' '
- add = ' '
- else:
- gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
- add = gc_refer_to_nick_char + ' '
- message_buffer.insert_at_cursor(start + nick + add)
-
- def grant_voice(self, widget, nick):
- """
- Grant voice privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'participant')
-
- def revoke_voice(self, widget, nick):
- """
- Revoke voice privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'visitor')
-
- def grant_moderator(self, widget, nick):
- """
- Grant moderator privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'moderator')
-
- def revoke_moderator(self, widget, nick):
- """
- Revoke moderator privilege to a user
- """
- gajim.connections[self.account].gc_set_role(self.room_jid, nick,
- 'participant')
-
- def ban(self, widget, jid):
- """
- Ban a user
- """
- def on_ok(reason):
- gajim.connections[self.account].gc_set_affiliation(self.room_jid,
- jid, 'outcast', reason)
-
- # to ban we know the real jid. so jid is not fakejid
- nick = gajim.get_nick_from_jid(jid)
- # ask for reason
- dialogs.InputDialog(_('Banning %s') % nick,
- _('You may specify a reason below:'), ok_handler=on_ok,
- transient_for=self.parent_win.window)
-
- def grant_membership(self, widget, jid):
- """
- Grant membership privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'member')
-
- def revoke_membership(self, widget, jid):
- """
- Revoke membership privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'none')
-
- def grant_admin(self, widget, jid):
- """
- Grant administrative privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'admin')
-
- def revoke_admin(self, widget, jid):
- """
- Revoke administrative privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'member')
-
- def grant_owner(self, widget, jid):
- """
- Grant owner privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'owner')
-
- def revoke_owner(self, widget, jid):
- """
- Revoke owner privilege to a user
- """
- gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
- 'admin')
-
- def on_info(self, widget, nick):
- """
- Call vcard_information_window class to display user's information
- """
- gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
- nick)
- contact = gc_contact.as_contact()
- if contact.jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][contact.jid].\
- window.present()
- else:
- gajim.interface.instances[self.account]['infos'][contact.jid] = \
- vcard.VcardWindow(contact, self.account, gc_contact)
-
- def on_history(self, widget, nick):
- jid = gajim.construct_fjid(self.room_jid, nick)
- self._on_history_menuitem_activate(widget=widget, jid=jid)
-
- def on_add_to_roster(self, widget, jid):
- dialogs.AddNewContactWindow(self.account, jid)
-
- def on_block(self, widget, nick):
- fjid = self.room_jid + '/' + nick
- connection = gajim.connections[self.account]
- default = connection.privacy_default_list
- if fjid in connection.blocked_contacts:
- return
- max_order = connection.get_max_blocked_list_order()
- new_rule = {'order': str(max_order + 1), 'type': 'jid',
- 'action': 'deny', 'value' : fjid, 'child': ['message', 'iq',
- 'presence-out']}
- connection.blocked_list.append(new_rule)
- connection.blocked_contacts.append(fjid)
- self.draw_contact(nick)
- connection.set_privacy_list(default, connection.blocked_list)
- if len(connection.blocked_list) == 1:
- connection.set_default_list(default)
-
- def on_unblock(self, widget, nick):
- fjid = self.room_jid + '/' + nick
- connection = gajim.connections[self.account]
- default = connection.privacy_default_list
- connection.new_blocked_list = []
- # needed for draw_contact:
- if fjid in connection.blocked_contacts:
- connection.blocked_contacts.remove(fjid)
- self.draw_contact(nick)
- for rule in connection.blocked_list:
- if rule['action'] != 'deny' or rule['type'] != 'jid' \
- or rule['value'] != fjid:
- connection.new_blocked_list.append(rule)
-
- if len(connection.new_blocked_list) == 0:
- connection.blocked_list = []
- connection.blocked_contacts = []
- connection.blocked_groups = []
- connection.set_default_list('')
- connection.del_privacy_list(default)
- if 'privay_list_block' in gajim.interface.instances[self.account]:
- del gajim.interface.instances[self.account]\
- ['privay_list_block']
- else:
- connection.set_privacy_list(default, connection.new_blocked_list)
-
- def on_voice_checkmenuitem_activate(self, widget, nick):
- if widget.get_active():
- self.grant_voice(widget, nick)
- else:
- self.revoke_voice(widget, nick)
-
- def on_moderator_checkmenuitem_activate(self, widget, nick):
- if widget.get_active():
- self.grant_moderator(widget, nick)
- else:
- self.revoke_moderator(widget, nick)
-
- def on_member_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_membership(widget, jid)
- else:
- self.revoke_membership(widget, jid)
-
- def on_admin_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_admin(widget, jid)
- else:
- self.revoke_admin(widget, jid)
-
- def on_owner_checkmenuitem_activate(self, widget, jid):
- if widget.get_active():
- self.grant_owner(widget, jid)
- else:
- self.revoke_owner(widget, jid)
diff --git a/src/groups.py b/src/groups.py
deleted file mode 100644
index 3ead0aa92..000000000
--- a/src/groups.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/groups.py
-##
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''Window to create new post for discussion groups service.'''
-
-from common import gajim
-from nbxmpp import Node
-import gtkgui_helpers
-
-class GroupsPostWindow:
- def __init__(self, account, servicejid, groupid):
- """
- Open new 'create post' window to create message for groupid on servicejid
- service
- """
- assert isinstance(servicejid, str)
- assert isinstance(groupid, str)
-
- self.account = account
- self.servicejid = servicejid
- self.groupid = groupid
-
- self.xml = gtkgui_helpers.get_gtk_builder('groups_post_window.ui')
- self.window = self.xml.get_object('groups_post_window')
- for name in ('from_entry', 'subject_entry', 'contents_textview'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_cancel_button_clicked(self, w):
- """
- Close window
- """
- self.window.destroy()
-
- def on_send_button_clicked(self, w):
- """
- Gather info from widgets and send it as a message
- """
- # constructing item to publish... that's atom:entry element
- item = Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'})
- author = item.addChild('author')
- author.addChild('name', {}, [self.from_entry.get_text()])
- item.addChild('generator', {}, ['Gajim'])
- item.addChild('title', {}, [self.subject_entry.get_text()])
-
- buf = self.contents_textview.get_buffer()
- item.addChild('content', {}, [buf.get_text(buf.get_start_iter(), buf.get_end_iter(), True)])
-
- # publish it to node
- gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0')
-
- # close the window
- self.window.destroy()
diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py
deleted file mode 100644
index 77e2890a5..000000000
--- a/src/gtkexcepthook.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gtkexcepthook.py
-##
-## Copyright (C) 2016 Philipp Hörist <philipp AT hoerist.com>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Stephan Erb <steve-e AT h3c.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import sys
-import os
-import traceback
-import threading
-import webbrowser
-import gi
-
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from io import StringIO
-from common import configpaths
-
-glade_file = os.path.join(configpaths.get('GUI'), 'exception_dialog.ui')
-
-_exception_in_progress = threading.Lock()
-
-
-def _hook(type_, value, tb):
- if not _exception_in_progress.acquire(False):
- # Exceptions have piled up, so we use the default exception
- # handler for such exceptions
- sys.__excepthook__(type_, value, tb)
- return
-
- ExceptionDialog(type_, value, tb)
- _exception_in_progress.release()
-
-
-class ExceptionDialog():
- def __init__(self, type_, value, tb):
- builder = Gtk.Builder()
- builder.add_from_file(glade_file)
- self.dialog = builder.get_object("ExceptionDialog")
- builder.connect_signals(self)
- builder.get_object("report_btn").grab_focus()
- self.exception_view = builder.get_object("exception_view")
- buffer_ = self.exception_view.get_buffer()
- trace = StringIO()
- traceback.print_exception(type_, value, tb, None, trace)
- buffer_.set_text(trace.getvalue())
- self.exception_view.set_editable(False)
- self.dialog.run()
-
- def on_report_clicked(self, *args):
- url = 'https://dev.gajim.org/gajim/gajim/issues'
- webbrowser.open(url, new=2)
-
- def on_close_clicked(self, *args):
- self.dialog.destroy()
-
-
-def init():
- # gdb/kdm etc if we use startx this is not True
- if os.name == 'nt' or not sys.stderr.isatty():
- # FIXME: maybe always show dialog?
- sys.excepthook = _hook
-
-# this is just to assist testing (python gtkexcepthook.py)
-if __name__ == '__main__':
- init()
- print(sys.version)
- raise Exception()
diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py
deleted file mode 100644
index 78f188cfc..000000000
--- a/src/gtkgui_helpers.py
+++ /dev/null
@@ -1,1188 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gtkgui_helpers.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2007 Junglecow J <junglecow AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 James Newton <redshodan AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import xml.sax.saxutils
-import gi
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GLib
-from gi.repository import Pango
-import os
-import sys
-try:
- from PIL import Image
-except:
- pass
-from io import BytesIO
-
-import logging
-log = logging.getLogger('gajim.gtkgui_helpers')
-
-from common import i18n
-from common import gajim
-from common import pep
-from common import configpaths
-
-gtk_icon_theme = Gtk.IconTheme.get_default()
-gtk_icon_theme.append_search_path(gajim.ICONS_DIR)
-
-class Color:
- BLACK = Gdk.RGBA(red=0, green=0, blue=0, alpha=1)
-
-def get_icon_pixmap(icon_name, size=16, color=None, quiet=False):
- try:
- iconinfo = gtk_icon_theme.lookup_icon(icon_name, size, 0)
- if not iconinfo:
- raise GLib.GError
- if color:
- pixbuf, was_symbolic = iconinfo.load_symbolic(*color)
- return pixbuf
- return iconinfo.load_icon()
- except GLib.GError as e:
- if not quiet:
- log.error('Unable to load icon %s: %s' % (icon_name, str(e)))
-
-def get_icon_path(icon_name, size=16):
- try:
- icon_info = gtk_icon_theme.lookup_icon(icon_name, size, 0)
- if icon_info == None:
- log.error('Icon not found: %s' % icon_name)
- return ""
- else:
- return icon_info.get_filename()
- except GLib.GError as e:
- log.error("Unable to find icon %s: %s" % (icon_name, str(e)))
-
-import vcard
-import dialogs
-
-
-HAS_PYWIN32 = True
-if os.name == 'nt':
- try:
- import win32file
- import win32con
- import pywintypes
- except ImportError:
- HAS_PYWIN32 = False
-
-from common import helpers
-
-screen_w = Gdk.Screen.width()
-screen_h = Gdk.Screen.height()
-
-def add_image_to_button(button, icon_name):
- img = Gtk.Image()
- path_img = get_icon_path(icon_name)
- img.set_from_file(path_img)
- button.set_image(img)
-
-GUI_DIR = os.path.join(gajim.DATA_DIR, 'gui')
-def get_gtk_builder(file_name, widget=None):
- file_path = os.path.join(GUI_DIR, file_name)
- builder = Gtk.Builder()
- builder.set_translation_domain(i18n.APP)
- if widget:
- builder.add_objects_from_file(file_path, [widget])
- else:
- builder.add_from_file(file_path)
- return builder
-
-def get_completion_liststore(entry):
- """
- Create a completion model for entry widget completion list consists of
- (Pixbuf, Text) rows
- """
- completion = Gtk.EntryCompletion()
- liststore = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
-
- render_pixbuf = Gtk.CellRendererPixbuf()
- completion.pack_start(render_pixbuf, False)
- completion.add_attribute(render_pixbuf, 'pixbuf', 0)
-
- render_text = Gtk.CellRendererText()
- completion.pack_start(render_text, True)
- completion.add_attribute(render_text, 'text', 1)
- completion.set_property('text_column', 1)
- completion.set_model(liststore)
- entry.set_completion(completion)
- return liststore
-
-
-def popup_emoticons_under_button(menu, button, parent_win):
- """
- Popup the emoticons menu under button, which is in parent_win
- """
- window_x1, window_y1 = parent_win.get_origin()[1:]
-
- def position_menu_under_button(menu, _x=None, _y=None, data=None):
- # inline function, which will not keep refs, when used as CB
- alloc = button.get_allocation()
- button_x, button_y = alloc.x, alloc.y
- translated_coordinates = button.translate_coordinates(
- gajim.interface.roster.window, 0, 0)
- if translated_coordinates:
- button_x, button_y = translated_coordinates
-
- # now convert them to X11-relative
- window_x, window_y = window_x1, window_y1
- x = window_x + button_x
- y = window_y + button_y
-
- menu_height = menu.get_preferred_size()[0].height
-
- ## should we pop down or up?
- if (y + alloc.height + menu_height < Gdk.Screen.height()):
- # now move the menu below the button
- y += alloc.height
- else:
- # now move the menu above the button
- y -= menu_height
-
- # push_in is True so all the menuitems are always inside screen
- push_in = True
- return (x, y, push_in)
-
- menu.popup(None, None, position_menu_under_button, None, 1, 0)
-
-def get_theme_font_for_option(theme, option):
- """
- Return string description of the font, stored in theme preferences
- """
- font_name = gajim.config.get_per('themes', theme, option)
- font_desc = Pango.FontDescription()
- font_prop_str = gajim.config.get_per('themes', theme, option + 'attrs')
- if font_prop_str:
- if font_prop_str.find('B') != -1:
- font_desc.set_weight(Pango.Weight.BOLD)
- if font_prop_str.find('I') != -1:
- font_desc.set_style(Pango.Style.ITALIC)
- fd = Pango.FontDescription(font_name)
- fd.merge(font_desc, True)
- return fd.to_string()
-
-def get_default_font():
- """
- Get the desktop setting for application font first check for GNOME, then
- Xfce and last KDE it returns None on failure or else a string 'Font Size'
- """
- try:
- gi.require_version('GConf', '2.0')
- from gi.repository import GConf
- client = GConf.Client.get_default()
- value = client.get_string("/desktop/gnome/interface/font_name")
- return value.decode("utf8")
- except ValueError:
- pass
-
- # try to get Xfce default font
- # Xfce 4.2 and higher follow freedesktop.org's Base Directory Specification
- # see http://www.xfce.org/~benny/xfce/file-locations.html
- # and http://freedesktop.org/Standards/basedir-spec
- xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '')
- if xdg_config_home == '':
- xdg_config_home = os.path.expanduser('~/.config') # default
- xfce_config_file = os.path.join(xdg_config_home,
- 'xfce4/mcs_settings/Gtk.xml')
-
- kde_config_file = os.path.expanduser('~/.kde/share/config/kdeglobals')
-
- if os.path.exists(xfce_config_file):
- try:
- for line in open(xfce_config_file):
- if line.find('name="Gtk/FontName"') != -1:
- start = line.find('value="') + 7
- return line[start:line.find('"', start)]
- except Exception:
- #we talk about file
- print(_('Error: cannot open %s for reading') % xfce_config_file,
- file=sys.stderr)
-
- elif os.path.exists(kde_config_file):
- try:
- for line in open(kde_config_file):
- if line.find('font=') == 0: # font=Verdana,9,other_numbers
- start = 5 # 5 is len('font=')
- line = line[start:]
- values = line.split(',')
- font_name = values[0]
- font_size = values[1]
- font_string = '%s %s' % (font_name, font_size) # Verdana 9
- return font_string
- except Exception:
- #we talk about file
- print(_('Error: cannot open %s for reading') % kde_config_file,
- file=sys.stderr)
-
- return None
-
-def get_running_processes():
- """
- Return running processes or None (if /proc does not exist)
- """
- if os.path.isdir('/proc'):
- # under Linux: checking if 'gnome-session' or
- # 'startkde' programs were run before gajim, by
- # checking /proc (if it exists)
- #
- # if something is unclear, read `man proc`;
- # if /proc exists, directories that have only numbers
- # in their names contain data about processes.
- # /proc/[xxx]/exe is a symlink to executable started
- # as process number [xxx].
- # filter out everything that we are not interested in:
- files = os.listdir('/proc')
-
- # files that doesn't have only digits in names...
- files = [f for f in files if f.isdigit()]
-
- # files that aren't directories...
- files = [f for f in files if os.path.isdir('/proc/' + f)]
-
- # processes owned by somebody not running gajim...
- # (we check if we have access to that file)
- files = [f for f in files if os.access('/proc/' + f +'/exe', os.F_OK)]
-
- # be sure that /proc/[number]/exe is really a symlink
- # to avoid TBs in incorrectly configured systems
- files = [f for f in files if os.path.islink('/proc/' + f + '/exe')]
-
- # list of processes
- processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f \
- in files]
-
- return processes
- return []
-
-def move_window(window, x, y):
- """
- Move the window, but also check if out of screen
- """
- if x < 0:
- x = 0
- if y < 0:
- y = 0
- w, h = window.get_size()
- if x + w > screen_w:
- x = screen_w - w
- if y + h > screen_h:
- y = screen_h - h
- window.move(x, y)
-
-def resize_window(window, w, h):
- """
- Resize window, but also checks if huge window or negative values
- """
- if not w or not h:
- return
- if w > screen_w:
- w = screen_w
- if h > screen_h:
- h = screen_h
- window.resize(abs(w), abs(h))
-
-def at_the_end(widget):
- """Determines if a Scrollbar in a GtkScrolledWindow is at the end.
-
- Args:
- widget (GtkScrolledWindow)
-
- Returns:
- bool: The return value is True if at the end, False if not.
- """
- adj_v = widget.get_vadjustment()
- max_scroll_pos = adj_v.get_upper() - adj_v.get_page_size()
- at_the_end = (adj_v.get_value() == max_scroll_pos)
- return at_the_end
-
-def scroll_to_end(widget):
- """Scrolls to the end of a GtkScrolledWindow.
-
- Args:
- widget (GtkScrolledWindow)
-
- Returns:
- bool: The return value is False so it can be used with GLib.idle_add.
- """
- adj_v = widget.get_vadjustment()
- max_scroll_pos = adj_v.get_upper() - adj_v.get_page_size()
- adj_v.set_value(max_scroll_pos)
-
- adj_h = widget.get_hadjustment()
- adj_h.set_value(0)
- return False
-
-
-class HashDigest:
- def __init__(self, algo, digest):
- self.algo = self.cleanID(algo)
- self.digest = self.cleanID(digest)
-
- def cleanID(self, id_):
- id_ = id_.strip().lower()
- for strip in (' :.-_'):
- id_ = id_.replace(strip, '')
- return id_
-
- def __eq__(self, other):
- sa, sd = self.algo, self.digest
- if isinstance(other, self.__class__):
- oa, od = other.algo, other.digest
- elif isinstance(other, str):
- sa, oa, od = None, None, self.cleanID(other)
- elif isinstance(other, tuple) and len(other) == 2:
- oa, od = self.cleanID(other[0]), self.cleanID(other[1])
- else:
- return False
-
- return sa == oa and sd == od
-
- def __ne__(self, other):
- return not self == other
-
- def __hash__(self):
- return self.algo ^ self.digest
-
- def __str__(self):
- prettydigest = ''
- for i in range(0, len(self.digest), 2):
- prettydigest += self.digest[i:i + 2] + ':'
- return prettydigest[:-1]
-
- def __repr__(self):
- return "%s(%s, %s)" % (self.__class__, repr(self.algo), repr(str(self)))
-
-class ServersXMLHandler(xml.sax.ContentHandler):
- def __init__(self):
- xml.sax.ContentHandler.__init__(self)
- self.servers = []
-
- def startElement(self, name, attributes):
- if name == 'item':
- if 'jid' in attributes.getNames():
- self.servers.append(attributes.getValue('jid'))
-
- def endElement(self, name):
- pass
-
-def parse_server_xml(path_to_file):
- try:
- handler = ServersXMLHandler()
- xml.sax.parse(path_to_file, handler)
- return handler.servers
- # handle exception if unable to open file
- except IOError as message:
- print(_('Error reading file:') + str(message), file=sys.stderr)
- # handle exception parsing file
- except xml.sax.SAXParseException as message:
- print(_('Error parsing file:') + str(message), file=sys.stderr)
-
-def set_unset_urgency_hint(window, unread_messages_no):
- """
- Sets/unset urgency hint in window argument depending if we have unread
- messages or not
- """
- if gajim.config.get('use_urgency_hint'):
- if unread_messages_no > 0:
- window.props.urgency_hint = True
- else:
- window.props.urgency_hint = False
-
-def get_abspath_for_script(scriptname, want_type = False):
- """
- Check if we are svn or normal user and return abspath to asked script if
- want_type is True we return 'svn' or 'install'
- """
- if os.path.isdir('.svn'): # we are svn user
- type_ = 'svn'
- cwd = os.getcwd() # it's always ending with src
-
- if scriptname == 'gajim-remote':
- path_to_script = cwd + '/gajim-remote.py'
-
- elif scriptname == 'gajim':
- script = '#!/bin/sh\n' # the script we may create
- script += 'cd %s' % cwd
- path_to_script = cwd + '/../scripts/gajim_sm_script'
-
- try:
- if os.path.exists(path_to_script):
- os.remove(path_to_script)
-
- f = open(path_to_script, 'w')
- script += '\nexec python -OOt gajim.py $0 $@\n'
- f.write(script)
- f.close()
- os.chmod(path_to_script, 0o700)
- except OSError: # do not traceback (could be a permission problem)
- #we talk about a file here
- s = _('Could not write to %s. Session Management support will '
- 'not work') % path_to_script
- print(s, file=sys.stderr)
-
- else: # normal user (not svn user)
- type_ = 'install'
- # always make it like '/usr/local/bin/gajim'
- path_to_script = helpers.is_in_path(scriptname, True)
-
-
- if want_type:
- return path_to_script, type_
- else:
- return path_to_script
-
-# feeding the image directly into the pixbuf seems possible, but is error prone and causes image distortions and segfaults.
-# see http://stackoverflow.com/a/8892894/3528174
-# and https://developer.gnome.org/gdk-pixbuf/unstable/gdk-pixbuf-Image-Data-in-Memory.html#gdk-pixbuf-new-from-bytes
-# to learn how this could be done (or look into the mercurial history)
-def get_pixbuf_from_data(file_data, want_type = False):
- """
- Get image data and returns GdkPixbuf.Pixbuf if want_type is True it also
- returns 'jpeg', 'png' etc
- """
- pixbufloader = GdkPixbuf.PixbufLoader()
- try:
- pixbufloader.write(file_data)
- pixbufloader.close()
- pixbuf = pixbufloader.get_pixbuf()
- except GLib.GError: # 'unknown image format'
- pixbufloader.close()
-
- # try to open and convert this image to png using pillow (if available)
- log.debug("loading avatar using pixbufloader failed, trying to convert avatar image using pillow (if available)")
- try:
- avatar = Image.open(BytesIO(file_data)).convert("RGBA")
- arr = GLib.Bytes.new(avatar.tobytes())
- width, height = avatar.size
- pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(arr, GdkPixbuf.Colorspace.RGB, True, 8, width, height, width * 4)
- except:
- log.info("Could not use pillow to convert avatar image, image cannot be displayed")
- if want_type:
- return None, None
- else:
- return None
-
- if want_type:
- typ = pixbufloader.get_format() and pixbufloader.get_format().get_name() or None
- return pixbuf, typ
- else:
- return pixbuf
-
-def get_cursor(attr):
- display = Gdk.Display.get_default()
- cursor = getattr(Gdk.CursorType, attr)
- return Gdk.Cursor.new_for_display(display, cursor)
-
-def get_current_desktop(window):
- """
- Return the current virtual desktop for given window
-
- NOTE: Window is a GDK window.
- """
- prop = window.property_get('_NET_CURRENT_DESKTOP')
- if prop is None: # it means it's normal window (not root window)
- # so we look for it's current virtual desktop in another property
- prop = window.property_get('_NET_WM_DESKTOP')
-
- if prop is not None:
- # f.e. prop is ('CARDINAL', 32, [0]) we want 0 or 1.. from [0]
- current_virtual_desktop_no = prop[2][0]
- return current_virtual_desktop_no
-
-def possibly_move_window_in_current_desktop(window):
- """
- Moves GTK window to current virtual desktop if it is not in the current
- virtual desktop
-
- NOTE: Window is a GDK window.
- """
- #TODO: property_get doesn't work:
- #prop_atom = Gdk.Atom.intern('_NET_CURRENT_DESKTOP', False)
- #type_atom = Gdk.Atom.intern("CARDINAL", False)
- #w = Gdk.Screen.get_default().get_root_window()
- #Gdk.property_get(w, prop_atom, type_atom, 0, 9999, False)
- return False
- if os.name == 'nt':
- return False
-
- root_window = Gdk.Screen.get_default().get_root_window()
- # current user's vd
- current_virtual_desktop_no = get_current_desktop(root_window)
-
- # vd roster window is in
- window_virtual_desktop = get_current_desktop(window.window)
-
- # if one of those is None, something went wrong and we cannot know
- # VD info, just hide it (default action) and not show it afterwards
- if None not in (window_virtual_desktop, current_virtual_desktop_no):
- if current_virtual_desktop_no != window_virtual_desktop:
- # we are in another VD that the window was
- # so show it in current VD
- window.present()
- return True
- return False
-
-def file_is_locked(path_to_file):
- """
- Return True if file is locked
-
- NOTE: Windows only.
- """
- if os.name != 'nt': # just in case
- return
-
- if not HAS_PYWIN32:
- return
-
- secur_att = pywintypes.SECURITY_ATTRIBUTES()
- secur_att.Initialize()
-
- try:
- # try make a handle for READING the file
- hfile = win32file.CreateFile(
- path_to_file, # path to file
- win32con.GENERIC_READ, # open for reading
- 0, # do not share with other proc
- secur_att,
- win32con.OPEN_EXISTING, # existing file only
- win32con.FILE_ATTRIBUTE_NORMAL, # normal file
- 0 # no attr. template
- )
- except pywintypes.error:
- return True
- else: # in case all went ok, close file handle (go to hell WinAPI)
- hfile.Close()
- return False
-
-def get_fade_color(treeview, selected, focused):
- """
- Get a gdk RGBA color that is between foreground and background in 0.3
- 0.7 respectively colors of the cell for the given treeview
- """
- context = treeview.get_style_context()
- if selected:
- if focused: # is the window focused?
- state = Gtk.StateFlags.SELECTED
- else: # is it not? NOTE: many gtk themes change bg on this
- state = Gtk.StateFlags.ACTIVE
- else:
- state = Gtk.StateFlags.NORMAL
- bg = context.get_background_color(state)
- fg = context.get_color(state)
-
- p = 0.3 # background
- q = 0.7 # foreground # p + q should do 1.0
- return Gdk.RGBA(bg.red*p + fg.red*q, bg.green*p + fg.green*q,
- bg.blue*p + fg.blue*q)
-
-def get_scaled_pixbuf_by_size(pixbuf, width, height):
- # Pixbuf size
- pix_width = pixbuf.get_width()
- pix_height = pixbuf.get_height()
- # don't make avatars bigger than they are
- if pix_width < width and pix_height < height:
- return pixbuf # we don't want to make avatar bigger
-
- ratio = float(pix_width) / float(pix_height)
- if ratio > 1:
- w = width
- h = int(w / ratio)
- else:
- h = height
- w = int(h * ratio)
- scaled_buf = pixbuf.scale_simple(w, h, GdkPixbuf.InterpType.HYPER)
- return scaled_buf
-
-def get_scaled_pixbuf(pixbuf, kind):
- """
- Return scaled pixbuf, keeping ratio etc or None kind is either "chat",
- "roster", "notification", "tooltip", "vcard"
- """
- # resize to a width / height for the avatar not to have distortion
- # (keep aspect ratio)
- width = gajim.config.get(kind + '_avatar_width')
- height = gajim.config.get(kind + '_avatar_height')
- if width < 1 or height < 1:
- return None
-
- return get_scaled_pixbuf_by_size(pixbuf, width, height)
-
-def get_avatar_pixbuf_from_cache(fjid, use_local=True):
- """
- Check if jid has cached avatar and if that avatar is valid image (can be
- shown)
-
- Returns None if there is no image in vcard/
- Returns 'ask' if cached vcard should not be used (user changed his vcard, so
- we have new sha) or if we don't have the vcard
- """
- jid, nick = gajim.get_room_and_nick_from_fjid(fjid)
- if gajim.config.get('hide_avatar_of_transport') and\
- gajim.jid_is_transport(jid):
- # don't show avatar for the transport itself
- return None
-
- if any(jid in gajim.contacts.get_gc_list(acc) for acc in \
- gajim.contacts.get_accounts()):
- is_groupchat_contact = True
- else:
- is_groupchat_contact = False
-
- puny_jid = helpers.sanitize_filename(jid)
- if is_groupchat_contact:
- puny_nick = helpers.sanitize_filename(nick)
- path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid,
- puny_nick) + '_local'
- else:
- path = os.path.join(gajim.VCARD_PATH, puny_jid)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
- '_local'
- if use_local:
- for extension in ('.png', '.jpeg'):
- local_avatar_path = local_avatar_basepath + extension
- if os.path.isfile(local_avatar_path):
- avatar_file = open(local_avatar_path, 'rb')
- avatar_data = avatar_file.read()
- avatar_file.close()
- return get_pixbuf_from_data(avatar_data)
-
- if not os.path.isfile(path):
- return 'ask'
-
- vcard_dict = list(gajim.connections.values())[0].get_cached_vcard(fjid,
- is_groupchat_contact)
- if not vcard_dict: # This can happen if cached vcard is too old
- return 'ask'
- if 'PHOTO' not in vcard_dict:
- return None
- pixbuf = vcard.get_avatar_pixbuf_encoded_mime(vcard_dict['PHOTO'])[0]
- return pixbuf
-
-def make_gtk_month_python_month(month):
- """
- GTK starts counting months from 0, so January is 0 but Python's time start
- from 1, so align to Python
-
- NOTE: Month MUST be an integer.
- """
- return month + 1
-
-def make_python_month_gtk_month(month):
- return month - 1
-
-def make_color_string(color):
- """
- Create #aabbcc color string from gtk color
- """
- col = '#'
- for i in ('red', 'green', 'blue'):
- h = hex(int(getattr(color, i) / (16*16)))
- h = h.split('x')[1]
- if len(h) == 1:
- h = '0' + h
- col += h
- return col
-
-def make_pixbuf_grayscale(pixbuf):
- pixbuf2 = pixbuf.copy()
- pixbuf.saturate_and_pixelate(pixbuf2, 0.0, False)
- return pixbuf2
-
-def escape_underscore(s):
- """
- Escape underlines to prevent them from being interpreted as keyboard
- accelerators
- """
- return s.replace('_', '__')
-
-def get_state_image_from_file_path_show(file_path, show):
- state_file = show.replace(' ', '_')
- files = []
- files.append(os.path.join(file_path, state_file + '.png'))
- files.append(os.path.join(file_path, state_file + '.gif'))
- image = Gtk.Image()
- image.set_from_pixbuf(None)
- for file_ in files:
- if os.path.exists(file_):
- image.set_from_file(file_)
- break
-
- return image
-
-def get_possible_button_event(event):
- """
- Mouse or keyboard caused the event?
- """
- if event.type == Gdk.EventType.KEY_PRESS:
- return 0 # no event.button so pass 0
- # BUTTON_PRESS event, so pass event.button
- return event.button
-
-def destroy_widget(widget):
- widget.destroy()
-
-def on_avatar_save_as_menuitem_activate(widget, jid, default_name=''):
- def on_continue(response, file_path):
- if response < 0:
- return
- pixbuf = get_avatar_pixbuf_from_cache(jid)
- extension = os.path.splitext(file_path)[1]
- if not extension:
- # Silently save as Jpeg image
- image_format = 'jpeg'
- file_path += '.jpeg'
- elif extension == 'jpg':
- image_format = 'jpeg'
- else:
- image_format = extension[1:] # remove leading dot
-
- # Save image
- try:
- pixbuf.savev(file_path, image_format, [], [])
- except Exception as e:
- log.debug('Error saving avatar: %s' % str(e))
- if os.path.exists(file_path):
- os.remove(file_path)
- new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
- def on_ok(file_path, pixbuf):
- pixbuf.savev(file_path, 'jpeg', [], [])
- dialogs.ConfirmationDialog(_('Extension not supported'),
- _('Image cannot be saved in %(type)s format. Save as '
- '%(new_filename)s?') % {'type': image_format,
- 'new_filename': new_file_path},
- on_response_ok = (on_ok, new_file_path, pixbuf))
- else:
- dialog.destroy()
-
- def on_ok(widget):
- file_path = dialog.get_filename()
- if os.path.exists(file_path):
- # check if we have write permissions
- if not os.access(file_path, os.W_OK):
- file_name = os.path.basename(file_path)
- dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"') % \
- file_name, _('A file with this name already exists and you '
- 'do not have permission to overwrite it.'))
- return
- dialog2 = dialogs.FTOverwriteConfirmationDialog(
- _('This file already exists'), _('What do you want to do?'),
- propose_resume=False, on_response=(on_continue, file_path),
- transient_for=dialog)
- dialog2.set_destroy_with_parent(True)
- else:
- dirname = os.path.dirname(file_path)
- if not os.access(dirname, os.W_OK):
- dialogs.ErrorDialog(_('Directory "%s" is not writable') % \
- dirname, _('You do not have permission to create files in '
- 'this directory.'))
- return
-
- on_continue(0, file_path)
-
- def on_cancel(widget):
- dialog.destroy()
-
- dialog = dialogs.FileChooserDialog(title_text=_('Save Image as…'),
- action=Gtk.FileChooserAction.SAVE, buttons=(Gtk.STOCK_CANCEL,
- Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
- default_response=Gtk.ResponseType.OK,
- current_folder=gajim.config.get('last_save_dir'), on_response_ok=on_ok,
- on_response_cancel=on_cancel)
-
- dialog.set_current_name(default_name + '.jpeg')
- dialog.connect('delete-event', lambda widget, event:
- on_cancel(widget))
-
-def on_bm_header_changed_state(widget, event):
- widget.set_state(Gtk.StateType.NORMAL) #do not allow selected_state
-
-def create_combobox(value_list, selected_value = None):
- """
- Value_list is [(label1, value1)]
- """
- liststore = Gtk.ListStore(str, str)
- combobox = Gtk.ComboBox.new_with_model(liststore)
- cell = Gtk.CellRendererText()
- combobox.pack_start(cell, True)
- combobox.add_attribute(cell, 'text', 0)
- i = -1
- for value in value_list:
- liststore.append(value)
- if selected_value == value[1]:
- i = value_list.index(value)
- if i > -1:
- combobox.set_active(i)
- combobox.show_all()
- return combobox
-
-def create_list_multi(value_list, selected_values=None):
- """
- Value_list is [(label1, value1)]
- """
- liststore = Gtk.ListStore(str, str)
- treeview = Gtk.TreeView.new_with_model(liststore)
- treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
- treeview.set_headers_visible(False)
- col = Gtk.TreeViewColumn()
- treeview.append_column(col)
- cell = Gtk.CellRendererText()
- col.pack_start(cell, True, True, 0)
- col.set_attributes(cell, text=0)
- for value in value_list:
- iter = liststore.append(value)
- if value[1] in selected_values:
- treeview.get_selection().select_iter(iter)
- treeview.show_all()
- return treeview
-
-def load_iconset(path, pixbuf2=None, transport=False):
- """
- Load full iconset from the given path, and add pixbuf2 on top left of each
- static images
- """
- path += '/'
- if transport:
- list_ = ('online', 'chat', 'away', 'xa', 'dnd', 'offline',
- 'not in roster')
- else:
- list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'offline', 'error', 'requested', 'event', 'opened',
- 'closed', 'not in roster', 'muc_active', 'muc_inactive')
- if pixbuf2:
- list_ = ('connecting', 'online', 'chat', 'away', 'xa', 'dnd',
- 'offline', 'error', 'requested', 'event', 'not in roster')
- return _load_icon_list(list_, path, pixbuf2)
-
-def load_icon(icon_name):
- """
- Load an icon from the iconset in 16x16
- """
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16', '')
- icon_list = _load_icon_list([icon_name], path)
- return icon_list[icon_name]
-
-def load_mood_icon(icon_name):
- """
- Load an icon from the mood iconset in 16x16
- """
- iconset = gajim.config.get('mood_iconset')
- path = os.path.join(helpers.get_mood_iconset_path(iconset), '')
- icon_list = _load_icon_list([icon_name], path)
- return icon_list[icon_name]
-
-def load_activity_icon(category, activity = None):
- """
- Load an icon from the activity iconset in 16x16
- """
- iconset = gajim.config.get('activity_iconset')
- path = os.path.join(helpers.get_activity_iconset_path(iconset),
- category, '')
- if activity is None:
- activity = 'category'
- icon_list = _load_icon_list([activity], path)
- return icon_list[activity]
-
-def get_pep_as_pixbuf(pep_class):
- if isinstance(pep_class, pep.UserMoodPEP):
- assert not pep_class._retracted
- received_mood = pep_class._pep_specific_data['mood']
- mood = received_mood if received_mood in pep.MOODS else 'unknown'
- pixbuf = load_mood_icon(mood).get_pixbuf()
- return pixbuf
- elif isinstance(pep_class, pep.UserTunePEP):
- icon = get_icon_pixmap('audio-x-generic', quiet=True)
- if not icon:
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
- 'music.png')
- return GdkPixbuf.Pixbuf.new_from_file(path)
- return icon
- elif isinstance(pep_class, pep.UserActivityPEP):
- assert not pep_class._retracted
- pep_ = pep_class._pep_specific_data
- activity = pep_['activity']
-
- has_known_activity = activity in pep.ACTIVITIES
- has_known_subactivity = (has_known_activity and ('subactivity' in pep_)
- and (pep_['subactivity'] in pep.ACTIVITIES[activity]))
-
- if has_known_activity:
- if has_known_subactivity:
- subactivity = pep_['subactivity']
- return load_activity_icon(activity, subactivity).get_pixbuf()
- else:
- return load_activity_icon(activity).get_pixbuf()
- else:
- return load_activity_icon('unknown').get_pixbuf()
- elif isinstance(pep_class, pep.UserLocationPEP):
- icon = get_icon_pixmap('applications-internet', quiet=True)
- if not icon:
- icon = get_icon_pixmap('gajim-earth')
- return icon
- return None
-
-def load_icons_meta():
- """
- Load and return - AND + small icons to put on top left of an icon for meta
- contacts
- """
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- # try to find opened_meta.png file, else opened.png else nopixbuf merge
- path_opened = os.path.join(path, 'opened_meta.png')
- if not os.path.isfile(path_opened):
- path_opened = os.path.join(path, 'opened.png')
- if os.path.isfile(path_opened):
- pixo = GdkPixbuf.Pixbuf.new_from_file(path_opened)
- else:
- pixo = None
- # Same thing for closed
- path_closed = os.path.join(path, 'opened_meta.png')
- if not os.path.isfile(path_closed):
- path_closed = os.path.join(path, 'closed.png')
- if os.path.isfile(path_closed):
- pixc = GdkPixbuf.Pixbuf.new_from_file(path_closed)
- else:
- pixc = None
- return pixo, pixc
-
-def _load_icon_list(icons_list, path, pixbuf2 = None):
- """
- Load icons in icons_list from the given path, and add pixbuf2 on top left of
- each static images
- """
- imgs = {}
- for icon in icons_list:
- # try to open a pixfile with the correct method
- icon_file = icon.replace(' ', '_')
- files = []
- files.append(path + icon_file + '.gif')
- files.append(path + icon_file + '.png')
- image = Gtk.Image()
- image.show()
- imgs[icon] = image
- for file_ in files: # loop seeking for either gif or png
- if os.path.exists(file_):
- image.set_from_file(file_)
- if pixbuf2 and image.get_storage_type() == Gtk.ImageType.PIXBUF:
- # add pixbuf2 on top-left corner of image
- pixbuf1 = image.get_pixbuf()
- pixbuf2.composite(pixbuf1, 0, 0,
- pixbuf2.get_property('width'),
- pixbuf2.get_property('height'), 0, 0, 1.0, 1.0,
- GdkPixbuf.InterpType.NEAREST, 255)
- image.set_from_pixbuf(pixbuf1)
- break
- return imgs
-
-def make_jabber_state_images():
- """
- Initialize jabber_state_images dictionary
- """
- iconset = gajim.config.get('iconset')
- if iconset:
- if helpers.get_iconset_path(iconset):
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- if not os.path.exists(path):
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
- else:
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
- else:
- iconset = gajim.config.DEFAULT_ICONSET
- gajim.config.set('iconset', iconset)
-
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- gajim.interface.jabber_state_images['16'] = load_iconset(path)
-
- pixo, pixc = load_icons_meta()
- gajim.interface.jabber_state_images['opened'] = load_iconset(path, pixo)
- gajim.interface.jabber_state_images['closed'] = load_iconset(path, pixc)
-
- path = os.path.join(helpers.get_iconset_path(iconset), '32x32')
- gajim.interface.jabber_state_images['32'] = load_iconset(path)
-
- path = os.path.join(helpers.get_iconset_path(iconset), '24x24')
- if (os.path.exists(path)):
- gajim.interface.jabber_state_images['24'] = load_iconset(path)
- else:
- # Resize 32x32 icons to 24x24
- for each in gajim.interface.jabber_state_images['32']:
- img = Gtk.Image()
- pix = gajim.interface.jabber_state_images['32'][each]
- pix_type = pix.get_storage_type()
- if pix_type == Gtk.ImageType.ANIMATION:
- animation = pix.get_animation()
- pixbuf = animation.get_static_image()
- elif pix_type == Gtk.ImageType.EMPTY:
- pix = gajim.interface.jabber_state_images['16'][each]
- pix_16_type = pix.get_storage_type()
- if pix_16_type == Gtk.ImageType.ANIMATION:
- animation = pix.get_animation()
- pixbuf = animation.get_static_image()
- else:
- pixbuf = pix.get_pixbuf()
- else:
- pixbuf = pix.get_pixbuf()
- scaled_pix = pixbuf.scale_simple(24, 24, GdkPixbuf.InterpType.BILINEAR)
- img.set_from_pixbuf(scaled_pix)
- gajim.interface.jabber_state_images['24'][each] = img
-
-def reload_jabber_state_images():
- make_jabber_state_images()
- gajim.interface.roster.update_jabber_state_images()
-
-def label_set_autowrap(widget):
- """
- Make labels automatically re-wrap if their containers are resized.
- Accepts label or container widgets
- """
- if isinstance (widget, Gtk.Container):
- children = widget.get_children()
- for i in list(range (len (children))):
- label_set_autowrap(children[i])
- elif isinstance(widget, Gtk.Label):
- widget.set_line_wrap(True)
- widget.connect_after('size-allocate', __label_size_allocate)
-
-def __label_size_allocate(widget, allocation):
- """
- Callback which re-allocates the size of a label
- """
- layout = widget.get_layout()
-
- lw_old, lh_old = layout.get_size()
- # fixed width labels
- if lw_old/Pango.SCALE == allocation.width:
- return
-
- # set wrap width to the Pango.Layout of the labels ###
- widget.set_alignment(0.0, 0.0)
- layout.set_width (allocation.width * Pango.SCALE)
- lh = layout.get_size()[1]
-
- if lh_old != lh:
- widget.set_size_request (-1, lh / Pango.SCALE)
-
-def get_action(action):
- return gajim.app.lookup_action(action)
-
-def load_css():
- path = os.path.join(configpaths.get('DATA'), 'style', 'gajim.css')
- try:
- with open(path, "r") as f:
- css = f.read()
- except Exception as exc:
- print('Error loading css: %s', exc)
- return
-
- provider = Gtk.CssProvider()
- css = "\n".join((css, convert_config_to_css()))
- provider.load_from_data(bytes(css.encode()))
- Gtk.StyleContext.add_provider_for_screen(
- Gdk.Screen.get_default(),
- provider,
- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
-
-def convert_config_to_css():
- css = ''
- themed_widgets = {
- 'ChatControl-BannerEventBox': ('bannerbgcolor', 'background'),
- 'ChatControl-BannerNameLabel': ('bannertextcolor', 'color'),
- 'ChatControl-BannerLabel': ('bannertextcolor', 'color'),
- 'GroupChatControl-BannerEventBox': ('bannerbgcolor', 'background'),
- 'GroupChatControl-BannerNameLabel': ('bannertextcolor', 'color'),
- 'GroupChatControl-BannerLabel': ('bannertextcolor', 'color'),
- 'Discovery-BannerEventBox': ('bannerbgcolor', 'background'),
- 'Discovery-BannerLabel': ('bannertextcolor', 'color')}
-
- classes = {'state_composing_color': ('', 'color'),
- 'state_inactive_color': ('', 'color'),
- 'state_gone_color': ('', 'color'),
- 'state_paused_color': ('', 'color'),
- 'msgcorrectingcolor': ('text', 'background'),
- 'state_muc_directed_msg_color': ('', 'color'),
- 'state_muc_msg_color': ('', 'color')}
-
-
- theme = gajim.config.get('roster_theme')
- for key, values in themed_widgets.items():
- config, attr = values
- css += '#{} {{'.format(key)
- value = gajim.config.get_per('themes', theme, config)
- if value:
- css += '{attr}: {color};\n'.format(attr=attr, color=value)
- css += '}\n'
-
- for key, values in classes.items():
- node, attr = values
- value = gajim.config.get_per('themes', theme, key)
- if value:
- css += '.theme_{cls} {node} {{ {attr}: {color}; }}\n'.format(
- cls=key, node=node, attr=attr, color=value)
-
- css += add_css_font()
-
- return css
-
-def add_css_class(widget, class_name):
- style = widget.get_style_context()
- for css_cls in style.list_classes():
- if css_cls.startswith('theme_'):
- style.remove_class(css_cls)
- if class_name:
- style.add_class('theme_' + class_name)
-
-def remove_css_class(widget, class_name):
- style = widget.get_style_context()
- style.remove_class('theme_' + class_name)
-
-def add_css_font():
- conversation_font = gajim.config.get('conversation_font')
- if not conversation_font:
- return ''
- font = Pango.FontDescription(conversation_font)
- unit = "pt" if Gtk.check_version(3, 22, 0) is None else "px"
- css = """
- .font_custom {{
- font-family: {family};
- font-size: {size}{unit};
- font-weight: {weight};
- }}""".format(
- family=font.get_family(),
- size=int(round(font.get_size() / Pango.SCALE)),
- unit=unit,
- weight=int(font.get_weight()))
- css = css.replace("font-size: 0{unit};".format(unit=unit), "")
- css = css.replace("font-weight: 0;", "")
- css = "\n".join(filter(lambda x: x.strip(), css.splitlines()))
- return css
diff --git a/src/gtkspell.py b/src/gtkspell.py
deleted file mode 100644
index bb75b8e1c..000000000
--- a/src/gtkspell.py
+++ /dev/null
@@ -1,84 +0,0 @@
-## src/gtkspell.py
-##
-## (C) 2008 Thorsten P. 'dGhvcnN0ZW5wIEFUIHltYWlsIGNvbQ==\n'.decode("base64")
-## (C) 2015 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-
-from gi.repository import GObject
-from gi.repository import Gtk
-import gi
-gi.require_version('GtkSpell', '3.0')
-from gi.repository import GtkSpell
-
-def ensure_attached(func):
- def f(self, *args, **kwargs):
- if self.spell:
- func(self, *args, **kwargs)
- else:
- raise RuntimeError("Spell object is already detached")
- return f
-
-
-class Spell(GObject.GObject):
- __gsignals__ = {
- 'language_changed': (GObject.SignalFlags.RUN_FIRST, None, (str,))
- }
-
- def __init__(self, textview, language=None, create=True, jid=None,
- per_type=None):
- GObject.GObject.__init__(self)
- if not isinstance(textview, Gtk.TextView):
- raise TypeError("Textview must be derived from Gtk.TextView")
- spell = GtkSpell.Checker.get_from_text_view(textview)
- if create:
- if spell:
- raise RuntimeError("Textview has already a Spell obj attached")
- self.spell = GtkSpell.Checker.new()
- if not self.spell:
- raise OSError("Unable to create spell object.")
- if not self.spell.attach(textview):
- raise OSError("Unable to attach spell object.")
- if not self.spell.set_language(language):
- raise OSError("Unable to set language: '%s'" % language)
- self.spell.connect('language-changed', self.on_language_changed)
-
- else:
- if spell:
- self.spell = spell
- else:
- raise RuntimeError("Textview has no Spell object attached")
-
- def on_language_changed(self, spell, lang):
- self.emit('language_changed', lang)
-
- @ensure_attached
- def set_language(self, language):
- if not self.spell.set_language(language):
- raise OSError("Unable to set language: '%s'" % language)
-
- @ensure_attached
- def recheck_all(self):
- self.spell.recheck_all()
-
- @ensure_attached
- def detach(self):
- self.spell.detach()
- self.spell = None
-
-GObject.type_register(Spell)
-
-def get_from_text_view(textview):
- return Spell(textview, create=False)
diff --git a/src/gui_interface.py b/src/gui_interface.py
deleted file mode 100644
index ebfc4cfe4..000000000
--- a/src/gui_interface.py
+++ /dev/null
@@ -1,3177 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gajim.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2004-2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005 Alex Podaras <bigpod AT gmail.com>
-## Norman Rasmussen <norman AT rasmussen.co.za>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Alex Mauer <hawke AT hawkesnest.net>
-## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## James Newton <redshodan AT gmail.com>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-import os
-import sys
-import re
-import time
-import math
-from subprocess import Popen
-
-from gi.repository import Gtk
-from gi.repository import GdkPixbuf
-from gi.repository import GLib
-
-from common import i18n
-from common import gajim
-from common import events
-
-from common import dbus_support
-if dbus_support.supported:
- from music_track_listener import MusicTrackListener
- from common import location_listener
- import dbus
-
-import gtkgui_helpers
-import gui_menu_builder
-import dialogs
-import notify
-import message_control
-
-from chat_control_base import ChatControlBase
-from chat_control import ChatControl
-from groupchat_control import GroupchatControl
-from groupchat_control import PrivateChatControl
-from message_window import MessageWindowMgr
-
-from atom_window import AtomWindow
-from session import ChatControlSession
-
-from common import sleepy
-
-from nbxmpp import idlequeue
-from nbxmpp import Hashes2
-from common.zeroconf import connection_zeroconf
-from common import resolver
-from common import caps_cache
-from common import proxy65_manager
-from common import socks5
-from common import helpers
-from common import passwords
-from common import logging_helpers
-from common.connection_handlers_events import OurShowEvent, \
- FileRequestErrorEvent, FileTransferCompletedEvent
-from common.connection import Connection
-from common.file_props import FilesProp
-from common import pep
-
-import roster_window
-import profile_window
-import config
-from threading import Thread
-from common import ged
-
-from common.configpaths import gajimpaths
-config_filename = gajimpaths['CONFIG_FILE']
-
-from common import optparser
-parser = optparser.OptionsParser(config_filename)
-
-import logging
-log = logging.getLogger('gajim.interface')
-
-class Interface:
-
-################################################################################
-### Methods handling events from connection
-################################################################################
-
- def handle_event_db_error(self, unused, error):
- #('DB_ERROR', account, error)
- if self.db_error_dialog:
- return
- self.db_error_dialog = dialogs.ErrorDialog(_('Database Error'), error)
- def destroyed(win):
- self.db_error_dialog = None
- self.db_error_dialog.connect('destroy', destroyed)
-
- @staticmethod
- def handle_event_information(obj):
- if obj.popup:
- if obj.level == 'error':
- cls = dialogs.ErrorDialog
- elif obj.level == 'warn':
- cls = dialogs.WarningDialog
- elif obj.level == 'info':
- cls = dialogs.InformationDialog
- else:
- return
-
- cls(obj.pri_txt, GLib.markup_escape_text(obj.sec_txt))
-
- def handle_ask_new_nick(self, account, room_jid, parent_win):
- title = _('Unable to join group chat')
- prompt = _('Your desired nickname in group chat\n'
- '<b>%s</b>\n'
- 'is in use or registered by another occupant.\n'
- 'Please specify another nickname below:') % room_jid
- check_text = _('Always use this nickname when there is a conflict')
- if 'change_nick_dialog' in self.instances:
- self.instances['change_nick_dialog'].add_room(account, room_jid,
- prompt)
- else:
- self.instances['change_nick_dialog'] = dialogs.ChangeNickDialog(
- account, room_jid, title, prompt, transient_for=parent_win)
-
- @staticmethod
- def handle_event_http_auth(obj):
- #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
- def response(account, answer):
- obj.conn.build_http_auth_answer(obj.stanza, answer)
-
- 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 obj.msg:
- sec_msg = obj.msg + '\n' + sec_msg
- dialog = dialogs.YesNoDialog(_('HTTP (%(method)s) Authorization for '
- '%(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_iq_error(self, obj):
- #('ERROR_ANSWER', account, (id_, fjid, errmsg, errcode))
- if str(obj.errcode) in ('400', '403', '406') and obj.id_:
- # show the error dialog
- ft = self.instances['file_transfers']
- sid = obj.id_
- if len(obj.id_) > 3 and obj.id_[2] == '_':
- sid = obj.id_[3:]
- file_props = FilesProp.getFileProp(obj.conn.name, sid)
- if file_props :
- if str(obj.errcode) == '400':
- file_props.error = -3
- else:
- file_props.error = -4
- gajim.nec.push_incoming_event(FileRequestErrorEvent(None,
- conn=obj.conn, jid=obj.jid, file_props=file_props,
- error_msg=obj.errmsg))
- obj.conn.disconnect_transfer(file_props)
- return
- elif str(obj.errcode) == '404':
- sid = obj.id_
- if len(obj.id_) > 3 and obj.id_[2] == '_':
- sid = obj.id_[3:]
- file_props = FilesProp.getFileProp(obj.conn.name, sid)
- if file_props:
- self.handle_event_file_send_error(obj.conn.name, (obj.fjid,
- file_props))
- obj.conn.disconnect_transfer(file_props)
- return
-
- ctrl = self.msg_win_mgr.get_control(obj.fjid, obj.conn.name)
- if ctrl and ctrl.type_id == message_control.TYPE_GC:
- ctrl.print_conversation('Error %s: %s' % (obj.errcode, obj.errmsg))
-
- @staticmethod
- def handle_event_connection_lost(obj):
- # ('CONNECTION_LOST', account, [title, text])
- path = gtkgui_helpers.get_icon_path('gajim-connection_lost', 48)
- account = obj.conn.name
- notify.popup(_('Connection Failed'), account, account,
- '', path, obj.title, obj.msg)
-
- @staticmethod
- def unblock_signed_in_notifications(account):
- gajim.block_signed_in_notifications[account] = False
-
- def handle_event_status(self, obj): # OUR status
- #('STATUS', account, show)
- account = obj.conn.name
- if obj.show in ('offline', 'error'):
- for name in list(self.instances[account]['online_dialog'].keys()):
- # .keys() is needed to not have a dictionary length changed
- # during iteration error
- self.instances[account]['online_dialog'][name].destroy()
- if name in self.instances[account]['online_dialog']:
- # destroy handler may have already removed it
- del self.instances[account]['online_dialog'][name]
- for request in self.gpg_passphrase.values():
- if request:
- request.interrupt(account=account)
- if account in self.pass_dialog:
- self.pass_dialog[account].window.destroy()
- if obj.show == 'offline':
- gajim.block_signed_in_notifications[account] = True
- else:
- # 30 seconds after we change our status to sth else than offline
- # we stop blocking notifications of any kind
- # this prevents from getting the roster items as 'just signed in'
- # contacts. 30 seconds should be enough time
- GLib.timeout_add_seconds(30, self.unblock_signed_in_notifications,
- account)
-
- if account in self.show_vcard_when_connect and obj.show not in (
- 'offline', 'error'):
- self.edit_own_details(account)
-
- def edit_own_details(self, account):
- jid = gajim.get_jid_from_account(account)
- if 'profile' not in self.instances[account]:
- self.instances[account]['profile'] = \
- profile_window.ProfileWindow(account, gajim.interface.roster.window)
- gajim.connections[account].request_vcard(jid)
-
- @staticmethod
- def handle_gc_error(gc_control, pritext, sectext):
- if gc_control and gc_control.autorejoin is not None:
- if gc_control.error_dialog:
- gc_control.error_dialog.destroy()
- def on_close(dummy):
- gc_control.error_dialog.destroy()
- gc_control.error_dialog = None
- gc_control.error_dialog = dialogs.ErrorDialog(pritext, sectext,
- on_response_ok=on_close, on_response_cancel=on_close)
- gc_control.error_dialog.set_modal(False)
- if gc_control.parent_win:
- gc_control.error_dialog.set_transient_for(
- gc_control.parent_win.window)
- else:
- d = dialogs.ErrorDialog(pritext, sectext)
- if gc_control and gc_control.parent_win:
- d.set_transient_for(gc_control.parent_win.window)
- d.set_modal(False)
-
- def handle_gc_password_required(self, account, room_jid, nick):
- def on_ok(text):
- gajim.connections[account].join_gc(nick, room_jid, text)
- gajim.gc_passwords[room_jid] = text
- gc_control.error_dialog = None
-
- def on_cancel():
- # get and destroy window
- if room_jid in gajim.interface.minimized_controls[account]:
- self.roster.on_disconnect(None, room_jid, account)
- else:
- win = self.msg_win_mgr.get_window(room_jid, account)
- ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
- win.remove_tab(ctrl, 3)
- gc_control.error_dialog = None
-
- gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
- if gc_control:
- if gc_control.error_dialog:
- gc_control.error_dialog.destroy()
-
- gc_control.error_dialog = dialogs.InputDialog(_('Password Required'),
- _('A Password is required to join the room %s. Please type it.') % \
- room_jid, is_modal=False, ok_handler=on_ok,
- cancel_handler=on_cancel)
- gc_control.error_dialog.input_entry.set_visibility(False)
-
- def handle_event_gc_presence(self, obj):
- gc_control = obj.gc_control
- parent_win = None
- if gc_control and gc_control.parent_win:
- parent_win = gc_control.parent_win.window
- if obj.ptype == 'error':
- if obj.errcode == '503':
- # maximum user number reached
- self.handle_gc_error(gc_control,
- _('Unable to join group chat'),
- _('<b>%s</b> is full')\
- % obj.room_jid)
- elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'):
- # password required to join
- self.handle_gc_password_required(obj.conn.name, obj.room_jid,
- obj.nick)
- elif (obj.errcode == '403') or (obj.errcon == 'forbidden'):
- # we are banned
- self.handle_gc_error(gc_control, _('Unable to join group chat'),
- _('You are banned from group chat <b>%s</b>.') % \
- obj.room_jid)
- elif (obj.errcode == '404') or (obj.errcon in ('item-not-found',
- 'remote-server-not-found')):
- # group chat does not exist
- self.handle_gc_error(gc_control, _('Unable to join group chat'),
- _('Group chat <b>%s</b> does not exist.') % obj.room_jid)
- elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'):
- self.handle_gc_error(gc_control, _('Unable to join group chat'),
- _('Group chat creation is not permitted.'))
- elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'):
- self.handle_gc_error(gc_control, _('Unable to join groupchat'),
- _('You must use your registered nickname in <b>%s</b>.')\
- % obj.room_jid)
- elif (obj.errcode == '407') or (obj.errcon == \
- 'registration-required'):
- self.handle_gc_error(gc_control, _('Unable to join group chat'),
- _('You are not in the members list in groupchat %s.') % \
- obj.room_jid)
- elif (obj.errcode == '409') or (obj.errcon == 'conflict'):
- self.handle_ask_new_nick(obj.conn.name, obj.room_jid, parent_win)
- elif gc_control:
- gc_control.print_conversation('Error %s: %s' % (obj.errcode,
- obj.errmsg))
- if gc_control and gc_control.autorejoin:
- gc_control.autorejoin = False
-
- @staticmethod
- def handle_event_gc_message(obj):
- if not obj.stanza.getTag('body'): # no <body>
- # It could be a voice request. See
- # http://www.xmpp.org/extensions/xep-0045.html#voiceapprove
- if obj.msg_obj.form_node:
- dialogs.SingleMessageWindow(obj.conn.name, obj.fjid,
- action='receive', from_whom=obj.fjid,
- subject='', message='', resource='', session=None,
- form_node=obj.msg_obj.form_node)
-
- def handle_event_presence(self, obj):
- # 'NOTIFY' (account, (jid, status, status message, resource,
- # priority, # keyID, timestamp, contact_nickname))
- #
- # Contact changed show
-
- account = obj.conn.name
- jid = obj.jid
- show = obj.show
- status = obj.status
- resource = obj.resource or ''
-
- jid_list = gajim.contacts.get_jid_list(account)
-
- # unset custom status
- if (obj.old_show == 0 and obj.new_show > 1) or \
- (obj.old_show > 1 and obj.new_show == 0 and obj.conn.connected > 1):
- if account in self.status_sent_to_users and \
- jid in self.status_sent_to_users[account]:
- del self.status_sent_to_users[account][jid]
-
- if gajim.jid_is_transport(jid):
- # It must be an agent
-
- # transport just signed in/out, don't show
- # popup notifications for 30s
- account_jid = account + '/' + jid
- gajim.block_signed_in_notifications[account_jid] = True
- GLib.timeout_add_seconds(30, self.unblock_signed_in_notifications,
- account_jid)
-
- highest = gajim.contacts.get_contact_with_highest_priority(account, jid)
- is_highest = (highest and highest.resource == resource)
-
- ctrl = self.msg_win_mgr.get_control(jid, account)
- if ctrl and ctrl.session and len(obj.contact_list) > 1:
- ctrl.remove_session(ctrl.session)
-
- def handle_event_msgerror(self, obj):
- #'MSGERROR' (account, (jid, error_code, error_msg, msg, time[session]))
- account = obj.conn.name
- jids = obj.fjid.split('/', 1)
- jid = jids[0]
-
- session = obj.session
-
- gc_control = self.msg_win_mgr.get_gc_control(jid, account)
- if not gc_control and \
- jid in self.minimized_controls[account]:
- gc_control = self.minimized_controls[account][jid]
- if gc_control and gc_control.type_id != message_control.TYPE_GC:
- gc_control = None
- if gc_control:
- if len(jids) > 1: # it's a pm
- nick = jids[1]
-
- if session:
- ctrl = session.control
- else:
- ctrl = self.msg_win_mgr.get_control(obj.fjid, account)
-
- if not ctrl:
- tv = gc_control.list_treeview
- model = tv.get_model()
- iter_ = gc_control.get_contact_iter(nick)
- if iter_:
- show = model[iter_][3]
- else:
- show = 'offline'
- gc_c = gajim.contacts.create_gc_contact(room_jid=jid,
- account=account, name=nick, show=show)
- ctrl = self.new_private_chat(gc_c, account, session)
-
- ctrl.contact.our_chatstate = False
- ctrl.print_conversation(_('Error %(code)s: %(msg)s') % {
- 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
- return
-
- gc_control.print_conversation(_('Error %(code)s: %(msg)s') % {
- 'code': obj.error_code, 'msg': obj.error_msg}, 'status')
- if gc_control.parent_win and \
- gc_control.parent_win.get_active_jid() == jid:
- gc_control.set_subject(gc_control.subject)
- return
-
- if gajim.jid_is_transport(jid):
- jid = jid.replace('@', '')
- msg = obj.error_msg
- if obj.msg:
- msg = _('error while sending %(message)s ( %(error)s )') % {
- 'message': obj.msg, 'error': msg}
- if session:
- session.roster_message(jid, msg, obj.time_, msg_type='error')
-
- @staticmethod
- def handle_event_msgsent(obj):
- #('MSGSENT', account, (jid, msg, keyID))
- # do not play sound when standalone chatstate message (eg no msg)
- if obj.message and gajim.config.get_per('soundevents', 'message_sent',
- 'enabled'):
- helpers.play_sound('message_sent')
-
- @staticmethod
- def handle_event_msgnotsent(obj):
- #('MSGNOTSENT', account, (jid, ierror_msg, msg, time, session))
- msg = _('error while sending %(message)s ( %(error)s )') % {
- 'message': obj.message, 'error': obj.error}
- if not obj.session:
- # No session. This can happen when sending a message from
- # gajim-remote
- log.warning(msg)
- return
- obj.session.roster_message(obj.jid, msg, obj.time_, obj.conn.name,
- msg_type='error')
-
- def handle_event_subscribe_presence(self, obj):
- #('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
- account = obj.conn.name
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- if obj.jid in self.instances[account]['sub_request']:
- self.instances[account]['sub_request'][obj.jid].window.destroy()
- self.instances[account]['sub_request'][obj.jid] = \
- dialogs.SubscriptionRequestWindow(obj.jid, obj.status, account,
- obj.user_nick)
- return
-
- event = events.SubscriptionRequestEvent(obj.status, obj.user_nick)
- self.add_event(account, obj.jid, event)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-subscription_request',
- 48)
- event_type = _('Subscription request')
- notify.popup(event_type, obj.jid, account, 'subscription_request',
- path, event_type, obj.jid)
-
- def handle_event_subscribed_presence(self, obj):
- #('SUBSCRIBED', account, (jid, resource))
- account = obj.conn.name
- if obj.jid in gajim.contacts.get_jid_list(account):
- c = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
- c.resource = obj.resource
- self.roster.remove_contact_from_groups(c.jid, account,
- [_('Not in Roster'), _('Observers')], update=False)
- else:
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if obj.jid in attached_keys:
- keyID = attached_keys[attached_keys.index(obj.jid) + 1]
- name = obj.jid.split('@', 1)[0]
- name = name.split('%', 1)[0]
- contact1 = gajim.contacts.create_contact(jid=obj.jid,
- account=account, name=name, groups=[], show='online',
- status='online', ask='to', resource=obj.resource, keyID=keyID)
- gajim.contacts.add_contact(account, contact1)
- self.roster.add_contact(obj.jid, account)
- dialogs.InformationDialog(_('Authorization accepted'),
- _('The contact "%s" has authorized you to see his or her status.')
- % obj.jid)
-
- def show_unsubscribed_dialog(self, account, contact):
- def on_yes(is_checked, list_):
- self.roster.on_req_usub(None, list_)
- list_ = [(contact, account)]
- dialogs.YesNoDialog(
- _('Contact "%s" removed subscription from you') % contact.jid,
- _('You will always see them as offline.\nDo you want to '
- 'remove them from your contact list?'),
- on_response_yes=(on_yes, list_))
- # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
- # not show deny
-
- def handle_event_unsubscribed_presence(self, obj):
- #('UNSUBSCRIBED', account, jid)
- account = obj.conn.name
- contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
- if not contact:
- return
-
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- self.show_unsubscribed_dialog(account, contact)
- return
-
- event = events.UnsubscribedEvent(contact)
- self.add_event(account, obj.jid, event)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-unsubscribed', 48)
- event_type = _('Unsubscribed')
- notify.popup(event_type, obj.jid, account, 'unsubscribed', path,
- event_type, obj.jid)
-
- @staticmethod
- def handle_event_register_agent_info(obj):
- # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form))
- # info in a dataform if is_form is True
- if obj.is_form or 'instructions' in obj.config:
- config.ServiceRegistrationWindow(obj.agent, obj.config,
- obj.conn.name, obj.is_form)
- else:
- dialogs.ErrorDialog(_('Contact with "%s" cannot be established') % \
- obj.agent, _('Check your connection or try again later.'))
-
- def handle_event_vcard(self, obj):
- # ('VCARD', account, data)
- '''vcard holds the vcard data'''
- our_jid = gajim.get_jid_from_account(obj.conn.name)
- if obj.jid == our_jid:
- if obj.nickname:
- gajim.nicks[obj.conn.name] = obj.nickname
- if obj.conn.name in self.show_vcard_when_connect:
- self.show_vcard_when_connect.remove(obj.conn.name)
-
- def handle_event_last_status_time(self, obj):
- # ('LAST_STATUS_TIME', account, (jid, resource, seconds, status))
- account = obj.conn.name
- c = gajim.contacts.get_contact(account, obj.jid, obj.resource)
- tooltip_window = self.roster.tree.get_tooltip_window()
- if obj.seconds < 0:
- if tooltip_window:
- tooltip_window.update_last_time(c, True)
- return
-
- if c: # c can be none if it's a gc contact
- if obj.status:
- c.status = obj.status
- self.roster.draw_contact(c.jid, account) # draw offline status
- 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
-
- # Set last time on roster tooltip
- if tooltip_window:
- tooltip_window.update_last_time(c)
-
- def handle_event_gc_config(self, obj):
- #('GC_CONFIG', account, (jid, form_node)) config is a dict
- account = obj.conn.name
- if obj.jid in gajim.automatic_rooms[account]:
- if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
- # We're converting chat to muc. allow participants to invite
- for f in obj.dataform.iter_fields():
- if f.var == 'muc#roomconfig_allowinvites':
- f.value = True
- elif f.var == 'muc#roomconfig_publicroom':
- f.value = False
- elif f.var == 'muc#roomconfig_membersonly':
- f.value = True
- elif f.var == 'public_list':
- f.value = False
- obj.conn.send_gc_config(obj.jid, obj.dataform.get_purged())
- user_list = {}
- for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
- user_list[jid] = {'affiliation': 'member'}
- obj.conn.send_gc_affiliation_list(obj.jid, user_list)
- else:
- # use default configuration
- obj.conn.send_gc_config(obj.jid, obj.form_node)
- # invite contacts
- # check if it is necessary to add <continue />
- continue_tag = False
- if 'continue_tag' in gajim.automatic_rooms[account][obj.jid]:
- continue_tag = True
- if 'invities' in gajim.automatic_rooms[account][obj.jid]:
- for jid in gajim.automatic_rooms[account][obj.jid]['invities']:
- obj.conn.send_invite(obj.jid, jid,
- continue_tag=continue_tag)
- gc_control = self.msg_win_mgr.get_gc_control(obj.jid,
- account)
- if gc_control:
- gc_control.print_conversation(
- _('%(jid)s has been invited in this room') % {
- 'jid': jid}, graphics=False)
- del gajim.automatic_rooms[account][obj.jid]
- elif obj.jid not in self.instances[account]['gc_config']:
- self.instances[account]['gc_config'][obj.jid] = \
- config.GroupchatConfigWindow(account, obj.jid, obj.dataform)
-
- def handle_event_gc_affiliation(self, obj):
- #('GC_AFFILIATION', account, (room_jid, users_dict))
- account = obj.conn.name
- if obj.jid in self.instances[account]['gc_config']:
- self.instances[account]['gc_config'][obj.jid].\
- affiliation_list_received(obj.users_dict)
-
- def handle_event_gc_decline(self, obj):
- account = obj.conn.name
- gc_control = self.msg_win_mgr.get_gc_control(obj.room_jid, account)
- if gc_control:
- if obj.reason:
- gc_control.print_conversation(
- _('%(jid)s declined the invitation: %(reason)s') % {
- 'jid': obj.jid_from, 'reason': obj.reason}, graphics=False)
- else:
- gc_control.print_conversation(
- _('%(jid)s declined the invitation') % {
- 'jid': obj.jid_from}, graphics=False)
-
- def handle_event_gc_invitation(self, obj):
- #('GC_INVITATION', (room_jid, jid_from, reason, password, is_continued))
- account = obj.conn.name
- if helpers.allow_popup_window(account) or not self.systray_enabled:
- dialogs.InvitationReceivedDialog(account, obj.room_jid,
- obj.jid_from, obj.password, obj.reason,
- is_continued=obj.is_continued)
- return
-
- event = events.GcInvitationtEvent(obj.room_jid, obj.reason,
- obj.password, obj.is_continued, obj.jid_from)
- self.add_event(account, obj.jid_from, event)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-gc_invitation', 48)
- event_type = _('Groupchat Invitation')
- notify.popup(event_type, obj.jid_from, account, 'gc-invitation',
- path, event_type, obj.room_jid)
-
- def forget_gpg_passphrase(self, keyid):
- if keyid in self.gpg_passphrase:
- del self.gpg_passphrase[keyid]
- return False
-
- def handle_event_bad_gpg_passphrase(self, obj):
- #('BAD_PASSPHRASE', account, ())
- if obj.use_gpg_agent:
- sectext = _('You configured Gajim to use OpenPGP agent, but there '
- 'is no OpenPGP agent running or it returned a wrong passphrase.'
- '\n')
- sectext += _('You are currently connected without your OpenPGP '
- 'key.')
- dialogs.WarningDialog(_('Wrong passphrase'), sectext)
- else:
- path = gtkgui_helpers.get_icon_path('gtk-dialog-warning', 48)
- account = obj.conn.name
- notify.popup('warning', account, account, '', path,
- _('Wrong OpenPGP passphrase'),
- _('You are currently connected without your OpenPGP key.'))
- self.forget_gpg_passphrase(obj.keyID)
-
- @staticmethod
- def handle_event_client_cert_passphrase(obj):
- def on_ok(passphrase, checked):
- obj.conn.on_client_cert_passphrase(passphrase, obj.con, obj.port,
- obj.secure_tuple)
-
- def on_cancel():
- obj.conn.on_client_cert_passphrase('', obj.con, obj.port,
- obj.secure_tuple)
-
- dialogs.PassphraseDialog(_('Certificate Passphrase Required'),
- _('Enter the certificate passphrase for account %s') % \
- obj.conn.name, ok_handler=on_ok, cancel_handler=on_cancel)
-
- def handle_event_gpg_password_required(self, obj):
- #('GPG_PASSWORD_REQUIRED', account, (callback,))
- if obj.keyid in self.gpg_passphrase:
- request = self.gpg_passphrase[obj.keyid]
- else:
- request = PassphraseRequest(obj.keyid)
- self.gpg_passphrase[obj.keyid] = request
- request.add_callback(obj.conn.name, obj.callback)
-
- @staticmethod
- def handle_event_gpg_trust_key(obj):
- #('GPG_ALWAYS_TRUST', account, callback)
- def on_yes(checked):
- if checked:
- obj.conn.gpg.always_trust.append(obj.keyID)
- obj.callback(True)
-
- def on_no():
- obj.callback(False)
-
- dialogs.YesNoDialog(_('Untrusted OpenPGP key'), _('The OpenPGP key '
- 'used to encrypt this chat is not trusted. Do you really want to '
- 'encrypt this message?'), checktext=_('_Do not ask me again'),
- on_response_yes=on_yes, on_response_no=on_no)
-
- def handle_event_password_required(self, obj):
- #('PASSWORD_REQUIRED', account, None)
- account = obj.conn.name
- if account in self.pass_dialog:
- return
- text = _('Enter your password for account %s') % account
-
- def on_ok(passphrase, save):
- if save:
- gajim.config.set_per('accounts', account, 'savepass', True)
- passwords.save_password(account, passphrase)
- obj.conn.set_password(passphrase)
- del self.pass_dialog[account]
-
- def on_cancel():
- self.roster.set_state(account, 'offline')
- self.roster.update_status_combobox()
- del self.pass_dialog[account]
-
- self.pass_dialog[account] = dialogs.PassphraseDialog(
- _('Password Required'), text, _('Save password'), ok_handler=on_ok,
- cancel_handler=on_cancel)
-
- def handle_oauth2_credentials(self, obj):
- account = obj.conn.name
- def on_ok(refresh):
- gajim.config.set_per('accounts', account, 'oauth2_refresh_token',
- refresh)
- st = gajim.config.get_per('accounts', account, 'last_status')
- msg = helpers.from_one_line(gajim.config.get_per('accounts',
- account, 'last_status_msg'))
- gajim.interface.roster.send_status(account, st, msg)
- del self.pass_dialog[account]
-
- def on_cancel():
- gajim.config.set_per('accounts', account, 'oauth2_refresh_token',
- '')
- self.roster.set_state(account, 'offline')
- self.roster.update_status_combobox()
- del self.pass_dialog[account]
-
- instruction = _('Please copy / paste the refresh token from the website'
- ' that has just been opened.')
- self.pass_dialog[account] = dialogs.InputTextDialog(
- _('Oauth2 Credentials'), instruction, is_modal=False,
- ok_handler=on_ok, cancel_handler=on_cancel)
-
- def handle_event_roster_info(self, obj):
- #('ROSTER_INFO', account, (jid, name, sub, ask, groups))
- account = obj.conn.name
- contacts = gajim.contacts.get_contacts(account, obj.jid)
- if (not obj.sub or obj.sub == 'none') and \
- (not obj.ask or obj.ask == 'none') and not obj.nickname and \
- not obj.groups:
- # contact removed us.
- if contacts:
- self.roster.remove_contact(obj.jid, account, backend=True)
- return
- elif not contacts:
- if obj.sub == 'remove':
- return
- # Add new contact to roster
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if obj.jid in attached_keys:
- keyID = attached_keys[attached_keys.index(obj.jid) + 1]
- contact = gajim.contacts.create_contact(jid=obj.jid,
- account=account, name=obj.nickname, groups=obj.groups,
- show='offline', sub=obj.sub, ask=obj.ask, keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- self.roster.add_contact(obj.jid, account)
- else:
- # If contact has changed (sub, ask or group) update roster
- # Mind about observer status changes:
- # According to xep 0162, a contact is not an observer anymore when
- # we asked for auth, so also remove him if ask changed
- old_groups = contacts[0].groups
- if obj.sub == 'remove':
- # another of our instance removed a contact. Remove it here too
- self.roster.remove_contact(obj.jid, account, backend=True)
- return
- update = False
- if contacts[0].sub != obj.sub or contacts[0].ask != obj.ask\
- or old_groups != obj.groups:
- # c.get_shown_groups() has changed. Reflect that in
- # roster_window
- self.roster.remove_contact(obj.jid, account, force=True)
- update = True
- for contact in contacts:
- contact.name = obj.nickname or ''
- contact.sub = obj.sub
- contact.ask = obj.ask
- contact.groups = obj.groups or []
- if update:
- self.roster.add_contact(obj.jid, account)
- # Refilter and update old groups
- for group in old_groups:
- self.roster.draw_group(group, account)
- self.roster.draw_contact(obj.jid, account)
- if obj.jid in self.instances[account]['sub_request'] and obj.sub in (
- 'from', 'both'):
- self.instances[account]['sub_request'][obj.jid].window.destroy()
-
- def handle_event_bookmarks(self, obj):
- # ('BOOKMARKS', account, [{name,jid,autojoin,password,nick}, {}])
- # We received a bookmark item from the server (JEP48)
- # Auto join GC windows if neccessary
-
- gui_menu_builder.build_bookmark_menu(obj.conn.name)
- invisible_show = gajim.SHOW_LIST.index('invisible')
- # do not autojoin if we are invisible
- if obj.conn.connected == invisible_show:
- return
-
- GLib.idle_add(self.auto_join_bookmarks, obj.conn.name)
-
- def handle_event_file_send_error(self, account, array):
- jid = array[0]
- file_props = array[1]
- ft = self.instances['file_transfers']
- ft.set_status(file_props, 'stop')
-
- if helpers.allow_popup_window(account):
- ft.show_send_error(file_props)
- return
-
- event = events.FileSendErrorEvent(file_props)
- self.add_event(account, jid, event)
-
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
- event_type = _('File Transfer Error')
- notify.popup(event_type, jid, account, 'file-send-error', path,
- event_type, file_props.name)
-
- @staticmethod
- def handle_event_gmail_notify(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 e-mail on %(gmail_mail_address)s') % \
- {'gmail_mail_address': jid}
- text = i18n.ngettext('You have %d new e-mail conversation',
- 'You have %d new e-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
-
- 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, obj.conn.name, 'gmail',
- path_to_image=path, title=title, text=text)
-
- def handle_event_file_request_error(self, obj):
- # ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
- ft = self.instances['file_transfers']
- ft.set_status(obj.file_props, 'stop')
- errno = obj.file_props.error
-
- if helpers.allow_popup_window(obj.conn.name):
- if errno in (-4, -5):
- ft.show_stopped(obj.jid, obj.file_props, obj.error_msg)
- else:
- ft.show_request_error(obj.file_props)
- return
-
- if errno in (-4, -5):
- event_class = events.FileErrorEvent
- msg_type = 'file-error'
- else:
- event_class = events.FileRequestErrorEvent
- msg_type = 'file-request-error'
-
- event = event_class(obj.file_props)
- self.add_event(obj.conn.name, obj.jid, event)
-
- if helpers.allow_showing_notification(obj.conn.name):
- # check if we should be notified
- path = gtkgui_helpers.get_icon_path('gajim-ft_error', 48)
- event_type = _('File Transfer Error')
- notify.popup(event_type, obj.jid, obj.conn.name, msg_type, path,
- title=event_type, text=obj.file_props.name)
-
- def handle_event_file_request(self, obj):
- account = obj.conn.name
- if obj.jid not in gajim.contacts.get_jid_list(account):
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if obj.jid in attached_keys:
- keyID = attached_keys[attached_keys.index(obj.jid) + 1]
- contact = gajim.contacts.create_not_in_roster_contact(jid=obj.jid,
- account=account, keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- self.roster.add_contact(obj.jid, account)
- contact = gajim.contacts.get_first_contact_from_jid(account, obj.jid)
- if obj.file_props.session_type == 'jingle':
- request = obj.stanza.getTag('jingle').getTag('content')\
- .getTag('description').getTag('request')
- if request:
- # If we get a request instead
- ft_win = self.instances['file_transfers']
- ft_win.add_transfer(account, contact, obj.file_props)
- return
- if helpers.allow_popup_window(account):
- self.instances['file_transfers'].show_file_request(account, contact,
- obj.file_props)
- return
- event = events.FileRequestEvent(obj.file_props)
- self.add_event(account, obj.jid, event)
- if helpers.allow_showing_notification(account):
- path = gtkgui_helpers.get_icon_path('gajim-ft_request', 48)
- txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
- account, obj.jid)
- event_type = _('File Transfer Request')
- notify.popup(event_type, obj.jid, account, 'file-request',
- path_to_image=path, title=event_type, text=txt)
-
- @staticmethod
- def handle_event_file_error(title, message):
- dialogs.ErrorDialog(title, message)
-
- def handle_event_file_progress(self, account, file_props):
- if time.time() - self.last_ftwindow_update > 0.5:
- # update ft window every 500ms
- self.last_ftwindow_update = time.time()
- self.instances['file_transfers'].set_progress(file_props.type_,
- file_props.sid, file_props.received_len)
-
- def __compare_hashes(self, account, file_props):
- session = gajim.connections[account].get_jingle_session(jid=None,
- sid=file_props.sid)
- ft_win = self.instances['file_transfers']
- h = Hashes2()
- try:
- file_ = open(file_props.file_name, 'rb')
- except:
- return
- hash_ = h.calculateHash(file_props.algo, file_)
- file_.close()
- # If the hash we received and the hash of the file are the same,
- # then the file is not corrupt
- jid = file_props.sender
- if file_props.hash_ == hash_:
- GLib.idle_add(self.popup_ft_result, account, jid, file_props)
- GLib.idle_add(ft_win.set_status, file_props, 'ok')
- else:
- # wrong hash, we need to get the file again!
- file_props.error = -10
- GLib.idle_add(self.popup_ft_result, account, jid, file_props)
- GLib.idle_add(ft_win.set_status, file_props, 'hash_error')
- # End jingle session
- if session:
- session.end_session()
-
- def handle_event_file_rcv_completed(self, account, file_props):
- ft = self.instances['file_transfers']
- if file_props.error == 0:
- ft.set_progress(file_props.type_, file_props.sid,
- file_props.received_len)
- gajim.nec.push_incoming_event(FileTransferCompletedEvent(None,
- file_props=file_props))
- else:
- ft.set_status(file_props, 'stop')
- if file_props.stalled or file_props.paused:
- return
-
- if file_props.type_ == 'r': # we receive a file
- gajim.socks5queue.remove_receiver(file_props.sid, True, True)
- if file_props.session_type == 'jingle':
- if file_props.hash_ and file_props.error == 0:
- # We compare hashes in a new thread
- self.hashThread = Thread(target=self.__compare_hashes,
- args=(account, file_props))
- self.hashThread.start()
- else:
- # We disn't get the hash, sender probably don't support that
- jid = file_props.sender
- self.popup_ft_result(account, jid, file_props)
- if file_props.error == 0:
- ft.set_status(file_props, 'ok')
- session = gajim.connections[account].get_jingle_session(jid=None,
- sid=file_props.sid)
- # End jingle session
- # TODO: only if there are no other parallel downloads in this session
- if session:
- session.end_session()
- else: # we send a file
- jid = file_props.receiver
- gajim.socks5queue.remove_sender(file_props.sid, True, True)
- self.popup_ft_result(account, jid, file_props)
-
- def popup_ft_result(self, account, jid, file_props):
- ft = self.instances['file_transfers']
- if helpers.allow_popup_window(account):
- if file_props.error == 0:
- if gajim.config.get('notify_on_file_complete'):
- ft.show_completed(jid, file_props)
- elif file_props.error == -1:
- ft.show_stopped(jid, file_props,
- error_msg=_('Remote contact stopped transfer'))
- elif file_props.error == -6:
- ft.show_stopped(jid, file_props,
- error_msg=_('Error opening file'))
- elif file_props.error == -10:
- ft.show_hash_error(jid, file_props, account)
- elif file_props.error == -12:
- ft.show_stopped(jid, file_props,
- error_msg=_('SSL certificate error'))
- return
-
- msg_type = ''
- event_type = ''
- if file_props.error == 0 and gajim.config.get(
- 'notify_on_file_complete'):
- event_class = events.FileCompletedEvent
- msg_type = 'file-completed'
- event_type = _('File Transfer Completed')
- elif file_props.error in (-1, -6):
- event_class = events.FileStoppedEvent
- msg_type = 'file-stopped'
- event_type = _('File Transfer Stopped')
- elif file_props.error == -10:
- event_class = events.FileHashErrorEvent
- msg_type = 'file-hash-error'
- event_type = _('File Transfer Failed')
-
- if event_type == '':
- # FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
- # this should never happen but it does. see process_result() in
- # socks5.py
- # who calls this func (sth is really wrong unless this func is also
- # registered as progress_cb
- return
-
- if msg_type:
- event = event_class(file_props)
- self.add_event(account, jid, event)
-
- if file_props is not None:
- if file_props.type_ == 'r':
- # get the name of the sender, as it is in the roster
- sender = file_props.sender.split('/')[0]
- name = gajim.contacts.get_first_contact_from_jid(account,
- sender).get_shown_name()
- filename = os.path.basename(file_props.file_name)
- if event_type == _('File Transfer Completed'):
- txt = _('%(filename)s received from %(name)s.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_done'
- elif event_type == _('File Transfer Stopped'):
- txt = _('File transfer of %(filename)s from %(name)s '
- 'stopped.') % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- else: # ft hash error
- txt = _('File transfer of %(filename)s from %(name)s '
- 'failed.') % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- else:
- receiver = file_props.receiver
- if hasattr(receiver, 'jid'):
- receiver = receiver.jid
- receiver = receiver.split('/')[0]
- # get the name of the contact, as it is in the roster
- name = gajim.contacts.get_first_contact_from_jid(account,
- receiver).get_shown_name()
- filename = os.path.basename(file_props.file_name)
- if event_type == _('File Transfer Completed'):
- txt = _('You successfully sent %(filename)s to %(name)s.')\
- % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_done'
- elif event_type == _('File Transfer Stopped'):
- txt = _('File transfer of %(filename)s to %(name)s '
- 'stopped.') % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- else: # ft hash error
- txt = _('File transfer of %(filename)s to %(name)s '
- 'failed.') % {'filename': filename, 'name': name}
- img_name = 'gajim-ft_stopped'
- path = gtkgui_helpers.get_icon_path(img_name, 48)
- else:
- txt = ''
- path = ''
-
- if gajim.config.get('notify_on_file_complete') and \
- (gajim.config.get('autopopupaway') or \
- gajim.connections[account].connected in (2, 3)):
- # we want to be notified and we are online/chat or we don't mind
- # bugged when away/na/busy
- notify.popup(event_type, jid, account, msg_type, path_to_image=path,
- title=event_type, text=txt)
-
- @staticmethod
- def ask_offline_status(account):
- for contact in gajim.contacts.iter_contacts(account):
- gajim.connections[account].request_last_status_time(contact.jid,
- contact.resource)
-
- def handle_event_signed_in(self, obj):
- """
- SIGNED_IN event is emitted when we sign in, so handle it
- """
- # ('SIGNED_IN', account, ())
- # block signed in notifications for 30 seconds
- account = obj.conn.name
- gajim.block_signed_in_notifications[account] = True
- state = self.sleeper.getState()
- connected = obj.conn.connected
- if gajim.config.get('ask_offline_status_on_connection'):
- # Ask offline status in 1 minute so w'are sure we got all online
- # presences
- GLib.timeout_add_seconds(60, self.ask_offline_status, account)
- if state != sleepy.STATE_UNKNOWN and connected in (2, 3):
- # we go online or free for chat, so we activate auto status
- gajim.sleeper_state[account] = 'online'
- elif not ((state == sleepy.STATE_AWAY and connected == 4) or \
- (state == sleepy.STATE_XA and connected == 5)):
- # If we are autoaway/xa and come back after a disconnection, do
- # nothing
- # Else disable autoaway
- gajim.sleeper_state[account] = 'off'
-
- if obj.conn.archiving_136_supported and gajim.config.get_per('accounts',
- account, 'sync_logs_with_server'):
- # Start merging logs from server
- obj.conn.request_modifications_page(gajim.config.get_per('accounts',
- account, 'last_archiving_time'))
- gajim.config.set_per('accounts', account, 'last_archiving_time',
- time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
- if obj.conn.archiving_313_supported and gajim.config.get_per('accounts',
- account, 'sync_logs_with_server'):
- mam_id = gajim.config.get_per('accounts', account, 'last_mam_id')
- if mam_id:
- obj.conn.request_archive(after=mam_id)
- else:
- obj.conn.request_archive(start='2013-02-24T03:51:42Z')
-
- invisible_show = gajim.SHOW_LIST.index('invisible')
- # We cannot join rooms if we are invisible
- if connected == invisible_show:
- return
- # send currently played music
- if obj.conn.pep_supported and dbus_support.supported and \
- gajim.config.get_per('accounts', account, 'publish_tune'):
- self.enable_music_listener()
- # enable location listener
- if obj.conn.pep_supported and dbus_support.supported and \
- gajim.config.get_per('accounts', account, 'publish_location'):
- location_listener.enable()
-
-
- @staticmethod
- def handle_event_metacontacts(obj):
- gajim.contacts.define_metacontacts(obj.conn.name, obj.meta_list)
-
- @staticmethod
- def handle_atom_entry(obj):
- AtomWindow.newAtomEntry(obj.atom_entry)
-
- @staticmethod
- def handle_event_failed_decrypt(obj):
- details = _('Unable to decrypt message from %s\nIt may have been '
- 'tampered with.') % obj.fjid
- dialogs.WarningDialog(_('Unable to decrypt message'), details)
-
- def handle_event_zc_name_conflict(self, obj):
- def on_ok(new_name):
- gajim.config.set_per('accounts', obj.conn.name, 'name', new_name)
- show = obj.conn.old_show
- status = obj.conn.status
- obj.conn.username = new_name
- obj.conn.change_status(show, status)
- def on_cancel():
- obj.conn.change_status('offline', '')
-
- dlg = dialogs.InputDialog(_('Username Conflict'),
- _('Please type a new username for your local account'),
- input_str=obj.alt_name, is_modal=True, ok_handler=on_ok,
- cancel_handler=on_cancel, transient_for=self.roster.window)
-
- def handle_event_resource_conflict(self, obj):
- # ('RESOURCE_CONFLICT', account, ())
- # First we go offline, but we don't overwrite status message
- account = obj.conn.name
- conn = obj.conn
- self.roster.send_status(account, 'offline', conn.status)
- def on_ok(new_resource):
- gajim.config.set_per('accounts', account, 'resource', new_resource)
- self.roster.send_status(account, conn.old_show, conn.status)
- proposed_resource = conn.server_resource
- proposed_resource += gajim.config.get('gc_proposed_nick_char')
- dlg = dialogs.ResourceConflictDialog(_('Resource Conflict'),
- _('You are already connected to this account with the same '
- 'resource. Please type a new one'), resource=proposed_resource,
- ok_handler=on_ok)
-
- def handle_event_jingleft_cancel(self, obj):
- ft = self.instances['file_transfers']
- file_props = None
- # get the file_props of our session
- file_props = FilesProp.getFileProp(obj.conn.name, obj.sid)
- if not file_props:
- return
- ft.set_status(file_props, 'stop')
- file_props.error = -4 # is it the right error code?
- ft.show_stopped(obj.jid, file_props, 'Peer cancelled ' +
- 'the transfer')
-
- def handle_event_jingle_incoming(self, obj):
- # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
- # data...))
- # TODO: conditional blocking if peer is not in roster
-
- account = obj.conn.name
- content_types = set(c[0] for c in obj.contents)
-
- # check type of jingle session
- if 'audio' in content_types or 'video' in content_types:
- # a voip session...
- # we now handle only voip, so the only thing we will do here is
- # not to return from function
- pass
- else:
- # unknown session type... it should be declined in common/jingle.py
- return
-
- ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
- or self.msg_win_mgr.get_control(obj.jid, account))
- if ctrl:
- if 'audio' in content_types:
- ctrl.set_audio_state('connection_received', obj.sid)
- if 'video' in content_types:
- ctrl.set_video_state('connection_received', obj.sid)
-
- dlg = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
- if dlg:
- dlg.add_contents(content_types)
- return
-
- if helpers.allow_popup_window(account):
- dialogs.VoIPCallReceivedDialog(account, obj.fjid, obj.sid,
- content_types)
- return
-
- event = events.JingleIncomingEvent(obj.fjid, obj.sid, content_types)
- self.add_event(account, obj.jid, event)
-
- if helpers.allow_showing_notification(account):
- # TODO: we should use another pixmap ;-)
- txt = _('%s wants to start a voice chat.') % \
- gajim.get_name_from_jid(account, obj.fjid)
- path = gtkgui_helpers.get_icon_path('gajim-mic_active', 48)
- event_type = _('Voice Chat Request')
- notify.popup(event_type, obj.fjid, account, 'jingle-incoming',
- path_to_image=path, title=event_type, text=txt)
-
- def handle_event_jingle_connected(self, obj):
- # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
- if obj.media in ('audio', 'video'):
- account = obj.conn.name
- ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
- or self.msg_win_mgr.get_control(obj.jid, account))
- if ctrl:
- if obj.media == 'audio':
- ctrl.set_audio_state('connected', obj.sid)
- else:
- ctrl.set_video_state('connected', obj.sid)
-
- def handle_event_jingle_disconnected(self, obj):
- # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
- account = obj.conn.name
- ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
- or self.msg_win_mgr.get_control(obj.jid, account))
- if ctrl:
- if obj.media is None:
- ctrl.stop_jingle(sid=obj.sid, reason=obj.reason)
- elif obj.media == 'audio':
- ctrl.set_audio_state('stop', sid=obj.sid, reason=obj.reason)
- elif obj.media == 'video':
- ctrl.set_video_state('stop', sid=obj.sid, reason=obj.reason)
- dialog = dialogs.VoIPCallReceivedDialog.get_dialog(obj.fjid, obj.sid)
- if dialog:
- if obj.media is None:
- dialog.dialog.destroy()
- else:
- dialog.remove_contents((obj.media, ))
-
- def handle_event_jingle_error(self, obj):
- # ('JINGLE_ERROR', account, (peerjid, sid, reason))
- account = obj.conn.name
- ctrl = (self.msg_win_mgr.get_control(obj.fjid, account)
- or self.msg_win_mgr.get_control(obj.jid, account))
- if ctrl:
- ctrl.set_audio_state('error', reason=obj.reason)
-
- @staticmethod
- def handle_event_roster_item_exchange(obj):
- # data = (action in [add, delete, modify], exchange_list, jid_from)
- dialogs.RosterItemExchangeWindow(obj.conn.name, obj.action,
- obj.exchange_items_list, obj.fjid)
-
- def handle_event_ssl_error(self, obj):
- # ('SSL_ERROR', account, (text, errnum, cert, sha1_fingerprint, sha256_fingerprint))
- account = obj.conn.name
- server = gajim.config.get_per('accounts', account, 'hostname')
-
- def on_ok(is_checked):
- del self.instances[account]['online_dialog']['ssl_error']
- if is_checked[0]:
- # Check if cert is already in file
- certs = ''
- if os.path.isfile(gajim.MY_CACERTS):
- f = open(gajim.MY_CACERTS)
- certs = f.read()
- f.close()
- if obj.cert in certs:
- dialogs.ErrorDialog(_('Certificate Already in File'),
- _('This certificate is already in file %s, so it\'s '
- 'not added again.') % gajim.MY_CACERTS)
- else:
- f = open(gajim.MY_CACERTS, 'a')
- f.write(server + '\n')
- f.write(obj.cert + '\n\n')
- f.close()
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
- obj.fingerprint_sha1)
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha256',
- obj.fingerprint_sha256)
- if is_checked[1]:
- ignore_ssl_errors = gajim.config.get_per('accounts', account,
- 'ignore_ssl_errors').split()
- ignore_ssl_errors.append(str(obj.error_num))
- gajim.config.set_per('accounts', account, 'ignore_ssl_errors',
- ' '.join(ignore_ssl_errors))
- obj.conn.ssl_certificate_accepted()
-
- def on_cancel():
- del self.instances[account]['online_dialog']['ssl_error']
- obj.conn.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
- show='offline'))
-
- pritext = _('Error verifying SSL certificate')
- sectext = _('There was an error verifying the SSL certificate of your '
- 'XMPP server: %(error)s\nDo you still want to connect to this '
- 'server?') % {'error': obj.error_text}
- if obj.error_num in (18, 27):
- checktext1 = _('Add this certificate to the list of trusted '
- 'certificates.\nSHA-1 fingerprint of the certificate:\n%s'
- '\nSHA256 fingerprint of the certificate:\n%s') % \
- (obj.fingerprint_sha1, obj.fingerprint_sha256)
- else:
- checktext1 = ''
- checktext2 = _('Ignore this error for this certificate.')
- if 'ssl_error' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['ssl_error'].destroy()
- self.instances[account]['online_dialog']['ssl_error'] = \
- dialogs.SSLErrorDialog(obj.conn.name, obj.certificate, pritext,
- sectext, checktext1, checktext2, on_response_ok=on_ok,
- on_response_cancel=on_cancel)
- self.instances[account]['online_dialog']['ssl_error'].set_title(
- _('SSL Certificate Verification for %s') % account)
-
- def handle_event_non_anonymous_server(self, obj):
- account = obj.conn.name
- server = gajim.config.get_per('accounts', account, 'hostname')
- dialogs.ErrorDialog(_('Non Anonymous Server'), sectext='Server "%s"'
- 'does not support anonymous connection' % server,
- transient_for=self.roster.window)
-
- def handle_event_fingerprint_error(self, obj):
- # ('FINGERPRINT_ERROR', account, (new_fingerprint_sha1,new_fingerprint_sha256,))
- account = obj.conn.name
- def on_yes(is_checked):
- del self.instances[account]['online_dialog']['fingerprint_error']
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha1',
- obj.new_fingerprint_sha1)
- gajim.config.set_per('accounts', account, 'ssl_fingerprint_sha256',
- obj.new_fingerprint_sha256)
- # Reset the ignored ssl errors
- gajim.config.set_per('accounts', account, 'ignore_ssl_errors', '')
- obj.conn.ssl_certificate_accepted()
-
- def on_no():
- del self.instances[account]['online_dialog']['fingerprint_error']
- obj.conn.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
- show='offline'))
-
- pritext = _('SSL certificate error')
- sectext = _('It seems the SSL certificate of account %(account)s has '
- 'changed and is not valid or your connection is being compromised.\n\n'
- 'Old SHA-1 fingerprint: '
- '%(old_sha1)s\nOld SHA-256 fingerprint: %(old_sha256)s\n\n'
- 'New SHA-1 fingerprint: %(new_sha1)s\nNew SHA-256 fingerprint: '
- '%(new_sha256)s\n\nDo you still want to connect '
- 'and update the fingerprint of the certificate?') % \
- {'account': account,
- 'old_sha1': gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha1'),
- 'old_sha256': gajim.config.get_per('accounts', account, 'ssl_fingerprint_sha256'),
- 'new_sha1': obj.new_fingerprint_sha1,
- 'new_sha256': obj.new_fingerprint_sha256}
- if 'fingerprint_error' in self.instances[account]['online_dialog']:
- self.instances[account]['online_dialog']['fingerprint_error'].\
- destroy()
- self.instances[account]['online_dialog']['fingerprint_error'] = \
- dialogs.CheckFingerprintDialog(pritext, sectext, on_response_yes=on_yes,
- on_response_no=on_no, account=obj.conn.name,
- certificate=obj.certificate)
-
- def handle_event_plain_connection(self, obj):
- # ('PLAIN_CONNECTION', account, (connection))
- def on_ok(is_checked):
- if not is_checked[0]:
- if is_checked[1]:
- gajim.config.set_per('accounts', obj.conn.name,
- 'action_when_plaintext_connection', 'disconnect')
- on_cancel()
- return
- # On cancel call del self.instances, so don't call it another time
- # before
- del self.instances[obj.conn.name]['online_dialog']\
- ['plain_connection']
- if is_checked[1]:
- gajim.config.set_per('accounts', obj.conn.name,
- 'action_when_plaintext_connection', 'connect')
- obj.conn.connection_accepted(obj.xmpp_client, 'plain')
-
- def on_cancel():
- del self.instances[obj.conn.name]['online_dialog']\
- ['plain_connection']
- obj.conn.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
- show='offline'))
-
- if 'plain_connection' in self.instances[obj.conn.name]['online_dialog']:
- self.instances[obj.conn.name]['online_dialog']['plain_connection'].\
- destroy()
- self.instances[obj.conn.name]['online_dialog']['plain_connection'] = \
- dialogs.PlainConnectionDialog(obj.conn.name, on_ok, on_cancel)
-
- def handle_event_insecure_ssl_connection(self, obj):
- # ('INSECURE_SSL_CONNECTION', account, (connection, connection_type))
- def on_ok(is_checked):
- if not is_checked[0]:
- on_cancel()
- return
- del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
- if is_checked[1]:
- gajim.config.set_per('accounts', obj.conn.name,
- 'warn_when_insecure_ssl_connection', False)
- if obj.conn.connected == 0:
- # We have been disconnecting (too long time since window is
- # opened)
- # re-connect with auto-accept
- obj.conn.connection_auto_accepted = True
- show, msg = obj.conn.continue_connect_info[:2]
- self.roster.send_status(obj.conn.name, show, msg)
- return
- obj.conn.connection_accepted(obj.xmpp_client, obj.conn_type)
-
- def on_cancel():
- del self.instances[obj.conn.name]['online_dialog']['insecure_ssl']
- obj.conn.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
- show='offline'))
-
- pritext = _('Insecure connection')
- sectext = _('You are about to send your password on an insecure '
- 'connection. You should install PyOpenSSL to prevent that. Are you '
- 'sure you want to do that?')
- checktext1 = _('Yes, I really want to connect insecurely')
- checktext2 = _('_Do not ask me again')
- if 'insecure_ssl' in self.instances[obj.conn.name]['online_dialog']:
- self.instances[obj.conn.name]['online_dialog']['insecure_ssl'].\
- destroy()
- self.instances[obj.conn.name]['online_dialog']['insecure_ssl'] = \
- dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
- checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
- is_modal=False)
-
- def handle_event_insecure_password(self, obj):
- # ('INSECURE_PASSWORD', account, ())
- def on_ok(is_checked):
- if not is_checked[0]:
- on_cancel()
- return
- del self.instances[obj.conn.name]['online_dialog']\
- ['insecure_password']
- if is_checked[1]:
- gajim.config.set_per('accounts', obj.conn.name,
- 'warn_when_insecure_password', False)
- if obj.conn.connected == 0:
- # We have been disconnecting (too long time since window is
- # opened)
- # re-connect with auto-accept
- obj.conn.connection_auto_accepted = True
- show, msg = obj.conn.continue_connect_info[:2]
- self.roster.send_status(obj.conn.name, show, msg)
- return
- obj.conn.accept_insecure_password()
-
- def on_cancel():
- del self.instances[obj.conn.name]['online_dialog']\
- ['insecure_password']
- obj.conn.disconnect(on_purpose=True)
- gajim.nec.push_incoming_event(OurShowEvent(None, conn=obj.conn,
- show='offline'))
-
- pritext = _('Insecure connection')
- sectext = _('You are about to send your password unencrypted on an '
- 'insecure connection. Are you sure you want to do that?')
- checktext1 = _('Yes, I really want to connect insecurely')
- checktext2 = _('_Do not ask me again')
- if 'insecure_password' in self.instances[obj.conn.name]\
- ['online_dialog']:
- self.instances[obj.conn.name]['online_dialog']\
- ['insecure_password'].destroy()
- self.instances[obj.conn.name]['online_dialog']['insecure_password'] = \
- dialogs.ConfirmationDialogDoubleCheck(pritext, sectext, checktext1,
- checktext2, on_response_ok=on_ok, on_response_cancel=on_cancel,
- is_modal=False)
-
- def create_core_handlers_list(self):
- self.handlers = {
- 'DB_ERROR': [self.handle_event_db_error],
- 'FILE_SEND_ERROR': [self.handle_event_file_send_error],
- 'atom-entry-received': [self.handle_atom_entry],
- 'bad-gpg-passphrase': [self.handle_event_bad_gpg_passphrase],
- 'bookmarks-received': [self.handle_event_bookmarks],
- 'client-cert-passphrase': [
- self.handle_event_client_cert_passphrase],
- 'connection-lost': [self.handle_event_connection_lost],
- 'failed-decrypt': [(self.handle_event_failed_decrypt, ged.GUI2)],
- 'file-request-error': [self.handle_event_file_request_error],
- 'file-request-received': [self.handle_event_file_request],
- 'fingerprint-error': [self.handle_event_fingerprint_error],
- 'gc-invitation-received': [self.handle_event_gc_invitation],
- 'gc-decline-received': [self.handle_event_gc_decline],
- 'gc-presence-received': [self.handle_event_gc_presence],
- 'gc-message-received': [self.handle_event_gc_message],
- 'gmail-notify': [self.handle_event_gmail_notify],
- 'gpg-password-required': [self.handle_event_gpg_password_required],
- 'gpg-trust-key': [self.handle_event_gpg_trust_key],
- 'http-auth-received': [self.handle_event_http_auth],
- 'information': [self.handle_event_information],
- 'insecure-password': [self.handle_event_insecure_password],
- 'insecure-ssl-connection': \
- [self.handle_event_insecure_ssl_connection],
- 'iq-error-received': [self.handle_event_iq_error],
- 'jingle-connected-received': [self.handle_event_jingle_connected],
- 'jingle-disconnected-received': [
- self.handle_event_jingle_disconnected],
- 'jingle-error-received': [self.handle_event_jingle_error],
- 'jingle-request-received': [self.handle_event_jingle_incoming],
- 'jingleFT-cancelled-received': [self.handle_event_jingleft_cancel],
- 'last-result-received': [self.handle_event_last_status_time],
- 'message-error': [self.handle_event_msgerror],
- 'message-not-sent': [self.handle_event_msgnotsent],
- 'message-sent': [self.handle_event_msgsent],
- 'metacontacts-received': [self.handle_event_metacontacts],
- 'muc-admin-received': [self.handle_event_gc_affiliation],
- 'muc-owner-received': [self.handle_event_gc_config],
- 'oauth2-credentials-required': [self.handle_oauth2_credentials],
- 'our-show': [self.handle_event_status],
- 'password-required': [self.handle_event_password_required],
- 'plain-connection': [self.handle_event_plain_connection],
- 'presence-received': [self.handle_event_presence],
- 'register-agent-info-received': [self.handle_event_register_agent_info],
- 'roster-info': [self.handle_event_roster_info],
- 'roster-item-exchange-received': \
- [self.handle_event_roster_item_exchange],
- 'signed-in': [self.handle_event_signed_in],
- 'ssl-error': [self.handle_event_ssl_error],
- 'non-anonymous-server-error': [self.handle_event_non_anonymous_server],
- 'stream-conflict-received': [self.handle_event_resource_conflict],
- 'subscribe-presence-received': [
- self.handle_event_subscribe_presence],
- 'subscribed-presence-received': [
- self.handle_event_subscribed_presence],
- 'unsubscribed-presence-received': [
- self.handle_event_unsubscribed_presence],
- 'vcard-received': [self.handle_event_vcard],
- 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
- }
-
- def register_core_handlers(self):
- """
- Register core handlers in Global Events Dispatcher (GED).
-
- This is part of rewriting whole events handling system to use GED.
- """
- for event_name, event_handlers in self.handlers.items():
- for event_handler in event_handlers:
- prio = ged.GUI1
- if type(event_handler) == tuple:
- prio = event_handler[1]
- event_handler = event_handler[0]
- gajim.ged.register_event_handler(event_name, prio,
- event_handler)
-
-################################################################################
-### Methods dealing with gajim.events
-################################################################################
-
- def add_event(self, account, jid, event):
- """
- Add an event to the gajim.events var
- """
- # We add it to the gajim.events queue
- # Do we have a queue?
- jid = gajim.get_jid_without_resource(jid)
- no_queue = len(gajim.events.get_events(account, jid)) == 0
- # event can be in common.events.*
- # event_type can be in advancedNotificationWindow.events_list
- event_types = {'file-request': 'ft_request',
- 'file-completed': 'ft_finished'}
- event_type = event_types.get(event.type_)
- show_in_roster = notify.get_show_in_roster(event_type, account, jid)
- show_in_systray = notify.get_show_in_systray(event_type, account, jid)
- event.show_in_roster = show_in_roster
- event.show_in_systray = show_in_systray
- gajim.events.add_event(account, jid, event)
-
- self.roster.show_title()
- if no_queue: # We didn't have a queue: we change icons
- if gajim.contacts.get_contact_with_highest_priority(account, jid):
- self.roster.draw_contact(jid, account)
- else:
- self.roster.add_to_not_in_the_roster(account, jid)
-
- # Select the big brother contact in roster, it's visible because it has
- # events.
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- gajim.contacts.get_nearby_family_and_big_brother(family,
- account)
- else:
- bb_jid, bb_account = jid, account
- self.roster.select_contact(bb_jid, bb_account)
-
- def handle_event(self, account, fjid, type_):
- w = None
- ctrl = None
- session = None
-
- resource = gajim.get_resource_from_jid(fjid)
- jid = gajim.get_jid_without_resource(fjid)
-
- if type_ in ('printed_gc_msg', 'printed_marked_gc_msg', 'gc_msg'):
- w = self.msg_win_mgr.get_window(jid, account)
- if jid in self.minimized_controls[account]:
- self.roster.on_groupchat_maximized(None, jid, account)
- return
- else:
- ctrl = self.msg_win_mgr.get_gc_control(jid, account)
-
- elif type_ in ('printed_chat', 'chat', ''):
- # '' is for log in/out notifications
-
- if type_ != '':
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- event = gajim.events.get_first_event(account, jid, type_)
- if not event:
- return
-
- if type_ == 'printed_chat':
- ctrl = event.control
- elif type_ == 'chat':
- session = event.session
- ctrl = session.control
- elif type_ == '':
- ctrl = self.msg_win_mgr.get_control(fjid, account)
-
- if not ctrl:
- highest_contact = gajim.contacts.\
- get_contact_with_highest_priority(account, jid)
- # jid can have a window if this resource was lower when he sent
- # message and is now higher because the other one is offline
- if resource and highest_contact.resource == resource and \
- not self.msg_win_mgr.has_window(jid, account):
- # remove resource of events too
- gajim.events.change_jid(account, fjid, jid)
- resource = None
- fjid = jid
- contact = None
- if resource:
- contact = gajim.contacts.get_contact(account, jid, resource)
- if not contact:
- contact = highest_contact
-
- ctrl = self.new_chat(contact, account, resource=resource,
- session=session)
-
- gajim.last_message_time[account][jid] = 0 # long time ago
-
- w = ctrl.parent_win
- elif type_ in ('printed_pm', 'pm'):
- # assume that the most recently updated control we have for this
- # party is the one that this event was in
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- event = gajim.events.get_first_event(account, jid, type_)
- if not event:
- return
-
- if type_ == 'printed_pm':
- ctrl = event.control
- elif type_ == 'pm':
- session = event.session
-
- if session and session.control:
- ctrl = session.control
- elif not ctrl:
- room_jid = jid
- nick = resource
- gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
- nick)
- if gc_contact:
- show = gc_contact.show
- else:
- show = 'offline'
- gc_contact = gajim.contacts.create_gc_contact(
- room_jid=room_jid, account=account, name=nick,
- show=show)
-
- if not session:
- session = gajim.connections[account].make_new_session(
- fjid, None, type_='pm')
-
- self.new_private_chat(gc_contact, account, session=session)
- ctrl = session.control
-
- w = ctrl.parent_win
- elif type_ in ('normal', 'file-request', 'file-request-error',
- 'file-send-error', 'file-error', 'file-stopped', 'file-completed',
- 'file-hash-error', 'jingle-incoming'):
- # Get the first single message event
- event = gajim.events.get_first_event(account, fjid, type_)
- if not event:
- # default to jid without resource
- event = gajim.events.get_first_event(account, jid, type_)
- if not event:
- return
- # Open the window
- self.roster.open_event(account, jid, event)
- else:
- # Open the window
- self.roster.open_event(account, fjid, event)
- elif type_ == 'gmail':
- url = gajim.connections[account].gmail_url
- if url:
- helpers.launch_browser_mailer('url', url)
- elif type_ == 'gc-invitation':
- event = gajim.events.get_first_event(account, jid, type_)
- dialogs.InvitationReceivedDialog(account, event.room_jid, jid,
- event.password, event.reason, event.is_continued)
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- elif type_ == 'subscription_request':
- event = gajim.events.get_first_event(account, jid, type_)
- dialogs.SubscriptionRequestWindow(jid, event.text, account,
- event.nick)
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- elif type_ == 'unsubscribed':
- event = gajim.events.get_first_event(account, jid, type_)
- self.show_unsubscribed_dialog(account, event.contact)
- gajim.events.remove_events(account, jid, event)
- self.roster.draw_contact(jid, account)
- if w:
- w.set_active_tab(ctrl)
- w.window.get_window().focus(Gtk.get_current_event_time())
- # Using isinstance here because we want to catch all derived types
- if isinstance(ctrl, ChatControlBase):
- tv = ctrl.conv_textview
- tv.scroll_to_end_iter()
-
-################################################################################
-### Methods dealing with emoticons
-################################################################################
-
- @staticmethod
- def image_is_ok(image):
- if not os.path.exists(image):
- return False
- img = Gtk.Image()
- try:
- img.set_from_file(image)
- except Exception:
- return False
- t = img.get_storage_type()
- if t != Gtk.ImageType.PIXBUF and t != Gtk.ImageType.ANIMATION:
- return False
- return True
-
- @property
- def basic_pattern_re(self):
- if not self._basic_pattern_re:
- self._basic_pattern_re = re.compile(self.basic_pattern,
- re.IGNORECASE)
- return self._basic_pattern_re
-
- @property
- def emot_and_basic_re(self):
- if not self._emot_and_basic_re:
- self._emot_and_basic_re = re.compile(self.emot_and_basic,
- re.IGNORECASE + re.UNICODE)
- return self._emot_and_basic_re
-
- @property
- def sth_at_sth_dot_sth_re(self):
- if not self._sth_at_sth_dot_sth_re:
- self._sth_at_sth_dot_sth_re = re.compile(self.sth_at_sth_dot_sth)
- return self._sth_at_sth_dot_sth_re
-
- @property
- def invalid_XML_chars_re(self):
- if not self._invalid_XML_chars_re:
- self._invalid_XML_chars_re = re.compile(self.invalid_XML_chars)
- return self._invalid_XML_chars_re
-
- def make_regexps(self):
- # regexp meta characters are: . ^ $ * + ? { } [ ] \ | ( )
- # one escapes the metachars with \
- # \S matches anything but ' ' '\t' '\n' '\r' '\f' and '\v'
- # \s matches any whitespace character
- # \w any alphanumeric character
- # \W any non-alphanumeric character
- # \b means word boundary. This is a zero-width assertion that
- # matches only at the beginning or end of a word.
- # ^ matches at the beginning of lines
- #
- # * means 0 or more times
- # + means 1 or more times
- # ? means 0 or 1 time
- # | means or
- # [^*] anything but '*' (inside [] you don't have to escape metachars)
- # [^\s*] anything but whitespaces and '*'
- # (?<!\S) is a one char lookbehind assertion and asks for any leading
- # whitespace
- # and mathces beginning of lines so we have correct formatting detection
- # even if the the text is just '*foo*'
- # (?!\S) is the same thing but it's a lookahead assertion
- # \S*[^\s\W] --> in the matching string don't match ? or ) etc.. if at
- # the end
- # so http://be) will match http://be and http://be)be) will match
- # http://be)be
-
- legacy_prefixes = r"((?<=\()(www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$"\
- r"&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+(?=\)))"\
- r"|((www|ftp)\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]"\
- r"|%[A-Fa-f0-9]{2})+"\
- r"\.([A-Za-z0-9\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+)"
- # NOTE: it's ok to catch www.gr such stuff exist!
-
- # FIXME: recognize xmpp: and treat it specially
- links = r"((?<=\()[A-Za-z][A-Za-z0-9\+\.\-]*:"\
- r"([\w\.\-_~:/\?#\[\]@!\$&'\(\)\*\+,;=]|%[A-Fa-f0-9]{2})+"\
- r"(?=\)))|(\w[\w\+\.\-]*:([^<>\s]|%[A-Fa-f0-9]{2})+)"
-
- # 2nd one: at_least_one_char@at_least_one_char.at_least_one_char
- mail = r'\bmailto:\S*[^\s\W]|' r'\b\S+@\S+\.\S*[^\s\W]'
-
- # detects eg. *b* *bold* *bold bold* test *bold* *bold*! (*bold*)
- # doesn't detect (it's a feature :P) * bold* *bold * * bold * test*bold*
- formatting = r'|(?<!\w)' r'\*[^\s*]' r'([^*]*[^\s*])?' r'\*(?!\w)|'\
- r'(?<!\S)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\S)|'\
- r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
-
- basic_pattern = links + '|' + mail + '|' + legacy_prefixes
-
- link_pattern = basic_pattern
- self.link_pattern_re = re.compile(link_pattern, re.I | re.U)
-
- if gajim.config.get('ascii_formatting'):
- basic_pattern += formatting
- self.basic_pattern = basic_pattern
-
- emoticons_pattern = ''
- if gajim.config.get('emoticons_theme'):
- # When an emoticon is bordered by an alpha-numeric character it is
- # NOT expanded. e.g., foo:) NO, foo :) YES, (brb) NO, (:)) YES, etc
- # We still allow multiple emoticons side-by-side like :P:P:P
- # sort keys by length so :qwe emot is checked before :q
- keys = sorted(self.emoticons, key=len, reverse=True)
- emoticons_pattern_prematch = ''
- emoticons_pattern_postmatch = ''
- emoticon_length = 0
- for emoticon in keys: # travel thru emoticons list
- emoticon_escaped = re.escape(emoticon) # espace regexp metachars
- # | means or in regexp
- emoticons_pattern += emoticon_escaped + '|'
- if (emoticon_length != len(emoticon)):
- # Build up expressions to match emoticons next to others
- emoticons_pattern_prematch = \
- emoticons_pattern_prematch[:-1] + ')|(?<='
- emoticons_pattern_postmatch = \
- emoticons_pattern_postmatch[:-1] + ')|(?='
- emoticon_length = len(emoticon)
- emoticons_pattern_prematch += emoticon_escaped + '|'
- emoticons_pattern_postmatch += emoticon_escaped + '|'
- # We match from our list of emoticons, but they must either have
- # whitespace, or another emoticon next to it to match successfully
- # [\w.] alphanumeric and dot (for not matching 8) in (2.8))
- emoticons_pattern = '|' + r'(?:(?<![\w.]' + \
- emoticons_pattern_prematch[:-1] + '))' + '(?:' + \
- emoticons_pattern[:-1] + ')' + r'(?:(?![\w]' + \
- emoticons_pattern_postmatch[:-1] + '))'
-
- # because emoticons match later (in the string) they need to be after
- # basic matches that may occur earlier
- self.emot_and_basic = basic_pattern + emoticons_pattern
-
- # needed for xhtml display
- self.emot_only = emoticons_pattern
-
- # at least one character in 3 parts (before @, after @, after .)
- self.sth_at_sth_dot_sth = r'\S+@\S+\.\S*[^\s)?]'
-
- # Invalid XML chars
- self.invalid_XML_chars = '[\x00-\x08]|[\x0b-\x0c]|[\x0e-\x1f]|'\
- '[\ud800-\udfff]|[\ufffe-\uffff]'
-
- def popup_emoticons_under_button(self, button, parent_win):
- """
- Popup the emoticons menu under button, located in parent_win
- """
- gtkgui_helpers.popup_emoticons_under_button(self.emoticons_menu,
- button, parent_win)
-
- def prepare_emoticons_menu(self):
- menu = Gtk.Menu()
- def emoticon_clicked(w, str_):
- if self.emoticon_menuitem_clicked:
- self.emoticon_menuitem_clicked(str_)
- # don't keep reference to CB of object
- # this will prevent making it uncollectable
- self.emoticon_menuitem_clicked = None
- def selection_done(widget):
- # remove reference to CB of object, which will
- # make it uncollectable
- self.emoticon_menuitem_clicked = None
- counter = 0
- # Calculate the side lenght of the popup to make it a square
- size = int(round(math.sqrt(len(self.emoticons_images))))
- for image in self.emoticons_images:
- # In Gtk 3.6, Gtk.MenuItem() doesn't contain a label child
- item = Gtk.MenuItem.new_with_label('q')
- img = Gtk.Image()
- if isinstance(image[1], GdkPixbuf.PixbufAnimation):
- img.set_from_animation(image[1])
- else:
- img.set_from_pixbuf(image[1])
- c = item.get_child()
- item.remove(c)
- item.add(img)
- item.connect('activate', emoticon_clicked, image[0])
- # add tooltip with ascii
- item.set_tooltip_text(image[0])
- menu.attach(item, counter % size, counter % size + 1,
- counter / size, counter / size + 1)
- counter += 1
- menu.connect('selection-done', selection_done)
- menu.show_all()
- return menu
-
- def _init_emoticons(self, path, need_reload = False):
- #initialize emoticons dictionary and unique images list
- self.emoticons_images = list()
- self.emoticons = dict()
- self.emoticons_animations = dict()
-
- sys.path.insert(0, path)
- import emoticons
- try:
- if need_reload:
- # we need to reload else that doesn't work when changing
- # emoticons set
- import imp
- imp.reload(emoticons)
- emots = emoticons.emoticons
- self.emoticons_sorting = None
- try:
- self.emoticons_sorting = emoticons.sorting
- except:
- pass
- except Exception as e:
- return True
- for emot_filename in emots:
- emot_file = os.path.join(path, emot_filename)
- if not self.image_is_ok(emot_file):
- continue
- for emot in emots[emot_filename]:
- emot = emot
- # This avoids duplicated emoticons with the same image eg. :)
- # and :-)
- if not emot_file in self.emoticons.values():
- if emot_file.endswith('.gif'):
- pix = GdkPixbuf.PixbufAnimation.new_from_file(emot_file)
- else:
- pix = GdkPixbuf.Pixbuf.new_from_file_at_size(emot_file,
- 16, 16)
- self.emoticons_images.append((emot, pix))
- self.emoticons[emot.upper()] = emot_file
- def emoticons_sorter(item):
- try:
- return self.emoticons_sorting.index(item[0])
- except:
- return 0
- self.emoticons_images = sorted(self.emoticons_images, key=emoticons_sorter)
- del emoticons
- sys.path.remove(path)
-
- def init_emoticons(self, need_reload = False):
- emot_theme = gajim.config.get('emoticons_theme')
- if not emot_theme:
- return
-
- transient_for = None
- if 'preferences' in gajim.interface.instances:
- transient_for = gajim.interface.instances['preferences'].window
-
- path = os.path.join(gajim.DATA_DIR, 'emoticons', emot_theme)
- if not os.path.exists(path):
- # It's maybe a user theme
- path = os.path.join(gajim.MY_EMOTS_PATH, emot_theme)
- if not os.path.exists(path):
- # theme doesn't exist, disable emoticons
- dialogs.WarningDialog(_('Emoticons disabled'),
- _('Your configured emoticons theme has not been found, so '
- 'emoticons have been disabled.'),
- transient_for=transient_for)
- gajim.config.set('emoticons_theme', '')
- return
- if self._init_emoticons(path, need_reload):
- dialogs.WarningDialog(_('Emoticons disabled'),
- _('Your configured emoticons theme cannot been loaded. You '
- 'maybe need to update the format of emoticons.py file. See '
- 'http://trac.gajim.org/wiki/Emoticons for more details.'),
- transient_for=transient_for)
- gajim.config.set('emoticons_theme', '')
- return
- if len(self.emoticons) == 0:
- # maybe old format of emoticons file, try to convert it
- try:
- import pprint
- import emoticons
- emots = emoticons.emoticons
- fd = open(os.path.join(path, 'emoticons.py'), 'w')
- fd.write('emoticons = ')
- pprint.pprint( dict([
- (file_, [i for i in emots.keys() if emots[i] == file_])
- for file_ in set(emots.values())]), fd)
- fd.close()
- del emoticons
- self._init_emoticons(path, need_reload=True)
- except Exception:
- pass
- if len(self.emoticons) == 0:
- dialogs.WarningDialog(_('Emoticons disabled'),
- _('Your configured emoticons theme cannot been loaded. You '
- 'maybe need to update the format of emoticons.py file. See '
- 'http://trac.gajim.org/wiki/Emoticons for more details.'),
- transient_for=transient_for)
- gajim.config.set('emoticons_theme', '')
- if self.emoticons_menu:
- self.emoticons_menu.destroy()
- self.emoticons_menu = self.prepare_emoticons_menu()
-
-################################################################################
-### Methods for opening new messages controls
-################################################################################
-
- def join_gc_room(self, account, room_jid, nick, password, minimize=False,
- is_continued=False):
- """
- Join the room immediately
- """
-
- if gajim.contacts.get_contact(account, room_jid) and \
- not gajim.contacts.get_contact(account, room_jid).is_groupchat():
- dialogs.ErrorDialog(_('This is not a group chat'),
- _('%s is already in your roster. Please check if %s is a '
- 'correct group chat name. If it is, delete it from your roster '
- 'and try joining the group chat again.') % (room_jid, room_jid))
- return
-
- if not nick:
- nick = gajim.nicks[account]
-
- minimized_control = gajim.interface.minimized_controls[account].get(
- room_jid, None)
-
- if (self.msg_win_mgr.has_window(room_jid, account) or \
- minimized_control) and gajim.gc_connected[account][room_jid]:
- if self.msg_win_mgr.has_window(room_jid, account):
- gc_ctrl = self.msg_win_mgr.get_gc_control(room_jid, account)
- win = gc_ctrl.parent_win
- win.set_active_tab(gc_ctrl)
- else:
- self.roster.on_groupchat_maximized(None, room_jid, account)
- dialogs.ErrorDialog(_('You are already in group chat %s') % \
- room_jid)
- return
-
- invisible_show = gajim.SHOW_LIST.index('invisible')
- if gajim.connections[account].connected == invisible_show:
- dialogs.ErrorDialog(
- _('You cannot join a group chat while you are invisible'))
- return
-
- if minimized_control is None and not self.msg_win_mgr.has_window(
- room_jid, account):
- # Join new groupchat
- if minimize:
- # GCMIN
- contact = gajim.contacts.create_contact(jid=room_jid,
- account=account, name=nick)
- gc_control = GroupchatControl(None, contact, account)
- gajim.interface.minimized_controls[account][room_jid] = \
- gc_control
- self.roster.add_groupchat(room_jid, account)
- else:
- self.new_room(room_jid, nick, account,
- is_continued=is_continued)
- elif minimized_control is None:
- # We are already in that groupchat
- gc_control = self.msg_win_mgr.get_gc_control(room_jid, account)
- gc_control.nick = nick
- gc_control.parent_win.set_active_tab(gc_control)
-
- # Connect
- gajim.connections[account].join_gc(nick, room_jid, password)
- if password:
- gajim.gc_passwords[room_jid] = password
-
- def new_room(self, room_jid, nick, account, is_continued=False):
- # Get target window, create a control, and associate it with the window
- # GCMIN
- contact = gajim.contacts.create_contact(jid=room_jid, account=account,
- name=nick)
- mw = self.msg_win_mgr.get_window(contact.jid, account)
- if not mw:
- mw = self.msg_win_mgr.create_window(contact, account,
- GroupchatControl.TYPE_ID)
- gc_control = GroupchatControl(mw, contact, account,
- is_continued=is_continued)
- mw.new_tab(gc_control)
- mw.set_active_tab(gc_control)
-
- def new_private_chat(self, gc_contact, account, session=None):
- conn = gajim.connections[account]
- if not session and gc_contact.get_full_jid() in conn.sessions:
- sessions = [s for s in conn.sessions[gc_contact.get_full_jid()].\
- values() if isinstance(s, ChatControlSession)]
-
- # look for an existing session with a chat control
- for s in sessions:
- if s.control:
- session = s
- break
- if not session and not len(sessions) == 0:
- # there are no sessions with chat controls, just take the first
- # one
- session = sessions[0]
- if not session:
- # couldn't find an existing ChatControlSession, just make a new one
- session = conn.make_new_session(gc_contact.get_full_jid(), None,
- 'pm')
-
- contact = gc_contact.as_contact()
- if not session.control:
- message_window = self.msg_win_mgr.get_window(
- gc_contact.get_full_jid(), account)
- if not message_window:
- message_window = self.msg_win_mgr.create_window(contact,
- account, message_control.TYPE_PM)
-
- session.control = PrivateChatControl(message_window, gc_contact,
- contact, account, session)
- message_window.new_tab(session.control)
-
- if gajim.events.get_events(account, gc_contact.get_full_jid()):
- # We call this here to avoid race conditions with widget validation
- session.control.read_queue()
-
- return session.control
-
- def new_chat(self, contact, account, resource=None, session=None):
- # Get target window, create a control, and associate it with the window
- type_ = message_control.TYPE_CHAT
-
- fjid = contact.jid
- if resource:
- fjid += '/' + resource
-
- mw = self.msg_win_mgr.get_window(fjid, account)
- if not mw:
- mw = self.msg_win_mgr.create_window(contact, account, type_,
- resource)
-
- chat_control = ChatControl(mw, contact, account, session, resource)
-
- mw.new_tab(chat_control)
-
- if len(gajim.events.get_events(account, fjid)):
- # We call this here to avoid race conditions with widget validation
- chat_control.read_queue()
-
- return chat_control
-
- def new_chat_from_jid(self, account, fjid, message=None):
- jid, resource = gajim.get_room_and_nick_from_fjid(fjid)
- contact = gajim.contacts.get_contact(account, jid, resource)
- added_to_roster = False
- if not contact:
- added_to_roster = True
- contact = self.roster.add_to_not_in_the_roster(account, jid,
- resource=resource)
-
- ctrl = self.msg_win_mgr.get_control(fjid, account)
-
- if not ctrl:
- ctrl = self.new_chat(contact, account,
- resource=resource)
- if len(gajim.events.get_events(account, fjid)):
- ctrl.read_queue()
-
- if message:
- buffer_ = ctrl.msg_textview.get_buffer()
- buffer_.set_text(message)
- mw = ctrl.parent_win
- mw.set_active_tab(ctrl)
- # For JEP-0172
- if added_to_roster:
- ctrl.user_nick = gajim.nicks[account]
- GLib.idle_add(mw.window.grab_focus)
-
- return ctrl
-
- def on_open_chat_window(self, widget, contact, account, resource=None,
- session=None):
- # Get the window containing the chat
- fjid = contact.jid
-
- if resource:
- fjid += '/' + resource
-
- ctrl = None
-
- if session:
- ctrl = session.control
- if not ctrl:
- win = self.msg_win_mgr.get_window(fjid, account)
-
- if win:
- ctrl = win.get_control(fjid, account)
-
- if not ctrl:
- ctrl = self.new_chat(contact, account, resource=resource,
- session=session)
- # last message is long time ago
- gajim.last_message_time[account][ctrl.get_full_jid()] = 0
-
- win = ctrl.parent_win
-
- win.set_active_tab(ctrl)
-
- if gajim.connections[account].is_zeroconf and \
- gajim.connections[account].status in ('offline', 'invisible'):
- ctrl = win.get_control(fjid, account)
- if ctrl:
- ctrl.got_disconnected()
-
-################################################################################
-### Other Methods
-################################################################################
-
- @staticmethod
- def change_awn_icon_status(status):
- if not dbus_support.supported:
- # do nothing if user doesn't have D-Bus bindings
- return
- try:
- bus = dbus.SessionBus()
- if not 'com.google.code.Awn' in bus.list_names():
- # Awn is not installed
- return
- except Exception:
- return
- iconset = gajim.config.get('iconset')
- prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32')
- if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'):
- status = status + '.png'
- elif status == 'online':
- prefix = ''
- status = gtkgui_helpers.get_icon_path('org.gajim.Gajim', 32)
- path = os.path.join(prefix, status)
- try:
- obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn')
- awn = dbus.Interface(obj, 'com.google.code.Awn')
- awn.SetTaskIconByName('Gajim', os.path.abspath(path))
- except Exception:
- pass
-
- def enable_music_listener(self):
- listener = MusicTrackListener.get()
- if not self.music_track_changed_signal:
- self.music_track_changed_signal = listener.connect(
- 'music-track-changed', self.music_track_changed)
- track = listener.get_playing_track()
- self.music_track_changed(listener, track)
-
- def disable_music_listener(self):
- listener = MusicTrackListener.get()
- listener.disconnect(self.music_track_changed_signal)
- self.music_track_changed_signal = None
-
- @staticmethod
- def music_track_changed(unused_listener, music_track_info, account=None):
- if not account:
- accounts = gajim.connections.keys()
- else:
- accounts = [account]
-
- is_paused = hasattr(music_track_info, 'paused') and \
- music_track_info.paused == 0
- if not music_track_info or is_paused:
- artist = title = source = ''
- else:
- artist = music_track_info.artist
- title = music_track_info.title
- source = music_track_info.album
- for acct in accounts:
- if not gajim.account_is_connected(acct):
- continue
- if not gajim.connections[acct].pep_supported:
- continue
- if not gajim.config.get_per('accounts', acct, 'publish_tune'):
- continue
- if gajim.connections[acct].music_track_info == music_track_info:
- continue
- gajim.connections[acct].send_tune(artist, title, source)
- gajim.connections[acct].music_track_info = music_track_info
-
- def read_sleepy(self):
- """
- Check idle status and change that status if needed
- """
- if not self.sleeper.poll():
- # idle detection is not supported in that OS
- return False # stop looping in vain
- state = self.sleeper.getState()
- for account in gajim.connections:
- if account not in gajim.sleeper_state or \
- not gajim.sleeper_state[account]:
- continue
- if state == sleepy.STATE_AWAKE and \
- gajim.sleeper_state[account] in ('autoaway', 'autoxa'):
- # we go online
- self.roster.send_status(account, 'online',
- gajim.status_before_autoaway[account])
- gajim.status_before_autoaway[account] = ''
- gajim.sleeper_state[account] = 'online'
- elif state == sleepy.STATE_AWAY and \
- gajim.sleeper_state[account] == 'online' and \
- gajim.config.get('autoaway'):
- # we save out online status
- gajim.status_before_autoaway[account] = \
- gajim.connections[account].status
- # we go away (no auto status) [we pass True to auto param]
- auto_message = gajim.config.get('autoaway_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S', '%(status)s')
- auto_message = auto_message.replace('$T', '%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoawaytime')
- }
- self.roster.send_status(account, 'away', auto_message,
- auto=True)
- gajim.sleeper_state[account] = 'autoaway'
- elif state == sleepy.STATE_XA and \
- gajim.sleeper_state[account] in ('online', 'autoaway',
- 'autoaway-forced') and gajim.config.get('autoxa'):
- # we go extended away [we pass True to auto param]
- auto_message = gajim.config.get('autoxa_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S', '%(status)s')
- auto_message = auto_message.replace('$T', '%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoxatime')
- }
- self.roster.send_status(account, 'xa', auto_message, auto=True)
- gajim.sleeper_state[account] = 'autoxa'
- return True # renew timeout (loop for ever)
-
- def autoconnect(self):
- """
- Auto connect at startup
- """
- # dict of account that want to connect sorted by status
- shows = {}
- for a in gajim.connections:
- if gajim.config.get_per('accounts', a, 'autoconnect'):
- if gajim.config.get_per('accounts', a, 'restore_last_status'):
- self.roster.send_status(a, gajim.config.get_per('accounts',
- a, 'last_status'), helpers.from_one_line(
- gajim.config.get_per('accounts', a, 'last_status_msg')))
- continue
- show = gajim.config.get_per('accounts', a, 'autoconnect_as')
- if not show in gajim.SHOW_LIST:
- continue
- if not show in shows:
- shows[show] = [a]
- else:
- shows[show].append(a)
- def on_message(message, pep_dict):
- if message is None:
- return
- for a in shows[show]:
- self.roster.send_status(a, show, message)
- self.roster.send_pep(a, pep_dict)
- for show in shows:
- message = self.roster.get_status_message(show, on_message)
- return False
-
- def show_systray(self):
- self.systray_enabled = True
- self.systray.show_icon()
-
- def hide_systray(self):
- self.systray_enabled = False
- self.systray.hide_icon()
-
- @staticmethod
- def on_launch_browser_mailer(widget, url, kind):
- helpers.launch_browser_mailer(kind, url)
-
- def process_connections(self):
- """
- Called each foo (200) miliseconds. Check for idlequeue timeouts
- """
- try:
- gajim.idlequeue.process()
- except Exception:
- # Otherwise, an exception will stop our loop
- timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
- if in_seconds:
- GLib.timeout_add_seconds(timeout, self.process_connections)
- else:
- GLib.timeout_add(timeout, self.process_connections)
- raise
- return True # renew timeout (loop for ever)
-
- @staticmethod
- def save_config():
- err_str = parser.write()
- if err_str is not None:
- print(err_str, file=sys.stderr)
- # it is good to notify the user
- # in case he or she cannot see the output of the console
- dialogs.ErrorDialog(_('Could not save your settings and '
- 'preferences'), err_str)
- sys.exit()
-
- @staticmethod
- def save_avatar_files(jid, photo, puny_nick = None, local = False):
- """
- Save an avatar to a separate file, and generate files for dbus
- notifications. An avatar can be given as a pixmap directly or as an
- decoded image
- """
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
- if puny_nick:
- path_to_file = os.path.join(path_to_file, puny_nick)
- # remove old avatars
- for typ in ('jpeg', 'png'):
- if local:
- path_to_original_file = path_to_file + '_local'+ '.' + typ
- else:
- path_to_original_file = path_to_file + '.' + typ
- if os.path.isfile(path_to_original_file):
- os.remove(path_to_original_file)
- if local and photo:
- pixbuf = photo
- typ = 'png'
- extension = '_local.png' # save local avatars as png file
- else:
- pixbuf, typ = gtkgui_helpers.get_pixbuf_from_data(photo,
- want_type=True)
- if pixbuf is None:
- return
- if typ not in ('jpeg', 'png'):
- gajim.log.info('gtkpixbuf cannot save other than jpeg and '\
- 'png formats. saving \'%s\' avatar as png file (originaly: %s)'\
- % (jid, typ))
- typ = 'png'
- extension = '.' + typ
- path_to_original_file = path_to_file + extension
- try:
- pixbuf.savev(path_to_original_file, typ, [], [])
- except Exception as e:
- log.error('Error writing avatar file %s: %s' % (
- path_to_original_file, str(e)))
- # Generate and save the resized, color avatar
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'notification')
- if pixbuf:
- path_to_normal_file = path_to_file + '_notif_size_colored' + \
- extension
- try:
- pixbuf.savev(path_to_normal_file, 'png', [], [])
- except Exception as e:
- log.error('Error writing avatar file %s: %s' % \
- (path_to_original_file, str(e)))
- # Generate and save the resized, black and white avatar
- bwbuf = gtkgui_helpers.get_scaled_pixbuf(
- gtkgui_helpers.make_pixbuf_grayscale(pixbuf), 'notification')
- if bwbuf:
- path_to_bw_file = path_to_file + '_notif_size_bw' + extension
- try:
- bwbuf.savev(path_to_bw_file, 'png', [], [])
- except Exception as e:
- log.error('Error writing avatar file %s: %s' % \
- (path_to_original_file, str(e)))
-
- @staticmethod
- def remove_avatar_files(jid, puny_nick = None, local = False):
- """
- Remove avatar files of a jid
- """
- puny_jid = helpers.sanitize_filename(jid)
- path_to_file = os.path.join(gajim.AVATAR_PATH, puny_jid)
- if puny_nick:
- path_to_file = os.path.join(path_to_file, puny_nick)
- for ext in ('.jpeg', '.png'):
- if local:
- ext = '_local' + ext
- path_to_original_file = path_to_file + ext
- if os.path.isfile(path_to_file + ext):
- os.remove(path_to_file + ext)
- if os.path.isfile(path_to_file + '_notif_size_colored' + ext):
- os.remove(path_to_file + '_notif_size_colored' + ext)
- if os.path.isfile(path_to_file + '_notif_size_bw' + ext):
- os.remove(path_to_file + '_notif_size_bw' + ext)
-
- def auto_join_bookmarks(self, account):
- """
- Autojoin bookmarked GCs that have 'auto join' on for this account
- """
- for bm in gajim.connections[account].bookmarks:
- if bm['autojoin'] in ('1', 'true'):
- jid = bm['jid']
- # Only join non-opened groupchats. Opened one are already
- # auto-joined on re-connection
- if not jid in gajim.gc_connected[account]:
- # we are not already connected
- minimize = bm['minimize'] in ('1', 'true')
- self.join_gc_room(account, jid, bm['nick'],
- bm['password'], minimize = minimize)
- elif jid in self.minimized_controls[account]:
- # more or less a hack:
- # On disconnect the minimized gc contact instances
- # were set to offline. Reconnect them to show up in the
- # roster.
- self.roster.add_groupchat(jid, account)
-
- def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password,
- nick):
- """
- Add a bookmark for this account, sorted in bookmark list
- """
- bm = {
- 'name': name,
- 'jid': jid,
- 'autojoin': autojoin,
- 'minimize': minimize,
- 'password': password,
- 'nick': nick
- }
- place_found = False
- index = 0
- # check for duplicate entry and respect alpha order
- for bookmark in gajim.connections[account].bookmarks:
- if bookmark['jid'] == bm['jid']:
- dialogs.ErrorDialog(
- _('Bookmark already set'),
- _('Group Chat "%s" is already in your bookmarks.') % \
- bm['jid'])
- return
- if bookmark['name'] > bm['name']:
- place_found = True
- break
- index += 1
- if place_found:
- gajim.connections[account].bookmarks.insert(index, bm)
- else:
- gajim.connections[account].bookmarks.append(bm)
- gajim.connections[account].store_bookmarks()
- gui_menu_builder.build_bookmark_menu(account)
- dialogs.InformationDialog(
- _('Bookmark has been added successfully'),
- _('You can manage your bookmarks via Actions menu in your roster.'))
-
-
- # does JID exist only within a groupchat?
- def is_pm_contact(self, fjid, account):
- bare_jid = gajim.get_jid_without_resource(fjid)
-
- gc_ctrl = self.msg_win_mgr.get_gc_control(bare_jid, account)
-
- if not gc_ctrl and \
- bare_jid in self.minimized_controls[account]:
- gc_ctrl = self.minimized_controls[account][bare_jid]
-
- return gc_ctrl and gc_ctrl.type_id == message_control.TYPE_GC
-
- @staticmethod
- def get_pep_icon(pep_obj):
- if isinstance(pep_obj, pep.UserMoodPEP):
- received_mood = pep_obj._pep_specific_data['mood']
- mood = received_mood if received_mood in pep.MOODS else 'unknown'
- return gtkgui_helpers.load_mood_icon(mood).get_pixbuf()
- elif isinstance(pep_obj, pep.UserTunePEP):
- path = os.path.join(gajim.DATA_DIR, 'emoticons', 'static', 'music.png')
- return GdkPixbuf.Pixbuf.new_from_file(path)
- elif isinstance(pep_obj, pep.UserActivityPEP):
- pep_ = pep_obj._pep_specific_data
- activity = pep_['activity']
-
- has_known_activity = activity in pep.ACTIVITIES
- has_known_subactivity = (has_known_activity and ('subactivity' in
- pep_) and (pep_['subactivity'] in pep.ACTIVITIES[activity]))
-
- if has_known_activity:
- if has_known_subactivity:
- subactivity = pep_['subactivity']
- return gtkgui_helpers.load_activity_icon(activity,
- subactivity).get_pixbuf()
- else:
- return gtkgui_helpers.load_activity_icon(activity).\
- get_pixbuf()
- else:
- return gtkgui_helpers.load_activity_icon('unknown').get_pixbuf()
- elif isinstance(pep_obj, pep.UserLocationPEP):
- icon = gtkgui_helpers.get_icon_pixmap('applications-internet',
- quiet=True)
- return icon
-
- @staticmethod
- def create_ipython_window():
- try:
- from ipython_view import IPythonView
- except ImportError:
- print('ipython_view not found')
- return
- from gi.repository import Pango
-
- if os.name == 'nt':
- font = 'Lucida Console 9'
- else:
- font = 'Luxi Mono 10'
-
- window = Gtk.Window()
- window.set_size_request(750, 550)
- window.set_resizable(True)
- sw = Gtk.ScrolledWindow()
- sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- view = IPythonView()
- view.override_font(Pango.FontDescription(font))
- view.set_wrap_mode(Gtk.WrapMode.CHAR)
- sw.add(view)
- window.add(sw)
- window.show_all()
- def on_delete(win, event):
- win.hide()
- return True
- window.connect('delete_event', on_delete)
- view.updateNamespace({'gajim': gajim})
- gajim.ipython_window = window
-
- def run(self, app):
- if gajim.config.get('trayicon') != 'never':
- self.show_systray()
-
- self.roster = roster_window.RosterWindow(app)
- if self.msg_win_mgr.mode == \
- MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- self.msg_win_mgr.create_window(None, None, None)
-
- # 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()
- self.roster._after_fill()
-
- # get instances for windows/dialogs that will show_all()/hide()
- self.instances['file_transfers'] = dialogs.FileTransfersWindow()
-
- GLib.timeout_add(100, self.autoconnect)
- timeout, in_seconds = gajim.idlequeue.PROCESS_TIMEOUT
- if in_seconds:
- GLib.timeout_add_seconds(timeout, self.process_connections)
- else:
- GLib.timeout_add(timeout, self.process_connections)
- GLib.timeout_add_seconds(gajim.config.get(
- 'check_idle_every_foo_seconds'), self.read_sleepy)
-
- def remote_init():
- if gajim.config.get('remote_control'):
- try:
- import remote_control
- self.remote_ctrl = remote_control.Remote()
- except Exception:
- pass
- GLib.timeout_add_seconds(5, remote_init)
-
- def __init__(self):
- gajim.interface = self
- gajim.thread_interface = ThreadInterface
- # This is the manager and factory of message windows set by the module
- self.msg_win_mgr = None
- self.jabber_state_images = {'16': {}, '24': {}, '32': {}, 'opened': {},
- 'closed': {}}
- self.emoticons_menu = None
- # handler when an emoticon is clicked in emoticons_menu
- self.emoticon_menuitem_clicked = None
- self.minimized_controls = {}
- self.status_sent_to_users = {}
- self.status_sent_to_groups = {}
- self.gpg_passphrase = {}
- self.pass_dialog = {}
- self.db_error_dialog = None
- self.default_colors = {
- 'inmsgcolor': gajim.config.get('inmsgcolor'),
- 'outmsgcolor': gajim.config.get('outmsgcolor'),
- 'inmsgtxtcolor': gajim.config.get('inmsgtxtcolor'),
- 'outmsgtxtcolor': gajim.config.get('outmsgtxtcolor'),
- 'statusmsgcolor': gajim.config.get('statusmsgcolor'),
- 'urlmsgcolor': gajim.config.get('urlmsgcolor'),
- 'markedmsgcolor': gajim.config.get('markedmsgcolor'),
- }
-
- self.handlers = {}
- self.roster = None
- self._invalid_XML_chars_re = None
- self._basic_pattern_re = None
- self._emot_and_basic_re = None
- self._sth_at_sth_dot_sth_re = None
- self.link_pattern_re = None
- self.invalid_XML_chars = None
- self.basic_pattern = None
- self.emot_and_basic = None
- self.sth_at_sth_dot_sth = None
- self.emot_only = None
- self.emoticons = []
- self.emoticons_animations = {}
- self.emoticons_images = {}
- self.emoticons_sorting = None
-
- cfg_was_read = parser.read()
-
- if not cfg_was_read:
- # enable plugin_installer by default when creating config file
- gajim.config.set_per('plugins', 'plugin_installer', 'active', True)
-
- gajim.logger.reset_shown_unread_messages()
- # override logging settings from config (don't take care of '-q' option)
- if gajim.config.get('verbose'):
- logging_helpers.set_verbose()
-
- for account in gajim.config.get_per('accounts'):
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- gajim.ZEROCONF_ACC_NAME = account
- break
- # Is gnome configured to activate row on single click ?
-# try:
-# gi.require_version('GConf', '2.0')
-# from gi.repository import GConf
-# client = GConf.Client.get_default()
-# click_policy = client.get_string(
-# '/apps/nautilus/preferences/click_policy')
-# if click_policy == 'single':
-# gajim.single_click = True
-# except Exception:
-# pass
- # add default status messages if there is not in the config file
- if len(gajim.config.get_per('statusmsg')) == 0:
- default = gajim.config.statusmsg_default
- for msg in default:
- gajim.config.add_per('statusmsg', msg)
- gajim.config.set_per('statusmsg', msg, 'message',
- default[msg][0])
- gajim.config.set_per('statusmsg', msg, 'activity',
- default[msg][1])
- gajim.config.set_per('statusmsg', msg, 'subactivity',
- default[msg][2])
- gajim.config.set_per('statusmsg', msg, 'activity_text',
- default[msg][3])
- gajim.config.set_per('statusmsg', msg, 'mood',
- default[msg][4])
- gajim.config.set_per('statusmsg', msg, 'mood_text',
- default[msg][5])
- #add default themes if there is not in the config file
- theme = gajim.config.get('roster_theme')
- if not theme in gajim.config.get_per('themes'):
- gajim.config.set('roster_theme', _('default'))
- if len(gajim.config.get_per('themes')) == 0:
- d = ['accounttextcolor', 'accountbgcolor', 'accountfont',
- 'accountfontattrs', 'grouptextcolor', 'groupbgcolor',
- 'groupfont', 'groupfontattrs', 'contacttextcolor',
- 'contactbgcolor', 'contactfont', 'contactfontattrs',
- 'bannertextcolor', 'bannerbgcolor']
-
- default = gajim.config.themes_default
- for theme_name in default:
- gajim.config.add_per('themes', theme_name)
- theme = default[theme_name]
- for o in d:
- gajim.config.set_per('themes', theme_name, o,
- theme[d.index(o)])
- # Add Tor proxy if there is not in the config
- if len(gajim.config.get_per('proxies')) == 0:
- default = gajim.config.proxies_default
- for proxy in default:
- gajim.config.add_per('proxies', proxy)
- gajim.config.set_per('proxies', proxy, 'type',
- default[proxy][0])
- gajim.config.set_per('proxies', proxy, 'host',
- default[proxy][1])
- gajim.config.set_per('proxies', proxy, 'port',
- default[proxy][2])
-
-
- gajim.idlequeue = idlequeue.get_idlequeue()
- # resolve and keep current record of resolved hosts
- gajim.resolver = resolver.get_resolver(gajim.idlequeue)
- gajim.socks5queue = socks5.SocksQueue(gajim.idlequeue,
- self.handle_event_file_rcv_completed,
- self.handle_event_file_progress,
- self.handle_event_file_error)
- gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
- gajim.default_session_type = ChatControlSession
-
- # Creating Network Events Controller
- from common import nec
- gajim.nec = nec.NetworkEventsController()
- gajim.notification = notify.Notification()
-
- self.create_core_handlers_list()
- self.register_core_handlers()
-
- if gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'active') \
- and gajim.HAVE_ZEROCONF:
- gajim.connections[gajim.ZEROCONF_ACC_NAME] = \
- connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
- for account in gajim.config.get_per('accounts'):
- if not gajim.config.get_per('accounts', account, 'is_zeroconf') and\
- gajim.config.get_per('accounts', account, 'active'):
- gajim.connections[account] = Connection(account)
-
- # gtk hooks
-# Gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail')
-# Gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url')
-# Gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
-
- self.instances = {}
-
- for a in gajim.connections:
- self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {},
- 'search': {}, 'online_dialog': {}, 'sub_request': {}}
- # online_dialog contains all dialogs that have a meaning only when
- # we are not disconnected
- self.minimized_controls[a] = {}
- gajim.contacts.add_account(a)
- gajim.groups[a] = {}
- gajim.gc_connected[a] = {}
- gajim.automatic_rooms[a] = {}
- gajim.newly_added[a] = []
- gajim.to_be_removed[a] = []
- gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
- gajim.block_signed_in_notifications[a] = True
- gajim.sleeper_state[a] = 0
- gajim.encrypted_chats[a] = []
- gajim.last_message_time[a] = {}
- gajim.status_before_autoaway[a] = ''
- gajim.transport_avatar[a] = {}
- gajim.gajim_optional_features[a] = []
- gajim.caps_hash[a] = ''
-
- helpers.update_optional_features()
- # prepopulate data which we are sure of; note: we do not log these info
- for account in gajim.connections:
- gajimcaps = caps_cache.capscache[('sha-1',
- gajim.caps_hash[account])]
- gajimcaps.identities = [gajim.gajim_identity]
- gajimcaps.features = gajim.gajim_common_features + \
- gajim.gajim_optional_features[account]
-
- self.remote_ctrl = None
-
- import network_watcher
-
- if dbus_support.supported:
- import upower_listener
- import logind_listener
-
- # Handle gnome screensaver
- if dbus_support.supported:
- def gnome_screensaver_ActiveChanged_cb(active):
- if not active:
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- gajim.sleeper_state[account] == 'autoaway-forced':
- # We came back online ofter gnome-screensaver
- # autoaway
- self.roster.send_status(account, 'online',
- gajim.status_before_autoaway[account])
- gajim.status_before_autoaway[account] = ''
- gajim.sleeper_state[account] = 'online'
- return
- if not gajim.config.get('autoaway'):
- # Don't go auto away if user disabled the option
- return
- for account in gajim.connections:
- if account not in gajim.sleeper_state or \
- not gajim.sleeper_state[account]:
- continue
- if gajim.sleeper_state[account] == 'online':
- if not gajim.account_is_connected(account):
- continue
- # we save out online status
- gajim.status_before_autoaway[account] = \
- gajim.connections[account].status
- # we go away (no auto status) [we pass True to auto
- # param]
- auto_message = gajim.config.get('autoaway_message')
- if not auto_message:
- auto_message = gajim.connections[account].status
- else:
- auto_message = auto_message.replace('$S',
- '%(status)s')
- auto_message = auto_message.replace('$T',
- '%(time)s')
- auto_message = auto_message % {
- 'status': gajim.status_before_autoaway[account],
- 'time': gajim.config.get('autoxatime')}
- self.roster.send_status(account, 'away', auto_message,
- auto=True)
- gajim.sleeper_state[account] = 'autoaway-forced'
-
- try:
- bus = dbus.SessionBus()
- bus.add_signal_receiver(gnome_screensaver_ActiveChanged_cb,
- 'ActiveChanged', 'org.gnome.ScreenSaver')
- except Exception:
- pass
-
- self.show_vcard_when_connect = []
-
- self.sleeper = sleepy.Sleepy(
- gajim.config.get('autoawaytime') * 60, # make minutes to seconds
- gajim.config.get('autoxatime') * 60)
-
- gtkgui_helpers.make_jabber_state_images()
-
- self.systray_enabled = False
-
- import statusicon
- self.systray = statusicon.StatusIcon()
-
- pixs = []
- for size in (16, 32, 48, 64, 128):
- pix = gtkgui_helpers.get_icon_pixmap('org.gajim.Gajim', size)
- if pix:
- pixs.append(pix)
- if pixs:
- # set the icon to all windows
- Gtk.Window.set_default_icon_list(pixs)
-
- self.init_emoticons()
- self.make_regexps()
-
- # get transports type from DB
- gajim.transport_type = gajim.logger.get_transports_type()
-
- # test is dictionnary is present for speller
- if gajim.config.get('use_speller'):
- lang = gajim.config.get('speller_language')
- if not lang:
- lang = gajim.LANG
- tv = Gtk.TextView()
- try:
- import gtkspell
- spell = gtkspell.Spell(tv, lang)
- except (ImportError, TypeError, RuntimeError, OSError, ValueError):
- dialogs.AspellDictError(lang)
-
- if gajim.config.get('soundplayer') == '':
- # only on first time Gajim starts
- commands = ('paplay', 'aplay', 'play', 'ossplay')
- for command in commands:
- if helpers.is_in_path(command):
- if command == 'paplay':
- command += ' -n gajim --property=media.role=event'
- if command in ('aplay', 'play'):
- command += ' -q'
- elif command == 'ossplay':
- command += ' -qq'
- gajim.config.set('soundplayer', command)
- break
-
- self.last_ftwindow_update = 0
-
- self.music_track_changed_signal = None
-
-
-class PassphraseRequest:
- def __init__(self, keyid):
- self.keyid = keyid
- self.callbacks = []
- self.dialog_created = False
- self.dialog = None
- self.passphrase = None
- self.completed = False
-
- def interrupt(self, account=None):
- if account:
- for (acct, cb) in self.callbacks:
- if acct == account:
- self.callbacks.remove((acct, cb))
- else:
- self.callbacks = []
- if not len(self.callbacks):
- self.dialog.window.destroy()
-
- def run_callback(self, account, callback):
- gajim.connections[account].gpg_passphrase(self.passphrase)
- callback()
-
- def add_callback(self, account, cb):
- if self.completed:
- self.run_callback(account, cb)
- else:
- self.callbacks.append((account, cb))
- if not self.dialog_created:
- self.create_dialog(account)
-
- def complete(self, passphrase):
- self.passphrase = passphrase
- self.completed = True
- if passphrase is not None:
- GLib.timeout_add_seconds(30, gajim.interface.forget_gpg_passphrase,
- self.keyid)
- for (account, cb) in self.callbacks:
- self.run_callback(account, cb)
- self.callbacks = []
-
- def create_dialog(self, account):
- title = _('Passphrase Required')
- second = _('Enter OpenPGP key passphrase for key %(keyid)s (account '
- '%(account)s).') % {'keyid': self.keyid, 'account': account}
-
- def _cancel():
- # user cancelled, continue without GPG
- self.complete(None)
-
- def _ok(passphrase, checked, count):
- result = gajim.connections[account].test_gpg_passphrase(passphrase)
- if result == 'ok':
- # passphrase is good
- self.complete(passphrase)
- return
- elif result == 'expired':
- dialogs.ErrorDialog(_('OpenPGP key expired'),
- _('Your OpenPGP key has expired, you will be connected to '
- '%s without OpenPGP.') % account)
- # Don't try to connect with GPG
- gajim.connections[account].continue_connect_info[2] = False
- self.complete(None)
- return
-
- if count < 3:
- # ask again
- dialogs.PassphraseDialog(_('Wrong Passphrase'),
- _('Please retype your OpenPGP passphrase or press Cancel.'),
- ok_handler=(_ok, count + 1), cancel_handler=_cancel)
- else:
- # user failed 3 times, continue without GPG
- self.complete(None)
-
- self.dialog = dialogs.PassphraseDialog(title, second, ok_handler=(_ok,
- 1), cancel_handler=_cancel)
- self.dialog_created = True
-
-
-class ThreadInterface:
- def __init__(self, func, func_args=(), callback=None, callback_args=()):
- """
- Call a function in a thread
- """
- def thread_function(func, func_args, callback, callback_args):
- output = func(*func_args)
- if callback:
- GLib.idle_add(callback, output, *callback_args)
-
- Thread(target=thread_function, args=(func, func_args, callback,
- callback_args)).start()
diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py
deleted file mode 100644
index d96a6ad18..000000000
--- a/src/gui_menu_builder.py
+++ /dev/null
@@ -1,770 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/gui_menu_builder.py
-##
-## Copyright (C) 2009-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk, Gio, GLib
-import os
-import gtkgui_helpers
-import message_control
-
-from common import gajim
-from common import helpers
-from common import i18n
-from nbxmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION
-from nbxmpp.protocol import NS_JINGLE_FILE_TRANSFER_5, NS_CONFERENCE
-from gtkgui_helpers import get_action
-
-def build_resources_submenu(contacts, account, action, room_jid=None,
- room_account=None, cap=None):
- """
- Build a submenu with contact's resources. room_jid and room_account are for
- action self.on_invite_to_room
- """
- roster = gajim.interface.roster
- sub_menu = Gtk.Menu()
-
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = gajim.config.DEFAULT_ICONSET
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for c in contacts:
- item = Gtk.MenuItem.new_with_label(
- '%s (%s)' % (c.resource, str(c.priority)))
- sub_menu.append(item)
-
- if action == roster.on_invite_to_room:
- item.connect('activate', action, [(c, account)], room_jid,
- room_account, c.resource)
- elif action == roster.on_invite_to_new_room:
- item.connect('activate', action, [(c, account)], c.resource)
- else: # start_chat, execute_command, send_file
- item.connect('activate', action, c, account, c.resource)
-
- if cap and not c.supports(cap):
- item.set_sensitive(False)
-
- return sub_menu
-
-def build_invite_submenu(invite_menuitem, list_, ignore_rooms=None,
-show_bookmarked=False, force_resource=False):
- """
- list_ in a list of (contact, account)
- force_resource means we want to send invitation even if there is only one
- resource
- """
- if ignore_rooms is None:
- ignore_rooms = []
- roster = gajim.interface.roster
- # used if we invite only one contact with several resources
- contact_list = []
- if len(list_) == 1:
- contact, account = list_[0]
- contact_list = gajim.contacts.get_contacts(account, contact.jid)
- contacts_transport = -1
- connected_accounts = []
- # -1 is at start, False when not from the same, None when jabber
- for (contact, account) in list_:
- if not account in connected_accounts:
- connected_accounts.append(account)
- transport = gajim.get_transport_name_from_jid(contact.jid)
- if transport == 'jabber':
- transport = None
- if contacts_transport == -1:
- contacts_transport = transport
- elif contacts_transport != transport:
- contacts_transport = False
-
- if contacts_transport == False:
- # they are not all from the same transport
- invite_menuitem.set_sensitive(False)
- return
- invite_to_submenu = Gtk.Menu()
- invite_menuitem.set_submenu(invite_to_submenu)
- invite_to_new_room_menuitem = Gtk.MenuItem.new_with_mnemonic(_(
- '_New Group Chat'))
- if len(contact_list) > 1: # several resources
- invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
- contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
- elif len(list_) == 1 and contact.supports(NS_MUC):
- invite_menuitem.set_sensitive(True)
- # use resource if it's self contact
- if contact.jid == gajim.get_jid_from_account(account) or force_resource:
- resource = contact.resource
- else:
- resource = None
- invite_to_new_room_menuitem.connect('activate',
- roster.on_invite_to_new_room, list_, resource)
- elif len(list_) > 1:
- list2 = []
- for (c, a) in list_:
- if c.supports(NS_MUC):
- list2.append((c, a))
- if len(list2) > 0:
- invite_to_new_room_menuitem.connect('activate',
- roster.on_invite_to_new_room, list2, None)
- else:
- invite_menuitem.set_sensitive(False)
- else:
- invite_menuitem.set_sensitive(False)
- # transform None in 'jabber'
- c_t = contacts_transport or 'jabber'
- muc_jid = {}
- for account in connected_accounts:
- for t in gajim.connections[account].muc_jid:
- muc_jid[t] = gajim.connections[account].muc_jid[t]
- if c_t not in muc_jid:
- invite_to_new_room_menuitem.set_sensitive(False)
- rooms = [] # a list of (room_jid, account) tuple
- invite_to_submenu.append(invite_to_new_room_menuitem)
- minimized_controls = []
- for account in connected_accounts:
- minimized_controls += \
- list(gajim.interface.minimized_controls[account].values())
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + minimized_controls:
- acct = gc_control.account
- if acct not in connected_accounts:
- continue
- room_jid = gc_control.room_jid
- if room_jid in ignore_rooms:
- continue
- if room_jid in gajim.gc_connected[acct] and \
- gajim.gc_connected[acct][room_jid] and \
- contacts_transport in ['jabber', None]:
- rooms.append((room_jid, acct))
- if len(rooms):
- item = Gtk.SeparatorMenuItem.new() # separator
- invite_to_submenu.append(item)
- for (room_jid, account) in rooms:
- menuitem = Gtk.MenuItem.new_with_label(room_jid.split('@')[0])
- if len(contact_list) > 1: # several resources
- menuitem.set_submenu(build_resources_submenu(
- contact_list, account, roster.on_invite_to_room, room_jid,
- account))
- else:
- # use resource if it's self contact
- if contact.jid == gajim.get_jid_from_account(account):
- resource = contact.resource
- else:
- resource = None
- menuitem.connect('activate', roster.on_invite_to_room, list_,
- room_jid, account, resource)
- invite_to_submenu.append(menuitem)
-
- if not show_bookmarked:
- return
- rooms2 = [] # a list of (room_jid, account) tuple
- r_jids = [] # list of room jids
- for account in connected_accounts:
- for room in gajim.connections[account].bookmarks:
- r_jid = room['jid']
- if r_jid in r_jids:
- continue
- if r_jid not in gajim.gc_connected[account] or not \
- gajim.gc_connected[account][r_jid]:
- rooms2.append((r_jid, account))
- r_jids.append(r_jid)
-
- if not rooms2:
- return
- item = Gtk.SeparatorMenuItem.new() # separator
- invite_to_submenu.append(item)
- for (room_jid, account) in rooms2:
- menuitem = Gtk.MenuItem.new_with_label(room_jid.split('@')[0])
- if len(contact_list) > 1: # several resources
- menuitem.set_submenu(build_resources_submenu(
- contact_list, account, roster.on_invite_to_room, room_jid,
- account))
- else:
- # use resource if it's self contact
- if contact.jid == gajim.get_jid_from_account(account):
- resource = contact.resource
- else:
- resource = None
- menuitem.connect('activate', roster.on_invite_to_room, list_,
- room_jid, account, resource)
- invite_to_submenu.append(menuitem)
-
-def get_contact_menu(contact, account, use_multiple_contacts=True,
-show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
-control=None, gc_contact=None, is_anonymous=True):
- """
- Build contact popup menu for roster and chat window. If control is not set,
- we hide invite_contacts_menuitem
- """
- if not contact:
- return
-
- jid = contact.jid
- our_jid = jid == gajim.get_jid_from_account(account)
- roster = gajim.interface.roster
-
- xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui')
- contact_context_menu = xml.get_object('contact_context_menu')
-
- start_chat_menuitem = xml.get_object('start_chat_menuitem')
- execute_command_menuitem = xml.get_object('execute_command_menuitem')
- rename_menuitem = xml.get_object('rename_menuitem')
- edit_groups_menuitem = xml.get_object('edit_groups_menuitem')
- send_file_menuitem = xml.get_object('send_file_menuitem')
- assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem')
- add_special_notification_menuitem = xml.get_object(
- 'add_special_notification_menuitem')
- information_menuitem = xml.get_object('information_menuitem')
- history_menuitem = xml.get_object('history_menuitem')
- send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem')
- send_single_message_menuitem = xml.get_object('send_single_message_menuitem')
- invite_menuitem = xml.get_object('invite_menuitem')
- block_menuitem = xml.get_object('block_menuitem')
- unblock_menuitem = xml.get_object('unblock_menuitem')
- ignore_menuitem = xml.get_object('ignore_menuitem')
- unignore_menuitem = xml.get_object('unignore_menuitem')
- set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem')
- # Subscription submenu
- subscription_menuitem = xml.get_object('subscription_menuitem')
- send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \
- subscription_menuitem.get_submenu().get_children()
- add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem')
- remove_from_roster_menuitem = xml.get_object(
- 'remove_from_roster_menuitem')
- manage_contact_menuitem = xml.get_object('manage_contact')
- convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem')
- last_separator = xml.get_object('last_separator')
-
- items_to_hide = []
-
- contacts = gajim.contacts.get_contacts(account, jid)
- if len(contacts) > 1 and use_multiple_contacts: # several resources
- start_chat_menuitem.set_submenu(build_resources_submenu(contacts,
- account, gajim.interface.on_open_chat_window))
- send_file_menuitem.set_submenu(build_resources_submenu(contacts,
- account, roster.on_send_file_menuitem_activate, cap=NS_FILE))
- execute_command_menuitem.set_submenu(build_resources_submenu(
- contacts, account, roster.on_execute_command, cap=NS_COMMANDS))
- else:
- start_chat_menuitem.connect('activate',
- gajim.interface.on_open_chat_window, contact, account)
- if contact.supports(NS_FILE) or contact.supports(NS_JINGLE_FILE_TRANSFER_5):
- send_file_menuitem.set_sensitive(True)
- send_file_menuitem.connect('activate',
- roster.on_send_file_menuitem_activate, contact, account)
- else:
- send_file_menuitem.set_sensitive(False)
-
- if contact.supports(NS_COMMANDS):
- execute_command_menuitem.set_sensitive(True)
- if gc_contact and gc_contact.jid and not is_anonymous:
- execute_command_menuitem.connect('activate',
- roster.on_execute_command, gc_contact, account,
- gc_contact.resource)
- else:
- execute_command_menuitem.connect('activate',
- roster.on_execute_command, contact, account,
- contact.resource)
- else:
- execute_command_menuitem.set_sensitive(False)
-
- rename_menuitem.connect('activate', roster.on_rename, 'contact', jid,
- account)
- history_menuitem.connect('activate', roster.on_history, contact, account)
-
- if control:
- convert_to_gc_menuitem.connect('activate',
- control._on_convert_to_gc_menuitem_activate)
- else:
- items_to_hide.append(convert_to_gc_menuitem)
-
- if _('Not in Roster') not in contact.get_shown_groups():
- # contact is in normal group
- edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact,
- account)])
-
- if gajim.connections[account].gpg:
- assign_openpgp_key_menuitem.connect('activate',
- roster.on_assign_pgp_key, contact, account)
- else:
- assign_openpgp_key_menuitem.set_sensitive(False)
- else:
- # contact is in group 'Not in Roster'
- edit_groups_menuitem.set_sensitive(False)
- assign_openpgp_key_menuitem.set_sensitive(False)
-
- # Hide items when it's self contact row
- if our_jid:
- items_to_hide += [rename_menuitem, edit_groups_menuitem]
-
- # Unsensitive many items when account is offline
- if gajim.account_is_disconnected(account):
- for widget in (start_chat_menuitem, rename_menuitem,
- edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem,
- information_menuitem):
- widget.set_sensitive(False)
-
- if not show_start_chat:
- items_to_hide.append(start_chat_menuitem)
-
- if not show_buttonbar_items:
- items_to_hide += [history_menuitem, send_file_menuitem,
- information_menuitem, convert_to_gc_menuitem, last_separator]
-
- if not control:
- items_to_hide.append(convert_to_gc_menuitem)
-
- # Hide items when it's a pm
- if gc_contact:
- items_to_hide += [rename_menuitem, edit_groups_menuitem,
- subscription_menuitem, remove_from_roster_menuitem]
-
- for item in items_to_hide:
- item.set_no_show_all(True)
- item.hide()
-
- # Zeroconf Account
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- for item in (send_custom_status_menuitem, send_single_message_menuitem,
- invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem,
- unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem,
- manage_contact_menuitem, convert_to_gc_menuitem):
- item.set_no_show_all(True)
- item.hide()
-
- if contact.show in ('offline', 'error'):
- information_menuitem.set_sensitive(False)
- send_file_menuitem.set_sensitive(False)
- else:
- information_menuitem.connect('activate', roster.on_info_zeroconf,
- contact, account)
-
- contact_context_menu.connect('selection-done',
- gtkgui_helpers.destroy_widget)
- contact_context_menu.show_all()
- return contact_context_menu
-
- # normal account
-
- # send custom status icon
- blocked = False
- if helpers.jid_is_blocked(account, jid):
- blocked = True
- else:
- for group in contact.get_shown_groups():
- if helpers.group_is_blocked(account, group):
- blocked = True
- break
- transport = gajim.get_transport_name_from_jid(jid, use_config_setting=False)
- if transport and transport != 'jabber':
- # Transport contact, send custom status unavailable
- send_custom_status_menuitem.set_sensitive(False)
- elif blocked:
- send_custom_status_menuitem.set_sensitive(False)
-
- if gc_contact:
- if not gc_contact.jid:
- # it's a pm and we don't know real JID
- invite_menuitem.set_sensitive(False)
- else:
- bookmarked = False
- c_ = gajim.contacts.get_contact(account, gc_contact.jid,
- gc_contact.resource)
- if c_ and c_.supports(NS_CONFERENCE):
- bookmarked=True
- build_invite_submenu(invite_menuitem, [(gc_contact, account)],
- show_bookmarked=bookmarked)
- else:
- force_resource = False
- if control and control.resource:
- force_resource = True
- build_invite_submenu(invite_menuitem, [(contact, account)],
- show_bookmarked=contact.supports(NS_CONFERENCE),
- force_resource=force_resource)
-
- if gajim.account_is_disconnected(account):
- invite_menuitem.set_sensitive(False)
-
- # One or several resource, we do the same for send_custom_status
- status_menuitems = Gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- # icon MUST be different instance for every item
- status_menuitem = Gtk.MenuItem.new_with_label(helpers.get_uf_show(s))
- status_menuitem.connect('activate', roster.on_send_custom_status,
- [(contact, account)], s)
- status_menuitems.append(status_menuitem)
-
- send_single_message_menuitem.connect('activate',
- roster.on_send_single_message_menuitem_activate, account, contact)
-
- remove_from_roster_menuitem.connect('activate', roster.on_req_usub,
- [(contact, account)])
- information_menuitem.connect('activate', roster.on_info, contact, account)
-
- if _('Not in Roster') not in contact.get_shown_groups():
- # contact is in normal group
- add_to_roster_menuitem.hide()
- add_to_roster_menuitem.set_no_show_all(True)
-
- if contact.sub in ('from', 'both'):
- send_auth_menuitem.set_sensitive(False)
- else:
- send_auth_menuitem.connect('activate', roster.authorize, jid, account)
- if contact.sub in ('to', 'both'):
- ask_auth_menuitem.set_sensitive(False)
- add_special_notification_menuitem.connect('activate',
- roster.on_add_special_notification_menuitem_activate, jid)
- else:
- ask_auth_menuitem.connect('activate', roster.req_sub, jid,
- _('I would like to add you to my roster'), account,
- contact.groups, contact.name)
- transport = gajim.get_transport_name_from_jid(jid,
- use_config_setting=False)
- if contact.sub in ('to', 'none') or transport not in ['jabber', None]:
- revoke_auth_menuitem.set_sensitive(False)
- else:
- revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid,
- account)
-
- elif gajim.connections[account].roster_supported:
- # contact is in group 'Not in Roster'
- add_to_roster_menuitem.set_no_show_all(False)
- subscription_menuitem.set_sensitive(False)
-
- add_to_roster_menuitem.connect('activate', roster.on_add_to_roster,
- contact, account)
- else:
- add_to_roster_menuitem.hide()
- add_to_roster_menuitem.set_no_show_all(True)
- subscription_menuitem.set_sensitive(False)
-
- set_custom_avatar_menuitem.connect('activate',
- roster.on_set_custom_avatar_activate, contact, account)
-
- # Hide items when it's self contact row
- if our_jid:
- manage_contact_menuitem.set_sensitive(False)
-
- # Unsensitive items when account is offline
- if gajim.account_is_disconnected(account):
- for widget in (send_single_message_menuitem, subscription_menuitem,
- add_to_roster_menuitem, remove_from_roster_menuitem,
- execute_command_menuitem, send_custom_status_menuitem):
- widget.set_sensitive(False)
-
- if gajim.connections[account] and (gajim.connections[account].\
- privacy_rules_supported or gajim.connections[account].blocking_supported):
- if helpers.jid_is_blocked(account, jid):
- block_menuitem.set_no_show_all(True)
- block_menuitem.hide()
- if gajim.get_transport_name_from_jid(jid, use_config_setting=False)\
- and transport != 'jabber':
- unblock_menuitem.set_no_show_all(True)
- unblock_menuitem.hide()
- unignore_menuitem.set_no_show_all(False)
- unignore_menuitem.connect('activate', roster.on_unblock, [(contact,
- account)])
- else:
- unblock_menuitem.connect('activate', roster.on_unblock, [(contact,
- account)])
- else:
- unblock_menuitem.set_no_show_all(True)
- unblock_menuitem.hide()
- if gajim.get_transport_name_from_jid(jid, use_config_setting=False)\
- and transport != 'jabber':
- block_menuitem.set_no_show_all(True)
- block_menuitem.hide()
- ignore_menuitem.set_no_show_all(False)
- ignore_menuitem.connect('activate', roster.on_block, [(contact,
- account)])
- else:
- block_menuitem.connect('activate', roster.on_block, [(contact,
- account)])
- else:
- unblock_menuitem.set_no_show_all(True)
- block_menuitem.set_sensitive(False)
- unblock_menuitem.hide()
-
- contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- contact_context_menu.show_all()
- return contact_context_menu
-
-def get_transport_menu(contact, account):
- roster = gajim.interface.roster
- jid = contact.jid
-
- menu = Gtk.Menu()
-
- # Send single message
- item = Gtk.MenuItem.new_with_mnemonic(_('Send Single _Message…'))
- item.connect('activate', roster.on_send_single_message_menuitem_activate,
- account, contact)
- menu.append(item)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- blocked = False
- if helpers.jid_is_blocked(account, jid):
- blocked = True
-
- # Send Custom Status
- send_custom_status_menuitem = Gtk.MenuItem.new_with_mnemonic(
- _('Send Cus_tom Status'))
- if blocked:
- send_custom_status_menuitem.set_sensitive(False)
- else:
- status_menuitems = Gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- status_menuitem = Gtk.MenuItem.new_with_label(helpers.get_uf_show(
- s))
- status_menuitem.connect('activate', roster.on_send_custom_status,
- [(contact, account)], s)
- status_menuitems.append(status_menuitem)
- menu.append(send_custom_status_menuitem)
- if gajim.account_is_disconnected(account):
- send_custom_status_menuitem.set_sensitive(False)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- # Execute Command
- item = Gtk.MenuItem.new_with_mnemonic(_('E_xecute Command…'))
- menu.append(item)
- item.connect('activate', roster.on_execute_command, contact, account,
- contact.resource)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Manage Transport submenu
- item = Gtk.MenuItem.new_with_mnemonic(_('_Manage Transport'))
- manage_transport_submenu = Gtk.Menu()
- item.set_submenu(manage_transport_submenu)
- menu.append(item)
-
- # Modify Transport
- item = Gtk.MenuItem.new_with_mnemonic(_('_Modify Transport'))
- manage_transport_submenu.append(item)
- item.connect('activate', roster.on_edit_agent, contact, account)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Rename
- item = Gtk.MenuItem.new_with_mnemonic(_('_Rename…'))
- manage_transport_submenu.append(item)
- item.connect('activate', roster.on_rename, 'agent', jid, account)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- manage_transport_submenu.append(item)
-
- # Block
- if blocked:
- item = Gtk.MenuItem.new_with_mnemonic(_('_Unblock'))
- item.connect('activate', roster.on_unblock, [(contact, account)])
- else:
- item = Gtk.MenuItem.new_with_mnemonic(_('_Block'))
- item.connect('activate', roster.on_block, [(contact, account)])
-
- manage_transport_submenu.append(item)
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- # Remove
- item = Gtk.MenuItem.new_with_mnemonic(_('Remo_ve'))
- manage_transport_submenu.append(item)
- item.connect('activate', roster.on_remove_agent, [(contact, account)])
- if gajim.account_is_disconnected(account):
- item.set_sensitive(False)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- # Information
- information_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Information'))
- menu.append(information_menuitem)
- information_menuitem.connect('activate', roster.on_info, contact, account)
- if gajim.account_is_disconnected(account):
- information_menuitem.set_sensitive(False)
-
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- return menu
-
-'''
-Build dynamic Application Menus
-'''
-
-
-def get_bookmarks_menu(account, rebuild=False):
- if not gajim.connections[account].bookmarks:
- return None
- menu = Gio.Menu()
-
- # Build Join Groupchat
- action = 'app.{}-join-groupchat'.format(account)
- menuitem = Gio.MenuItem.new(_('Join Group Chat'), action)
- variant = GLib.Variant('s', account)
- menuitem.set_action_and_target_value(action, variant)
- menu.append_item(menuitem)
-
- # Build Bookmarks
- section = Gio.Menu()
- for bookmark in gajim.connections[account].bookmarks:
- name = bookmark['name']
- if not name:
- # No name was given for this bookmark.
- # Use the first part of JID instead...
- name = bookmark['jid'].split("@")[0]
-
- # Shorten long names
- name = (name[:42] + '..') if len(name) > 42 else name
-
- action = 'app.{}-activate-bookmark'.format(account)
- menuitem = Gio.MenuItem.new(name, action)
-
- # Create Variant Dict
- dict_ = {'account': GLib.Variant('s', account),
- 'jid': GLib.Variant('s', bookmark['jid'])}
- if bookmark['nick']:
- dict_['nick'] = GLib.Variant('s', bookmark['nick'])
- if bookmark['password']:
- dict_['password'] = GLib.Variant('s', bookmark['password'])
- variant_dict = GLib.Variant('a{sv}', dict_)
-
- menuitem.set_action_and_target_value(action, variant_dict)
- section.append_item(menuitem)
- menu.append_section(None, section)
- if not rebuild:
- get_action(account + '-activate-bookmark').set_enabled(True)
-
- return menu
-
-
-def get_account_menu(account):
- '''
- [(action, label/sub_menu)]
- action: string
- label: string
- sub menu: list
- '''
- account_menu = [
- ('-add-contact', _('Add Contact...')),
- ('-join-groupchat', _('Join Group Chat')),
- ('-profile', _('Profile')),
- ('-services', _('Discover Services')),
- ('-start-chat', _('Start Chat...')),
- ('-start-single-chat', _('Send Single Message...')),
- ('Advanced', [
- ('-archive', _('Archiving Preferences')),
- ('-privacylists', _('Privacy Lists')),
- ('-xml-console', _('XML Console'))
- ]),
- ('Admin', [
- ('-send-server-message', _('Send Server Message...')),
- ('-set-motd', _('Set MOTD...')),
- ('-update-motd', _('Update MOTD...')),
- ('-delete-motd', _('Delete MOTD...'))
- ]),
- ]
-
- def build_menu(preset):
- menu = Gio.Menu()
- for item in preset:
- if isinstance(item[1], str):
- action, label = item
- if action == '-join-groupchat':
- bookmark_menu = get_bookmarks_menu(account, True)
- if bookmark_menu:
- menu.append_submenu(label, bookmark_menu)
- continue
- action = 'app.{}{}'.format(account, action)
- menuitem = Gio.MenuItem.new(label, action)
- variant = GLib.Variant('s', account)
- menuitem.set_action_and_target_value(action, variant)
- menu.append_item(menuitem)
- else:
- label, sub_menu = item
- # This is a submenu
- submenu = build_menu(sub_menu)
- menu.append_submenu(label, submenu)
- return menu
-
- return build_menu(account_menu)
-
-
-def build_accounts_menu():
- menubar = gajim.app.get_menubar()
- # Accounts Submenu
- acc_menu = menubar.get_item_link(0, 'submenu')
- acc_menu.remove_all()
- accounts_list = sorted(gajim.contacts.get_accounts())
- if not accounts_list:
- no_accounts = _('No Accounts available')
- acc_menu.append_item(Gio.MenuItem.new(no_accounts, None))
- return
- if len(accounts_list) > 1:
- for acc in accounts_list:
- acc_menu.append_submenu(
- acc, get_account_menu(acc))
- else:
- acc_menu = get_account_menu(accounts_list[0])
- menubar.remove(0)
- menubar.insert_submenu(0, 'Accounts', acc_menu)
-
-
-def build_bookmark_menu(account):
- menubar = gajim.app.get_menubar()
- bookmark_menu = get_bookmarks_menu(account)
- if not bookmark_menu:
- return
-
- # Accounts Submenu
- acc_menu = menubar.get_item_link(0, 'submenu')
-
- # We have more than one Account active
- if acc_menu.get_item_link(0, 'submenu'):
- for i in range(acc_menu.get_n_items()):
- label = acc_menu.get_item_attribute_value(i, 'label')
- if label.get_string() == account:
- menu = acc_menu.get_item_link(i, 'submenu')
- else:
- # We have only one Account active
- menu = acc_menu
- label = menu.get_item_attribute_value(1, 'label').get_string()
- menu.remove(1)
- menu.insert_submenu(1, label, bookmark_menu)
-
-
-def get_encryption_menu(contact, type_id):
- menu = Gio.Menu()
- menu.append(
- 'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid,
- 'disabled'))
- for name, plugin in gajim.plugin_manager.encryption_plugins.items():
- if type_id == 'gc':
- if not hasattr(plugin, 'allow_groupchat'):
- continue
- if type_id == 'pm':
- if not hasattr(plugin, 'allow_privatchat'):
- continue
- menu_action = 'win.{}-encryptiongroup::{}'.format(
- contact.jid, name)
- menu.append(name, menu_action)
- if menu.get_n_items() == 1:
- return None
- return menu
diff --git a/src/history_manager.py b/src/history_manager.py
deleted file mode 100644
index 6b06c731a..000000000
--- a/src/history_manager.py
+++ /dev/null
@@ -1,674 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/history_manager.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-## NOTE: some method names may match those of logger.py but that's it
-## someday (TM) should have common class
-## that abstracts db connections and helpers on it
-## the same can be said for history_window.py
-
-import os
-import sys
-import signal
-import gi
-gi.require_version('Gtk', '3.0')
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GLib
-import time
-
-import getopt
-from common import i18n
-
-def parseOpts():
- config_path = None
-
- try:
- shortargs = 'hc:'
- longargs = 'help config-path='
- opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
- except getopt.error as msg:
- print(str(msg))
- print('for help use --help')
- sys.exit(2)
- for o, a in opts:
- if o in ('-h', '--help'):
- print(_('Usage:') + \
- '\n gajim-history-manager [options] filename\n\n' + \
- _('Options:') + \
- '\n -h, --help ' + \
- _('Show this help message and exit') + \
- '\n -c, --config-path ' + _('Choose folder for logfile') + '\n')
- sys.exit()
- elif o in ('-c', '--config-path'):
- config_path = a
- return config_path
-
-config_path = parseOpts()
-del parseOpts
-
-import common.configpaths
-common.configpaths.gajimpaths.init(config_path)
-del config_path
-from common import gajim
-import gtkgui_helpers
-from common.logger import LOG_DB_PATH, JIDConstant, KindConstant
-from common import helpers
-import dialogs
-
-from enum import IntEnum, unique
-
-@unique
-class Column(IntEnum):
- UNIXTIME = 2
- MESSAGE = 3
- SUBJECT = 4
- NICKNAME = 5
-
-
-import sqlite3 as sqlite
-
-
-class HistoryManager:
- def __init__(self):
- pixs = []
- for size in (16, 32, 48, 64, 128):
- pix = gtkgui_helpers.get_icon_pixmap('org.gajim.Gajim', size)
- if pix:
- pixs.append(pix)
- if pixs:
- # set the icon to all windows
- Gtk.Window.set_default_icon_list(pixs)
-
- if not os.path.exists(LOG_DB_PATH):
- dialogs.ErrorDialog(_('Cannot find history logs database'),
- '%s does not exist.' % LOG_DB_PATH)
- sys.exit()
-
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui')
- self.window = xml.get_object('history_manager_window')
- self.jids_listview = xml.get_object('jids_listview')
- self.logs_listview = xml.get_object('logs_listview')
- self.search_results_listview = xml.get_object('search_results_listview')
- self.search_entry = xml.get_object('search_entry')
- self.logs_scrolledwindow = xml.get_object('logs_scrolledwindow')
- self.search_results_scrolledwindow = xml.get_object(
- 'search_results_scrolledwindow')
- self.welcome_vbox = xml.get_object('welcome_vbox')
-
- self.jids_already_in = [] # holds jids that we already have in DB
- self.AT_LEAST_ONE_DELETION_DONE = False
-
- self.con = sqlite.connect(LOG_DB_PATH, timeout=20.0,
- isolation_level='IMMEDIATE')
- self.cur = self.con.cursor()
-
- self._init_jids_listview()
- self._init_logs_listview()
- self._init_search_results_listview()
-
- self._fill_jids_listview()
-
- self.search_entry.grab_focus()
-
- self.window.show_all()
-
- xml.connect_signals(self)
-
- def _init_jids_listview(self):
- self.jids_liststore = Gtk.ListStore(str, str) # jid, jid_id
- self.jids_listview.set_model(self.jids_liststore)
- self.jids_listview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
-
- renderer_text = Gtk.CellRendererText() # holds jid
- col = Gtk.TreeViewColumn(_('JID'), renderer_text, text=0)
- self.jids_listview.append_column(col)
-
- self.jids_listview.get_selection().connect('changed',
- self.on_jids_listview_selection_changed)
-
- def _init_logs_listview(self):
- # log_line_id(HIDDEN), jid_id(HIDDEN), time, message, subject, nickname
- self.logs_liststore = Gtk.ListStore(str, str, str, str, str, str)
- self.logs_listview.set_model(self.logs_liststore)
- self.logs_listview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
-
- renderer_text = Gtk.CellRendererText() # holds time
- col = Gtk.TreeViewColumn(_('Date'), renderer_text, text=Column.UNIXTIME)
- # user can click this header and sort
- col.set_sort_column_id(Column.UNIXTIME)
- col.set_resizable(True)
- self.logs_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds nickname
- col = Gtk.TreeViewColumn(_('Nickname'), renderer_text, text=Column.NICKNAME)
- # user can click this header and sort
- col.set_sort_column_id(Column.NICKNAME)
- col.set_resizable(True)
- col.set_visible(False)
- self.nickname_col_for_logs = col
- self.logs_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds message
- col = Gtk.TreeViewColumn(_('Message'), renderer_text, markup=Column.MESSAGE)
- # user can click this header and sort
- col.set_sort_column_id(Column.MESSAGE)
- col.set_resizable(True)
- self.message_col_for_logs = col
- self.logs_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds subject
- col = Gtk.TreeViewColumn(_('Subject'), renderer_text, text=Column.SUBJECT)
- col.set_sort_column_id(Column.SUBJECT) # user can click this header and sort
- col.set_resizable(True)
- col.set_visible(False)
- self.subject_col_for_logs = col
- self.logs_listview.append_column(col)
-
- def _init_search_results_listview(self):
- # log_line_id (HIDDEN), jid, time, message, subject, nickname
- self.search_results_liststore = Gtk.ListStore(int, str, str, str, str,
- str)
- self.search_results_listview.set_model(self.search_results_liststore)
-
- renderer_text = Gtk.CellRendererText() # holds JID (who said this)
- col = Gtk.TreeViewColumn(_('JID'), renderer_text, text=1)
- col.set_sort_column_id(1) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds time
- col = Gtk.TreeViewColumn(_('Date'), renderer_text, text=Column.UNIXTIME)
- # user can click this header and sort
- col.set_sort_column_id(Column.UNIXTIME)
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds message
- col = Gtk.TreeViewColumn(_('Message'), renderer_text, text=Column.MESSAGE)
- col.set_sort_column_id(Column.MESSAGE) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds subject
- col = Gtk.TreeViewColumn(_('Subject'), renderer_text, text=Column.SUBJECT)
- col.set_sort_column_id(Column.SUBJECT) # user can click this header and sort
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- renderer_text = Gtk.CellRendererText() # holds nickname
- col = Gtk.TreeViewColumn(_('Nickname'), renderer_text, text=Column.NICKNAME)
- # user can click this header and sort
- col.set_sort_column_id(Column.NICKNAME)
- col.set_resizable(True)
- self.search_results_listview.append_column(col)
-
- def on_history_manager_window_delete_event(self, widget, event):
- if not self.AT_LEAST_ONE_DELETION_DONE:
- Gtk.main_quit()
- return
-
- def on_yes(clicked):
- self.cur.execute('VACUUM')
- self.con.commit()
- Gtk.main_quit()
-
- def on_no():
- Gtk.main_quit()
-
- dialog = dialogs.YesNoDialog(
- _('Do you want to clean up the database? '
- '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
- _('Normally allocated database size will not be freed, '
- 'it will just become reusable. If you really want to reduce '
- 'database filesize, click YES, else click NO.'
- '\n\nIn case you click YES, please wait…'),
- on_response_yes=on_yes, on_response_no=on_no)
- dialog.set_title(_('Database Cleanup'))
- button_box = dialog.get_children()[0].get_children()[1]
- button_box.get_children()[0].grab_focus()
-
- def _fill_jids_listview(self):
- # get those jids that have at least one entry in logs
- self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN ('
- 'SELECT distinct logs.jid_id FROM logs) ORDER BY jid')
- # list of tuples: [('aaa@bbb',), ('cc@dd',)]
- rows = self.cur.fetchall()
- for row in rows:
- self.jids_already_in.append(row[0]) # jid
- self.jids_liststore.append([row[0], str(row[1])]) # jid, jid_id
-
- def on_jids_listview_selection_changed(self, widget, data=None):
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- self.logs_liststore.clear() # clear the store
-
- self.welcome_vbox.hide()
- self.search_results_scrolledwindow.hide()
- self.logs_scrolledwindow.show()
-
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(Gtk.TreeRowReference.new(liststore, path))
-
- for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected
- path = rowref.get_path()
- if path is None:
- continue
- jid = liststore[path][0] # jid
- self._fill_logs_listview(jid)
-
- def _get_jid_id(self, jid):
- """
- jids table has jid and jid_id
- logs table has log_id, jid_id, contact_name, time, kind, show, message
-
- So to ask logs we need jid_id that matches our jid in jids table this
- method wants jid and returns the jid_id for later sql-ing on logs
- """
- if jid.find('/') != -1: # if it has a /
- jid_is_from_pm = self._jid_is_from_pm(jid)
- if not jid_is_from_pm: # it's normal jid with resource
- jid = jid.split('/', 1)[0] # remove the resource
- self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,))
- jid_id = self.cur.fetchone()[0]
- return str(jid_id)
-
- def _get_jid_from_jid_id(self, jid_id):
- """
- jids table has jid and jid_id
-
- This method accepts jid_id and returns the jid for later sql-ing on logs
- """
- self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
- jid = self.cur.fetchone()[0]
- return jid
-
- def _jid_is_from_pm(self, jid):
- """
- If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
- is not a normal guy and nkour is not his resource? We ask if gajim@conf
- is already in jids (with type room jid). This fails if user disables
- logging for room and only enables for pm (so higly unlikely) and if we
- fail we do not go chaos (user will see the first pm as if it was message
- in room's public chat) and after that everything is ok
- """
- possible_room_jid = jid.split('/', 1)[0]
-
- self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
- (possible_room_jid, JIDConstant.ROOM_TYPE))
- row = self.cur.fetchone()
- if row is None:
- return False
- else:
- return True
-
- def _jid_is_room_type(self, jid):
- """
- Return True/False if given id is room type or not eg. if it is room
- """
- self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
- row = self.cur.fetchone()
- if row is None:
- raise
- elif row[0] == JIDConstant.ROOM_TYPE:
- return True
- else: # normal type
- return False
-
- def _fill_logs_listview(self, jid):
- """
- Fill the listview with all messages that user sent to or received from
- JID
- """
- # no need to lower jid in this context as jid is already lowered
- # as we use those jids from db
- jid_id = self._get_jid_id(jid)
- self.cur.execute('''
- SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show
- FROM logs
- WHERE jid_id = ?
- ORDER BY time
- ''', (jid_id,))
-
- results = self.cur.fetchall()
-
- if self._jid_is_room_type(jid): # is it room?
- self.nickname_col_for_logs.set_visible(True)
- self.subject_col_for_logs.set_visible(False)
- else:
- self.nickname_col_for_logs.set_visible(False)
- self.subject_col_for_logs.set_visible(True)
-
- for row in results:
- # exposed in UI (TreeViewColumns) are only
- # time, message, subject, nickname
- # but store in liststore
- # log_line_id, jid_id, time, message, subject, nickname
- log_line_id, jid_id, time_, kind, message, subject, nickname, \
- show = row
- try:
- time_ = time.strftime('%x', time.localtime(float(time_)))
- except ValueError:
- pass
- else:
- color = None
- if kind in (KindConstant.SINGLE_MSG_RECV,
- KindConstant.CHAT_MSG_RECV, KindConstant.GC_MSG):
- # it is the other side
- color = gajim.config.get('inmsgcolor') # so incoming color
- elif kind in (KindConstant.SINGLE_MSG_SENT,
- KindConstant.CHAT_MSG_SENT): # it is us
- color = gajim.config.get('outmsgcolor') # so outgoing color
- elif kind in (KindConstant.STATUS,
- KindConstant.GCSTATUS): # is is statuses
- # so status color
- color = gajim.config.get('statusmsgcolor')
- # include status into (status) message
- if message is None:
- message = ''
- else:
- message = ' : ' + message
- message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + \
- message
-
- message_ = '<span'
- if color:
- message_ += ' foreground="%s"' % color
- message_ += '>%s</span>' % GLib.markup_escape_text(message)
- self.logs_liststore.append((str(log_line_id), str(jid_id),
- time_, message_, subject, nickname))
-
- def _fill_search_results_listview(self, text):
- """
- Ask db and fill listview with results that match text
- """
- self.search_results_liststore.clear()
- like_sql = '%' + text + '%'
- self.cur.execute('''
- SELECT log_line_id, jid_id, time, message, subject, contact_name
- FROM logs
- WHERE message LIKE ? OR subject LIKE ?
- ORDER BY time
- ''', (like_sql, like_sql))
-
- results = self.cur.fetchall()
- for row in results:
- # exposed in UI (TreeViewColumns) are only
- # JID, time, message, subject, nickname
- # but store in liststore
- # log_line_id, jid (from jid_id), time, message, subject, nickname
- log_line_id, jid_id, time_, message, subject, nickname = row
- try:
- time_ = time.strftime('%x', time.localtime(float(time_)))
- except ValueError:
- pass
- else:
- jid = self._get_jid_from_jid_id(jid_id)
-
- self.search_results_liststore.append((log_line_id, jid, time_,
- message, subject, nickname))
-
- def on_logs_listview_key_press_event(self, widget, event):
- liststore, list_of_paths = self.logs_listview.get_selection()\
- .get_selected_rows()
- if event.keyval == Gdk.KEY_Delete:
- self._delete_logs(liststore, list_of_paths)
-
- def on_listview_button_press_event(self, widget, event):
- if event.button == 3: # right click
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui',
- 'context_menu')
- if Gtk.Buildable.get_name(widget) != 'jids_listview':
- xml.get_object('export_menuitem').hide()
- xml.get_object('delete_menuitem').connect('activate',
- self.on_delete_menuitem_activate, widget)
-
- xml.connect_signals(self)
- xml.get_object('context_menu').popup(None, None, None, None,
- event.button, event.time)
- return True
-
- def on_export_menuitem_activate(self, widget):
- xml = gtkgui_helpers.get_gtk_builder('history_manager.ui',
- 'filechooserdialog')
- xml.connect_signals(self)
-
- dlg = xml.get_object('filechooserdialog')
- dlg.set_title(_('Exporting History Logs…'))
- dlg.set_current_folder(gajim.HOME_DIR)
- dlg.props.do_overwrite_confirmation = True
- response = dlg.run()
-
- if response == Gtk.ResponseType.OK: # user want us to export ;)
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- path_to_file = dlg.get_filename()
- self._export_jids_logs_to_file(liststore, list_of_paths,
- path_to_file)
-
- dlg.destroy()
-
- def on_delete_menuitem_activate(self, widget, listview):
- widget_name = Gtk.Buildable.get_name(listview)
- liststore, list_of_paths = listview.get_selection().get_selected_rows()
- if widget_name == 'jids_listview':
- self._delete_jid_logs(liststore, list_of_paths)
- elif widget_name in ('logs_listview', 'search_results_listview'):
- self._delete_logs(liststore, list_of_paths)
- else: # Huh ? We don't know this widget
- return
-
- def on_jids_listview_key_press_event(self, widget, event):
- liststore, list_of_paths = self.jids_listview.get_selection()\
- .get_selected_rows()
- if event.keyval == Gdk.KEY_Delete:
- self._delete_jid_logs(liststore, list_of_paths)
-
- def _export_jids_logs_to_file(self, liststore, list_of_paths, path_to_file):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(Gtk.TreeRowReference.new(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- jid_id = liststore[path][1]
- self.cur.execute('''
- SELECT time, kind, message, contact_name FROM logs
- WHERE jid_id = ?
- ORDER BY time
- ''', (jid_id,))
-
- # FIXME: we may have two contacts selected to export. fix that
- # AT THIS TIME FIRST EXECUTE IS LOST! WTH!!!!!
- results = self.cur.fetchall()
- #print results[0]
- file_ = open(path_to_file, 'w')
- for row in results:
- # in store: time, kind, message, contact_name FROM logs
- # in text: JID or You or nickname (if it's gc_msg), time, message
- time_, kind, message, nickname = row
- if kind in (KindConstant.SINGLE_MSG_RECV,
- KindConstant.CHAT_MSG_RECV):
- who = self._get_jid_from_jid_id(jid_id)
- elif kind in (KindConstant.SINGLE_MSG_SENT,
- KindConstant.CHAT_MSG_SENT):
- who = _('You')
- elif kind == KindConstant.GC_MSG:
- who = nickname
- else: # status or gc_status. do not save
- #print kind
- continue
-
- try:
- time_ = time.strftime('%c', time.localtime(float(time_)))
- except ValueError:
- pass
-
- file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {
- 'who': who, 'time': time_, 'message': message})
-
- def _delete_jid_logs(self, liststore, list_of_paths):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- def on_ok(liststore, list_of_paths):
- # delete all rows from db that match jid_id
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(Gtk.TreeRowReference.new(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- jid_id = liststore[path][1]
- del liststore[path] # remove from UI
- # remove from db
- self.cur.execute('''
- DELETE FROM logs
- WHERE jid_id = ?
- ''', (jid_id,))
-
- # now delete "jid, jid_id" row from jids table
- self.cur.execute('''
- DELETE FROM jids
- WHERE jid_id = ?
- ''', (jid_id,))
-
- self.con.commit()
-
- self.AT_LEAST_ONE_DELETION_DONE = True
-
- if paths_len == 1:
- jid_id = '<i>%s</i>' % liststore[list_of_paths[0]][0]
- pri_text = _('Do you wish to delete all correspondence with %(jid)s?') \
- % {'jid': jid_id}
- else:
- pri_text = _(
- 'Do you wish to delete all correspondence with the selected contacts?')
- dialog = dialogs.ConfirmationDialog('',
- _('This can not be undone.'), on_response_ok=(on_ok,
- liststore, list_of_paths))
- dialog.set_title(_('Deletion Confirmation'))
- dialog.set_markup(pri_text)
- ok_button = dialog.get_children()[0].get_children()[1].get_children()[0]
- ok_button.grab_focus()
- dialog.set_transient_for(self.window)
-
- def _delete_logs(self, liststore, list_of_paths):
- paths_len = len(list_of_paths)
- if paths_len == 0: # nothing is selected
- return
-
- def on_ok(liststore, list_of_paths):
- # delete rows from db that match log_line_id
- list_of_rowrefs = []
- for path in list_of_paths: # make them treerowrefs (it's needed)
- list_of_rowrefs.append(Gtk.TreeRowReference.new(liststore, path))
-
- for rowref in list_of_rowrefs:
- path = rowref.get_path()
- if path is None:
- continue
- log_line_id = liststore[path][0]
- del liststore[path] # remove from UI
- # remove from db
- self.cur.execute('''
- DELETE FROM logs
- WHERE log_line_id = ?
- ''', (log_line_id,))
-
- self.con.commit()
-
- self.AT_LEAST_ONE_DELETION_DONE = True
-
- pri_text = i18n.ngettext(
- 'Do you really want to delete the selected message?',
- 'Do you really want to delete the selected messages?', paths_len)
- dialog = dialogs.ConfirmationDialog(pri_text,
- _('This is an irreversible operation.'), on_response_ok=(on_ok,
- liststore, list_of_paths))
- dialog.set_title(_('Deletion Confirmation'))
- ok_button = dialog.get_children()[0].get_children()[1].get_children()[0]
- ok_button.grab_focus()
- dialog.set_transient_for(self.window)
-
- def on_search_db_button_clicked(self, widget):
- text = self.search_entry.get_text()
- if not text:
- return
-
- self.welcome_vbox.hide()
- self.logs_scrolledwindow.hide()
- self.search_results_scrolledwindow.show()
-
- self._fill_search_results_listview(text)
-
- def on_search_results_listview_row_activated(self, widget, path, column):
- # get log_line_id, jid_id from row we double clicked
- log_line_id = self.search_results_liststore[path][0]
- jid = self.search_results_liststore[path][1]
- # make it string as in gtk liststores I have them all as strings
- # as this is what db returns so I don't have to fight with types
- jid_id = self._get_jid_id(jid)
-
- iter_ = self.jids_liststore.get_iter_first()
- while iter_:
- # self.jids_liststore[iter_][1] holds jid_ids
- if self.jids_liststore[iter_][1] == jid_id:
- break
- iter_ = self.jids_liststore.iter_next(iter_)
-
- if iter_ is None:
- return
-
- path = self.jids_liststore.get_path(iter_)
- self.jids_listview.set_cursor(path)
-
- iter_ = self.logs_liststore.get_iter_first()
- while iter_:
- # self.logs_liststore[iter_][0] holds lon_line_ids
- if self.logs_liststore[iter_][0] == log_line_id:
- break
- iter_ = self.logs_liststore.iter_next(iter_)
-
- path = self.logs_liststore.get_path(iter_)
- self.logs_listview.scroll_to_cell(path)
-
-if __name__ == '__main__':
- signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
- HistoryManager()
- Gtk.main()
diff --git a/src/history_window.py b/src/history_window.py
deleted file mode 100644
index fb5190f65..000000000
--- a/src/history_window.py
+++ /dev/null
@@ -1,670 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/history_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GLib
-import time
-import calendar
-
-from enum import IntEnum, unique
-
-import gtkgui_helpers
-import conversation_textview
-import dialogs
-
-from common import gajim
-from common import helpers
-from common import exceptions
-
-from common.logger import ShowConstant, KindConstant
-
-@unique
-class InfoColumn(IntEnum):
- '''Completion dict'''
- JID = 0
- ACCOUNT = 1
- NAME = 2
- COMPLETION = 3
-
-@unique
-class Column(IntEnum):
- LOG_JID = 0
- CONTACT_NAME = 1
- UNIXTIME = 2
- MESSAGE = 3
- TIME = 4
- LOG_LINE_ID = 5
-
-class HistoryWindow:
- """
- Class for browsing logs of conversations with contacts
- """
-
- def __init__(self, jid = None, account = None):
- xml = gtkgui_helpers.get_gtk_builder('history_window.ui')
- self.window = xml.get_object('history_window')
- self.calendar = xml.get_object('calendar')
- scrolledwindow = xml.get_object('scrolledwindow')
- self.history_textview = conversation_textview.ConversationTextview(
- account, used_in_history_window = True)
- scrolledwindow.add(self.history_textview.tv)
- self.history_buffer = self.history_textview.tv.get_buffer()
- self.history_buffer.create_tag('highlight', background = 'yellow')
- self.history_buffer.create_tag('invisible', invisible=True)
- self.checkbutton = xml.get_object('log_history_checkbutton')
- self.checkbutton.connect('toggled',
- self.on_log_history_checkbutton_toggled)
- self.show_status_checkbutton = xml.get_object('show_status_checkbutton')
- self.search_entry = xml.get_object('search_entry')
- self.query_liststore = xml.get_object('query_liststore')
- self.jid_entry = xml.get_object('query_entry')
- self.jid_entry.connect('activate', self.on_jid_entry_activate)
- self.results_treeview = xml.get_object('results_treeview')
- self.results_window = xml.get_object('results_scrolledwindow')
- self.search_in_date = xml.get_object('search_in_date')
-
- # jid, contact_name, date, message, time, log_line_id
- model = Gtk.ListStore(str, str, str, str, str, int)
- self.results_treeview.set_model(model)
- col = Gtk.TreeViewColumn(_('Name'))
- self.results_treeview.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', Column.CONTACT_NAME)
- col.set_sort_column_id(Column.CONTACT_NAME) # user can click this header and sort
- col.set_resizable(True)
-
- col = Gtk.TreeViewColumn(_('Date'))
- self.results_treeview.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', Column.UNIXTIME)
- col.set_sort_column_id(Column.UNIXTIME) # user can click this header and sort
- col.set_resizable(True)
-
- col = Gtk.TreeViewColumn(_('Message'))
- self.results_treeview.append_column(col)
- renderer = Gtk.CellRendererText()
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', Column.MESSAGE)
- col.set_resizable(True)
-
- self.jid = None # The history we are currently viewing
- self.account = None
- self.completion_dict = {}
- self.accounts_seen_online = [] # Update dict when new accounts connect
- self.jids_to_search = []
-
- # This will load history too
- task = self._fill_completion_dict()
- GLib.idle_add(next, task)
-
- if jid:
- self.jid_entry.set_text(jid)
- else:
- self._load_history(None)
-
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('history_window_width'),
- gajim.config.get('history_window_height'))
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('history_window_x-position'),
- gajim.config.get('history_window_y-position'))
-
- xml.connect_signals(self)
- self.window.show_all()
-
- def _fill_completion_dict(self):
- """
- Fill completion_dict for key auto completion. Then load history for
- current jid (by calling another function)
-
- Key will be either jid or full_completion_name (contact name or long
- description like "pm-contact from groupchat....").
-
- {key : (jid, account, nick_name, full_completion_name}
- This is a generator and does pseudo-threading via idle_add().
- """
- liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry)
-
- # Add all jids in logs.db:
- db_jids = gajim.logger.get_jids_in_db()
- completion_dict = dict.fromkeys(db_jids)
-
- self.accounts_seen_online = list(gajim.contacts.get_accounts())
-
- # Enhance contacts of online accounts with contact. Needed for mapping below
- for account in self.accounts_seen_online:
- completion_dict.update(helpers.get_contact_dict_for_account(account))
-
- muc_active_img = gtkgui_helpers.load_icon('muc_active')
- contact_img = gajim.interface.jabber_state_images['16']['online']
- muc_active_pix = muc_active_img.get_pixbuf()
- contact_pix = contact_img.get_pixbuf()
-
- keys = list(completion_dict.keys())
- # Move the actual jid at first so we load history faster
- actual_jid = self.jid_entry.get_text()
- if actual_jid in keys:
- keys.remove(actual_jid)
- keys.insert(0, actual_jid)
- if '' in keys:
- keys.remove('')
- if None in keys:
- keys.remove(None)
- # Map jid to info tuple
- # Warning : This for is time critical with big DB
- for key in keys:
- completed = key
- completed2 = None
- contact = completion_dict[completed]
- if contact:
- info_name = contact.get_shown_name()
- info_completion = info_name
- info_jid = contact.jid
- else:
- # Corrensponding account is offline, we know nothing
- info_name = completed.split('@')[0]
- info_completion = completed
- info_jid = completed
-
- info_acc = self._get_account_for_jid(info_jid)
-
- if gajim.logger.jid_is_room_jid(completed) or\
- gajim.logger.jid_is_from_pm(completed):
- pix = muc_active_pix
- if gajim.logger.jid_is_from_pm(completed):
- # It's PM. Make it easier to find
- room, nick = gajim.get_room_and_nick_from_fjid(completed)
- info_completion = '%s from %s' % (nick, room)
- completed = info_completion
- info_completion2 = '%s/%s' % (room, nick)
- completed2 = info_completion2
- info_name = nick
- else:
- pix = contact_pix
-
- if len(completed) > 70:
- completed = completed[:70] + '[\u2026]'
- liststore.append((pix, completed))
- self.completion_dict[key] = (info_jid, info_acc, info_name,
- info_completion)
- self.completion_dict[completed] = (info_jid, info_acc,
- info_name, info_completion)
- if completed2:
- if len(completed2) > 70:
- completed2 = completed2[:70] + '[\u2026]'
- liststore.append((pix, completed2))
- self.completion_dict[completed2] = (info_jid, info_acc,
- info_name, info_completion2)
- if key == actual_jid:
- self._load_history(info_jid, info_acc)
- yield True
- keys.sort()
- yield False
-
- def _get_account_for_jid(self, jid):
- """
- Return the corresponding account of the jid. May be None if an account
- could not be found
- """
- accounts = gajim.contacts.get_accounts()
- account = None
- for acc in accounts:
- jid_list = gajim.contacts.get_jid_list(acc)
- gc_list = gajim.contacts.get_gc_list(acc)
- if jid in jid_list or jid in gc_list:
- account = acc
- break
- return account
-
- def on_history_window_destroy(self, widget):
- self.history_textview.del_handlers()
- del gajim.interface.instances['logs']
-
- def on_history_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.save_state()
- self.window.destroy()
-
- def on_close_button_clicked(self, widget):
- self.save_state()
- self.window.destroy()
-
- def on_jid_entry_activate(self, widget):
- jid = self.jid_entry.get_text()
- account = None # we don't know the account, could be any. Search for it!
- self._load_history(jid, account)
- self.results_window.set_property('visible', False)
-
- def on_jid_entry_focus(self, widget, event):
- widget.select_region(0, -1) # select text
-
- def _load_history(self, jid_or_name, account=None):
- """
- Load history for the given jid/name and show it
- """
- if jid_or_name and jid_or_name in self.completion_dict:
- # a full qualified jid or a contact name was entered
- info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name]
- self.jids_to_search = [info_jid]
- self.jid = info_jid
-
- if account:
- self.account = account
- else:
- self.account = info_account
- if self.account is None:
- # We don't know account. Probably a gc not opened or an
- # account not connected.
- # Disable possibility to say if we want to log or not
- self.checkbutton.set_sensitive(False)
- else:
- # Are log disabled for account ?
- if self.account in gajim.config.get_per('accounts', self.account,
- 'no_log_for').split(' '):
- self.checkbutton.set_active(False)
- self.checkbutton.set_sensitive(False)
- else:
- # Are log disabled for jid ?
- log = True
- if self.jid in gajim.config.get_per('accounts', self.account,
- 'no_log_for').split(' '):
- log = False
- self.checkbutton.set_active(log)
- self.checkbutton.set_sensitive(True)
-
- self.jids_to_search = [info_jid]
-
- # select logs for last date we have logs with contact
- self.calendar.set_sensitive(True)
- last_log = \
- gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
-
- date = time.localtime(last_log)
-
- y, m, d = date[0], date[1], date[2]
- gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
- self.calendar.select_month(gtk_month, y)
- self.calendar.select_day(d)
-
- self.search_entry.set_sensitive(True)
- self.search_entry.grab_focus()
-
- title = _('Conversation History with %s') % info_name
- self.window.set_title(title)
- self.jid_entry.set_text(info_completion)
-
- else: # neither a valid jid, nor an existing contact name was entered
- # we have got nothing to show or to search in
- self.jid = None
- self.account = None
-
- self.history_buffer.set_text('') # clear the buffer
- self.search_entry.set_sensitive(False)
-
- self.checkbutton.set_sensitive(False)
- self.calendar.set_sensitive(False)
- self.calendar.clear_marks()
-
- self.results_window.set_property('visible', False)
-
- title = _('Conversation History')
- self.window.set_title(title)
-
- def on_calendar_day_selected(self, widget):
- if not self.jid:
- return
- year, month, day = self.calendar.get_date() # integers
- month = gtkgui_helpers.make_gtk_month_python_month(month)
- self._load_conversation(year, month, day)
-
- def on_calendar_month_changed(self, widget):
- """
- Ask for days in this month, if they have logs it bolds them (marks them)
- """
- if not self.jid:
- return
- year, month, day = widget.get_date() # integers
- if year < 1900:
- widget.select_month(0, 1900)
- widget.select_day(1)
- return
-
- # in gtk January is 1, in python January is 0,
- # I want the second
- # first day of month is 1 not 0
- widget.clear_marks()
- month = gtkgui_helpers.make_gtk_month_python_month(month)
- days_in_this_month = calendar.monthrange(year, month)[1]
- try:
- log_days = gajim.logger.get_days_with_logs(self.jid, year, month,
- days_in_this_month, self.account)
- except exceptions.PysqliteOperationalError as e:
- dialogs.ErrorDialog(_('Disk Error'), str(e))
- return
- for day in log_days:
- widget.mark_day(day)
-
- def _get_string_show_from_constant_int(self, show):
- if show == ShowConstant.ONLINE:
- show = 'online'
- elif show == ShowConstant.CHAT:
- show = 'chat'
- elif show == ShowConstant.AWAY:
- show = 'away'
- elif show == ShowConstant.XA:
- show = 'xa'
- elif show == ShowConstant.DND:
- show = 'dnd'
- elif show == ShowConstant.OFFLINE:
- show = 'offline'
-
- return show
-
- def _load_conversation(self, year, month, day):
- """
- Load the conversation between `self.jid` and `self.account` held on the
- given date into the history textbuffer. Values for `month` and `day`
- are 1-based.
- """
- self.history_buffer.set_text('')
- self.last_time_printout = 0
- show_status = self.show_status_checkbutton.get_active()
-
- conversation = gajim.logger.get_conversation_for_date(
- self.jid, year, month, day, self.account)
- for message in conversation:
- if not show_status and message.kind in (KindConstant.GCSTATUS,
- KindConstant.STATUS):
- continue
- self._add_message(message)
-
- def _add_message(self, msg):
- if not msg.message and msg.kind not in (KindConstant.STATUS,
- KindConstant.GCSTATUS):
- return
-
- tim = msg.time
- kind = msg.kind
- show = msg.show
- message = msg.message
- subject = msg.subject
- log_line_id = msg.log_line_id
- contact_name = msg.contact_name
- additional_data = msg.additional_data
-
- buf = self.history_buffer
- end_iter = buf.get_end_iter()
-
- # Make the beginning of every message searchable by its log_line_id
- buf.create_mark(str(log_line_id), end_iter, left_gravity=True)
-
- if gajim.config.get('print_time') == 'always':
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- tim = time.strftime(timestamp_str, time.localtime(float(tim)))
- buf.insert(end_iter, tim)
- elif gajim.config.get('print_time') == 'sometimes':
- every_foo_seconds = 60 * gajim.config.get(
- 'print_ichat_every_foo_minutes')
- seconds_passed = tim - self.last_time_printout
- if seconds_passed > every_foo_seconds:
- self.last_time_printout = tim
- tim = time.strftime('%X ', time.localtime(float(tim)))
- buf.insert_with_tags_by_name(end_iter, tim + '\n',
- 'time_sometimes')
-
- tag_name = ''
- tag_msg = ''
-
- show = self._get_string_show_from_constant_int(show)
-
- if kind == KindConstant.GC_MSG:
- tag_name = 'incoming'
- elif kind in (KindConstant.SINGLE_MSG_RECV, KindConstant.CHAT_MSG_RECV):
- contact_name = self.completion_dict[self.jid][InfoColumn.NAME]
- tag_name = 'incoming'
- tag_msg = 'incomingtxt'
- elif kind in (KindConstant.SINGLE_MSG_SENT, KindConstant.CHAT_MSG_SENT):
- if self.account:
- contact_name = gajim.nicks[self.account]
- else:
- # we don't have roster, we don't know our own nick, use first
- # account one (urk!)
- account = list(gajim.contacts.get_accounts())[0]
- contact_name = gajim.nicks[account]
- tag_name = 'outgoing'
- tag_msg = 'outgoingtxt'
- elif kind == KindConstant.GCSTATUS:
- # message here (if not None) is status message
- if message:
- message = _('%(nick)s is now %(status)s: %(status_msg)s') %\
- {'nick': contact_name, 'status': helpers.get_uf_show(show),
- 'status_msg': message }
- else:
- message = _('%(nick)s is now %(status)s') % {'nick': contact_name,
- 'status': helpers.get_uf_show(show) }
- tag_msg = 'status'
- else: # 'status'
- # message here (if not None) is status message
- if show is None: # it means error
- if message:
- message = _('Error: %s') % message
- else:
- message = _('Error')
- elif message:
- message = _('Status is now: %(status)s: %(status_msg)s') % \
- {'status': helpers.get_uf_show(show), 'status_msg': message}
- else:
- message = _('Status is now: %(status)s') % { 'status':
- helpers.get_uf_show(show) }
- tag_msg = 'status'
-
- if message.startswith('/me ') or message.startswith('/me\n'):
- tag_msg = tag_name
- else:
- # do not do this if gcstats, avoid dupping contact_name
- # eg. nkour: nkour is now Offline
- if contact_name and kind != KindConstant.GCSTATUS:
- # add stuff before and after contact name
- before_str = gajim.config.get('before_nickname')
- before_str = helpers.from_one_line(before_str)
- after_str = gajim.config.get('after_nickname')
- after_str = helpers.from_one_line(after_str)
- format = before_str + contact_name + after_str + ' '
- if tag_name:
- buf.insert_with_tags_by_name(end_iter, format, tag_name)
- else:
- buf.insert(end_iter, format)
- if subject:
- message = _('Subject: %s\n') % subject + message
- xhtml = None
- if message.startswith('<body '):
- xhtml = message
-
- if tag_msg:
- self.history_textview.print_real_text(message, [tag_msg],
- name=contact_name, xhtml=xhtml, additional_data=additional_data)
- else:
- self.history_textview.print_real_text(message, name=contact_name,
- xhtml=xhtml, additional_data=additional_data)
- self.history_textview.print_real_text('\n', text_tags=['eol'])
-
- def on_search_entry_activate(self, widget):
- text = self.search_entry.get_text()
- model = self.results_treeview.get_model()
- model.clear()
- if text == '':
- self.results_window.set_property('visible', False)
- return
- else:
- self.results_window.set_property('visible', True)
-
- # perform search in preselected jids
- # jids are preselected with the query_entry
- for jid in self.jids_to_search:
- account = self.completion_dict[jid][InfoColumn.ACCOUNT]
- if account is None:
- # We do not know an account. This can only happen if the contact is offine,
- # or if we browse a groupchat history. The account is not needed, a dummy can
- # be set.
- # This may leed to wrong self nick in the displayed history (Uggh!)
- account = list(gajim.contacts.get_accounts())[0]
-
- year, month, day = False, False, False
- if self.search_in_date.get_active():
- year, month, day = self.calendar.get_date() # integers
- month = gtkgui_helpers.make_gtk_month_python_month(month)
-
- show_status = self.show_status_checkbutton.get_active()
- results = gajim.logger.search_log(jid, text, account, year, month, day)
- #FIXME:
- # add "subject: | message: " in message column if kind is single
- # also do we need show at all? (we do not search on subject)
- for row in results:
- if not show_status and row.kind in (KindConstant.GCSTATUS,
- KindConstant.STATUS):
- continue
-
- contact_name = row.contact_name
- if not contact_name:
- if row.kind == KindConstant.CHAT_MSG_SENT: # it's us! :)
- contact_name = gajim.nicks[account]
- else:
- contact_name = self.completion_dict[jid][InfoColumn.NAME]
-
- local_time = time.localtime(row.time)
- date = time.strftime('%Y-%m-%d', local_time)
-
- model.append((jid, contact_name, date, row.message,
- str(row.time), row.log_line_id))
-
- def on_results_treeview_row_activated(self, widget, path, column):
- """
- A row was double clicked, get date from row, and select it in calendar
- which results to showing conversation logs for that date
- """
- # get currently selected date
- cur_year, cur_month, cur_day = self.calendar.get_date()
- cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month)
- model = widget.get_model()
- # make it a tuple (Y, M, D, 0, 0, 0...)
- tim = time.strptime(model[path][Column.UNIXTIME], '%Y-%m-%d')
- year = tim[0]
- gtk_month = tim[1]
- month = gtkgui_helpers.make_python_month_gtk_month(gtk_month)
- day = tim[2]
-
- # switch to belonging logfile if necessary
- log_jid = model[path][Column.LOG_JID]
- if log_jid != self.jid:
- self._load_history(log_jid, None)
-
- # avoid reruning mark days algo if same month and year!
- if year != cur_year or gtk_month != cur_month:
- self.calendar.select_month(month, year)
-
- if year != cur_year or gtk_month != cur_month or day != cur_day:
- self.calendar.select_day(day)
-
- self._scroll_to_message_and_highlight(model[path][Column.LOG_LINE_ID])
-
- def _scroll_to_message_and_highlight(self, log_line_id):
- """
- Scroll to a message and highlight it
- """
-
- def iterator_has_mark(iterator, mark_name):
- for mark in iterator.get_marks():
- if mark.get_name() == mark_name:
- return True
- return False
-
- # Clear previous search result by removing the highlighting. The scroll
- # mark is automatically removed when the new one is set.
- start = self.history_buffer.get_start_iter()
- end = self.history_buffer.get_end_iter()
- self.history_buffer.remove_tag_by_name('highlight', start, end)
-
- log_line_id = str(log_line_id)
- line = start
- while not iterator_has_mark(line, log_line_id):
- if not line.forward_line():
- return
-
- match_start = line
- match_end = match_start.copy()
- match_end.forward_to_tag_toggle(self.history_buffer.eol_tag)
-
- self.history_buffer.apply_tag_by_name('highlight', match_start, match_end)
- mark = self.history_buffer.create_mark('match', match_start, True)
- GLib.idle_add(self.history_textview.tv.scroll_to_mark, mark, 0, True, 0.0, 0.5)
-
- def on_log_history_checkbutton_toggled(self, widget):
- # log conversation history?
- oldlog = True
- no_log_for = gajim.config.get_per('accounts', self.account,
- 'no_log_for').split()
- if self.jid in no_log_for:
- oldlog = False
- log = widget.get_active()
- if not log and not self.jid in no_log_for:
- no_log_for.append(self.jid)
- if log and self.jid in no_log_for:
- no_log_for.remove(self.jid)
- if oldlog != log:
- gajim.config.set_per('accounts', self.account, 'no_log_for',
- ' '.join(no_log_for))
-
- def on_show_status_checkbutton_toggled(self, widget):
- # reload logs
- self.on_calendar_day_selected(None)
-
- def open_history(self, jid, account):
- """
- Load chat history of the specified jid
- """
- self.jid_entry.set_text(jid)
- if account and account not in self.accounts_seen_online:
- # Update dict to not only show bare jid
- GLib.idle_add(next, self._fill_completion_dict())
- else:
- # Only in that case because it's called by self._fill_completion_dict()
- # otherwise
- self._load_history(jid, account)
- self.results_window.set_property('visible', False)
-
- def save_state(self):
- x, y = self.window.get_window().get_root_origin()
- width, height = self.window.get_size()
-
- gajim.config.set('history_window_x-position', x)
- gajim.config.set('history_window_y-position', y)
- gajim.config.set('history_window_width', width)
- gajim.config.set('history_window_height', height)
diff --git a/src/htmltextview.py b/src/htmltextview.py
deleted file mode 100644
index 1a8d9509e..000000000
--- a/src/htmltextview.py
+++ /dev/null
@@ -1,1285 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/htmltextview.py
-##
-## Copyright (C) 2005 Gustavo J. A. M. Carneiro
-## Copyright (C) 2006 Santiago Gala
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-"""
-A Gtk.TextView-based renderer for XHTML-IM, as described in:
- http://xmpp.org/extensions/xep-0071.html
-
-Starting with the version posted by Gustavo Carneiro,
-I (Santiago Gala) am trying to make it more compatible
-with the markup that docutils generate, and also more
-modular.
-"""
-
-from gi.repository import GObject
-from gi.repository import GLib
-from gi.repository import Pango
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-import xml.sax
-import xml.sax.handler
-import re
-from io import StringIO
-import urllib
-
-if __name__ == '__main__':
- from common import i18n
- import common.configpaths
- common.configpaths.gajimpaths.init(None)
-from common import gajim
-import gtkgui_helpers
-from gtkgui_helpers import get_icon_pixmap
-from common import helpers
-from common.exceptions import GajimGeneralException
-import dialogs
-
-import tooltips
-import logging
-log = logging.getLogger('gajim.htmlview')
-
-__all__ = ['HtmlTextView']
-
-whitespace_rx = re.compile('\\s+')
-allwhitespace_rx = re.compile('^\\s*$')
-
-# pixels = points * display_resolution
-display_resolution = 0.3514598*(Gdk.Screen.height() /
- float(Gdk.Screen.height_mm()))
-
-# embryo of CSS classes
-classes = {
- #'system-message':';display: none',
- 'problematic': ';color: red',
-}
-
-# styles for elements
-element_styles = {
- 'u' : ';text-decoration: underline',
- 'em' : ';font-style: oblique',
- 'cite' : '; background-color:rgb(170,190,250);'
- 'font-style: oblique',
- 'li' : '; margin-left: 1em; margin-right: 10%',
- 'strong' : ';font-weight: bold',
- 'pre' : '; background-color:rgb(190,190,190);'
- 'font-family: monospace; white-space: pre;'
- 'margin-left: 1em; margin-right: 10%',
- 'kbd' : ';background-color:rgb(210,210,210);'
- 'font-family: monospace',
- 'blockquote' : '; background-color:rgb(170,190,250);'
- 'margin-left: 2em; margin-right: 10%',
- 'dt' : ';font-weight: bold; font-style: oblique',
- 'dd' : ';margin-left: 2em; font-style: oblique'
-}
-# no difference for the moment
-element_styles['dfn'] = element_styles['em']
-element_styles['var'] = element_styles['em']
-# deprecated, legacy, presentational
-element_styles['tt'] = element_styles['kbd']
-element_styles['i'] = element_styles['em']
-element_styles['b'] = element_styles['strong']
-
-# ==========
-# XEP-0071
-# ==========
-#
-# This Integration Set includes a subset of the modules defined for
-# XHTML 1.0 but does not redefine any existing modules, nor
-# does it define any new modules. Specifically, it includes the
-# following modules only:
-#
-# - Structure
-# - Text
-#
-# * Block
-#
-# phrasal
-# addr, blockquote, pre
-# Struc
-# div,p
-# Heading
-# h1, h2, h3, h4, h5, h6
-#
-# * Inline
-#
-# phrasal
-# abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var
-# structural
-# br, span
-#
-# - Hypertext (a)
-# - List (ul, ol, dl)
-# - Image (img)
-# - Style Attribute
-#
-# Therefore XHTML-IM uses the following content models:
-#
-# Block.mix
-# Block-like elements, e.g., paragraphs
-# Flow.mix
-# Any block or inline elements
-# Inline.mix
-# Character-level elements
-# InlineNoAnchor.class
-# Anchor element
-# InlinePre.mix
-# Pre element
-#
-# XHTML-IM also uses the following Attribute Groups:
-#
-# Core.extra.attrib
-# TBD
-# I18n.extra.attrib
-# TBD
-# Common.extra
-# style
-#
-#
-# ...
-# block level:
-# Heading h
-# ( pres = h1 | h2 | h3 | h4 | h5 | h6 )
-# Block ( phrasal = address | blockquote | pre )
-# NOT ( presentational = hr )
-# ( structural = div | p )
-# other: section
-# Inline ( phrasal = abbr | acronym | cite | code | dfn | em |
-# kbd | q | samp | strong | var )
-# NOT ( presentational = b | big | i | small | sub | sup | tt )
-# ( structural = br | span )
-# Param/Legacy param, font, basefont, center, s, strike, u, dir, menu,
-# isindex
-
-BLOCK_HEAD = set(( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', ))
-BLOCK_PHRASAL = set(( 'address', 'blockquote', 'pre', ))
-BLOCK_PRES = set(( 'hr', )) #not in xhtml-im
-BLOCK_STRUCT = set(( 'div', 'p', ))
-BLOCK_HACKS = set(( 'table', 'tr' )) # at the very least, they will start line ;)
-BLOCK = BLOCK_HEAD.union(BLOCK_PHRASAL).union(BLOCK_STRUCT).union(BLOCK_PRES).union(BLOCK_HACKS)
-
-INLINE_PHRASAL = set('abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var'.split(', '))
-INLINE_PRES = set('b, i, u, tt'.split(', ')) #not in xhtml-im
-INLINE_STRUCT = set('br, span'.split(', '))
-INLINE = INLINE_PHRASAL.union(INLINE_PRES).union(INLINE_STRUCT)
-
-LIST_ELEMS = set( 'dl, ol, ul'.split(', '))
-
-for name in BLOCK_HEAD:
- num = eval(name[1])
- size = (num-1) // 2
- weigth = (num - 1) % 2
- element_styles[name] = '; font-size: %s; %s' % ( ('large', 'medium', 'small')[size],
- ('font-weight: bold', 'font-style: oblique')[weigth],)
-
-def _parse_css_color(color):
- if color.startswith('rgb(') and color.endswith(')'):
- r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
- return Gdk.Color(r, g, b)
- else:
- return Gdk.color_parse(color)
-
-def style_iter(style):
- return ([x.strip() for x in item.split(':', 1)] for item in style.split(';')\
- if len(item.strip()))
-
-
-class HtmlHandler(xml.sax.handler.ContentHandler):
- """
- A handler to display html to a gtk textview
-
- It keeps a stack of "style spans" (start/end element pairs) and a stack of
- list counters, for nested lists.
- """
- def __init__(self, textview, conv_textview, startiter):
- xml.sax.handler.ContentHandler.__init__(self)
- self.textbuf = textview.get_buffer()
- self.textview = textview
- self.iter = startiter
- self.conv_textview = conv_textview
- self.text = ''
- self.starting=True
- self.preserve = False
- self.styles = [] # a Gtk.TextTag or None, for each span level
- self.list_counters = [] # stack (top at head) of list
- # counters, or None for unordered list
-
- def _parse_style_color(self, tag, value):
- color = _parse_css_color(value)
- tag.set_property('foreground-gdk', color)
-
- def _parse_style_background_color(self, tag, value):
- color = _parse_css_color(value)
- tag.set_property('background-gdk', color)
- tag.set_property('paragraph-background-gdk', color)
-
-
- def _get_current_attributes(self):
- attrs = self.textview.get_default_attributes()
- self.iter.backward_char()
- attrs = (self.iter.get_attributes())[1]
- self.iter.forward_char()
- return attrs
-
- def __parse_length_frac_size_allocate(self, textview, allocation, frac,
- callback, args):
- callback(allocation.width*frac, *args)
-
- def _parse_length(self, value, font_relative, block_relative, minl, maxl,
- callback, *args):
- """
- Parse/calc length, converting to pixels, calls callback(length, *args)
- when the length is first computed or changes
- """
- if value.endswith('%'):
- val = float(value[:-1])
- if val > 0:
- sign = 1
- elif val < 0:
- sign = -1
- else:
- sign = 0
- # limits: 1% to 500%
- val = sign*max(1, min(abs(val), 500))
- frac = val/100
- if font_relative:
- attrs = self._get_current_attributes()
- if not attrs.font:
- font_size = self.get_font_size()
- callback(frac*display_resolution*font_size, *args)
- elif block_relative:
- # CSS says 'Percentage values: refer to width of the closest
- # block-level ancestor'
- # This is difficult/impossible to implement, so we use
- # textview width instead; a reasonable approximation..
- alloc = self.textview.get_allocation()
- self.__parse_length_frac_size_allocate(self.textview, alloc,
- frac, callback, args)
- self.textview.connect('size-allocate',
- self.__parse_length_frac_size_allocate,
- frac, callback, args)
- else:
- callback(frac, *args)
- return
-
- def get_val():
- val = float(value[:-2])
- if val > 0:
- sign = 1
- elif val < 0:
- sign = -1
- else:
- sign = 0
- # validate length
- return sign*max(minl, min(abs(val*display_resolution), maxl))
- if value.endswith('pt'): # points
- callback(get_val()*display_resolution, *args)
-
- elif value.endswith('em'): # ems, the width of the element's font
- attrs = self._get_current_attributes()
- if not attrs.font:
- font_size = self.get_font_size()
- callback(get_val()*display_resolution*font_size, *args)
-
- elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
- # FIXME: figure out how to calculate this correctly
- # for now 'em' size is used as approximation
-
- attrs = self._get_current_attributes()
- if not attrs.font:
- font_size = self.get_font_size()
- callback(get_val()*display_resolution*font_size, *args)
-
- elif value.endswith('px'): # pixels
- callback(get_val(), *args)
-
- else:
- try:
- # TODO: isn't "no units" interpreted as pixels?
- val = int(value)
- if val > 0:
- sign = 1
- elif val < 0:
- sign = -1
- else:
- sign = 0
- # validate length
- val = sign*max(minl, min(abs(val), maxl))
- callback(val, *args)
- except Exception:
- log.warning('Unable to parse length value "%s"' % value)
-
- def __parse_font_size_cb(length, tag):
- tag.set_property('size-points', length/display_resolution)
- __parse_font_size_cb = staticmethod(__parse_font_size_cb)
-
- def _parse_style_display(self, tag, value):
- if value == 'none':
- tag.set_property('invisible', 'true')
- # FIXME: display: block, inline
-
- def _parse_style_font_size(self, tag, value):
- try:
- # see http://developer.gnome.org/pango/stable/pango-Text-Attributes.html#PANGO-SCALE-XX-SMALL:CAPS
- # http://consciouslyusing.blogspot.ru/2012/01/heads-up-missing-pango-text-scale.html
- scale = {
- #'xx-small': Pango.SCALE_XX_SMALL,
- #'x-small': Pango.SCALE_X_SMALL,
- #'small': Pango.SCALE_SMALL,
- #'medium': Pango.SCALE_MEDIUM,
- #'large': Pango.SCALE_LARGE,
- #'x-large': Pango.SCALE_X_LARGE,
- #'xx-large': Pango.SCALE_XX_LARGE,
- 'xx-small': 0.5787037037037,
- 'x-small': 0.6444444444444,
- 'small': 0.8333333333333,
- 'medium': 1.0,
- 'large': 1.2,
- 'x-large': 1.4399999999999,
- 'xx-large': 1.728,
- } [value]
- except KeyError:
- pass
- else:
- attrs = self._get_current_attributes()
- if attrs.font_scale ==0:
- tag.set_property('scale', scale)
- return
- if value == 'smaller':
- tag.set_property('scale', 0.8333333333333)
- return
- if value == 'larger':
- tag.set_property('scale', 1.2)
- return
- # font relative (5 ~ 4pt, 110 ~ 72pt)
- self._parse_length(value, True, False, 5, 110,self.__parse_font_size_cb,
- tag)
-
- def _parse_style_font_style(self, tag, value):
- try:
- style = {
- 'normal': Pango.Style.NORMAL,
- 'italic': Pango.Style.ITALIC,
- 'oblique': Pango.Style.OBLIQUE,
- } [value]
- except KeyError:
- log.warning('unknown font-style %s' % value)
- else:
- tag.set_property('style', style)
-
- def __frac_length_tag_cb(self, length, tag, propname):
- styles = self._get_style_tags()
- if styles:
- length += styles[-1].get_property(propname)
- tag.set_property(propname, length)
- #__frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
-
- def _parse_style_margin_left(self, tag, value):
- # block relative
- self._parse_length(value, False, True, 1, 1000,
- self.__frac_length_tag_cb, tag, 'left-margin')
-
- def _parse_style_margin_right(self, tag, value):
- # block relative
- self._parse_length(value, False, True, 1, 1000,
- self.__frac_length_tag_cb, tag, 'right-margin')
-
- def _parse_style_font_weight(self, tag, value):
- # TODO: missing 'bolder' and 'lighter'
- try:
- weight = {
- '100': Pango.Weight.ULTRALIGHT,
- '200': Pango.Weight.ULTRALIGHT,
- '300': Pango.Weight.LIGHT,
- '400': Pango.Weight.NORMAL,
- '500': Pango.Weight.NORMAL,
- '600': Pango.Weight.BOLD,
- '700': Pango.Weight.BOLD,
- '800': Pango.Weight.ULTRABOLD,
- '900': Pango.Weight.HEAVY,
- 'normal': Pango.Weight.NORMAL,
- 'bold': Pango.Weight.BOLD,
- } [value]
- except KeyError:
- log.warning('unknown font-style %s' % value)
- else:
- tag.set_property('weight', weight)
-
- def _parse_style_font_family(self, tag, value):
- tag.set_property('family', value)
-
- def _parse_style_text_align(self, tag, value):
- try:
- align = {
- 'left': Gtk.Justification.LEFT,
- 'right': Gtk.Justification.RIGHT,
- 'center': Gtk.Justification.CENTER,
- 'justify': Gtk.Justification.FILL,
- } [value]
- except KeyError:
- log.warning('Invalid text-align:%s requested' % value)
- else:
- tag.set_property('justification', align)
-
- def _parse_style_text_decoration(self, tag, value):
- values = value.split(' ')
- if 'none' in values:
- tag.set_property('underline', Pango.Underline.NONE)
- tag.set_property('strikethrough', False)
- if 'underline' in values:
- tag.set_property('underline', Pango.Underline.SINGLE)
- else:
- tag.set_property('underline', Pango.Underline.NONE)
- if 'line-through' in values:
- tag.set_property('strikethrough', True)
- else:
- tag.set_property('strikethrough', False)
- if 'blink' in values:
- log.warning('text-decoration:blink not implemented')
- if 'overline' in values:
- log.warning('text-decoration:overline not implemented')
-
- def _parse_style_white_space(self, tag, value):
- if value == 'pre':
- tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
- elif value == 'normal':
- tag.set_property('wrap_mode', Gtk.WrapMode.WORD)
- elif value == 'nowrap':
- tag.set_property('wrap_mode', Gtk.WrapMode.NONE)
-
- def __length_tag_cb(self, value, tag, propname):
- try:
- tag.set_property(propname, value)
- except Exception:
- log.warning( "Error with prop: " + propname + " for tag: " + str(tag))
-
-
- def _parse_style_width(self, tag, value):
- if value == 'auto':
- return
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
- tag, "width")
- def _parse_style_height(self, tag, value):
- if value == 'auto':
- return
- self._parse_length(value, False, False, 1, 1000, self.__length_tag_cb,
- tag, "height")
-
-
- # build a dictionary mapping styles to methods, for greater speed
- __style_methods = dict()
- for style in ('background-color', 'color', 'font-family', 'font-size',
- 'font-style', 'font-weight', 'margin-left', 'margin-right',
- 'text-align', 'text-decoration', 'white-space', 'display',
- 'width', 'height' ):
- try:
- method = locals()['_parse_style_%s' % style.replace('-', '_')]
- except KeyError:
- log.warning('Style attribute "%s" not yet implemented' % style)
- else:
- __style_methods[style] = method
- del style
- # --
-
- def _get_style_tags(self):
- return [tag for tag in self.styles if tag is not None]
-
- def _create_url(self, href, title, type_, id_):
- '''Process a url tag.
- '''
- tag = self.textbuf.create_tag(id_)
- if href and href[0] != '#':
- tag.href = href
- tag.type_ = type_ # to be used by the URL handler
- tag.connect('event', self.textview.hyperlink_handler, 'url')
- tag.set_property('foreground', gajim.config.get('urlmsgcolor'))
- tag.set_property('underline', Pango.Underline.SINGLE)
- tag.is_anchor = True
- if title:
- tag.title = title
- return tag
-
- def _update_img(self, output, attrs, img_mark, tags):
- '''Callback function called after the function helpers.download_image.
- '''
- mem, alt = output
- self._process_img(attrs, (mem, alt, img_mark, tags))
-
- def _process_img(self, attrs, loaded=None):
- '''Process a img tag.
- '''
- mem = ''
- update = False
- pixbuf = None
- replace_mark = None
- replace_tags = None
-
- try:
- if attrs['src'].startswith('data:image/'):
- # The "data" URL scheme http://tools.ietf.org/html/rfc2397
- import base64
- img = attrs['src'].split(',')[1]
- mem = base64.standard_b64decode(urllib.parse.unquote(
- img).encode('utf-8'))
- elif loaded is not None:
- (mem, alt, replace_mark, replace_tags) = loaded
- update = True
- else:
- if self.conv_textview:
- img_mark = self.textbuf.create_mark(None, self.iter, True)
- gajim.thread_interface(helpers.download_image, [
- self.conv_textview.account, attrs], self._update_img,
- [attrs, img_mark, self._get_style_tags()])
- alt = attrs.get('alt', '')
- if alt:
- alt += '\n'
- alt += _('Loading')
- pixbuf = get_icon_pixmap('gtk-no')
- if mem:
- # Caveat: GdkPixbuf is known not to be safe to load
- # images from network... this program is now potentially
- # hackable ;)
- loader = GdkPixbuf.PixbufLoader()
- dims = [0, 0]
- def height_cb(length):
- dims[1] = length
- def width_cb(length):
- dims[0] = length
- # process width and height attributes
- w = attrs.get('width')
- h = attrs.get('height')
- # override with width and height styles
- for attr, val in style_iter(attrs.get('style', '')):
- if attr == 'width':
- w = val
- elif attr == 'height':
- h = val
- if w:
- self._parse_length(w, False, False, 1, 1000, width_cb)
- if h:
- self._parse_length(h, False, False, 1, 1000, height_cb)
- def set_size(pixbuf, w, h, dims):
- """
- FIXME: Floats should be relative to the whole textview, and
- resize with it. This needs new pifbufs for every resize,
- GdkPixbuf.Pixbuf.scale_simple or similar.
- """
- if isinstance(dims[0], float):
- dims[0] = int(dims[0]*w)
- elif not dims[0]:
- dims[0] = w
- if isinstance(dims[1], float):
- dims[1] = int(dims[1]*h)
- if not dims[1]:
- dims[1] = h
- loader.set_size(*dims)
- if w or h:
- loader.connect('size-prepared', set_size, dims)
- loader.write(mem)
- loader.close()
- pixbuf = loader.get_pixbuf()
- alt = attrs.get('alt', '')
- working_iter = self.iter
- if replace_mark is not None:
- working_iter = self.textbuf.get_iter_at_mark(replace_mark)
- next_iter = working_iter.copy()
- next_iter.forward_char()
- self.textbuf.delete(working_iter, next_iter)
- self.textbuf.delete_mark(replace_mark)
- if pixbuf is not None:
- if replace_mark:
- tags = replace_tags
- else:
- tags = self._get_style_tags()
- if tags:
- tmpmark = self.textbuf.create_mark(None, working_iter, True)
- self.textbuf.insert_pixbuf(working_iter, pixbuf)
- self.starting = False
- if tags:
- start = self.textbuf.get_iter_at_mark(tmpmark)
- for tag in tags:
- self.textbuf.apply_tag(tag, start, working_iter)
- self.textbuf.delete_mark(tmpmark)
- else:
- self._insert_text('[IMG: %s]' % alt, working_iter)
- except Exception as ex:
- log.error('Error loading image ' + str(ex))
- pixbuf = None
- alt = attrs.get('alt', 'Broken image')
- try:
- loader.close()
- except Exception:
- pass
- return pixbuf
-
- def _begin_span(self, style, tag=None, id_=None):
- if style is None:
- self.styles.append(tag)
- return None
- if tag is None:
- if id_:
- tag = self.textbuf.create_tag(id_)
- else:
- tag = self.textbuf.create_tag() # we create anonymous tag
- for attr, val in style_iter(style):
- attr = attr.lower()
- val = val
- try:
- method = self.__style_methods[attr]
- except KeyError:
- log.warning('Style attribute "%s" requested '
- 'but not yet implemented' % attr)
- else:
- method(self, tag, val)
- self.styles.append(tag)
-
- def _end_span(self):
- self.styles.pop()
-
- def _jump_line(self):
- self.textbuf.insert_with_tags_by_name(self.iter, '\n', 'eol')
- self.starting = True
-
- def _insert_text(self, text, working_iter=None):
- if working_iter == None:
- working_iter = self.iter
- if self.starting and text != '\n':
- self.starting = (text[-1] == '\n')
- tags = self._get_style_tags()
- if tags:
- self.textbuf.insert_with_tags(working_iter, text, *tags)
- else:
- self.textbuf.insert(working_iter, text)
-
- def _starts_line(self):
- return self.starting or self.iter.starts_line()
-
- def _flush_text(self):
- if not self.text: return
- text, self.text = self.text, ''
- if not self.preserve:
- text = text.replace('\n', ' ')
- self.handle_specials(whitespace_rx.sub(' ', text))
- else:
- self._insert_text(text.strip('\n'))
-
- def _anchor_event(self, tag, textview, event, iter_, href, type_):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- self.textview.emit('url-clicked', href, type_)
- return True
- return False
-
- def handle_specials(self, text):
- if self.conv_textview:
- self.iter = self.conv_textview.detect_and_print_special_text(text,
- self._get_style_tags(), iter_=self.iter)
- else:
- self._insert_text(text)
-
- def characters(self, content):
- if self.preserve:
- self.text += content
- return
- if allwhitespace_rx.match(content) is not None and self._starts_line():
- return
- self.text += content
- self.starting = False
-
-
- def startElement(self, name, attrs):
- self._flush_text()
- klass = [i for i in attrs.get('class', ' ').split(' ') if i]
- style = ''
- #Add styles defined for classes
- for k in klass:
- if k in classes:
- style += classes[k]
-
- tag = None
- #FIXME: if we want to use id, it needs to be unique across
- # the whole textview, so we need to add something like the
- # message-id to it.
- #id_ = attrs.get('id',None)
- id_ = None
- if name == 'a':
- #TODO: accesskey, charset, hreflang, rel, rev, tabindex, type
- href = attrs.get('href', None)
- if not href:
- href = attrs.get('HREF', None)
- # Gaim sends HREF instead of href
- title = attrs.get('title', attrs.get('rel', href))
- type_ = attrs.get('type', None)
- tag = self._create_url(href, title, type_, id_)
- elif name == 'blockquote':
- cite = attrs.get('cite', None)
- if cite:
- tag = self.textbuf.create_tag(id_)
- tag.title = attrs.get('title', None)
- tag.is_anchor = True
- elif name in LIST_ELEMS:
- style += ';margin-left: 2em'
- elif name == 'img':
- tag = self._process_img(attrs)
- if name in element_styles:
- style += element_styles[name]
- # so that explicit styles override implicit ones,
- # we add the attribute last
- style += ";"+attrs.get('style', '')
- if style == '':
- style = None
- self._begin_span(style, tag, id_)
-
- if name == 'br':
- pass # handled in endElement
- elif name == 'hr':
- pass # handled in endElement
- elif name in BLOCK:
- if not self._starts_line():
- self._jump_line()
- if name == 'pre':
- self.preserve = True
- elif name == 'span':
- pass
- elif name in ('dl', 'ul'):
- if not self._starts_line():
- self._jump_line()
- self.list_counters.append(None)
- elif name == 'ol':
- if not self._starts_line():
- self._jump_line()
- self.list_counters.append(0)
- elif name == 'li':
- if self.list_counters[-1] is None:
- li_head = chr(0x2022)
- else:
- self.list_counters[-1] += 1
- li_head = '%i.' % self.list_counters[-1]
- self.text = ' '*len(self.list_counters)*4 + li_head + ' '
- self._flush_text()
- self.starting = True
- elif name == 'dd':
- self._jump_line()
- elif name == 'dt':
- if not self.starting:
- self._jump_line()
- elif name in ('a', 'img', 'body', 'html'):
- pass
- elif name in INLINE:
- pass
- else:
- log.warning('Unhandled element "%s"' % name)
-
- def endElement(self, name):
- endPreserving = False
- newLine = False
- if name == 'br':
- newLine = True
- elif name == 'hr':
- #FIXME: plenty of unused attributes (width, height,...) :)
- self._jump_line()
- self._insert_text('\u2015'*40)
- self._jump_line()
- elif name in LIST_ELEMS:
- self.list_counters.pop()
- elif name == 'li':
- newLine = True
- elif name == 'img':
- pass
- elif name == 'body' or name == 'html':
- pass
- elif name == 'a':
- pass
- elif name in INLINE:
- pass
- elif name in ('dd', 'dt', ):
- pass
- elif name in BLOCK:
- if name == 'pre':
- endPreserving = True
- elif name in BLOCK_STRUCT:
- newLine = True
- else:
- log.warning("Unhandled element '%s'" % name)
- self._flush_text()
- if endPreserving:
- self.preserve = False
- if newLine:
- self._jump_line()
- self._end_span()
-
- def get_font_size(self):
- context = self.conv_textview.tv.get_style_context()
- font = context.get_font(Gtk.StateType.NORMAL)
- return font.get_size() / Pango.SCALE
-
-class HtmlTextView(Gtk.TextView):
-
- def __init__(self):
- GObject.GObject.__init__(self)
- self.set_wrap_mode(Gtk.WrapMode.CHAR)
- self.set_editable(False)
- self._changed_cursor = False
- self.set_has_tooltip(True)
- self.connect('realize', self.on_html_text_view_realized)
- self.connect('unrealize', self.on_html_text_view_unrealized)
- self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
- self.id_ = self.connect('button-release-event',
- self.on_left_mouse_button_release)
- self.get_buffer().eol_tag = self.get_buffer().create_tag('eol')
- self.config = gajim.config
- self.interface = gajim.interface
- # end big hack
-
- def connect_tooltip(self, func=None):
- self.connect('query-tooltip', func or self.__query_tooltip)
-
- def create_tags(self):
- buffer_ = self.get_buffer()
-
- self.tagURL = buffer_.create_tag('url')
- color = gajim.config.get('urlmsgcolor')
- self.tagURL.set_property('foreground', color)
- self.tagURL.set_property('underline', Pango.Underline.SINGLE)
- self.tagURL.connect('event', self.hyperlink_handler, 'url')
-
- self.tagMail = buffer_.create_tag('mail')
- self.tagMail.set_property('foreground', color)
- self.tagMail.set_property('underline', Pango.Underline.SINGLE)
- self.tagMail.connect('event', self.hyperlink_handler, 'mail')
-
- self.tagXMPP = buffer_.create_tag('xmpp')
- self.tagXMPP.set_property('foreground', color)
- self.tagXMPP.set_property('underline', Pango.Underline.SINGLE)
- self.tagXMPP.connect('event', self.hyperlink_handler, 'xmpp')
-
- self.tagSthAtSth = buffer_.create_tag('sth_at_sth')
- self.tagSthAtSth.set_property('foreground', color)
- self.tagSthAtSth.set_property('underline', Pango.Underline.SINGLE)
- self.tagSthAtSth.connect('event', self.hyperlink_handler, 'sth_at_sth')
-
- def __query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
- window = widget.get_window(Gtk.TextWindowType.TEXT)
- x_pos, y_pos = self.window_to_buffer_coords(
- Gtk.TextWindowType.TEXT, x_pos, y_pos)
- if Gtk.MINOR_VERSION > 18:
- iter_ = self.get_iter_at_position(x_pos, y_pos)[1]
- else:
- iter_ = self.get_iter_at_position(x_pos, y_pos)[0]
- for tag in iter_.get_tags():
- if getattr(tag, 'is_anchor', False):
- text = getattr(tag, 'title', False)
- if text:
- if len(text) > 50:
- text = text[:47] + '…'
- tooltip.set_text(text)
- if not self._changed_cursor:
- window.set_cursor(gtkgui_helpers.get_cursor('HAND2'))
- self._changed_cursor = True
- return True
- if self._changed_cursor:
- window.set_cursor(gtkgui_helpers.get_cursor('XTERM'))
- self._changed_cursor = False
- return False
-
- def on_open_link_activate(self, widget, kind, text):
- helpers.launch_browser_mailer(kind, text)
-
- def on_copy_link_activate(self, widget, text):
- clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
- clip.set_text(text, -1)
-
-# def on_start_chat_activate(self, widget, jid):
-# gajim.interface.new_chat_from_jid(self.account, jid)
-
- def on_join_group_chat_menuitem_activate(self, widget, room_jid):
- try:
- dialogs.JoinGroupchatWindow(room_jid=room_jid)
- except GajimGeneralException:
- pass
-
- def on_add_to_roster_activate(self, widget, jid):
- dialogs.AddNewContactWindow(self.account, jid)
-
- def make_link_menu(self, event, kind, text):
- from gtkgui_helpers import get_gtk_builder
- xml = get_gtk_builder('chat_context_menu.ui')
- menu = xml.get_object('chat_context_menu')
- childs = menu.get_children()
- if kind == 'url':
- childs[0].connect('activate', self.on_copy_link_activate, text)
- childs[1].connect('activate', self.on_open_link_activate, kind,
- text)
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[6].hide() # join group chat
- childs[7].hide() # add to roster
- else: # It's a mail or a JID
- # load muc icon
- join_group_chat_menuitem = xml.get_object('join_group_chat_menuitem')
-
- text = text.lower()
- if text.startswith('xmpp:'):
- text = text[5:]
- childs[2].connect('activate', self.on_copy_link_activate, text)
- childs[3].connect('activate', self.on_open_link_activate, kind,
- text)
-# childs[5].connect('activate', self.on_start_chat_activate, text)
- childs[6].connect('activate',
- self.on_join_group_chat_menuitem_activate, text)
-
-# if self.account and gajim.connections[self.account].\
-# roster_supported:
-# childs[7].connect('activate',
-# self.on_add_to_roster_activate, text)
-# childs[7].show() # show add to roster menuitem
-# else:
-# childs[7].hide() # hide add to roster menuitem
-
- if kind == 'xmpp':
- childs[0].connect('activate', self.on_copy_link_activate,
- 'xmpp:' + text)
- childs[2].hide() # copy mail address
- childs[3].hide() # open mail composer
- elif kind == 'mail':
- childs[6].hide() # join group chat
-
- if kind != 'xmpp':
- childs[0].hide() # copy link location
- childs[1].hide() # open link in browser
- childs[4].hide() # jid section separator
- childs[5].hide() # start chat
- childs[7].hide() # add to roster
-
- menu.popup(None, None, None, event.button, event.time)
-
- def hyperlink_handler(self, texttag, widget, event, iter_, kind):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- begin_iter = iter_.copy()
- # we get the begining of the tag
- while not begin_iter.begins_tag(texttag):
- begin_iter.backward_char()
- end_iter = iter_.copy()
- # we get the end of the tag
- while not end_iter.ends_tag(texttag):
- end_iter.forward_char()
-
- # Detect XHTML-IM link
- word = getattr(texttag, 'href', None)
- if word:
- if word.startswith('xmpp'):
- kind = 'xmpp'
- elif word.startswith('mailto:'):
- kind = 'mail'
- elif gajim.interface.sth_at_sth_dot_sth_re.match(word):
- # it's a JID or mail
- kind = 'sth_at_sth'
- else:
- word = self.textview.get_buffer().get_text(begin_iter,
- end_iter)
-
- if event.button == 3: # right click
- self.make_link_menu(event, kind, word)
- return True
- else:
- # we launch the correct application
- if kind == 'xmpp':
- word = word[5:]
- if '?' in word:
- (jid, action) = word.split('?')
- if action == 'join':
- self.on_join_group_chat_menuitem_activate(None, jid)
- else:
- self.on_start_chat_activate(None, jid)
- else:
- self.on_start_chat_activate(None, word)
- else:
- helpers.launch_browser_mailer(kind, word)
-
- def _hyperlink_handler(self, texttag, widget, event, iter_, kind):
- # self.hyperlink_handler can be overwritten, so call it when needed
- self.hyperlink_handler(texttag, widget, event, iter_, kind)
-
- def display_html(self, html, textview, conv_textview, iter_=None):
- buffer_ = self.get_buffer()
- if iter_:
- eob = iter_
- else:
- eob = buffer_.get_end_iter()
- ## this works too if libxml2 is not available
- # parser = xml.sax.make_parser(['drv_libxml2'])
- # parser.setFeature(xml.sax.handler.feature_validation, True)
- parser = xml.sax.make_parser()
- parser.setContentHandler(HtmlHandler(textview, conv_textview, eob))
- parser.parse(StringIO(html))
-
- # too much space after :)
- #if not eob.starts_line():
- # buffer_.insert(eob, '\n')
-
- def on_html_text_view_copy_clipboard(self, unused_data):
- clipboard = self.get_clipboard(Gdk.SELECTION_CLIPBOARD)
- selected = self.get_selected_text()
- clipboard.set_text(selected, -1)
- self.emit_stop_by_name('copy-clipboard')
-
- def on_html_text_view_realized(self, unused_data):
- self.get_buffer().remove_selection_clipboard(self.get_clipboard(
- Gdk.SELECTION_PRIMARY))
-
- def on_html_text_view_unrealized(self, unused_data):
- self.get_buffer().add_selection_clipboard(self.get_clipboard(
- Gdk.SELECTION_PRIMARY))
-
- def on_left_mouse_button_release(self, widget, event):
- if event.button != 1:
- return
-
- bounds = self.get_buffer().get_selection_bounds()
- if bounds:
- # textview can be hidden while we add a new line in it.
- if self.has_screen():
- clipboard = self.get_clipboard(Gdk.SELECTION_PRIMARY)
- selected = self.get_selected_text()
- clipboard.set_text(selected, -1)
-
- def get_selected_text(self):
- bounds = self.get_buffer().get_selection_bounds()
- selection = ''
- if bounds:
- (search_iter, end) = bounds
-
- while (search_iter.compare(end)):
- character = search_iter.get_char()
- if character == '\ufffc':
- anchor = search_iter.get_child_anchor()
- if anchor:
- text = anchor.plaintext
- if text:
- selection+=text
- else:
- selection+=character
- else:
- selection+=character
- search_iter.forward_char()
- return selection
-
-change_cursor = None
-
-if __name__ == '__main__':
- from conversation_textview import ConversationTextview
- import gajim as gaj
-
- log = logging.getLogger()
- gaj.Interface()
-
- # create fake gajim.plugin_manager.gui_extension_point method for tests
- def gui_extension_point(*args):
- pass
- gajim.plugin_manager = gaj.Interface()
- gajim.plugin_manager.gui_extension_point = gui_extension_point
-
- htmlview = ConversationTextview(None)
-
- tooltip = tooltips.BaseTooltip()
-
- def on_textview_motion_notify_event(widget, event):
- """
- Change the cursor to a hand when we are over a mail or an url
- """
- global change_cursor
- w = htmlview.tv.get_window(Gtk.TextWindowType.TEXT)
- device = w.get_display().get_device_manager().get_client_pointer()
- pointer = w.get_device_position(device)
- x = pointer[1]
- y = pointer[2]
- tags = htmlview.tv.get_iter_at_location(x, y).get_tags()
- if change_cursor:
- w.set_cursor(gtkgui_helpers.get_cursor('XTERM'))
- change_cursor = None
- tag_table = htmlview.tv.get_buffer().get_tag_table()
- for tag in tags:
- try:
- if tag.is_anchor:
- w.set_cursor(gtkgui_helpers.get_cursor('HAND2'))
- change_cursor = tag
- elif tag == tag_table.lookup('focus-out-line'):
- over_line = True
- except Exception:
- pass
-
- #if line_tooltip.timeout != 0:
- # Check if we should hide the line tooltip
- # if not over_line:
- # line_tooltip.hide_tooltip()
- #if over_line and not line_tooltip.win:
- # line_tooltip.timeout = GLib.timeout_add(500,
- # show_line_tooltip)
- # htmlview.tv.get_window(Gtk.TextWindowType.TEXT).set_cursor(
- # gtkgui_helpers.get_cursor('LEFT_PTR'))
- # change_cursor = tag
-
- htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event)
-
- def handler(texttag, widget, event, iter_, kind):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- pass
-
- htmlview.tv.hyperlink_handler = htmlview.hyperlink_handler
-
- htmlview.print_real_text(None, xhtml='<div>'
- '<span style="color: red; text-decoration:underline">Hello</span><br/>\n'
- ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
- '<span style="font-size: 500%; font-family: serif">World</span>\n'
- '</div>\n')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p xmlns='http://www.w3.org/1999/xhtml'>a:b
- <a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google
- </a>
- </p><br/>
- </body>''')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p style='font-size:large'>
- <span style='font-style: italic'>O
- <span style='font-size:larger'>M</span>G</span>,
- I&apos;m <span style='color:green'>green</span>
- with <span style='font-weight: bold'>envy</span>!
- </p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- http://test.com/ testing links autolinkifying
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p>As Emerson said in his essay <span style='
- font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
- <p style='margin-left: 5px; margin-right: 2%'>
- &quot;A foolish consistency is the hobgoblin of little minds.&quot;
- </p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p style='text-align:center'>
- Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?
- </p>
- <p style='text-align:right'>
- <img src='http://www.xmpp.org/images/psa-license.jpg'
- alt='A License to Jabber' width='50%' height='50%'/>
- </p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <ul style='background-color:rgb(120,140,100)'>
- <li> One </li>
- <li> Two </li>
- <li> Three </li>
- </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n):
-def faciter(n,acc):
- if n==0: return acc
- return faciter(n-1, acc*n)
-if n&lt;0: raise ValueError('Must be non-negative')
-return faciter(n,1)</pre>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <ol style='background-color:rgb(120,140,100)'>
- <li> One </li>
- <li> Two is nested: <ul style='background-color:rgb(200,200,100)'>
- <li> One </li>
- <li style='font-size:50%'> Two </li>
- <li style='font-size:200%'> Three </li>
- <li style='font-size:9999pt'> Four </li>
- </ul></li>
- <li> Three </li></ol>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <p>
- <strong>
- <a href='xmpp:example@example.org'>xmpp link</a>
- </strong>: </p>
- <div xmlns='http://www.w3.org/1999/xhtml'>
- <cite style='margin: 7px;' title='xmpp:examples@example.org'>
- <p>
- <strong>examples@example.org wrote:</strong>
- </p>
- <p>this cite - bla bla bla, smile- :-) …</p>
- </cite>
- <div>
- <p>some text</p>
- </div>
- </div>
- <p/>
- <p>#232/1</p>
- </body>
- ''')
- htmlview.print_real_text(None, xhtml='<hr />')
- htmlview.print_real_text(None, xhtml='''
- <body xmlns='http://www.w3.org/1999/xhtml'>
- <br/>
-<img src='data:image/png;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw\
-AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz\
-ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp\
-a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl\
-ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis\
-F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH\
-hhx4dbgYKAAA7' alt='Larry'/>
- </body>
- ''')
- htmlview.tv.show()
- sw = Gtk.ScrolledWindow()
- sw.set_property('hscrollbar-policy', Gtk.PolicyType.AUTOMATIC)
- sw.set_property('vscrollbar-policy', Gtk.PolicyType.AUTOMATIC)
- sw.set_property('border-width', 0)
- sw.add(htmlview.tv)
- sw.show()
- frame = Gtk.Frame()
- frame.set_shadow_type(Gtk.ShadowType.IN)
- frame.show()
- frame.add(sw)
- w = Gtk.Window()
- w.add(frame)
- w.set_default_size(400, 300)
- w.show_all()
- w.connect('destroy', lambda w: Gtk.main_quit())
- Gtk.main()
diff --git a/src/ipython_view.py b/src/ipython_view.py
deleted file mode 100644
index 6d9902ff1..000000000
--- a/src/ipython_view.py
+++ /dev/null
@@ -1,629 +0,0 @@
-#!/usr/bin/python
-# -*- coding:utf-8 -*-
-## src/ipython_view.py
-##
-## Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-## Copyright (c) 2007, IBM Corporation
-## All rights reserved.
-
-## Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
-## * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-## * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-## * Neither the name of the IBM Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-
-## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""
-Provides IPython console widget
-
-@author: Eitan Isaacson
-@organization: IBM Corporation
-@copyright: Copyright (c) 2007 IBM Corporation
-@license: BSD
-
-All rights reserved. This program and the accompanying materials are made
-available under the terms of the BSD which accompanies this distribution, and
-is available at U{http://www.opensource.org/licenses/bsd-license.php}
-"""
-
-from functools import reduce
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GObject
-from gi.repository import GLib
-import re
-import sys
-import os
-from gi.repository import Pango
-from StringIO import StringIO
-
-try:
- import IPython
-except ImportError:
- IPython = None
-
-class IterableIPShell:
- """
- Create an IPython instance. Does not start a blocking event loop,
- instead allow single iterations. This allows embedding in GTK+
- without blockage
-
- @ivar IP: IPython instance.
- @type IP: IPython.iplib.InteractiveShell
- @ivar iter_more: Indicates if the line executed was a complete command,
- or we should wait for more.
- @type iter_more: integer
- @ivar history_level: The place in history where we currently are
- when pressing up/down.
- @type history_level: integer
- @ivar complete_sep: Seperation delimeters for completion function.
- @type complete_sep: _sre.SRE_Pattern
- """
- def __init__(self,argv=[],user_ns=None,user_global_ns=None, cin=None,
- cout=None,cerr=None, input_func=None):
- """
- @param argv: Command line options for IPython
- @type argv: list
- @param user_ns: User namespace.
- @type user_ns: dictionary
- @param user_global_ns: User global namespace.
- @type user_global_ns: dictionary.
- @param cin: Console standard input.
- @type cin: IO stream
- @param cout: Console standard output.
- @type cout: IO stream
- @param cerr: Console standard error.
- @type cerr: IO stream
- @param input_func: Replacement for builtin raw_input()
- @type input_func: function
- """
- io = IPython.utils.io
- if input_func:
- if IPython.version_info[0] >= 1:
- IPython.terminal.interactiveshell.raw_input_original = input_func
- else:
- IPython.frontend.terminal.interactiveshell.raw_input_original = input_func
- if cin:
- io.stdin = io.IOStream(cin)
- if cout:
- io.stdout = io.IOStream(cout)
- if cerr:
- io.stderr = io.IOStream(cerr)
-
- # This is to get rid of the blockage that accurs during
- # IPython.Shell.InteractiveShell.user_setup()
- io.raw_input = lambda x: None
-
- os.environ['TERM'] = 'dumb'
- excepthook = sys.excepthook
-
- from IPython.config.loader import Config
- cfg = Config()
- cfg.InteractiveShell.colors = "Linux"
-
- # InteractiveShell's __init__ overwrites io.stdout,io.stderr with
- # sys.stdout, sys.stderr, this makes sure they are right
- old_stdout, old_stderr = sys.stdout, sys.stderr
- sys.stdout, sys.stderr = io.stdout.stream, io.stderr.stream
-
- # InteractiveShell inherits from SingletonConfigurable so use instance()
- if IPython.version_info[0] >= 1:
- self.IP = IPython.terminal.embed.InteractiveShellEmbed.instance(config=cfg, user_ns=user_ns)
- else:
- self.IP = IPython.frontend.terminal.embed.InteractiveShellEmbed.instance(config=cfg, user_ns=user_ns)
-
- sys.stdout, sys.stderr = old_stdout, old_stderr
-
- self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
- header='IPython system call: ',
- local_ns=user_ns)
- #global_ns=user_global_ns)
- #verbose=self.IP.rc.system_verbose)
-
- self.IP.raw_input = input_func
- sys.excepthook = excepthook
- self.iter_more = 0
- self.history_level = 0
- self.complete_sep = re.compile('[\s\{\}\[\]\(\)]')
- self.updateNamespace({'exit':lambda:None})
- self.updateNamespace({'quit':lambda:None})
- self.IP.readline_startup_hook(self.IP.pre_readline)
- # Workaround for updating namespace with sys.modules
- #
- self.__update_namespace()
-
- def __update_namespace(self):
- '''
- Update self.IP namespace for autocompletion with sys.modules
- '''
- for k,v in sys.modules.items():
- if not '.' in k:
- self.IP.user_ns.update({k:v})
-
- def execute(self):
- """
- Execute the current line provided by the shell object
- """
- self.history_level = 0
- orig_stdout = sys.stdout
- sys.stdout = IPython.utils.io.stdout
-
- orig_stdin = sys.stdin
- sys.stdin = IPython.utils.io.stdin
- self.prompt = self.generatePrompt(self.iter_more)
-
- self.IP.hooks.pre_prompt_hook()
- if self.iter_more:
- try:
- self.prompt = self.generatePrompt(True)
- except:
- self.IP.showtraceback()
- if self.IP.autoindent:
- self.IP.rl_do_indent = True
-
- try:
- line = self.IP.raw_input(self.prompt)
- except KeyboardInterrupt:
- self.IP.write('\nKeyboardInterrupt\n')
- self.IP.input_splitter.reset()
- except:
- self.IP.showtraceback()
- else:
- self.IP.input_splitter.push(line)
- self.iter_more = self.IP.input_splitter.push_accepts_more()
- self.prompt = self.generatePrompt(self.iter_more)
- if (self.IP.SyntaxTB.last_syntax_error and
- self.IP.autoedit_syntax):
- self.IP.edit_syntax_error()
- if not self.iter_more:
- if IPython.version_info[0] >= 2:
- source_raw = self.IP.input_splitter.raw_reset()
- else:
- source_raw = self.IP.input_splitter.source_raw_reset()[1]
- self.IP.run_cell(source_raw, store_history=True)
- self.IP.rl_do_indent = False
- else:
- # TODO: Auto-indent
- #
- self.IP.rl_do_indent = True
-
- sys.stdout = orig_stdout
- sys.stdin = orig_stdin
- def generatePrompt(self, is_continuation):
- '''
- Generate prompt depending on is_continuation value
-
- @param is_continuation
- @type is_continuation: boolean
-
- @return: The prompt string representation
- @rtype: string
-
- '''
-
- # Backwards compatibility with ipyton-0.11
- #
- ver = IPython.__version__
- if '0.11' in ver:
- prompt = self.IP.hooks.generate_prompt(is_continuation)
- else:
- if is_continuation:
- prompt = self.IP.prompt_manager.render('in2')
- else:
- prompt = self.IP.prompt_manager.render('in')
-
- return prompt
-
- def historyBack(self):
- """
- Provide one history command back
-
- @return: The command string.
- @rtype: string
- """
- self.history_level -= 1
- return self._getHistory()
-
- def historyForward(self):
- """
- Provide one history command forward
-
- @return: The command string.
- @rtype: string
- """
- self.history_level += 1
- return self._getHistory()
-
- def _getHistory(self):
- """
- Get the command string of the current history level
-
- @return: Historic command string.
- @rtype: string
- """
- try:
- rv = self.IP.user_ns['In'][self.history_level].strip('\n')
- except IndexError:
- self.history_level = 0
- rv = ''
- return rv
-
- def updateNamespace(self, ns_dict):
- """
- Add the current dictionary to the shell namespace
-
- @param ns_dict: A dictionary of symbol-values.
- @type ns_dict: dictionary
- """
- self.IP.user_ns.update(ns_dict)
-
- def complete(self, line):
- """
- Returns an auto completed line and/or posibilities for completion
-
- @param line: Given line so far.
- @type line: string
-
- @return: Line completed as for as possible,
- and possible further completions.
- @rtype: tuple
- """
- split_line = self.complete_sep.split(line)
- if split_line[-1]:
- possibilities = self.IP.complete(split_line[-1])
- else:
- completed = line
- possibilities = ['',[]]
- if possibilities:
- def _commonPrefix(str1, str2):
- '''
- Reduction function. returns common prefix of two given strings.
-
- @param str1: First string.
- @type str1: string
- @param str2: Second string
- @type str2: string
-
- @return: Common prefix to both strings.
- @rtype: string
- '''
- for i in range(len(str1)):
- if not str2.startswith(str1[:i+1]):
- return str1[:i]
- return str1
-
- if possibilities[1]:
- common_prefix = reduce(_commonPrefix, possibilities[1]) or line[-1]
- completed = line[:-len(split_line[-1])]+common_prefix
- else:
- completed = line
- else:
- completed = line
- return completed, possibilities[1]
-
-
- def shell(self, cmd,verbose=0,debug=0,header=''):
- """
- Replacement method to allow shell commands without them blocking
-
- @param cmd: Shell command to execute.
- @type cmd: string
- @param verbose: Verbosity
- @type verbose: integer
- @param debug: Debug level
- @type debug: integer
- @param header: Header to be printed before output
- @type header: string
- """
- if verbose or debug: print(header+cmd)
- # flush stdout so we don't mangle python's buffering
- if not debug:
- input_, output = os.popen4(cmd)
- print(output.read())
- output.close()
- input_.close()
-
-class ConsoleView(Gtk.TextView):
- """
- Specialized text view for console-like workflow
-
- @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
- @type ANSI_COLORS: dictionary
-
- @ivar text_buffer: Widget's text buffer.
- @type text_buffer: Gtk.TextBuffer
- @ivar color_pat: Regex of terminal color pattern
- @type color_pat: _sre.SRE_Pattern
- @ivar mark: Scroll mark for automatic scrolling on input.
- @type mark: Gtk.TextMark
- @ivar line_start: Start of command line mark.
- @type line_start: Gtk.TextMark
- """
-
- ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
- '0;32': 'Green', '0;33': 'Brown',
- '0;34': 'Blue', '0;35': 'Purple',
- '0;36': 'Cyan', '0;37': 'LightGray',
- '1;30': 'DarkGray', '1;31': 'DarkRed',
- '1;32': 'SeaGreen', '1;33': 'Yellow',
- '1;34': 'LightBlue', '1;35': 'MediumPurple',
- '1;36': 'LightCyan', '1;37': 'White'}
-
- def __init__(self):
- """
- Initialize console view
- """
- GObject.GObject.__init__(self)
- self.override_font(Pango.FontDescription('Mono'))
- self.set_cursor_visible(True)
- self.text_buffer = self.get_buffer()
- self.mark = self.text_buffer.create_mark('scroll_mark',
- self.text_buffer.get_end_iter(),
- False)
- for code in self.ANSI_COLORS:
- self.text_buffer.create_tag(code,
- foreground=self.ANSI_COLORS[code],
- weight=700)
- self.text_buffer.create_tag('0')
- self.text_buffer.create_tag('notouch', editable=False)
- self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
- self.line_start = \
- self.text_buffer.create_mark('line_start',
- self.text_buffer.get_end_iter(), True)
- self.connect('key-press-event', self.onKeyPress)
-
- def write(self, text, editable=False):
- GLib.idle_add(self._write, text, editable)
-
- def _write(self, text, editable=False):
- """
- Write given text to buffer
-
- @param text: Text to append.
- @type text: string
- @param editable: If true, added text is editable.
- @type editable: boolean
- """
- segments = self.color_pat.split(text)
- segment = segments.pop(0)
- start_mark = self.text_buffer.create_mark(None,
- self.text_buffer.get_end_iter(),
- True)
- self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
-
- if segments:
- ansi_tags = self.color_pat.findall(text)
- for tag in ansi_tags:
- i = segments.index(tag)
- self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(),
- segments[i+1], str(tag))
- segments.pop(i)
- if not editable:
- self.text_buffer.apply_tag_by_name('notouch',
- self.text_buffer.get_iter_at_mark(start_mark),
- self.text_buffer.get_end_iter())
- self.text_buffer.delete_mark(start_mark)
- self.scroll_mark_onscreen(self.mark)
-
-
- def showPrompt(self, prompt):
- GLib.idle_add(self._showPrompt, prompt)
-
- def _showPrompt(self, prompt):
- """
- Print prompt at start of line
-
- @param prompt: Prompt to print.
- @type prompt: string
- """
- self._write(prompt)
- self.text_buffer.move_mark(self.line_start,
- self.text_buffer.get_end_iter())
-
- def changeLine(self, text):
- GLib.idle_add(self._changeLine, text)
-
- def _changeLine(self, text):
- """
- Replace currently entered command line with given text
-
- @param text: Text to use as replacement.
- @type text: string
- """
- iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
- iter_.forward_to_line_end()
- self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter_)
- self._write(text, True)
-
- def getCurrentLine(self):
- """
- Get text in current command line
-
- @return: Text of current command line.
- @rtype: string
- """
- rv = self.text_buffer.get_slice(
- self.text_buffer.get_iter_at_mark(self.line_start),
- self.text_buffer.get_end_iter(), False)
- return rv
-
- def showReturned(self, text):
- GLib.idle_add(self._showReturned, text)
-
- def _showReturned(self, text):
- """
- Show returned text from last command and print new prompt
-
- @param text: Text to show.
- @type text: string
- """
- iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
- iter_.forward_to_line_end()
- self.text_buffer.apply_tag_by_name(
- 'notouch',
- self.text_buffer.get_iter_at_mark(self.line_start),
- iter_)
- self._write('\n'+text)
- if text:
- self._write('\n')
- self._showPrompt(self.prompt)
- self.text_buffer.move_mark(self.line_start, self.text_buffer.get_end_iter())
- self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
-
- if self.IP.rl_do_indent:
- indentation = self.IP.input_splitter.indent_spaces * ' '
- self.text_buffer.insert_at_cursor(indentation)
-
- def onKeyPress(self, widget, event):
- """
- Key press callback used for correcting behavior for console-like
- interfaces. For example 'home' should go to prompt, not to begining of
- line
-
- @param widget: Widget that key press accored in.
- @type widget: Gtk.Widget
- @param event: Event object
- @type event: Gdk.Event
-
- @return: Return True if event should not trickle.
- @rtype: boolean
- """
- insert_mark = self.text_buffer.get_insert()
- insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
- selection_mark = self.text_buffer.get_selection_bound()
- selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
- start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
- if event.keyval == Gdk.KEY_Home:
- if event.get_state() == 0:
- self.text_buffer.place_cursor(start_iter)
- return True
- elif event.get_state() == Gdk.ModifierType.SHIFT_MASK:
- self.text_buffer.move_mark(insert_mark, start_iter)
- return True
- elif event.keyval == Gdk.KEY_Left:
- insert_iter.backward_cursor_position()
- if not insert_iter.editable(True):
- return True
- elif not event.string:
- pass
- elif start_iter.compare(insert_iter) <= 0 and \
- start_iter.compare(selection_iter) <= 0:
- pass
- elif start_iter.compare(insert_iter) > 0 and \
- start_iter.compare(selection_iter) > 0:
- self.text_buffer.place_cursor(start_iter)
- elif insert_iter.compare(selection_iter) < 0:
- self.text_buffer.move_mark(insert_mark, start_iter)
- elif insert_iter.compare(selection_iter) > 0:
- self.text_buffer.move_mark(selection_mark, start_iter)
-
- return self.onKeyPressExtend(event)
-
- def onKeyPressExtend(self, event):
- """
- For some reason we can't extend onKeyPress directly (bug #500900)
- """
- pass
-
-class IPythonView(ConsoleView, IterableIPShell):
- '''
- Sub-class of both modified IPython shell and L{ConsoleView} this makes
- a GTK+ IPython console.
- '''
- def __init__(self):
- """
- Initialize. Redirect I/O to console
- """
- ConsoleView.__init__(self)
- self.cout = StringIO()
- IterableIPShell.__init__(self, cout=self.cout, cerr=self.cout,
- input_func=self.raw_input)
-# self.connect('key_press_event', self.keyPress)
- self.interrupt = False
- self.execute()
- self.prompt = self.generatePrompt(False)
- self.cout.truncate(0)
- self.showPrompt(self.prompt)
-
- def raw_input(self, prompt=''):
- """
- Custom raw_input() replacement. Get's current line from console buffer
-
- @param prompt: Prompt to print. Here for compatability as replacement.
- @type prompt: string
-
- @return: The current command line text.
- @rtype: string
- """
- if self.interrupt:
- self.interrupt = False
- raise KeyboardInterrupt
- return self.getCurrentLine()
-
- def onKeyPressExtend(self, event):
- """
- Key press callback with plenty of shell goodness, like history,
- autocompletions, etc
-
- @param widget: Widget that key press occured in.
- @type widget: Gtk.Widget
- @param event: Event object.
- @type event: Gdk.Event
-
- @return: True if event should not trickle.
- @rtype: boolean
- """
- if event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == 99:
- self.interrupt = True
- self._processLine()
- return True
- elif event.keyval == Gdk.KEY_Return:
- self._processLine()
- return True
- elif event.keyval == Gdk.KEY_Up:
- self.changeLine(self.historyBack())
- return True
- elif event.keyval == Gdk.KEY_Down:
- self.changeLine(self.historyForward())
- return True
- elif event.keyval == Gdk.KEY_Tab:
- if not self.getCurrentLine().strip():
- return False
- completed, possibilities = self.complete(self.getCurrentLine())
- if len(possibilities) > 1:
- slice_ = self.getCurrentLine()
- self.write('\n')
- for symbol in possibilities:
- self.write(symbol+'\n')
- self.showPrompt(self.prompt)
- self.changeLine(completed or slice_)
- return True
-
- def _processLine(self):
- """
- Process current command line
- """
- self.history_pos = 0
- self.execute()
- rv = self.cout.getvalue()
- if rv: rv = rv.strip('\n')
- self.showReturned(rv)
- self.cout.truncate(0)
- self.cout.seek(0)
diff --git a/src/logind_listener.py b/src/logind_listener.py
deleted file mode 100644
index b447fdb1a..000000000
--- a/src/logind_listener.py
+++ /dev/null
@@ -1,114 +0,0 @@
-## src/logind_listener.py
-##
-## Copyright (C) 2014 Kamil Paral <kamil.paral AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-Watch for system suspend using systemd-logind.
-Documentation: http://www.freedesktop.org/wiki/Software/systemd/inhibit
-'''
-
-import os
-import logging
-
-from common import dbus_support
-from common import gajim
-
-log = logging.getLogger('gajim.logind_listener')
-supported = False
-fd = -1 # file descriptor of the inhibitor; negative number means we don't
- # hold any (yet)
-
-
-def on_suspend(active):
- '''Signal handler for suspend event'''
-
- global fd
-
- if not active:
- # we just resumed, we should take another inhibitor
- get_inhibitor()
- return
-
- # we're going for suspend, let's disconnect
- log.debug('System suspend detected, disconnecting from network…')
- for name, conn in gajim.connections.items():
- if gajim.account_is_connected(name):
- conn.old_show = gajim.SHOW_LIST[conn.connected]
- st = conn.status
- conn.change_status('offline', _('Machine going to sleep'))
- conn.status = st
- conn.time_to_reconnect = 5
-
- # close the file descriptor and let the computer suspend
- if fd >= 0:
- os.close(fd)
- fd = -1
- else:
- # something is wrong, the system is suspending but we don't have
- # a lock file
- log.warning("System suspend detected, but we don't seem to be holding "
- "a file descriptor for sleep inihibitor")
-
-def get_inhibitor():
- '''Ask for a suspend delay inhibitor'''
-
- from common.dbus_support import system_bus, dbus
- bus = system_bus.bus()
- global fd
-
- if fd >= 0:
- # someting is wrong, we haven't closed the previous file descriptor
- # and we ask for yet another one
- log.warning('We are about to ask for a sleep inhibitor, but we seem '
- 'to be holding one already')
-
- login_object = bus.get_object('org.freedesktop.login1',
- '/org/freedesktop/login1')
- login_manager = dbus.Interface(login_object,
- 'org.freedesktop.login1.Manager')
-
- ret = login_manager.Inhibit('sleep', 'Gajim', 'Disconnect from the network',
- 'delay')
- fd = ret.take()
-
-def set_listener():
- '''Set up a listener for suspend signals
-
- @return bool whether it succeeded
- '''
- from common.dbus_support import system_bus
- bus = system_bus.bus()
-
- if not 'org.freedesktop.login1' in bus.list_names():
- # logind is not present
- log.debug("logind is not on D-Bus, not activating logind listener")
- return False
-
- bus.add_signal_receiver(on_suspend, signal_name='PrepareForSleep',
- bus_name='org.freedesktop.login1',
- path='/org/freedesktop/login1',
- dbus_interface='org.freedesktop.login1.Manager')
- return True
-
-if dbus_support.supported:
- try:
- if set_listener():
- get_inhibitor()
- supported = True
- except Exception as ex:
- log.error("A problem occured while activating logind listener")
diff --git a/src/message_control.py b/src/message_control.py
deleted file mode 100644
index c07feb6b2..000000000
--- a/src/message_control.py
+++ /dev/null
@@ -1,257 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/message_control.py
-##
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-## Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-from common import ged
-from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
-
-# Derived types MUST register their type IDs here if custom behavor is required
-TYPE_CHAT = 'chat'
-TYPE_GC = 'gc'
-TYPE_PM = 'pm'
-
-####################
-
-class MessageControl(object):
- """
- An abstract base widget that can embed in the Gtk.Notebook of a
- MessageWindow
- """
-
- def __init__(self, type_id, parent_win, widget_name, contact, account,
- resource=None):
- # dict { cb id : widget}
- # keep all registered callbacks of widgets, created by self.xml
- self.handlers = {}
- self.type_id = type_id
- self.parent_win = parent_win
- self.widget_name = widget_name
- self.contact = contact
- self.account = account
- self.hide_chat_buttons = False
- self.resource = resource
-
- self.session = None
-
- gajim.last_message_time[self.account][self.get_full_jid()] = 0
-
- self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % widget_name)
- self.xml.connect_signals(self)
- self.widget = self.xml.get_object('%s_hbox' % widget_name)
-
- gajim.ged.register_event_handler('message-outgoing', ged.OUT_GUI1,
- self._nec_message_outgoing)
-
- def get_full_jid(self):
- fjid = self.contact.jid
- if self.resource:
- fjid += '/' + self.resource
- return fjid
-
- def set_control_active(self, state):
- """
- Called when the control becomes active (state is True) or inactive (state
- is False)
- """
- pass # Derived classes MUST implement this method
-
- def minimizable(self):
- """
- Called to check if control can be minimized
-
- Derived classes MAY implement this.
- """
- return False
-
- def safe_shutdown(self):
- """
- Called to check if control can be closed without loosing data.
- returns True if control can be closed safely else False
-
- Derived classes MAY implement this.
- """
- return True
-
- def allow_shutdown(self, method, on_response_yes, on_response_no,
- on_response_minimize):
- """
- Called to check is a control is allowed to shutdown.
- If a control is not in a suitable shutdown state this method
- should call on_response_no, else on_response_yes or
- on_response_minimize
-
- Derived classes MAY implement this.
- """
- on_response_yes(self)
-
- def shutdown(self):
- """
- Derived classes MUST implement this
- """
- gajim.ged.remove_event_handler('message-outgoing', ged.OUT_GUI1,
- self._nec_message_outgoing)
-
- def repaint_themed_widgets(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def update_ui(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def toggle_emoticons(self):
- """
- Derived classes MAY implement this
- """
- pass
-
- def update_font(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def update_tags(self):
- """
- Derived classes SHOULD implement this
- """
- pass
-
- def get_tab_label(self, chatstate):
- """
- Return a suitable tab label string. Returns a tuple such as: (label_str,
- color) either of which can be None if chatstate is given that means we
- have HE SENT US a chatstate and we want it displayed
-
- Derivded classes MUST implement this.
- """
- # Return a markup'd label and optional Gtk.Color in a tuple like:
- # return (label_str, None)
- pass
-
- def get_tab_image(self, count_unread=True):
- # Return a suitable tab image for display.
- # None clears any current label.
- return None
-
- def prepare_context_menu(self):
- """
- Derived classes SHOULD implement this
- """
- return None
-
- def chat_buttons_set_visible(self, state):
- """
- Derived classes MAY implement this
- """
- self.hide_chat_buttons = state
-
- def got_connected(self):
- pass
-
- def got_disconnected(self):
- pass
-
- def get_specific_unread(self):
- return len(gajim.events.get_events(self.account,
- self.contact.jid))
-
- def set_session(self, session):
- oldsession = None
- if hasattr(self, 'session'):
- oldsession = self.session
-
- if oldsession and session == oldsession:
- return
-
- self.session = session
-
- if session:
- session.control = self
-
- if session and oldsession:
- oldsession.control = None
-
- crypto_changed = bool(session and isinstance(session,
- EncryptedStanzaSession) and session.enable_encryption) != \
- bool(oldsession and isinstance(oldsession, EncryptedStanzaSession) \
- and oldsession.enable_encryption)
-
- archiving_changed = bool(session and isinstance(session,
- ArchivingStanzaSession) and session.archiving) != \
- bool(oldsession and isinstance(oldsession,
- ArchivingStanzaSession) and oldsession.archiving)
-
- if crypto_changed or archiving_changed:
- self.print_session_details(oldsession)
-
- def remove_session(self, session):
- if session != self.session:
- return
- self.session.control = None
- self.session = None
-
- def _nec_message_outgoing(self, obj):
- # Send the given message to the active tab.
- # Doesn't return None if error
- if obj.control != self:
- return
-
- obj.message = helpers.remove_invalid_xml_chars(obj.message)
- obj.original_message = obj.message
-
- conn = gajim.connections[self.account]
-
- if not self.session:
- if (not obj.resource and
- obj.jid != gajim.get_jid_from_account(self.account)):
- if self.resource:
- obj.resource = self.resource
- else:
- obj.resource = self.contact.resource
- sess = conn.find_controlless_session(obj.jid, resource=obj.resource)
-
- if self.resource:
- obj.jid += '/' + self.resource
-
- if not sess:
- if self.type_id == TYPE_PM:
- sess = conn.make_new_session(obj.jid, type_='pm')
- else:
- sess = conn.make_new_session(obj.jid)
-
- self.set_session(sess)
-
- obj.session = self.session
diff --git a/src/message_textview.py b/src/message_textview.py
deleted file mode 100644
index 83900df6e..000000000
--- a/src/message_textview.py
+++ /dev/null
@@ -1,310 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/message_textview.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2008-2009 Julien Pivotto <roidelapluie AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import gc
-
-from gi.repository import Gtk
-from gi.repository import GObject
-from gi.repository import GLib
-from gi.repository import Pango
-
-from common import gajim
-
-class MessageTextView(Gtk.TextView):
- """
- Class for the message textview (where user writes new messages) for
- chat/groupchat windows
- """
- UNDO_LIMIT = 20
-
- def __init__(self):
- GObject.GObject.__init__(self)
-
- # set properties
- self.set_border_width(1)
- self.set_accepts_tab(True)
- self.set_editable(True)
- self.set_cursor_visible(True)
- self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
- self.set_left_margin(2)
- self.set_right_margin(2)
- self.set_pixels_above_lines(2)
- self.set_pixels_below_lines(2)
-
- # set undo list
- self.undo_list = []
- # needed to know if we undid something
- self.undo_pressed = False
- self.lang = None # Lang used for spell checking
- _buffer = self.get_buffer()
- self.begin_tags = {}
- self.end_tags = {}
- self.color_tags = []
- self.fonts_tags = []
- self.other_tags = {}
- self.other_tags['bold'] = _buffer.create_tag('bold')
- self.other_tags['bold'].set_property('weight', Pango.Weight.BOLD)
- self.begin_tags['bold'] = '<strong>'
- self.end_tags['bold'] = '</strong>'
- self.other_tags['italic'] = _buffer.create_tag('italic')
- self.other_tags['italic'].set_property('style', Pango.Style.ITALIC)
- self.begin_tags['italic'] = '<em>'
- self.end_tags['italic'] = '</em>'
- self.other_tags['underline'] = _buffer.create_tag('underline')
- self.other_tags['underline'].set_property('underline', Pango.Underline.SINGLE)
- self.begin_tags['underline'] = '<span style="text-decoration: underline;">'
- self.end_tags['underline'] = '</span>'
- self.other_tags['strike'] = _buffer.create_tag('strike')
- self.other_tags['strike'].set_property('strikethrough', True)
- self.begin_tags['strike'] = '<span style="text-decoration: line-through;">'
- self.end_tags['strike'] = '</span>'
-
- def make_clickable_urls(self, text):
- _buffer = self.get_buffer()
-
- start = 0
- end = 0
- index = 0
-
- new_text = ''
- iterator = gajim.interface.link_pattern_re.finditer(text)
- for match in iterator:
- start, end = match.span()
- url = text[start:end]
- if start != 0:
- text_before_special_text = text[index:start]
- else:
- text_before_special_text = ''
- # we insert normal text
- new_text += text_before_special_text + \
- '<a href="'+ url +'">' + url + '</a>'
-
- index = end # update index
-
- if end < len(text):
- new_text += text[end:]
-
- return new_text # the position after *last* special text
-
- def get_active_tags(self):
- start, finish = self.get_active_iters()
- active_tags = []
- for tag in start.get_tags():
- active_tags.append(tag.get_property('name'))
- return active_tags
-
- def get_active_iters(self):
- _buffer = self.get_buffer()
- return_val = _buffer.get_selection_bounds()
- if return_val: # if sth was selected
- start, finish = return_val[0], return_val[1]
- else:
- start, finish = _buffer.get_bounds()
- return (start, finish)
-
- def set_tag(self, tag):
- _buffer = self.get_buffer()
- start, finish = self.get_active_iters()
- if start.has_tag(self.other_tags[tag]):
- _buffer.remove_tag_by_name(tag, start, finish)
- else:
- if tag == 'underline':
- _buffer.remove_tag_by_name('strike', start, finish)
- elif tag == 'strike':
- _buffer.remove_tag_by_name('underline', start, finish)
- _buffer.apply_tag_by_name(tag, start, finish)
-
- def clear_tags(self):
- _buffer = self.get_buffer()
- start, finish = self.get_active_iters()
- _buffer.remove_all_tags(start, finish)
-
- def color_set(self, widget, response):
- if response == -6 or response == -4:
- widget.destroy()
- return
-
- color = widget.get_property('rgba')
- widget.destroy()
- _buffer = self.get_buffer()
- # Create #aabbcc color string from rgba color
- color_string = '#%02X%02X%02X' % (round(color.red*255),
- round(color.green*255), round(color.blue*255))
-
- tag_name = 'color' + color_string
- if not tag_name in self.color_tags:
- tagColor = _buffer.create_tag(tag_name)
- tagColor.set_property('foreground', color_string)
- self.begin_tags[tag_name] = '<span style="color: %s;">' % color_string
- self.end_tags[tag_name] = '</span>'
- self.color_tags.append(tag_name)
-
- start, finish = self.get_active_iters()
-
- for tag in self.color_tags:
- _buffer.remove_tag_by_name(tag, start, finish)
-
- _buffer.apply_tag_by_name(tag_name, start, finish)
-
- def font_set(self, widget, response, start, finish):
- if response == -6 or response == -4:
- widget.destroy()
- return
-
- font = widget.get_font()
- font_desc = widget.get_font_desc()
- family = font_desc.get_family()
- size = font_desc.get_size()
- size = size / Pango.SCALE
- weight = font_desc.get_weight()
- style = font_desc.get_style()
-
- widget.destroy()
-
- _buffer = self.get_buffer()
-
- tag_name = 'font' + font
- if not tag_name in self.fonts_tags:
- tagFont = _buffer.create_tag(tag_name)
- tagFont.set_property('font', family + ' ' + str(size))
- self.begin_tags[tag_name] = \
- '<span style="font-family: ' + family + '; ' + \
- 'font-size: ' + str(size) + 'px">'
- self.end_tags[tag_name] = '</span>'
- self.fonts_tags.append(tag_name)
-
- for tag in self.fonts_tags:
- _buffer.remove_tag_by_name(tag, start, finish)
-
- _buffer.apply_tag_by_name(tag_name, start, finish)
-
- if weight == Pango.Weight.BOLD:
- _buffer.apply_tag_by_name('bold', start, finish)
- else:
- _buffer.remove_tag_by_name('bold', start, finish)
-
- if style == Pango.Style.ITALIC:
- _buffer.apply_tag_by_name('italic', start, finish)
- else:
- _buffer.remove_tag_by_name('italic', start, finish)
-
- def get_xhtml(self):
- _buffer = self.get_buffer()
- old = _buffer.get_start_iter()
- tags = {}
- tags['bold'] = False
- iter_ = _buffer.get_start_iter()
- old = _buffer.get_start_iter()
- text = ''
- modified = False
-
- def xhtml_special(text):
- text = text.replace('<', '&lt;')
- text = text.replace('>', '&gt;')
- text = text.replace('&', '&amp;')
- text = text.replace('\n', '<br />')
- return text
-
- for tag in iter_.get_toggled_tags(True):
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags:
- continue
- text += self.begin_tags[tag_name]
- modified = True
- while (iter_.forward_to_tag_toggle(None) and not iter_.is_end()):
- text += xhtml_special(_buffer.get_text(old, iter_, True))
- old.forward_to_tag_toggle(None)
- new_tags, old_tags, end_tags = [], [], []
- for tag in iter_.get_toggled_tags(True):
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags:
- continue
- new_tags.append(tag_name)
- modified = True
-
- for tag in iter_.get_tags():
- tag_name = tag.get_property('name')
- if tag_name not in self.begin_tags or tag_name not in self.end_tags:
- continue
- if tag_name not in new_tags:
- old_tags.append(tag_name)
-
- for tag in iter_.get_toggled_tags(False):
- tag_name = tag.get_property('name')
- if tag_name not in self.end_tags:
- continue
- end_tags.append(tag_name)
-
- for tag in old_tags:
- text += self.end_tags[tag]
- for tag in end_tags:
- text += self.end_tags[tag]
- for tag in new_tags:
- text += self.begin_tags[tag]
- for tag in old_tags:
- text += self.begin_tags[tag]
-
- text += xhtml_special(_buffer.get_text(old, _buffer.get_end_iter(), True))
- for tag in iter_.get_toggled_tags(False):
- tag_name = tag.get_property('name')
- if tag_name not in self.end_tags:
- continue
- text += self.end_tags[tag_name]
-
- if modified:
- return '<p>' + self.make_clickable_urls(text) + '</p>'
- else:
- return None
-
- def destroy(self):
- GLib.idle_add(gc.collect)
-
- def clear(self, widget = None):
- """
- Clear text in the textview
- """
- _buffer = self.get_buffer()
- start, end = _buffer.get_bounds()
- _buffer.delete(start, end)
-
- def save_undo(self, text):
- self.undo_list.append(text)
- if len(self.undo_list) > self.UNDO_LIMIT:
- del self.undo_list[0]
- self.undo_pressed = False
-
- def undo(self, widget=None):
- """
- Undo text in the textview
- """
- _buffer = self.get_buffer()
- if self.undo_list:
- _buffer.set_text(self.undo_list.pop())
- self.undo_pressed = True
-
- def get_sensitive(self):
- # get sensitive is not in GTK < 2.18
- try:
- return super(MessageTextView, self).get_sensitive()
- except AttributeError:
- return self.get_property('sensitive')
diff --git a/src/message_window.py b/src/message_window.py
deleted file mode 100644
index e8f4ee5bc..000000000
--- a/src/message_window.py
+++ /dev/null
@@ -1,1312 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/message_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2008 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Geobert Quach <geobert AT gmail.com>
-## Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GObject
-from gi.repository import GLib
-import time
-
-import common
-import gtkgui_helpers
-import message_control
-import dialogs
-from chat_control_base import ChatControlBase
-from chat_control import ChatControl
-
-from common import gajim
-from gtkgui_helpers import get_action
-
-####################
-
-class MessageWindow(object):
- """
- Class for windows which contain message like things; chats, groupchats, etc
- """
-
- # DND_TARGETS is the targets needed by drag_source_set and drag_dest_set
- DND_TARGETS = [('GAJIM_TAB', 0, 81)]
- hid = 0 # drag_data_received handler id
- (
- CLOSE_TAB_MIDDLE_CLICK,
- CLOSE_ESC,
- CLOSE_CLOSE_BUTTON,
- CLOSE_COMMAND,
- CLOSE_CTRL_KEY
- ) = range(5)
-
- def __init__(self, acct, type_, parent_window=None, parent_paned=None):
- # A dictionary of dictionaries
- # where _contacts[account][jid] == A MessageControl
- self._controls = {}
-
- # If None, the window is not tied to any specific account
- self.account = acct
- # If None, the window is not tied to any specific type
- self.type_ = type_
- # dict { handler id: widget}. Keeps callbacks, which
- # lead to cylcular references
- self.handlers = {}
- # Don't show warning dialogs when we want to delete the window
- self.dont_warn_on_delete = False
-
- self.widget_name = 'message_window'
- self.xml = gtkgui_helpers.get_gtk_builder('%s.ui' % self.widget_name)
- self.window = self.xml.get_object(self.widget_name)
- self.notebook = self.xml.get_object('notebook')
- self.parent_paned = None
-
- if parent_window:
- orig_window = self.window
- self.window = parent_window
- self.parent_paned = parent_paned
- old_parent = self.notebook.get_parent()
- old_parent.remove(self.notebook)
- if gajim.config.get('roster_on_the_right'):
- child1 = self.parent_paned.get_child1()
- self.parent_paned.remove(child1)
- self.parent_paned.add(self.notebook)
- self.parent_paned.pack1(self.notebook, resize=False,
- shrink=True)
- self.parent_paned.pack2(child1, resize=True, shrink=True)
- else:
- self.parent_paned.add(self.notebook)
- self.parent_paned.pack2(self.notebook, resize=True, shrink=True)
- self.window.lookup_action('show-roster').set_enabled(True)
- orig_window.destroy()
- del orig_window
-
- # NOTE: we use 'connect_after' here because in
- # MessageWindowMgr._new_window we register handler that saves window
- # state when closing it, and it should be called before
- # MessageWindow._on_window_delete, which manually destroys window
- # through win.destroy() - this means no additional handlers for
- # 'delete-event' are called.
- id_ = self.window.connect_after('delete-event', self._on_window_delete)
- self.handlers[id_] = self.window
- id_ = self.window.connect('destroy', self._on_window_destroy)
- self.handlers[id_] = self.window
- id_ = self.window.connect('focus-in-event', self._on_window_focus)
- self.handlers[id_] = self.window
-
- keys=['<Control>f', '<Control>g', '<Control>h', '<Control>i',
- '<Control>l', '<Control>L', '<Control><Shift>n', '<Control>u',
- '<Control>b', '<Control>F4',
- '<Control>w', '<Control>Page_Up', '<Control>Page_Down', '<Alt>Right',
- '<Alt>Left', '<Alt>d', '<Alt>c', '<Alt>m', '<Alt>t', 'Escape'] + \
- ['<Alt>'+str(i) for i in range(10)]
- accel_group = Gtk.AccelGroup()
- for key in keys:
- keyval, mod = Gtk.accelerator_parse(key)
- accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE,
- self.accel_group_func)
- self.window.add_accel_group(accel_group)
-
- # gtk+ doesn't make use of the motion notify on gtkwindow by default
- # so this line adds that
- self.window.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
-
- id_ = self.notebook.connect('switch-page',
- self._on_notebook_switch_page)
- self.handlers[id_] = self.notebook
- id_ = self.notebook.connect('key-press-event',
- self._on_notebook_key_press)
- self.handlers[id_] = self.notebook
-
- # Tab customizations
- pref_pos = gajim.config.get('tabs_position')
- if pref_pos == 'bottom':
- nb_pos = Gtk.PositionType.BOTTOM
- elif pref_pos == 'left':
- nb_pos = Gtk.PositionType.LEFT
- elif pref_pos == 'right':
- nb_pos = Gtk.PositionType.RIGHT
- else:
- nb_pos = Gtk.PositionType.TOP
- self.notebook.set_tab_pos(nb_pos)
- window_mode = gajim.interface.msg_win_mgr.mode
- if gajim.config.get('tabs_always_visible') or \
- window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- self.notebook.set_show_tabs(True)
- else:
- self.notebook.set_show_tabs(False)
- self.notebook.set_show_border(gajim.config.get('tabs_border'))
- self.show_icon()
-
- def change_account_name(self, old_name, new_name):
- if old_name in self._controls:
- self._controls[new_name] = self._controls[old_name]
- del self._controls[old_name]
-
- for ctrl in self.controls():
- if ctrl.account == old_name:
- ctrl.account = new_name
- if self.account == old_name:
- self.account = new_name
-
- def change_jid(self, account, old_jid, new_jid):
- """
- Called when the full jid of the control is changed
- """
- if account not in self._controls:
- return
- if old_jid not in self._controls[account]:
- return
- if old_jid == new_jid:
- return
- self._controls[account][new_jid] = self._controls[account][old_jid]
- del self._controls[account][old_jid]
-
- def get_num_controls(self):
- return sum(len(d) for d in self._controls.values())
-
- def resize(self, width, height):
- gtkgui_helpers.resize_window(self.window, width, height)
-
- def _on_window_focus(self, widget, event):
- # window received focus, so if we had urgency REMOVE IT
- # NOTE: we do not have to read the message (it maybe in a bg tab)
- # to remove urgency hint so this functions does that
- gtkgui_helpers.set_unset_urgency_hint(self.window, False)
-
- ctrl = self.get_active_control()
- if ctrl:
- ctrl.set_control_active(True)
- # Undo "unread" state display, etc.
- if ctrl.type_id == message_control.TYPE_GC:
- self.redraw_tab(ctrl, 'active')
- else:
- # NOTE: we do not send any chatstate to preserve
- # inactive, gone, etc.
- self.redraw_tab(ctrl)
-
- def _on_window_delete(self, win, event):
- if self.dont_warn_on_delete:
- # Destroy the window
- return False
-
- # Number of controls that will be closed and for which we'll loose data:
- # chat, pm, gc that won't go in roster
- number_of_closed_control = 0
- for ctrl in self.controls():
- if not ctrl.safe_shutdown():
- number_of_closed_control += 1
-
- if number_of_closed_control > 1:
- def on_yes1(checked):
- if checked:
- gajim.config.set('confirm_close_multiple_tabs', False)
- self.dont_warn_on_delete = True
- for ctrl in self.controls():
- if ctrl.minimizable():
- ctrl.minimize()
- win.destroy()
-
- if not gajim.config.get('confirm_close_multiple_tabs'):
- for ctrl in self.controls():
- if ctrl.minimizable():
- ctrl.minimize()
- # destroy window
- return False
- dialogs.YesNoDialog(
- _('You are going to close several tabs'),
- _('Do you really want to close them all?'),
- checktext=_('_Do not ask me again'), on_response_yes=on_yes1,
- transient_for=self.window)
- return True
-
- def on_yes(ctrl):
- if self.on_delete_ok == 1:
- self.dont_warn_on_delete = True
- win.destroy()
- self.on_delete_ok -= 1
-
- def on_no(ctrl):
- return
-
- def on_minimize(ctrl):
- ctrl.minimize()
- if self.on_delete_ok == 1:
- self.dont_warn_on_delete = True
- win.destroy()
- self.on_delete_ok -= 1
-
- # Make sure all controls are okay with being deleted
- self.on_delete_ok = self.get_nb_controls()
- for ctrl in self.controls():
- ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON, on_yes, on_no,
- on_minimize)
- return True # halt the delete for the moment
-
- def _on_window_destroy(self, win):
- for ctrl in self.controls():
- ctrl.shutdown()
- self._controls.clear()
- # Clean up handlers connected to the parent window, this is important since
- # self.window may be the RosterWindow
- for i in list(self.handlers.keys()):
- if self.handlers[i].handler_is_connected(i):
- self.handlers[i].disconnect(i)
- del self.handlers[i]
- del self.handlers
-
- def new_tab(self, control):
- fjid = control.get_full_jid()
-
- if control.account not in self._controls:
- self._controls[control.account] = {}
-
- self._controls[control.account][fjid] = control
-
- if self.get_num_controls() == 2:
- # is first conversation_textview scrolled down ?
- scrolled = False
- first_widget = self.notebook.get_nth_page(0)
- ctrl = self._widget_to_control(first_widget)
- conv_textview = ctrl.conv_textview
- if conv_textview.at_the_end():
- scrolled = True
- self.notebook.set_show_tabs(True)
- if scrolled:
- GLib.idle_add(conv_textview.scroll_to_end_iter)
-
- # Add notebook page and connect up to the tab's close button
- xml = gtkgui_helpers.get_gtk_builder('message_window.ui', 'chat_tab_ebox')
- tab_label_box = xml.get_object('chat_tab_ebox')
- widget = xml.get_object('tab_close_button')
- # this reduces the size of the button
-# style = Gtk.RcStyle()
-# style.xthickness = 0
-# style.ythickness = 0
-# widget.modify_style(style)
-
- id_ = widget.connect('clicked', self._on_close_button_clicked, control)
- control.handlers[id_] = widget
-
- id_ = tab_label_box.connect('button-press-event',
- self.on_tab_eventbox_button_press_event, control.widget)
- control.handlers[id_] = tab_label_box
- self.notebook.append_page(control.widget, tab_label_box)
-
- self.notebook.set_tab_reorderable(control.widget, True)
-
- self.redraw_tab(control)
- if self.parent_paned:
- self.notebook.show_all()
- else:
- self.window.show_all()
- # NOTE: we do not call set_control_active(True) since we don't know
- # whether the tab is the active one.
- self.show_title()
- if self.get_num_controls() == 1:
- GLib.timeout_add(500, control.msg_textview.grab_focus)
-
- def on_tab_eventbox_button_press_event(self, widget, event, child):
- if event.button == 3: # right click
- n = self.notebook.page_num(child)
- self.notebook.set_current_page(n)
- self.popup_menu(event)
- elif event.button == 2: # middle click
- ctrl = self._widget_to_control(child)
- self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK)
- else:
- ctrl = self._widget_to_control(child)
- GLib.idle_add(ctrl.msg_textview.grab_focus)
-
- def accel_group_func(self, accel_group, acceleratable, keyval, modifier):
- st = '1234567890' # alt+1 means the first tab (tab 0)
- control = self.get_active_control()
- if not control:
- # No more control in this window
- return
-
- # CTRL mask
- if modifier & Gdk.ModifierType.CONTROL_MASK:
- if keyval == Gdk.KEY_h: # CTRL + h
- if Gtk.Settings.get_default().get_property(
- 'gtk-key-theme-name') != 'Emacs':
- control._on_history_menuitem_activate()
- return True
- elif control.type_id == message_control.TYPE_CHAT and \
- keyval == Gdk.KEY_f: # CTRL + f
- # CTRL + f moves cursor one char forward when user uses Emacs
- # theme
- if not Gtk.Settings.get_default().get_property(
- 'gtk-key-theme-name') == 'Emacs':
- if gajim.interface.msg_win_mgr.mode == \
- gajim.interface.msg_win_mgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- gajim.interface.roster.tree.grab_focus()
- return False
- control._on_send_file_menuitem_activate(None)
- return True
- elif control.type_id == message_control.TYPE_CHAT and \
- keyval == Gdk.KEY_g: # CTRL + g
- control._on_convert_to_gc_menuitem_activate(None)
- return True
- elif control.type_id in (message_control.TYPE_CHAT,
- message_control.TYPE_PM) and keyval == Gdk.KEY_i: # CTRL + i
- control._on_contact_information_menuitem_activate(None)
- return True
- elif keyval == Gdk.KEY_l or keyval == Gdk.KEY_L: # CTRL + l|L
- control.conv_textview.clear()
- return True
- elif keyval == Gdk.KEY_u: # CTRL + u: emacs style clear line
- control.clear(control.msg_textview)
- return True
- elif control.type_id == message_control.TYPE_GC and \
- keyval == Gdk.KEY_b: # CTRL + b
- # CTRL + b moves cursor one char backward when user uses Emacs
- # theme
- if not Gtk.Settings.get_default().get_property(
- 'gtk-key-theme-name') == 'Emacs':
- control._on_bookmark_room_menuitem_activate(None)
- return True
- # Tab switch bindings
- elif keyval == Gdk.KEY_F4: # CTRL + F4
- self.remove_tab(control, self.CLOSE_CTRL_KEY)
- return True
- elif keyval == Gdk.KEY_w: # CTRL + w
- # CTRL + w removes latest word before sursor when User uses emacs
- # theme
- if not Gtk.Settings.get_default().get_property(
- 'gtk-key-theme-name') == 'Emacs':
- self.remove_tab(control, self.CLOSE_CTRL_KEY)
- return True
- elif keyval in (Gdk.KEY_Page_Up, Gdk.KEY_Page_Down):
- # CTRL + PageUp | PageDown
- # Create event and send it to notebook
- event = Gdk.Event.new(Gdk.EventType.KEY_PRESS)
- event.window = self.window.get_window()
- event.time = int(time.time())
- event.state = Gdk.ModifierType.CONTROL_MASK
- event.keyval = int(keyval)
- self.notebook.event(event)
- return True
-
- if modifier & Gdk.ModifierType.SHIFT_MASK:
- # CTRL + SHIFT
- if control.type_id == message_control.TYPE_GC and \
- keyval == Gdk.KEY_n: # CTRL + SHIFT + n
- control._on_change_nick_menuitem_activate(None)
- return True
- # MOD1 (ALT) mask
- elif modifier & Gdk.ModifierType.MOD1_MASK:
- # Tab switch bindings
- if keyval == Gdk.KEY_Right: # ALT + RIGHT
- new = self.notebook.get_current_page() + 1
- if new >= self.notebook.get_n_pages():
- new = 0
- self.notebook.set_current_page(new)
- return True
- elif keyval == Gdk.KEY_Left: # ALT + LEFT
- new = self.notebook.get_current_page() - 1
- if new < 0:
- new = self.notebook.get_n_pages() - 1
- self.notebook.set_current_page(new)
- return True
- elif chr(keyval) in st: # ALT + 1,2,3..
- self.notebook.set_current_page(st.index(chr(keyval)))
- return True
- elif keyval == Gdk.KEY_c: # ALT + C toggles chat buttons
- control.chat_buttons_set_visible(not control.hide_chat_buttons)
- return True
- elif keyval == Gdk.KEY_m: # ALT + M show emoticons menu
- control.show_emoticons_menu()
- return True
- elif keyval == Gdk.KEY_d: # ALT + D show actions menu
- if Gtk.Settings.get_default().get_property(
- 'gtk-key-theme-name') != 'Emacs':
- control.on_actions_button_clicked(control.actions_button)
- return True
- elif control.type_id == message_control.TYPE_GC and \
- keyval == Gdk.KEY_t: # ALT + t
- control._on_change_subject_menuitem_activate(None)
- return True
- # Close tab bindings
- elif keyval == Gdk.KEY_Escape and \
- gajim.config.get('escape_key_closes'): # Escape
- self.remove_tab(control, self.CLOSE_ESC)
- return True
-
- def _on_close_button_clicked(self, button, control):
- """
- When close button is pressed: close a tab
- """
- self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
-
- def show_icon(self):
- window_mode = gajim.interface.msg_win_mgr.mode
- icon = None
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_NEVER:
- ctrl = self.get_active_control()
- if not ctrl:
- return
- icon = ctrl.get_tab_image(count_unread=False)
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
- pass # keep default icon
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
- if self.type_ == 'gc':
- icon = gtkgui_helpers.load_icon('muc_active')
- else:
- # chat, pm
- icon = gtkgui_helpers.load_icon('online')
- if icon:
- if isinstance(icon, GdkPixbuf.Pixbuf):
- self.window.set_icon(icon)
- else:
- self.window.set_icon(icon.get_pixbuf())
-
- def show_title(self, urgent=True, control=None):
- """
- Redraw the window's title
- """
- if not control:
- control = self.get_active_control()
- if not control:
- # No more control in this window
- return
- unread = 0
- for ctrl in self.controls():
- if ctrl.type_id == message_control.TYPE_GC and not \
- gajim.config.get('notify_on_all_muc_messages') and not \
- ctrl.attention_flag:
- # count only pm messages
- unread += ctrl.get_nb_unread_pm()
- continue
- unread += ctrl.get_nb_unread()
-
- unread_str = ''
- if unread > 1:
- unread_str = '[' + str(unread) + '] '
- elif unread == 1:
- unread_str = '* '
- else:
- urgent = False
-
- if control.type_id == message_control.TYPE_GC:
- name = control.room_jid.split('@')[0]
- urgent = control.attention_flag or \
- gajim.config.get('notify_on_all_muc_messages')
- else:
- name = control.contact.get_shown_name()
- if control.resource:
- name += '/' + control.resource
-
- window_mode = gajim.interface.msg_win_mgr.mode
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERTYPE:
- # Show the plural form since number of tabs > 1
- if self.type_ == 'chat':
- label = _('?Noun:Chats')
- elif self.type_ == 'gc':
- label = _('Groupchats')
- else:
- label = _('Private Chats')
- elif window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- label = None
- elif self.get_num_controls() == 1:
- label = name
- else:
- label = _('Messages')
-
- title = 'Gajim'
- if label:
- title = '%s - %s' % (label, title)
-
- if window_mode == MessageWindowMgr.ONE_MSG_WINDOW_PERACCT:
- title = title + ": " + control.account
-
- self.window.set_title(unread_str + title)
-
- if urgent:
- gtkgui_helpers.set_unset_urgency_hint(self.window, unread)
- else:
- gtkgui_helpers.set_unset_urgency_hint(self.window, False)
-
- def set_active_tab(self, ctrl):
- ctrl_page = self.notebook.page_num(ctrl.widget)
- self.notebook.set_current_page(ctrl_page)
- self.window.present()
- GLib.idle_add(ctrl.msg_textview.grab_focus)
-
- def remove_tab(self, ctrl, method, reason = None, force = False):
- """
- Reason is only for gc (offline status message) if force is True, do not
- ask any confirmation
- """
- def close(ctrl):
- if reason is not None: # We are leaving gc with a status message
- ctrl.shutdown(reason)
- else: # We are leaving gc without status message or it's a chat
- ctrl.shutdown()
- # Update external state
- gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
- types = ['printed_msg', 'chat', 'gc_msg'])
-
- fjid = ctrl.get_full_jid()
- jid = gajim.get_jid_without_resource(fjid)
-
- fctrl = self.get_control(fjid, ctrl.account)
- bctrl = self.get_control(jid, ctrl.account)
- # keep last_message_time around unless this was our last control with
- # that jid
- if not fctrl and not bctrl and \
- fjid in gajim.last_message_time[ctrl.account]:
- del gajim.last_message_time[ctrl.account][fjid]
-
- self.notebook.remove_page(self.notebook.page_num(ctrl.widget))
-
- del self._controls[ctrl.account][fjid]
-
- if len(self._controls[ctrl.account]) == 0:
- del self._controls[ctrl.account]
-
- self.check_tabs()
- self.show_title()
-
- def on_yes(ctrl):
- close(ctrl)
-
- def on_no(ctrl):
- return
-
- def on_minimize(ctrl):
- if method != self.CLOSE_COMMAND:
- ctrl.minimize()
- self.check_tabs()
- return
- close(ctrl)
-
- # Shutdown the MessageControl
- if force:
- close(ctrl)
- else:
- ctrl.allow_shutdown(method, on_yes, on_no, on_minimize)
-
- def check_tabs(self):
- if self.parent_paned:
- # Do nothing in single window mode
- pass
- elif self.get_num_controls() == 0:
- # These are not called when the window is destroyed like this, fake it
- gajim.interface.msg_win_mgr._on_window_delete(self.window, None)
- gajim.interface.msg_win_mgr._on_window_destroy(self.window)
- # dnd clean up
- self.notebook.drag_dest_unset()
- if self.parent_paned:
- # Don't close parent window, just remove the child
- child = self.parent_paned.get_child2()
- self.parent_paned.remove(child)
- self.window.lookup_action('show-roster').set_enabled(False)
- else:
- self.window.destroy()
- return # don't show_title, we are dead
- elif self.get_num_controls() == 1: # we are going from two tabs to one
- window_mode = gajim.interface.msg_win_mgr.mode
- show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') or \
- window_mode == MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER
- self.notebook.set_show_tabs(show_tabs_if_one_tab)
-
- def redraw_tab(self, ctrl, chatstate=None):
- tab = self.notebook.get_tab_label(ctrl.widget)
- if not tab:
- return
- hbox = tab.get_children()[0]
- status_img = hbox.get_children()[0]
- nick_label = hbox.get_children()[1]
-
- # Optionally hide close button
- close_button = hbox.get_children()[2]
- if gajim.config.get('tabs_close_button'):
- close_button.show()
- else:
- close_button.hide()
-
- # Update nick
- nick_label.set_max_width_chars(10)
- if isinstance(ctrl, ChatControl):
- tab_label_str = ctrl.get_tab_label()
- # Set Label Color
- class_name = 'state_{}_color'.format(chatstate)
- gtkgui_helpers.add_css_class(nick_label, class_name)
- else:
- tab_label_str, color = ctrl.get_tab_label(chatstate)
- # Set Label Color
- if color == 'active':
- gtkgui_helpers.add_css_class(nick_label, None)
- elif color is not None:
- gtkgui_helpers.add_css_class(nick_label, color)
-
- nick_label.set_markup(tab_label_str)
-
- tab_img = ctrl.get_tab_image()
- if tab_img:
- if isinstance(tab_img, GdkPixbuf.Pixbuf):
- status_img.set_from_pixbuf(tab_img)
- elif tab_img.get_storage_type() == Gtk.ImageType.ANIMATION:
- status_img.set_from_animation(tab_img.get_animation())
- else:
- status_img.set_from_pixbuf(tab_img.get_pixbuf())
-
- self.show_icon()
-
- def repaint_themed_widgets(self):
- """
- Repaint controls in the window with theme color
- """
- # iterate through controls and repaint
- for ctrl in self.controls():
- ctrl.repaint_themed_widgets()
-
- def _widget_to_control(self, widget):
- for ctrl in self.controls():
- if ctrl.widget == widget:
- return ctrl
- return None
-
- def get_active_control(self):
- notebook = self.notebook
- active_widget = notebook.get_nth_page(notebook.get_current_page())
- return self._widget_to_control(active_widget)
-
- def get_active_contact(self):
- ctrl = self.get_active_control()
- if ctrl:
- return ctrl.contact
- return None
-
- def get_active_jid(self):
- contact = self.get_active_contact()
- if contact:
- return contact.jid
- return None
-
- def is_active(self):
- return self.window.is_active()
-
- def get_origin(self):
- return self.window.get_window().get_origin()
-
- def get_control(self, key, acct):
- """
- Return the MessageControl for jid or n, where n is a notebook page index.
- When key is an int index acct may be None
- """
-
- if isinstance(key, str):
- jid = key
- try:
- return self._controls[acct][jid]
- except Exception:
- return None
- else:
- page_num = key
- notebook = self.notebook
- if page_num is None:
- page_num = notebook.get_current_page()
- nth_child = notebook.get_nth_page(page_num)
- return self._widget_to_control(nth_child)
-
- def has_control(self, jid, acct):
- return (acct in self._controls and jid in self._controls[acct])
-
- def change_key(self, old_jid, new_jid, acct):
- """
- Change the JID key of a control
- """
- try:
- # Check if controls exists
- ctrl = self._controls[acct][old_jid]
- except KeyError:
- return
-
- if new_jid in self._controls[acct]:
- self.remove_tab(self._controls[acct][new_jid],
- self.CLOSE_CLOSE_BUTTON, force=True)
-
- self._controls[acct][new_jid] = ctrl
- del self._controls[acct][old_jid]
-
- if old_jid in gajim.last_message_time[acct]:
- gajim.last_message_time[acct][new_jid] = \
- gajim.last_message_time[acct][old_jid]
- del gajim.last_message_time[acct][old_jid]
-
- def controls(self):
- for jid_dict in list(self._controls.values()):
- for ctrl in list(jid_dict.values()):
- yield ctrl
-
- def get_nb_controls(self):
- return sum(len(jid_dict) for jid_dict in self._controls.values())
-
- def move_to_next_unread_tab(self, forward):
- ind = self.notebook.get_current_page()
- current = ind
- found = False
- first_composing_ind = -1 # id of first composing ctrl to switch to
- # if no others controls have awaiting events
- # loop until finding an unread tab or having done a complete cycle
- while True:
- if forward == True: # look for the first unread tab on the right
- ind = ind + 1
- if ind >= self.notebook.get_n_pages():
- ind = 0
- else: # look for the first unread tab on the right
- ind = ind - 1
- if ind < 0:
- ind = self.notebook.get_n_pages() - 1
- ctrl = self.get_control(ind, None)
- if ctrl.get_nb_unread() > 0:
- found = True
- break # found
- elif gajim.config.get('ctrl_tab_go_to_next_composing') :
- # Search for a composing contact
- contact = ctrl.contact
- if first_composing_ind == -1 and contact.chatstate == 'composing':
- # If no composing contact found yet, check if this one is composing
- first_composing_ind = ind
- if ind == current:
- break # a complete cycle without finding an unread tab
- if found:
- self.notebook.set_current_page(ind)
- elif first_composing_ind != -1:
- self.notebook.set_current_page(first_composing_ind)
- else: # not found and nobody composing
- if forward: # CTRL + TAB
- if current < (self.notebook.get_n_pages() - 1):
- self.notebook.next_page()
- else: # traverse for ever (eg. don't stop at last tab)
- self.notebook.set_current_page(0)
- else: # CTRL + SHIFT + TAB
- if current > 0:
- self.notebook.prev_page()
- else: # traverse for ever (eg. don't stop at first tab)
- self.notebook.set_current_page(
- self.notebook.get_n_pages() - 1)
-
- def popup_menu(self, event):
- menu = self.get_active_control().prepare_context_menu()
- # show the menu
- menu.attach_to_widget(gajim.interface.roster.window, None)
- menu.show_all()
- menu.popup(None, None, None, None, event.button, event.time)
-
- def _on_notebook_switch_page(self, notebook, page, page_num):
- old_no = notebook.get_current_page()
- if old_no >= 0:
- old_ctrl = self._widget_to_control(notebook.get_nth_page(old_no))
- old_ctrl.set_control_active(False)
-
- new_ctrl = self._widget_to_control(notebook.get_nth_page(page_num))
- new_ctrl.set_control_active(True)
- self.show_title(control = new_ctrl)
-
- control = self.get_active_control()
- if isinstance(control, ChatControlBase):
- control.msg_textview.grab_focus()
-
- def _on_notebook_key_press(self, widget, event):
- # when tab itself is selected,
- # make sure <- and -> are allowed for navigating between tabs
- if event.keyval in (Gdk.KEY_Left, Gdk.KEY_Right):
- return False
-
- control = self.get_active_control()
-
- if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
- # CTRL + SHIFT + TAB
- if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
- event.keyval == Gdk.KEY_ISO_Left_Tab:
- self.move_to_next_unread_tab(False)
- return True
- # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
- elif event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
- control.conv_textview.tv.event(event)
- return True
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
- if event.keyval == Gdk.KEY_Tab: # CTRL + TAB
- self.move_to_next_unread_tab(True)
- return True
- # Ctrl+PageUP / DOWN has to be handled by notebook
- elif event.keyval == Gdk.KEY_Page_Down:
- self.move_to_next_unread_tab(True)
- return True
- elif event.keyval == Gdk.KEY_Page_Up:
- self.move_to_next_unread_tab(False)
- return True
- if event.keyval in (Gdk.KEY_Shift_L, Gdk.KEY_Shift_R,
- Gdk.KEY_Control_L, Gdk.KEY_Control_R, Gdk.KEY_Caps_Lock,
- Gdk.KEY_Shift_Lock, Gdk.KEY_Meta_L, Gdk.KEY_Meta_R,
- Gdk.KEY_Alt_L, Gdk.KEY_Alt_R, Gdk.KEY_Super_L,
- Gdk.KEY_Super_R, Gdk.KEY_Hyper_L, Gdk.KEY_Hyper_R):
- return True
-
- if isinstance(control, ChatControlBase):
- # we forwarded it to message textview
- control.msg_textview.event(event)
- control.msg_textview.grab_focus()
-
- def get_tab_at_xy(self, x, y):
- """
- Return the tab under xy and if its nearer from left or right side of the
- tab
- """
- page_num = -1
- to_right = False
- horiz = self.notebook.get_tab_pos() == Gtk.PositionType.TOP or \
- self.notebook.get_tab_pos() == Gtk.PositionType.BOTTOM
- for i in range(self.notebook.get_n_pages()):
- page = self.notebook.get_nth_page(i)
- tab = self.notebook.get_tab_label(page)
- tab_alloc = tab.get_allocation()
- if horiz:
- if (x >= tab_alloc.x) and \
- (x <= (tab_alloc.x + tab_alloc.width)):
- page_num = i
- if x >= tab_alloc.x + (tab_alloc.width / 2.0):
- to_right = True
- break
- else:
- if (y >= tab_alloc.y) and \
- (y <= (tab_alloc.y + tab_alloc.height)):
- page_num = i
-
- if y > tab_alloc.y + (tab_alloc.height / 2.0):
- to_right = True
- break
- return (page_num, to_right)
-
- def find_page_num_according_to_tab_label(self, tab_label):
- """
- Find the page num of the tab label
- """
- page_num = -1
- for i in range(self.notebook.get_n_pages()):
- page = self.notebook.get_nth_page(i)
- tab = self.notebook.get_tab_label(page)
- if tab == tab_label:
- page_num = i
- break
- return page_num
-
-################################################################################
-class MessageWindowMgr(GObject.GObject):
- """
- A manager and factory for MessageWindow objects
- """
-
- __gsignals__ = {
- 'window-delete': (GObject.SignalFlags.RUN_LAST, None, (object,)),
- }
-
- # These constants map to common.config.opt_one_window_types indices
- (
- ONE_MSG_WINDOW_NEVER,
- ONE_MSG_WINDOW_ALWAYS,
- ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER,
- ONE_MSG_WINDOW_PERACCT,
- ONE_MSG_WINDOW_PERTYPE,
- ) = range(5)
- # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS mode
- MAIN_WIN = 'main'
- # A key constant for the main window in ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER mode
- ROSTER_MAIN_WIN = 'roster'
-
- def __init__(self, parent_window, parent_paned):
- """
- A dictionary of windows; the key depends on the config:
- ONE_MSG_WINDOW_NEVER: The key is the contact JID
- ONE_MSG_WINDOW_ALWAYS: The key is MessageWindowMgr.MAIN_WIN
- ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER: The key is MessageWindowMgr.MAIN_WIN
- ONE_MSG_WINDOW_PERACCT: The key is the account name
- ONE_MSG_WINDOW_PERTYPE: The key is a message type constant
- """
- GObject.GObject.__init__(self)
- self._windows = {}
-
- # Map the mode to a int constant for frequent compares
- mode = gajim.config.get('one_message_window')
- self.mode = common.config.opt_one_window_types.index(mode)
-
- self.parent_win = parent_window
- self.parent_paned = parent_paned
-
- def change_account_name(self, old_name, new_name):
- for win in self.windows():
- win.change_account_name(old_name, new_name)
-
- def _new_window(self, acct, type_):
- parent_win = None
- parent_paned = None
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- parent_win = self.parent_win
- parent_paned = self.parent_paned
- win = MessageWindow(acct, type_, parent_win, parent_paned)
- # we track the lifetime of this window
- win.window.connect('delete-event', self._on_window_delete)
- win.window.connect('destroy', self._on_window_destroy)
- return win
-
- def _gtk_win_to_msg_win(self, gtk_win):
- for w in self.windows():
- if w.window == gtk_win:
- return w
- return None
-
- def get_window(self, jid, acct):
- for win in self.windows():
- if win.has_control(jid, acct):
- return win
-
- return None
-
- def has_window(self, jid, acct):
- return self.get_window(jid, acct) is not None
-
- def one_window_opened(self, contact=None, acct=None, type_=None):
- try:
- return \
- self._windows[self._mode_to_key(contact, acct, type_)] is not None
- except KeyError:
- return False
-
- def _resize_window(self, win, acct, type_):
- """
- Resizes window according to config settings
- """
- hpaned = gajim.config.get('roster_hpaned_position')
- if self.mode in (self.ONE_MSG_WINDOW_ALWAYS,
- self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER):
- size = (gajim.config.get('msgwin-width'),
- gajim.config.get('msgwin-height'))
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- # Need to add the size of the now visible paned handle, otherwise
- # the saved width of the message window decreases by this amount
- handle_size = win.parent_paned.style_get_property('handle-size')
- size = (hpaned + size[0] + handle_size, size[1])
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
- gajim.config.get_per('accounts', acct, 'msgwin-height'))
- elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE):
- if type_ == message_control.TYPE_PM:
- type_ = message_control.TYPE_CHAT
- opt_width = type_ + '-msgwin-width'
- opt_height = type_ + '-msgwin-height'
- size = (gajim.config.get(opt_width), gajim.config.get(opt_height))
- else:
- return
- win.resize(size[0], size[1])
- if win.parent_paned:
- win.parent_paned.set_position(hpaned)
-
- def _position_window(self, win, acct, type_):
- """
- Moves window according to config settings
- """
- if (self.mode in [self.ONE_MSG_WINDOW_NEVER,
- self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER]):
- return
-
- if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- pos = (gajim.config.get('msgwin-x-position'),
- gajim.config.get('msgwin-y-position'))
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- pos = (gajim.config.get_per('accounts', acct, 'msgwin-x-position'),
- gajim.config.get_per('accounts', acct, 'msgwin-y-position'))
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- pos = (gajim.config.get(type_ + '-msgwin-x-position'),
- gajim.config.get(type_ + '-msgwin-y-position'))
- else:
- return
-
- gtkgui_helpers.move_window(win.window, pos[0], pos[1])
-
- def _mode_to_key(self, contact, acct, type_, resource = None):
- if self.mode == self.ONE_MSG_WINDOW_NEVER:
- key = acct + contact.jid
- if resource:
- key += '/' + resource
- return key
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- return self.MAIN_WIN
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- return self.ROSTER_MAIN_WIN
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- return acct
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- return type_
-
- def create_window(self, contact, acct, type_, resource = None):
- win_acct = None
- win_type = None
- win_role = None # X11 window role
-
- win_key = self._mode_to_key(contact, acct, type_, resource)
- if self.mode == self.ONE_MSG_WINDOW_PERACCT:
- win_acct = acct
- win_role = acct
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- win_type = type_
- win_role = type_
- elif self.mode == self.ONE_MSG_WINDOW_NEVER:
- win_type = type_
- win_role = contact.jid
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS:
- win_role = 'messages'
-
- win = None
- try:
- win = self._windows[win_key]
- except KeyError:
- win = self._new_window(win_acct, win_type)
-
- if win_role:
- win.window.set_role(win_role)
-
- # Position and size window based on saved state and window mode
- if not self.one_window_opened(contact, acct, type_):
- if gajim.config.get('msgwin-max-state'):
- win.window.maximize()
- else:
- self._resize_window(win, acct, type_)
- self._position_window(win, acct, type_)
-
- self._windows[win_key] = win
- return win
-
- def change_key(self, old_jid, new_jid, acct):
- win = self.get_window(old_jid, acct)
- if self.mode == self.ONE_MSG_WINDOW_NEVER:
- old_key = acct + old_jid
- if old_jid not in self._windows:
- return
- new_key = acct + new_jid
- self._windows[new_key] = self._windows[old_key]
- del self._windows[old_key]
- win.change_key(old_jid, new_jid, acct)
-
- def _on_window_delete(self, win, event):
- self.save_state(self._gtk_win_to_msg_win(win))
- gajim.interface.save_config()
- return False
-
- def _on_window_destroy(self, win):
- for k in self._windows.keys():
- if self._windows[k].window == win:
- self.emit('window-delete', self._windows[k])
- del self._windows[k]
- return
-
- def get_control(self, jid, acct):
- """
- Amongst all windows, return the MessageControl for jid
- """
- win = self.get_window(jid, acct)
- if win:
- return win.get_control(jid, acct)
- return None
-
- def search_control(self, jid, account, resource=None):
- """
- Search windows with this policy:
- 1. try to find already opened tab for resource
- 2. find the tab for this jid with ctrl.resource not set
- 3. there is none
- """
- fjid = jid
- if resource:
- fjid += '/' + resource
- ctrl = self.get_control(fjid, account)
- if ctrl:
- return ctrl
- win = self.get_window(jid, account)
- if win:
- ctrl = win.get_control(jid, account)
- if not ctrl.resource and ctrl.type_id != message_control.TYPE_GC:
- return ctrl
- return None
-
- def get_gc_control(self, jid, acct):
- """
- Same as get_control. Was briefly required, is not any more. May be useful
- some day in the future?
- """
- ctrl = self.get_control(jid, acct)
- if ctrl and ctrl.type_id == message_control.TYPE_GC:
- return ctrl
- return None
-
- def get_controls(self, type_=None, acct=None):
- ctrls = []
- for c in self.controls():
- if acct and c.account != acct:
- continue
- if not type_ or c.type_id == type_:
- ctrls.append(c)
- return ctrls
-
- def windows(self):
- for w in list(self._windows.values()):
- yield w
-
- def controls(self):
- for w in self._windows.values():
- for c in w.controls():
- yield c
-
- def shutdown(self, width_adjust=0):
- for w in self.windows():
- self.save_state(w, width_adjust)
- if not w.parent_paned:
- w.window.hide()
- w.window.destroy()
-
- gajim.interface.save_config()
-
- def save_state(self, msg_win, width_adjust=0):
- # Save window size and position
- max_win_key = 'msgwin-max-state'
- pos_x_key = 'msgwin-x-position'
- pos_y_key = 'msgwin-y-position'
- size_width_key = 'msgwin-width'
- size_height_key = 'msgwin-height'
-
- acct = None
- x, y = msg_win.window.get_position()
- width, height = msg_win.window.get_size()
-
- # If any of these values seem bogus don't update.
- if x < 0 or y < 0 or width < 0 or height < 0:
- return
-
- elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
- acct = msg_win.account
- elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
- type_ = msg_win.type_
- pos_x_key = type_ + '-msgwin-x-position'
- pos_y_key = type_ + '-msgwin-y-position'
- size_width_key = type_ + '-msgwin-width'
- size_height_key = type_ + '-msgwin-height'
- elif self.mode == self.ONE_MSG_WINDOW_NEVER:
- type_ = msg_win.type_
- size_width_key = type_ + '-msgwin-width'
- size_height_key = type_ + '-msgwin-height'
- elif self.mode == self.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER:
- # Ignore any hpaned width
- width = msg_win.notebook.get_allocation().width
-
- if acct:
- gajim.config.set_per('accounts', acct, size_width_key, width)
- gajim.config.set_per('accounts', acct, size_height_key, height)
-
- if self.mode != self.ONE_MSG_WINDOW_NEVER:
- gajim.config.set_per('accounts', acct, pos_x_key, x)
- gajim.config.set_per('accounts', acct, pos_y_key, y)
-
- else:
- win_maximized = msg_win.window.get_window().get_state() == \
- Gdk.WindowState.MAXIMIZED
- gajim.config.set(max_win_key, win_maximized)
- width += width_adjust
- gajim.config.set(size_width_key, width)
- gajim.config.set(size_height_key, height)
-
- if self.mode != self.ONE_MSG_WINDOW_NEVER:
- gajim.config.set(pos_x_key, x)
- gajim.config.set(pos_y_key, y)
-
- def reconfig(self):
- for w in self.windows():
- self.save_state(w)
- mode = gajim.config.get('one_message_window')
- if self.mode == common.config.opt_one_window_types.index(mode):
- # No change
- return
- self.mode = common.config.opt_one_window_types.index(mode)
-
- controls = []
- for w in self.windows():
- # Note, we are taking care not to hide/delete the roster window when the
- # MessageWindow is embedded.
- if not w.parent_paned:
- w.window.hide()
- else:
- # Stash current size so it can be restored if the MessageWindow
- # is not longer embedded
- roster_width = w.parent_paned.get_position()
- gajim.config.set('roster_width', roster_width)
-
- while w.notebook.get_n_pages():
- page = w.notebook.get_nth_page(0)
- ctrl = w._widget_to_control(page)
- w.notebook.remove_page(0)
- page.unparent()
- controls.append(ctrl)
-
- # Must clear _controls to prevent MessageControl.shutdown calls
- w._controls = {}
- if not w.parent_paned:
- w.window.destroy()
- else:
- # Don't close parent window, just remove the child
- child = w.parent_paned.get_child2()
- w.parent_paned.remove(child)
- self.parent_win.lookup_action('show-roster').set_enabled(False)
- gtkgui_helpers.resize_window(w.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
-
- self._windows = {}
-
- for ctrl in controls:
- mw = self.get_window(ctrl.contact.jid, ctrl.account)
- if not mw:
- mw = self.create_window(ctrl.contact, ctrl.account,
- ctrl.type_id)
- ctrl.parent_win = mw
- mw.new_tab(ctrl)
-
- def save_opened_controls(self):
- if not gajim.config.get('remember_opened_chat_controls'):
- return
- chat_controls = {}
- for acct in gajim.connections:
- chat_controls[acct] = []
- for ctrl in self.get_controls(type_=message_control.TYPE_CHAT):
- acct = ctrl.account
- if ctrl.contact.jid not in chat_controls[acct]:
- chat_controls[acct].append(ctrl.contact.jid)
- for acct in gajim.connections:
- gajim.config.set_per('accounts', acct, 'opened_chat_controls',
- ','.join(chat_controls[acct]))
diff --git a/src/music_track_listener.py b/src/music_track_listener.py
deleted file mode 100644
index a4733efba..000000000
--- a/src/music_track_listener.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/music_track_listener.py
-##
-## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-from gi.repository import GObject
-if __name__ == '__main__':
- # install _() func before importing dbus_support
- from common import i18n
-
-from common import dbus_support
-if dbus_support.supported:
- import dbus
-
-class MusicTrackInfo(object):
- __slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
- 'paused']
-
-class MusicTrackListener(GObject.GObject):
- __gsignals__ = {
- 'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
- }
-
- _instance = None
- @classmethod
- def get(cls):
- if cls._instance is None:
- cls._instance = cls()
- return cls._instance
-
- def __init__(self):
- super(MusicTrackListener, self).__init__()
- self._last_playing_music = None
-
- bus = dbus.SessionBus()
-
- ## MPRIS
- bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
- 'org.freedesktop.MediaPlayer')
- bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
- 'org.freedesktop.MediaPlayer')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.freedesktop.MediaPlayer')
-
- ## Muine
- bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
- 'org.gnome.Muine.Player')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
- bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
- 'org.gnome.Muine.Player')
-
- ## Rhythmbox
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
- bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
- 'playingChanged', 'org.gnome.Rhythmbox.Player')
- bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
- 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
-
- ## Banshee
- bus.add_signal_receiver(self._banshee_state_changed_cb,
- 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='org.bansheeproject.Banshee')
-
- ## Quod Libet
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'SongStarted', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'Paused', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
- 'Unpaused', 'net.sacredchao.QuodLibet')
- bus.add_signal_receiver(self._player_name_owner_changed,
- 'NameOwnerChanged', 'org.freedesktop.DBus',
- arg0='net.sacredchao.QuodLibet')
-
- def _player_name_owner_changed(self, name, old, new):
- if not new:
- self.emit('music-track-changed', None)
-
- def _player_playing_changed_cb(self, playing):
- if playing:
- self.emit('music-track-changed', self._last_playing_music)
- else:
- self.emit('music-track-changed', None)
-
- def _player_playing_song_property_changed_cb(self, a, b, c, d):
- if b == 'rb:stream-song-title':
- self.emit('music-track-changed', self._last_playing_music)
-
- def _mpris_properties_extract(self, song):
- info = MusicTrackInfo()
- info.title = song.get('title', '')
- info.album = song.get('album', '')
- info.artist = song.get('artist', '')
- info.duration = int(song.get('length', 0))
- return info
-
- def _mpris_playing_changed_cb(self, playing):
- if type(playing) is dbus.Struct:
- if playing[0]:
- self.emit('music-track-changed', None)
- else:
- self.emit('music-track-changed', self._last_playing_music)
- else: # Workaround for e.g. Audacious
- if playing:
- self.emit('music-track-changed', None)
- else:
- self.emit('music-track-changed', self._last_playing_music)
-
- def _mpris_music_track_change_cb(self, arg):
- self._last_playing_music = self._mpris_properties_extract(arg)
- self.emit('music-track-changed', self._last_playing_music)
-
- def _muine_properties_extract(self, song_string):
- d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \
- song_string.split('\n'))
- info = MusicTrackInfo()
- info.title = d['title']
- info.album = d['album']
- info.artist = d['artist']
- info.duration = int(d['duration'])
- info.track_number = int(d['track_number'])
- return info
-
- def _muine_music_track_change_cb(self, arg):
- info = self._muine_properties_extract(arg)
- self.emit('music-track-changed', info)
-
- def _rhythmbox_playing_changed_cb(self, playing):
- if playing:
- info = self.get_playing_track()
- self.emit('music-track-changed', info)
- else:
- self.emit('music-track-changed', None)
-
- def _rhythmbox_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('title', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = int(props.get('duration', 0))
- info.track_number = int(props.get('track-number', 0))
- return info
-
- def _banshee_state_changed_cb(self, state):
- if state == 'playing':
- bus = dbus.SessionBus()
- banshee = bus.get_object('org.bansheeproject.Banshee',
- '/org/bansheeproject/Banshee/PlayerEngine')
- currentTrack = banshee.GetCurrentTrack()
- self._last_playing_music = self._banshee_properties_extract(
- currentTrack)
- self.emit('music-track-changed', self._last_playing_music)
- elif state == 'paused':
- self.emit('music-track-changed', None)
-
- def _banshee_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('name', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = int(props.get('length', 0))
- return info
-
- def _quodlibet_state_change_cb(self, state=None):
- info = self.get_playing_track()
- if info:
- self.emit('music-track-changed', info)
- else:
- self.emit('music-track-changed', None)
-
- def _quodlibet_properties_extract(self, props):
- info = MusicTrackInfo()
- info.title = props.get('title', None)
- info.album = props.get('album', None)
- info.artist = props.get('artist', None)
- info.duration = float(props.get('~#length', 0))
- return info
-
- def get_playing_track(self):
- '''Return a MusicTrackInfo for the currently playing
- song, or None if no song is playing'''
-
- bus = dbus.SessionBus()
-
- ## Check Muine playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.gnome.Muine'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.gnome.Muine'):
- test = True
- if test:
- obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
- player = dbus.Interface(obj, 'org.gnome.Muine.Player')
- if player.GetPlaying():
- song_string = player.GetCurrentSong()
- song = self._muine_properties_extract(song_string)
- self._last_playing_music = song
- return song
-
- ## Check Rhythmbox playing song
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.gnome.Rhythmbox'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.gnome.Rhythmbox'):
- test = True
- if test:
- rbshellobj = bus.get_object('org.gnome.Rhythmbox',
- '/org/gnome/Rhythmbox/Shell')
- player = dbus.Interface(
- bus.get_object('org.gnome.Rhythmbox',
- '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
- rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
- try:
- uri = player.getPlayingUri()
- except dbus.DBusException:
- uri = None
- if not uri:
- return None
- props = rbshell.getSongProperties(uri)
- info = self._rhythmbox_properties_extract(props)
- self._last_playing_music = info
- return info
-
- ## Check Banshee playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('org.bansheeproject.Banshee'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'org.bansheeproject.Banshee'):
- test = True
- if test:
- banshee = bus.get_object('org.bansheeproject.Banshee',
- '/org/bansheeproject/Banshee/PlayerEngine')
- currentTrack = banshee.GetCurrentTrack()
- if currentTrack:
- song = self._banshee_properties_extract(currentTrack)
- self._last_playing_music = song
- return song
-
- ## Check Quod Libet playing track
- test = False
- if hasattr(bus, 'name_has_owner'):
- if bus.name_has_owner('net.sacredchao.QuodLibet'):
- test = True
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
- 'net.sacredchao.QuodLibet'):
- test = True
- if test:
- quodlibet = bus.get_object('net.sacredchao.QuodLibet',
- '/net/sacredchao/QuodLibet')
- if quodlibet.IsPlaying():
- currentTrack = quodlibet.CurrentSong()
- song = self._quodlibet_properties_extract(currentTrack)
- self._last_playing_music = song
- return song
-
- return None
-
-# here we test :)
-if __name__ == '__main__':
- def music_track_change_cb(listener, music_track_info):
- if music_track_info is None:
- print('Stop!')
- else:
- print(music_track_info.title)
- listener = MusicTrackListener.get()
- listener.connect('music-track-changed', music_track_change_cb)
- track = listener.get_playing_track()
- if track is None:
- print('Now not playing anything')
- else:
- print('Now playing: "%s" by %s' % (track.title, track.artist))
- GObject.MainLoop().run()
diff --git a/src/negotiation.py b/src/negotiation.py
deleted file mode 100644
index d48c1085f..000000000
--- a/src/negotiation.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/negotiation.py
-##
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import gtkgui_helpers
-import dataforms_widget
-
-from common import dataforms
-from common import gajim
-import nbxmpp
-
-def describe_features(features):
- """
- A human-readable description of the features that have been negotiated
- """
- if features['logging'] == 'may':
- return _('- messages will be logged')
- elif features['logging'] == 'mustnot':
- return _('- messages will not be logged')
-
-class FeatureNegotiationWindow:
- def __init__(self, account, jid, session, form):
- self.account = account
- self.jid = jid
- self.form = form
- self.session = session
-
- self.xml = gtkgui_helpers.get_gtk_builder('data_form_window.ui', 'data_form_window')
- self.window = self.xml.get_object('data_form_window')
-
- config_vbox = self.xml.get_object('config_vbox')
- dataform = dataforms.ExtendForm(node = self.form)
- self.data_form_widget = dataforms_widget.DataFormWidget(dataform)
- self.data_form_widget.show()
- config_vbox.pack_start(self.data_form_widget, True, True, 0)
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_ok_button_clicked(self, widget):
- acceptance = nbxmpp.Message(self.jid)
- acceptance.setThread(self.session.thread_id)
- feature = acceptance.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- form = self.data_form_widget.data_form
- form.setAttr('type', 'submit')
-
- feature.addChild(node=form)
-
- gajim.connections[self.account].send_stanza(acceptance)
-
- self.window.destroy()
-
- def on_cancel_button_clicked(self, widget):
- rejection = nbxmpp.Message(self.jid)
- rejection.setThread(self.session.thread_id)
- feature = rejection.NT.feature
- feature.setNamespace(nbxmpp.NS_FEATURE)
-
- x = nbxmpp.DataForm(typ='submit')
- x.addChild(node=nbxmpp.DataField('FORM_TYPE', value='urn:xmpp:ssn'))
- x.addChild(node=nbxmpp.DataField('accept', value='false', typ='boolean'))
-
- feature.addChild(node=x)
-
- gajim.connections[self.account].send_stanza(rejection)
-
- self.window.destroy()
diff --git a/src/network_watcher.py b/src/network_watcher.py
deleted file mode 100644
index 6854e5de6..000000000
--- a/src/network_watcher.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/network_watcher.py
-##
-## Copyright (C) 2017 Philipp Hoerist <philipp AT hoerist.com>
-## Copyright © 2017 Jörg Sommer <joerg@alea.gnuu.de>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-import logging
-
-from gi.repository import Gio, GLib
-
-from common import gajim
-
-log = logging.getLogger('gajim.network_watcher')
-
-
-supported = False
-
-
-def watch_name(name):
- Gio.bus_watch_name(
- Gio.BusType.SYSTEM,
- name,
- Gio.BusNameWatcherFlags.NONE,
- appeared,
- None)
-
-
-def signal_received(connection, sender_name, object_path,
- interface_name, signal_name, parameters, *user_data):
- connected = None
- log.info('Signal received: %s - %s', interface_name, parameters)
- if interface_name == 'org.freedesktop.NetworkManager':
- # https://people.freedesktop.org/~lkundrak/nm-docs/nm-dbus-types.html
- connected = parameters[0] == 70
- elif interface_name == 'org.freedesktop.DBus.Properties' and len(parameters) >= 2 \
- and parameters[0] == 'org.freedesktop.network1.Manager' \
- and 'OperationalState' in parameters[1]:
- connected = parameters[1]['OperationalState'] == 'routable'
-
- if connected is not None:
- GLib.timeout_add_seconds(
- 2, update_connection_state,
- connected)
-
-
-def appeared(connection, name, name_owner, *user_data):
- global supported
- supported = True
- log.info('%s appeared', name)
- if name == 'org.freedesktop.NetworkManager':
- connection.signal_subscribe(
- 'org.freedesktop.NetworkManager',
- None,
- 'StateChanged',
- '/org/freedesktop/NetworkManager',
- None,
- Gio.DBusSignalFlags.NONE,
- signal_received,
- None)
- elif name == 'org.freedesktop.network1':
- connection.signal_subscribe(
- 'org.freedesktop.network1',
- None,
- 'PropertiesChanged',
- '/org/freedesktop/network1',
- None,
- Gio.DBusSignalFlags.NONE,
- signal_received,
- None)
-
-
-def update_connection_state(connected):
- if connected:
- for connection in gajim.connections.values():
- log.info('Connect %s', connection.name)
- connection.reconnect()
- else:
- for connection in gajim.connections.values():
- if connection.connected > 1:
- log.info('Disconnect %s', connection.name)
- connection.disconnectedReconnCB()
-
-
-watch_name('org.freedesktop.NetworkManager')
-watch_name('org.freedesktop.network1')
diff --git a/src/notify.py b/src/notify.py
deleted file mode 100644
index b7cab742f..000000000
--- a/src/notify.py
+++ /dev/null
@@ -1,479 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/notify.py
-##
-## Copyright (C) 2005 Sebastian Estienne
-## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-import os
-import time
-from dialogs import PopupNotificationWindow
-from gi.repository import GObject
-from gi.repository import GLib
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-from common import ged
-
-from common import dbus_support
-if dbus_support.supported:
- import dbus
-
-
-USER_HAS_PYNOTIFY = True # user has pynotify module
-try:
- import gi
- gi.require_version('Notify', '0.7')
- from gi.repository import Notify
- Notify.init('Gajim Notification')
-except ValueError:
- USER_HAS_PYNOTIFY = False
-
-def get_show_in_roster(event, account, contact, session=None):
- """
- Return True if this event must be shown in roster, else False
- """
- if event == 'gc_message_received':
- return True
- if event == 'message_received':
- if session and session.control:
- return False
- return True
-
-def get_show_in_systray(event, account, contact, type_=None):
- """
- Return True if this event must be shown in systray, else False
- """
- if type_ == 'printed_gc_msg' and not gajim.config.get(
- 'notify_on_all_muc_messages'):
- # it's not an highlighted message, don't show in systray
- return False
- return gajim.config.get('trayicon_notification_on_events')
-
-def popup(event_type, jid, account, msg_type='', path_to_image=None, title=None,
-text=None, timeout=-1):
- """
- Notify a user of an event. It first tries to a valid implementation of
- the Desktop Notification Specification. If that fails, then we fall back to
- the older style PopupNotificationWindow method
- """
- # default image
- if not path_to_image:
- path_to_image = gtkgui_helpers.get_icon_path('gajim-chat_msg_recv', 48)
-
- if timeout < 0:
- timeout = gajim.config.get('notification_timeout')
-
- # Try to show our popup via D-Bus and notification daemon
- if gajim.config.get('use_notif_daemon') and dbus_support.supported:
- try:
- DesktopNotification(event_type, jid, account, msg_type,
- path_to_image, title, GLib.markup_escape_text(text), timeout)
- return # sucessfully did D-Bus Notification procedure!
- except dbus.DBusException as e:
- # Connection to D-Bus failed
- gajim.log.debug(str(e))
- except TypeError as e:
- # This means that we sent the message incorrectly
- gajim.log.debug(str(e))
-
- # Ok, that failed. Let's try pynotify, which also uses notification daemon
- if gajim.config.get('use_notif_daemon') and USER_HAS_PYNOTIFY:
- if not text and event_type == 'new_message':
- # empty text for new_message means do_preview = False
- # -> default value for text
- _text = GLib.markup_escape_text(gajim.get_name_from_jid(account,
- jid))
- else:
- _text = GLib.markup_escape_text(text)
-
- if not title:
- _title = ''
- else:
- _title = title
-
- notification = Notify.Notification.new(_title, _text)
- notification.set_timeout(timeout*1000)
-
- notification.set_category(event_type)
- notification._data = {}
- notification._data["event_type"] = event_type
- notification._data["jid"] = jid
- notification._data["account"] = account
- notification._data["msg_type"] = msg_type
- notification.set_property('icon-name', path_to_image)
- if 'actions' in Notify.get_server_caps():
- notification.add_action('default', 'Default Action',
- on_pynotify_notification_clicked)
-
- try:
- notification.show()
- return
- except GObject.GError as e:
- # Connection to notification-daemon failed, see #2893
- gajim.log.debug(str(e))
-
- # Either nothing succeeded or the user wants old-style notifications
- instance = PopupNotificationWindow(event_type, jid, account, msg_type,
- path_to_image, title, text, timeout)
- gajim.interface.roster.popup_notification_windows.append(instance)
-
-def on_pynotify_notification_clicked(notification, action):
- jid = notification._data.jid
- account = notification._data.account
- msg_type = notification._data.msg_type
-
- notification.close()
- gajim.interface.handle_event(account, jid, msg_type)
-
-class Notification:
- """
- Handle notifications
- """
- def __init__(self):
- gajim.ged.register_event_handler('notification', ged.GUI2,
- self._nec_notification)
-
- def _nec_notification(self, obj):
- if obj.do_popup:
- if obj.popup_image:
- icon_path = gtkgui_helpers.get_icon_path(obj.popup_image, 48)
- if icon_path:
- image_path = icon_path
- elif obj.popup_image_path:
- image_path = obj.popup_image_path
- else:
- image_path = ''
- popup(obj.popup_event_type, obj.jid, obj.conn.name,
- obj.popup_msg_type, path_to_image=image_path,
- title=obj.popup_title, text=obj.popup_text,
- timeout=obj.popup_timeout)
-
- if obj.do_sound:
- if obj.sound_file:
- helpers.play_sound_file(obj.sound_file)
- elif obj.sound_event:
- helpers.play_sound(obj.sound_event)
-
- if obj.do_command:
- try:
- helpers.exec_command(obj.command, use_shell=True)
- except Exception:
- pass
-
-class NotificationResponseManager:
- """
- Collect references to pending DesktopNotifications and manages there
- signalling. This is necessary due to a bug in DBus where you can't remove a
- signal from an interface once it's connected
- """
-
- def __init__(self):
- self.pending = {}
- self.received = []
- self.interface = None
-
- def attach_to_interface(self):
- if self.interface is not None:
- return
- self.interface = dbus_support.get_notifications_interface()
- self.interface.connect_to_signal('ActionInvoked',
- self.on_action_invoked)
- self.interface.connect_to_signal('NotificationClosed', self.on_closed)
-
- def on_action_invoked(self, id_, reason):
- if id_ in self.pending:
- notification = self.pending[id_]
- notification.on_action_invoked(id_, reason)
- del self.pending[id_]
- return
- # got an action on popup that isn't handled yet? Maybe user clicked too
- # fast. Remember it.
- self.received.append((id_, time.time(), reason))
- if len(self.received) > 20:
- curt = time.time()
- for rec in self.received:
- diff = curt - rec[1]
- if diff > 10:
- self.received.remove(rec)
-
- def on_closed(self, id_, reason=None):
- if id_ in self.pending:
- del self.pending[id_]
-
- def add_pending(self, id_, object_):
- # Check to make sure that we handle an event immediately if we're adding
- # an id that's already been triggered
- for rec in self.received:
- if rec[0] == id_:
- object_.on_action_invoked(id_, rec[2])
- self.received.remove(rec)
- return
- if id_ not in self.pending:
- # Add it
- self.pending[id_] = object_
- else:
- # We've triggered an event that has a duplicate ID!
- gajim.log.debug('Duplicate ID of notification. Can\'t handle this.')
-
-notification_response_manager = NotificationResponseManager()
-
-class DesktopNotification:
- """
- A DesktopNotification that interfaces with D-Bus via the Desktop
- Notification Specification
- """
-
- def __init__(self, event_type, jid, account, msg_type='',
- path_to_image=None, title=None, text=None, timeout=-1):
- self.path_to_image = os.path.abspath(path_to_image)
- self.event_type = event_type
- self.title = title
- self.text = text
- self.timeout = timeout
- # 0.3.1 is the only version of notification daemon that has no way
- # to determine which version it is. If no method exists, it means
- # they're using that one.
- self.default_version = [0, 3, 1]
- self.account = account
- self.jid = jid
- self.msg_type = msg_type
-
- # default value of text
- if not text and event_type == 'new_message':
- # empty text for new_message means do_preview = False
- self.text = gajim.get_name_from_jid(account, jid)
-
- if not title:
- self.title = event_type # default value
-
- if event_type == _('Contact Signed In'):
- ntype = 'presence.online'
- elif event_type == _('Contact Signed Out'):
- ntype = 'presence.offline'
- elif event_type in (_('New Message'), _('New Single Message'),
- _('New Private Message')):
- ntype = 'im.received'
- elif event_type == _('File Transfer Request'):
- ntype = 'transfer'
- elif event_type == _('File Transfer Error'):
- ntype = 'transfer.error'
- elif event_type in (_('File Transfer Completed'),
- _('File Transfer Stopped')):
- ntype = 'transfer.complete'
- elif event_type == _('New E-mail'):
- ntype = 'email.arrived'
- elif event_type == _('Groupchat Invitation'):
- ntype = 'im.invitation'
- elif event_type == _('Contact Changed Status'):
- ntype = 'presence.status'
- elif event_type == _('Connection Failed'):
- ntype = 'connection.failed'
- elif event_type == _('Subscription request'):
- ntype = 'subscription.request'
- elif event_type == _('Unsubscribed'):
- ntype = 'unsubscribed'
- else:
- # default failsafe values
- self.path_to_image = gtkgui_helpers.get_icon_path(
- 'gajim-chat_msg_recv', 48)
- ntype = 'im' # Notification Type
-
- self.notif = dbus_support.get_notifications_interface(self)
- if self.notif is None:
- raise dbus.DBusException('unable to get notifications interface')
- self.ntype = ntype
-
- if self.kde_notifications:
- self.attempt_notify()
- else:
- self.capabilities = self.notif.GetCapabilities()
- if self.capabilities is None:
- self.capabilities = ['actions']
- self.get_version()
-
- def attempt_notify(self):
- ntype = self.ntype
- if self.kde_notifications:
- notification_text = ('<html><img src="%(image)s" align=left />' \
- '%(title)s<br/>%(text)s</html>') % {'title': self.title,
- 'text': self.text, 'image': self.path_to_image}
- gajim_icon = gtkgui_helpers.get_icon_path('org.gajim.Gajim', 48)
- try:
- self.notif.Notify(
- dbus.String(_('Gajim')), # app_name (string)
- dbus.UInt32(0), # replaces_id (uint)
- ntype, # event_id (string)
- dbus.String(gajim_icon), # app_icon (string)
- dbus.String(''), # summary (string)
- dbus.String(notification_text), # body (string)
- # actions (stringlist)
- (dbus.String('default'), dbus.String(self.event_type),
- dbus.String('ignore'), dbus.String(_('Ignore'))),
- [], # hints (not used in KDE yet)
- dbus.UInt32(self.timeout*1000), # timeout (int), in ms
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- return
- except Exception:
- pass
- version = self.version
- if version[:2] == [0, 2]:
- actions = {}
- if 'actions' in self.capabilities and self.msg_type:
- actions = {'default': 0}
- try:
- self.notif.Notify(
- dbus.String(_('Gajim')),
- dbus.String(self.path_to_image),
- dbus.UInt32(0),
- ntype,
- dbus.Byte(0),
- dbus.String(self.title),
- dbus.String(self.text),
- [dbus.String(self.path_to_image)],
- actions,
- [''],
- True,
- dbus.UInt32(self.timeout),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- except AttributeError:
- # we're actually dealing with the newer version
- version = [0, 3, 1]
- if version > [0, 3]:
- if gajim.interface.systray_enabled and \
- gajim.config.get('attach_notifications_to_systray'):
- status_icon = gajim.interface.systray.status_icon
- rect = status_icon.get_geometry()[2]
- x, y, width, height = rect.x, rect.y, rect.width, rect.height
- pos_x = x + (width / 2)
- pos_y = y + (height / 2)
- hints = {'x': pos_x, 'y': pos_y}
- else:
- hints = {}
- if version >= [0, 3, 2]:
- hints['urgency'] = dbus.Byte(0) # Low Urgency
- hints['category'] = dbus.String(ntype)
- # it seems notification-daemon doesn't like empty text
- if self.text:
- text = self.text
- if len(self.text) > 200:
- text = '%s\n…' % self.text[:200]
- else:
- text = ' '
- if os.environ.get('KDE_FULL_SESSION') == 'true':
- text = '<table style=\'padding: 3px\'><tr><td>' \
- '<img src=\"%s\"></td><td width=20> </td>' \
- '<td>%s</td></tr></table>' % (self.path_to_image,
- text)
- self.path_to_image = os.path.abspath(
- gtkgui_helpers.get_icon_path('org.gajim.Gajim', 48))
- actions = ()
- if 'actions' in self.capabilities and self.msg_type:
- actions = (dbus.String('default'), dbus.String(
- self.event_type))
- try:
- self.notif.Notify(
- dbus.String(_('Gajim')),
- # this notification does not replace other
- dbus.UInt32(0),
- dbus.String(self.path_to_image),
- dbus.String(self.title),
- dbus.String(text),
- actions,
- hints,
- dbus.UInt32(self.timeout*1000),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- except Exception as e:
- self.notify_another_way(e)
- else:
- try:
- self.notif.Notify(
- dbus.String(_('Gajim')),
- dbus.String(self.path_to_image),
- dbus.UInt32(0),
- dbus.String(self.title),
- dbus.String(self.text),
- dbus.String(''),
- hints,
- dbus.UInt32(self.timeout*1000),
- reply_handler=self.attach_by_id,
- error_handler=self.notify_another_way)
- except Exception as e:
- self.notify_another_way(e)
-
- def attach_by_id(self, id_):
- notification_response_manager.attach_to_interface()
- notification_response_manager.add_pending(id_, self)
-
- def notify_another_way(self, e):
- gajim.log.debug('Error when trying to use notification daemon: %s' % \
- str(e))
- instance = PopupNotificationWindow(self.event_type, self.jid,
- self.account, self.msg_type, self.path_to_image, self.title,
- self.text, self.timeout)
- gajim.interface.roster.popup_notification_windows.append(instance)
-
- def on_action_invoked(self, id_, reason):
- if self.notif is None:
- return
- self.notif.CloseNotification(dbus.UInt32(id_))
- self.notif = None
-
- if reason == 'ignore':
- return
-
- gajim.interface.handle_event(self.account, self.jid, self.msg_type)
-
- def version_reply_handler(self, name, vendor, version, spec_version=None):
- if spec_version:
- version = spec_version
- elif vendor == 'Xfce' and version.startswith('0.1.0'):
- version = '0.9'
- version_list = version.split('.')
- self.version = []
- try:
- while len(version_list):
- self.version.append(int(version_list.pop(0)))
- except ValueError:
- self.version_error_handler_3_x_try(None)
- self.attempt_notify()
-
- def get_version(self):
- self.notif.GetServerInfo(
- reply_handler=self.version_reply_handler,
- error_handler=self.version_error_handler_2_x_try)
-
- def version_error_handler_2_x_try(self, e):
- self.notif.GetServerInformation(
- reply_handler=self.version_reply_handler,
- error_handler=self.version_error_handler_3_x_try)
-
- def version_error_handler_3_x_try(self, e):
- self.version = self.default_version
- self.attempt_notify()
diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py
deleted file mode 100644
index a2c6986a6..000000000
--- a/src/plugins/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-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 .gajimplugin import GajimPlugin
-
-__all__ = ['PluginManager', 'GajimPlugin']
diff --git a/src/plugins/gajimplugin.py b/src/plugins/gajimplugin.py
deleted file mode 100644
index 18a8a75b0..000000000
--- a/src/plugins/gajimplugin.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-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
-import locale
-
-from common import gajim
-
-from plugins.helpers import log_calls, log
-from plugins.gui import GajimPluginConfigDialog
-
-import logging
-log = logging.getLogger('gajim.p.plugin')
-
-
-class GajimPlugin(object):
- '''
- Base class for implementing Gajim plugins.
- '''
- name = ''
- '''
- Name of plugin.
-
- Will be shown in plugins management GUI.
-
- :type: str
- '''
- short_name = ''
- '''
- Short name of plugin.
-
- Used for quick indentification of plugin.
-
- :type: str
-
- :todo: decide whether we really need this one, because class name (with
- module name) can act as such short name
- '''
- encryption_name = ''
- '''
- Name of the encryption scheme.
-
- The name that Gajim displays in the encryption menu.
- Leave empty if the plugin is not an encryption plugin.
-
- :type: str
-
- '''
- version = ''
- '''
- Version of plugin.
-
- :type: str
-
- :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 = ''
- '''
- Plugin description.
-
- :type: str
-
- :todo: should be allow rich text here (like HTML or reStructuredText)?
- '''
- authors = []
- '''
- Plugin authors.
-
- :type: [] of str
-
- :todo: should we decide on any particular format of author strings?
- Especially: should we force format of giving author's e-mail?
- '''
- homepage = ''
- '''
- URL to plug-in's homepage.
-
- :type: str
-
- :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 str (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.activatable = True
- self.available_text = ''
- 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 pickle
-
-class GajimPluginConfig():
- @log_calls('GajimPluginConfig')
- def __init__(self, plugin):
- self.plugin = plugin
- self.FILE_PATH = os.path.join(
- gajim.PLUGINS_CONFIG_DIR, self.plugin.short_name)
- self.data = {}
-
- @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()
-
- @log_calls('GajimPluginConfig')
- def __delitem__(self, key):
- del self.data[key]
- self.save()
-
- @log_calls('GajimPluginConfig')
- def __contains__(self, key):
- return key in self.data
-
- def __iter__(self):
- for k in self.data.keys():
- yield k
-
- def keys(self):
- return self.data.keys()
-
- def items(self):
- return self.data.items()
-
- @log_calls('GajimPluginConfig')
- def save(self):
- fd = open(self.FILE_PATH, 'wb')
- pickle.dump(self.data, fd)
- fd.close()
-
- @log_calls('GajimPluginConfig')
- def load(self):
- if os.path.isfile(self.FILE_PATH):
- fd = open(self.FILE_PATH, 'rb')
- try:
- self.data = pickle.load(fd)
- fd.close()
- except:
- fd.close()
- try:
- import shelve
- s = shelve.open(self.FILE_PATH)
- for (k, v) in s.items():
- self.data[k] = v
- if not isinstance(self.data, dict):
- raise GajimPluginException
- s.close()
- self.save()
- except:
- log.warning('%s plugin config file not readable. Saving it as '
- '%s and creating a new one' % (self.plugin.short_name,
- self.FILE_PATH.decode(locale.getpreferredencoding()) + '.bak'))
- if os.path.exists(self.FILE_PATH + '.bak'):
- os.remove(self.FILE_PATH + '.bak')
- os.rename(self.FILE_PATH, self.FILE_PATH + '.bak')
- self.data = {}
- self.save()
- else:
- self.data = {}
- self.save()
-
-
-class GajimPluginException(Exception):
- pass
-
-class GajimPluginInitError(GajimPluginException):
- pass
diff --git a/src/plugins/gui.py b/src/plugins/gui.py
deleted file mode 100644
index 15aee4295..000000000
--- a/src/plugins/gui.py
+++ /dev/null
@@ -1,353 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-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']
-
-from gi.repository import Pango
-from gi.repository import Gtk
-from gi.repository import GdkPixbuf
-from gi.repository import GLib, Gdk
-import os
-
-from enum import IntEnum, unique
-
-import gtkgui_helpers
-from dialogs import WarningDialog, YesNoDialog, ArchiveChooserDialog
-from htmltextview import HtmlTextView
-from common import gajim
-from plugins.helpers import log_calls
-from plugins.helpers import GajimPluginActivateException
-from plugins.plugins_i18n import _
-from common.exceptions import PluginsystemError
-
-@unique
-class Column(IntEnum):
- PLUGIN = 0
- NAME = 1
- ACTIVE = 2
- ACTIVATABLE = 3
- ICON = 4
-
-
-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', '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))
-
- self.plugin_description_textview = HtmlTextView()
- self.plugin_description_textview.connect_tooltip()
- self.plugin_description_textview.set_wrap_mode(Gtk.WrapMode.WORD)
- sw = self.xml.get_object('scrolledwindow2')
- sw.add(self.plugin_description_textview)
- self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool,
- GdkPixbuf.Pixbuf)
- self.installed_plugins_treeview.set_model(self.installed_plugins_model)
- self.installed_plugins_treeview.set_rules_hint(True)
-
- renderer = Gtk.CellRendererText()
- col = Gtk.TreeViewColumn(_('Plugin'))#, renderer, text=Column.NAME)
- cell = Gtk.CellRendererPixbuf()
- col.pack_start(cell, False)
- col.add_attribute(cell, 'pixbuf', Column.ICON)
- col.pack_start(renderer, True)
- col.add_attribute(renderer, 'text', Column.NAME)
- col.set_property('expand', True)
- self.installed_plugins_treeview.append_column(col)
-
- renderer = Gtk.CellRendererToggle()
- renderer.connect('toggled', self.installed_plugins_toggled_cb)
- col = Gtk.TreeViewColumn(_('Active'), renderer, active=Column.ACTIVE,
- activatable=Column.ACTIVATABLE)
- self.installed_plugins_treeview.append_column(col)
-
- self.def_icon = gtkgui_helpers.get_icon_pixmap('preferences-desktop')
-
- # 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.SelectionMode.SINGLE)
-
- self._clear_installed_plugin_info()
-
- self.fill_installed_plugins_model()
- root_iter = self.installed_plugins_model.get_iter_first()
- if root_iter:
- selection.select_iter(root_iter )
-
- self.xml.connect_signals(self)
-
- self.plugins_notebook.set_current_page(0)
- self.xml.get_object('close_button').grab_focus()
-
- # Adding GUI extension point for Plugins that want to hook the Plugin Window
- gajim.plugin_manager.gui_extension_point('plugin_window', self)
-
- self.window.show_all()
- gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
-
- def on_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_plugins_notebook_switch_page(self, widget, page, page_num):
- GLib.idle_add(self.xml.get_object('close_button').grab_focus)
-
- @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, Column.PLUGIN)
- plugin_name = model.get_value(iter, Column.NAME)
- is_active = model.get_value(iter, Column.ACTIVE)
-
- 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(plugin.authors)
- self.plugin_homepage_linkbutton.set_uri(plugin.homepage)
- self.plugin_homepage_linkbutton.set_label(plugin.homepage)
- label = self.plugin_homepage_linkbutton.get_children()[0]
- label.set_ellipsize(Pango.EllipsizeMode.END)
- self.plugin_homepage_linkbutton.set_property('sensitive', True)
-
- desc_textbuffer = self.plugin_description_textview.get_buffer()
- desc_textbuffer.set_text('')
- txt = plugin.description
- txt.replace('</body>', '')
- if plugin.available_text:
- txt += '<br/><br/>' + _('Warning: %s') % plugin.available_text
- if not txt.startswith('<body '):
- txt = '<body xmlns=\'http://www.w3.org/1999/xhtml\'>' + txt
- txt += ' </body>'
- self.plugin_description_textview.display_html(txt,
- self.plugin_description_textview, None)
-
- self.plugin_description_textview.set_property('sensitive', True)
- self.uninstall_plugin_button.set_property('sensitive',
- gajim.PLUGINS_DIRS[1] in plugin.__path__)
- self.configure_plugin_button.set_property(
- 'sensitive', plugin.config_dialog is not None)
-
- 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.SortType.ASCENDING)
-
- for plugin in pm.plugins:
- icon = self.get_plugin_icon(plugin)
- self.installed_plugins_model.append([plugin, plugin.name,
- plugin.active and plugin.activatable, plugin.activatable, icon])
-
- def get_plugin_icon(self, plugin):
- icon_file = os.path.join(plugin.__path__, os.path.split(
- plugin.__path__)[1]) + '.png'
- icon = self.def_icon
- if os.path.isfile(icon_file):
- icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16)
- return icon
-
- @log_calls('PluginsWindow')
- def installed_plugins_toggled_cb(self, cell, path):
- is_active = self.installed_plugins_model[path][Column.ACTIVE]
- plugin = self.installed_plugins_model[path][Column.PLUGIN]
-
- if is_active:
- gajim.plugin_manager.deactivate_plugin(plugin)
- else:
- try:
- gajim.plugin_manager.activate_plugin(plugin)
- except GajimPluginActivateException as e:
- WarningDialog(_('Plugin failed'), str(e),
- transient_for=self.window)
- return
-
- self.installed_plugins_model[path][Column.ACTIVE] = 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):
- selection = self.installed_plugins_treeview.get_selection()
- model, iter = selection.get_selected()
- if iter:
- plugin = model.get_value(iter, Column.PLUGIN)
- plugin_name = model.get_value(iter, Column.NAME)
- is_active = model.get_value(iter, Column.ACTIVE)
-
-
- 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):
- selection = self.installed_plugins_treeview.get_selection()
- model, iter = selection.get_selected()
- if iter:
- plugin = model.get_value(iter, Column.PLUGIN)
- plugin_name = model.get_value(iter, Column.NAME)
- is_active = model.get_value(iter, Column.ACTIVE)
- try:
- gajim.plugin_manager.remove_plugin(plugin)
- except PluginsystemError as e:
- WarningDialog(_('Unable to properly remove the plugin'),
- str(e), self.window)
- return
- model.remove(iter)
-
- @log_calls('PluginsWindow')
- def on_install_plugin_button_clicked(self, widget):
- def show_warn_dialog():
- text = _('Archive is malformed')
- dialog = WarningDialog(text, '', transient_for=self.window)
- dialog.set_modal(False)
- dialog.popup()
-
- def _on_plugin_exists(zip_filename):
- def on_yes(is_checked):
- plugin = gajim.plugin_manager.install_from_zip(zip_filename,
- True)
- if not plugin:
- show_warn_dialog()
- return
- model = self.installed_plugins_model
-
- for i, row in enumerate(model):
- if plugin == row[Column.PLUGIN]:
- model.remove(row.iter)
- break
-
- iter_ = model.append([plugin, plugin.name, False,
- plugin.activatable, self.get_plugin_icon(plugin)])
- sel = self.installed_plugins_treeview.get_selection()
- sel.select_iter(iter_)
-
- YesNoDialog(_('Plugin already exists'), sectext=_('Overwrite?'),
- on_response_yes=on_yes, transient_for=self.window)
-
- def _try_install(zip_filename):
- try:
- plugin = gajim.plugin_manager.install_from_zip(zip_filename)
- except PluginsystemError as er_type:
- error_text = str(er_type)
- if error_text == _('Plugin already exists'):
- _on_plugin_exists(zip_filename)
- return
-
- WarningDialog(error_text, '"%s"' % zip_filename, self.window)
- return
- if not plugin:
- show_warn_dialog()
- return
- model = self.installed_plugins_model
- iter_ = model.append([plugin, plugin.name, False,
- plugin.activatable, self.get_plugin_icon(plugin)])
- sel = self.installed_plugins_treeview.get_selection()
- sel.select_iter(iter_)
-
- self.dialog = ArchiveChooserDialog(
- on_response_ok=_try_install, transient_for=self.window)
-
-
-class GajimPluginConfigDialog(Gtk.Dialog):
-
- @log_calls('GajimPluginConfigDialog')
- def __init__(self, plugin, **kwargs):
- Gtk.Dialog.__init__(self, title='%s %s'%(plugin.name,
- _('Configuration')), **kwargs)
- self.plugin = plugin
- button = self.add_button('gtk-close', Gtk.ResponseType.CLOSE)
- button.connect('clicked', self.on_close_button_clicked)
-
- self.get_child().set_spacing(3)
-
- self.init()
-
- def on_close_dialog(self, widget, data):
- self.hide()
- return True
-
- def on_close_button_clicked(self, widget):
- self.hide()
-
- @log_calls('GajimPluginConfigDialog')
- def run(self, parent=None):
- self.set_transient_for(parent)
- self.on_run()
- self.show_all()
- self.connect('delete-event', self.on_close_dialog)
- result = super(GajimPluginConfigDialog, self)
- return result
-
- def init(self):
- pass
-
- def on_run(self):
- pass
diff --git a/src/plugins/helpers.py b/src/plugins/helpers.py
deleted file mode 100644
index b37076a87..000000000
--- a/src/plugins/helpers.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-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
-import functools
-
-log = logging.getLogger('gajim.plugin_system')
-'''
-Logger for code related to plug-in system.
-
-:type: logging.Logger
-'''
-
-class GajimPluginActivateException(Exception):
- '''
- Raised when activation failed
- '''
- pass
-
-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.__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
diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py
deleted file mode 100644
index bba599ce1..000000000
--- a/src/plugins/pluginmanager.py
+++ /dev/null
@@ -1,671 +0,0 @@
-# -*- coding: utf-8 -*-
-
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-'''
-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
-import zipfile
-from shutil import rmtree
-import configparser
-from pkg_resources import parse_version
-
-from common import gajim
-from common import nec
-from common.exceptions import PluginsystemError
-
-from plugins.helpers import log, log_calls, Singleton
-from plugins.helpers import GajimPluginActivateException
-from plugins.gajimplugin import GajimPlugin, GajimPluginException
-
-class PluginManager(metaclass=Singleton):
- '''
- 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.
- '''
-
- self.encryption_plugins = {}
- '''
- Registered names with instances of encryption Plugins.
- '''
-
- for path in [gajim.PLUGINS_DIRS[1], gajim.PLUGINS_DIRS[0]]:
- pc = PluginManager.scan_dir_for_plugins(path)
- self.add_plugins(pc)
- self._activate_all_plugins_from_global_config()
-
- @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)
-
- def _remove_plugin_entry_in_global_config(self, plugin):
- gajim.config.del_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 get_active_plugin(self, plugin_name):
- for plugin in self.active_plugins:
- if plugin.short_name == plugin_name:
- return plugin
- return None
-
- @log_calls('PluginManager')
- def extension_point(self, gui_extpoint_name, *args):
- '''
- Invokes all handlers (from plugins) for a particular extension point, but
- doesnt add it to collection for further processing.
- For example if you pass a message for encryption via extension point to a
- plugin, its undesired that the call is stored and replayed on activating the
- plugin. For example after an update.
-
- :param gui_extpoint_name: name of GUI extension point.
- :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
- '''
-
- self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name,
- *args)
-
- @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: 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
-
- :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: str
- :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:
- extension_points = list(self.gui_extension_points[gui_extpoint_name])
- for ext_point in extension_points:
- if args[0] in ext_point:
- self.gui_extension_points[gui_extpoint_name].remove(
- ext_point)
-
-
- @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]:
- try:
- handlers[0](*args)
- except Exception as e:
- log.warning('Error executing %s', handlers[0],
- exc_info=True)
-
- def _register_events_handlers_in_ged(self, plugin):
- for event_name, handler in plugin.events_handlers.items():
- 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.items():
- 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)
-
- def _remove_name_from_encryption_plugins(self, plugin):
- if plugin.encryption_name:
- del self.encryption_plugins[plugin.encryption_name]
-
- @log_calls('PluginManager')
- def activate_plugin(self, plugin):
- '''
- :param plugin: plugin to be activated
- :type plugin: class object of `GajimPlugin` subclass
- '''
- if not plugin.active and plugin.activatable:
-
- self._add_gui_extension_points_handlers_from_plugin(plugin)
- self._add_encryption_name_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)
-
- self.active_plugins.append(plugin)
- try:
- plugin.activate()
- except GajimPluginException as e:
- self.deactivate_plugin(plugin)
- raise GajimPluginActivateException(str(e))
- self._set_plugin_active_in_global_config(plugin)
- plugin.active = True
-
- 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.items():
- 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.items():
- 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:
- try:
- handler(*gui_extension_point_args)
- except Exception as e:
- log.warning('Error executing %s', handler,
- exc_info=True)
-
- self._remove_events_handler_from_ged(plugin)
- self._remove_network_events_from_nec(plugin)
- self._remove_name_from_encryption_plugins(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.items():
- self.gui_extension_points_handlers.setdefault(gui_extpoint_name,
- []).append(gui_extpoint_handlers)
-
- def _add_encryption_name_from_plugin(self, plugin):
- if plugin.encryption_name:
- self.encryption_plugins[plugin.encryption_name] = plugin
-
- @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.items():
- 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:
- try:
- handler(*gui_extension_point_args)
- except Exception as e:
- log.warning('Error executing %s', handler,
- exc_info=True)
-
-
- @log_calls('PluginManager')
- def _activate_all_plugins(self):
- '''
- Activates all plugins in `plugins`.
-
- Activated plugins are appended to `active_plugins` list.
- '''
- for plugin in self.plugins:
- try:
- self.activate_plugin(plugin)
- except GajimPluginActivateException:
- pass
-
- def _activate_all_plugins_from_global_config(self):
- for plugin in self.plugins:
- if self._plugin_is_active_in_global_config(plugin) and \
- plugin.activatable:
- try:
- self.activate_plugin(plugin)
- except GajimPluginActivateException:
- pass
-
- 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, scan_dirs=True, package=False):
- r'''
- Scans given directory for plugin classes.
-
- :param path: directory to scan for plugins
- :type path: str
-
- :param scan_dirs: folders inside path are processed as modules
- :type scan_dirs: boolean
-
- :param package: if path points to a single package folder
- :type package: boolean
-
- :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 zipped modules
- '''
- from plugins.plugins_i18n import _
- plugins_found = []
- conf = configparser.ConfigParser()
- fields = ('name', 'short_name', 'version', 'description', 'authors',
- 'homepage')
- if not os.path.isdir(path):
- return plugins_found
-
- if package:
- path, package_name = os.path.split(path)
- dir_list = [package_name]
- else:
- dir_list = os.listdir(path)
-
- sys.path.insert(0, path)
-
- for elem_name in dir_list:
- file_path = os.path.join(path, elem_name)
-
- if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'):
- module_name = os.path.splitext(elem_name)[0]
- elif os.path.isdir(file_path) and scan_dirs:
- module_name = elem_name
- file_path += os.path.sep
- else:
- continue
-
- manifest_path = os.path.join(os.path.dirname(file_path),
- 'manifest.ini')
- if scan_dirs and (not os.path.isfile(manifest_path)):
- continue
-
- # read metadata from manifest.ini
- conf.remove_section('info')
- with open(manifest_path, encoding='utf-8') as conf_file:
- try:
- conf.read_file(conf_file)
- except configparser.Error:
- log.warning(("Plugin {plugin} not loaded, error loading"
- " manifest").format(plugin=elem_name)
- , exc_info=True)
- continue
-
- min_v = conf.get('info', 'min_gajim_version', fallback=None)
- max_v = conf.get('info', 'max_gajim_version', fallback=None)
-
- gajim_v = gajim.config.get('version').split('-', 1)[0]
- gajim_v_cmp = parse_version(gajim_v)
-
- if min_v and gajim_v_cmp < parse_version(min_v):
- log.warning(('Plugin {plugin} not loaded, newer version of'
- 'gajim required: {gajim_v} < {min_v}').format(
- plugin=elem_name,
- gajim_v=gajim_v,
- min_v=min_v
- ))
- continue
- if max_v and gajim_v_cmp > parse_version(max_v):
- log.warning(('Plugin {plugin} not loaded, plugin incompatible '
- 'with current version of gajim: '
- '{gajim_v} > {max_v}').format(
- plugin=elem_name,
- gajim_v=gajim_v,
- max_v=max_v
- ))
- continue
-
- module = None
-
- try:
- if module_name in sys.modules:
- from imp import reload
- log.info('Reloading %s', module_name)
- module = reload(sys.modules[module_name])
- else:
- log.info('Loading %s', module_name)
- module = __import__(module_name)
- except Exception as error:
- log.warning(
- "While trying to load {plugin}, exception occurred".format(plugin=elem_name),
- exc_info=sys.exc_info()
- )
- continue
-
- if module is None:
- continue
-
- 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 not issubclass(module_attr, GajimPlugin) or \
- module_attr is GajimPlugin:
- continue
- log.debug('is subclass of GajimPlugin')
- module_attr.__path__ = os.path.abspath(
- os.path.dirname(file_path))
-
- for option in fields:
- if conf.get('info', option) is '':
- raise configparser.NoOptionError('field empty')
- if option == 'description':
- setattr(module_attr, option, _(conf.get('info', option)))
- continue
- setattr(module_attr, option, conf.get('info', option))
-
- plugins_found.append(module_attr)
- except TypeError:
- # set plugin localization
- try:
- module_attr._ = _
- except AttributeError:
- pass
- except configparser.NoOptionError:
- # all fields are required
- log.debug('%s : %s' % (module_attr_name,
- 'wrong manifest file. all fields are required!'))
- except configparser.NoSectionError:
- # info section are required
- log.debug('%s : %s' % (module_attr_name,
- 'wrong manifest file. info section are required!'))
- except configparser.MissingSectionHeaderError:
- # info section are required
- log.debug('%s : %s' % (module_attr_name,
- 'wrong manifest file. section are required!'))
-
- return plugins_found
-
- def install_from_zip(self, zip_filename, owerwrite=None):
- '''
- Install plugin from zip and return plugin
- '''
- try:
- zip_file = zipfile.ZipFile(zip_filename)
- except zipfile.BadZipfile:
- # it is not zip file
- raise PluginsystemError(_('Archive corrupted'))
- except IOError:
- raise PluginsystemError(_('Archive empty'))
-
- if zip_file.testzip():
- # CRC error
- raise PluginsystemError(_('Archive corrupted'))
-
- dirs = []
- manifest = None
- for filename in zip_file.namelist():
- if filename.startswith('.') or filename.startswith('/') or \
- ('/' not in filename):
- # members not safe
- raise PluginsystemError(_('Archive is malformed'))
- if filename.endswith('/') and filename.find('/', 0, -1) < 0:
- dirs.append(filename.strip('/'))
- if 'manifest.ini' in filename.split('/')[1]:
- manifest = True
- if not manifest:
- return
- if len(dirs) > 1:
- raise PluginsystemError(_('Archive is malformed'))
-
- base_dir, user_dir = gajim.PLUGINS_DIRS
- plugin_dir = os.path.join(user_dir, dirs[0])
-
- if os.path.isdir(plugin_dir):
- # Plugin dir already exists
- if not owerwrite:
- raise PluginsystemError(_('Plugin already exists'))
- self.remove_plugin(self.get_plugin_by_path(plugin_dir))
-
- zip_file.extractall(user_dir)
- zip_file.close()
-
- plugins = self.scan_dir_for_plugins(plugin_dir, package=True)
- if not plugins:
- return
- self.add_plugin(plugins[0])
- plugin = self.plugins[-1]
- return plugin
-
- def remove_plugin(self, plugin):
- '''
- Deactivate and remove plugin from `plugins` list
- '''
- def on_error(func, path, error):
- if func == os.path.islink:
- # if symlink
- os.unlink(path)
- return
- # access is denied or other
- raise PluginsystemError(error[1][1])
-
- if plugin:
- if plugin.active:
- self.deactivate_plugin(plugin)
- rmtree(plugin.__path__, False, on_error)
- self.plugins.remove(plugin)
- if self._plugin_has_entry_in_global_config(plugin):
- self._remove_plugin_entry_in_global_config(plugin)
- del sys.modules[plugin.__module__.split('.')[0]]
- del plugin.__module__.split('.')[-1]
- del plugin
-
- def get_plugin_by_path(self, plugin_dir):
- for plugin in self.plugins:
- if plugin.__path__ in plugin_dir:
- return plugin
diff --git a/src/plugins/plugins_i18n.py b/src/plugins/plugins_i18n.py
deleted file mode 100644
index d8faa60cc..000000000
--- a/src/plugins/plugins_i18n.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-## src/plugins/plugin_installer/plugins_i18n.py
-##
-## Copyright (C) 2010-2011 Denis Fomin <fominde AT gmail.com>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-import locale
-import gettext
-from os import path as os_path
-import os
-from common import gajim
-
-APP = 'gajim_plugins'
-plugins_locale_dir = os_path.join(gajim.PLUGINS_DIRS[1], 'locale')
-
-if os.name != 'nt':
- locale.setlocale(locale.LC_ALL, '')
- gettext.bindtextdomain(APP, plugins_locale_dir)
- gettext.textdomain(APP)
-
-try:
- t = gettext.translation(APP, plugins_locale_dir)
- _ = t.gettext
-except IOError:
- from common import i18n
- _ = gettext.gettext
diff --git a/src/profile_window.py b/src/profile_window.py
deleted file mode 100644
index 793db20ac..000000000
--- a/src/profile_window.py
+++ /dev/null
@@ -1,420 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/profile_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-# THIS FILE IS FOR **OUR** PROFILE (when we edit our INFO)
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import GObject
-from gi.repository import GLib
-import base64
-import mimetypes
-import os
-import time
-
-import gtkgui_helpers
-import dialogs
-import vcard
-
-from common import gajim
-from common import ged
-
-
-class ProfileWindow:
- """
- Class for our information window
- """
-
- def __init__(self, account, transient_for=None):
- self.xml = gtkgui_helpers.get_gtk_builder('profile_window.ui')
- self.window = self.xml.get_object('profile_window')
- self.window.set_transient_for(transient_for)
- self.progressbar = self.xml.get_object('progressbar')
- self.statusbar = self.xml.get_object('statusbar')
- self.context_id = self.statusbar.get_context_id('profile')
-
- self.account = account
- self.jid = gajim.get_jid_from_account(account)
-
- self.dialog = None
- self.avatar_mime_type = None
- self.avatar_encoded = None
- self.message_id = self.statusbar.push(self.context_id,
- _('Retrieving profile…'))
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
- self.remove_statusbar_timeout_id = None
-
- # Create Image for avatar button
- image = Gtk.Image()
- self.xml.get_object('PHOTO_button').set_image(image)
- self.xml.connect_signals(self)
- gajim.ged.register_event_handler('vcard-published', ged.GUI1,
- self._nec_vcard_published)
- gajim.ged.register_event_handler('vcard-not-published', ged.GUI1,
- self._nec_vcard_not_published)
- gajim.ged.register_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- self.window.show_all()
- self.xml.get_object('ok_button').grab_focus()
-
- def on_information_notebook_switch_page(self, widget, page, page_num):
- GLib.idle_add(self.xml.get_object('ok_button').grab_focus)
-
- def update_progressbar(self):
- self.progressbar.pulse()
- return True # loop forever
-
- def remove_statusbar(self, message_id):
- self.statusbar.remove(self.context_id, message_id)
- self.remove_statusbar_timeout_id = None
-
- def on_profile_window_destroy(self, widget):
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- if self.remove_statusbar_timeout_id is not None:
- GLib.source_remove(self.remove_statusbar_timeout_id)
- gajim.ged.remove_event_handler('vcard-published', ged.GUI1,
- self._nec_vcard_published)
- gajim.ged.remove_event_handler('vcard-not-published', ged.GUI1,
- self._nec_vcard_not_published)
- gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- del gajim.interface.instances[self.account]['profile']
- if self.dialog: # Image chooser dialog
- self.dialog.destroy()
-
- def on_profile_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_clear_button_clicked(self, widget):
- # empty the image
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- image.set_from_pixbuf(None)
- button.hide()
- text_button = self.xml.get_object('NOPHOTO_button')
- text_button.show()
- self.avatar_encoded = None
- self.avatar_mime_type = None
-
- def on_set_avatar_button_clicked(self, widget):
- def on_ok(widget, path_to_file):
- must_delete = False
- filesize = os.path.getsize(path_to_file) # in bytes
- invalid_file = False
- msg = ''
- if os.path.isfile(path_to_file):
- stat = os.stat(path_to_file)
- if stat[6] == 0:
- invalid_file = True
- msg = _('File is empty')
- else:
- invalid_file = True
- msg = _('File does not exist')
- if not invalid_file and filesize > 16384: # 16 kb
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(path_to_file)
- # get the image at 'notification size'
- # and hope that user did not specify in ACE crazy size
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
- 'tooltip')
- except GObject.GError as msg: # unknown format
- # msg should be string, not object instance
- msg = str(msg)
- invalid_file = True
- if invalid_file:
- if True: # keep identation
- dialogs.ErrorDialog(_('Could not load image'), msg,
- transient_for=self.window)
- return
- if filesize > 16384:
- if scaled_pixbuf:
- path_to_file = os.path.join(gajim.TMP,
- 'avatar_scaled.png')
- scaled_pixbuf.savev(path_to_file, 'png', [], [])
- must_delete = True
-
- with open(path_to_file, 'rb') as fd:
- data = fd.read()
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
- try:
- # rescale it
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- except AttributeError: # unknown format
- dialogs.ErrorDialog(_('Could not load image'),
- transient_for=self.window)
- return
- self.dialog.destroy()
- self.dialog = None
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- image.set_from_pixbuf(pixbuf)
- button.show()
- text_button = self.xml.get_object('NOPHOTO_button')
- text_button.hide()
- self.avatar_encoded = base64.b64encode(data).decode('utf-8')
- # returns None if unknown type
- self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
- if must_delete:
- try:
- os.remove(path_to_file)
- except OSError:
- gajim.log.debug('Cannot remove %s' % path_to_file)
-
- def on_clear(widget):
- self.dialog.destroy()
- self.dialog = None
- self.on_clear_button_clicked(widget)
-
- def on_cancel(widget):
- self.dialog.destroy()
- self.dialog = None
-
- if self.dialog:
- self.dialog.present()
- else:
- self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok,
- on_response_cancel = on_cancel, on_response_clear = on_clear)
-
- def on_PHOTO_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3 and self.avatar_encoded: # right click
- menu = Gtk.Menu()
-
- # Try to get pixbuf
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid,
- use_local=False)
-
- if pixbuf not in (None, 'ask'):
- nick = gajim.config.get_per('accounts', self.account, 'name')
- menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.jid, nick)
- menu.append(menuitem)
- # show clear
- menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
- menuitem.connect('activate', self.on_clear_button_clicked)
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.attach_to_widget(widget, None)
- menu.popup(None, None, None, None, event.button, event.time)
- elif event.button == 1: # left click
- self.on_set_avatar_button_clicked(widget)
-
- def on_BDAY_entry_focus_out_event(self, widget, event):
- txt = widget.get_text()
- if not txt:
- return
- try:
- time.strptime(txt, '%Y-%m-%d')
- except ValueError:
- if not widget.is_focus():
- pritext = _('Wrong date format')
- dialogs.ErrorDialog(pritext, _('Format of the date must be '
- 'YYYY-MM-DD'), transient_for=self.window)
- GLib.idle_add(lambda: widget.grab_focus())
- return True
-
- def set_value(self, entry_name, value):
- try:
- widget = self.xml.get_object(entry_name)
- val = widget.get_text()
- if val:
- value = val + ' / ' + value
- widget.set_text(value)
- except AttributeError:
- pass
-
- def set_values(self, vcard_):
- button = self.xml.get_object('PHOTO_button')
- image = button.get_image()
- text_button = self.xml.get_object('NOPHOTO_button')
- if not 'PHOTO' in vcard_:
- # set default image
- image.set_from_pixbuf(None)
- button.hide()
- text_button.show()
- for i in vcard_.keys():
- if i == 'PHOTO':
- pixbuf, self.avatar_encoded, self.avatar_mime_type = \
- vcard.get_avatar_pixbuf_encoded_mime(vcard_[i])
- if not pixbuf:
- image.set_from_pixbuf(None)
- button.hide()
- text_button.show()
- continue
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- image.set_from_pixbuf(pixbuf)
- button.show()
- text_button.hide()
- continue
- if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
- for entry in vcard_[i]:
- add_on = '_HOME'
- if 'WORK' in entry:
- add_on = '_WORK'
- for j in entry.keys():
- self.set_value(i + add_on + '_' + j + '_entry', entry[j])
- if isinstance(vcard_[i], dict):
- for j in vcard_[i].keys():
- self.set_value(i + '_' + j + '_entry', vcard_[i][j])
- else:
- if i == 'DESC':
- self.xml.get_object('DESC_textview').get_buffer().set_text(
- vcard_[i], len(vcard[i].encode('utf-8')))
- else:
- self.set_value(i + '_entry', vcard_[i])
- if self.update_progressbar_timeout_id is not None:
- if self.message_id:
- self.statusbar.remove(self.context_id, self.message_id)
- self.message_id = self.statusbar.push(self.context_id,
- _('Information received'))
- self.remove_statusbar_timeout_id = GLib.timeout_add_seconds(3,
- self.remove_statusbar, self.message_id)
- GLib.source_remove(self.update_progressbar_timeout_id)
- self.progressbar.hide()
- self.progressbar.set_fraction(0)
- self.update_progressbar_timeout_id = None
-
- def _nec_vcard_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.jid != self.jid:
- return
- self.set_values(obj.vcard_dict)
-
- def add_to_vcard(self, vcard_, entry, txt):
- """
- Add an information to the vCard dictionary
- """
- entries = entry.split('_')
- loc = vcard_
- if len(entries) == 3: # We need to use lists
- if entries[0] not in loc:
- loc[entries[0]] = []
- found = False
- for e in loc[entries[0]]:
- if entries[1] in e:
- e[entries[2]] = txt
- break
- else:
- loc[entries[0]].append({entries[1]: '', entries[2]: txt})
- return vcard_
- while len(entries) > 1:
- if entries[0] not in loc:
- loc[entries[0]] = {}
- loc = loc[entries[0]]
- del entries[0]
- loc[entries[0]] = txt
- return vcard_
-
- def make_vcard(self):
- """
- Make the vCard dictionary
- """
- entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
- 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
- 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
- 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
- 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
- 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
- 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
- vcard_ = {}
- for e in entries:
- txt = self.xml.get_object(e + '_entry').get_text()
- if txt != '':
- vcard_ = self.add_to_vcard(vcard_, e, txt)
-
- # DESC textview
- buff = self.xml.get_object('DESC_textview').get_buffer()
- start_iter = buff.get_start_iter()
- end_iter = buff.get_end_iter()
- txt = buff.get_text(start_iter, end_iter, False)
- if txt != '':
- vcard_['DESC'] = txt
-
- # Avatar
- if self.avatar_encoded:
- vcard_['PHOTO'] = {'BINVAL': self.avatar_encoded}
- if self.avatar_mime_type:
- vcard_['PHOTO']['TYPE'] = self.avatar_mime_type
- return vcard_
-
- def on_ok_button_clicked(self, widget):
- if self.update_progressbar_timeout_id:
- # Operation in progress
- return
- if gajim.connections[self.account].connected < 2:
- dialogs.ErrorDialog(_('You are not connected to the server'),
- _('Without a connection, you can not publish your contact '
- 'information.'), transient_for=self.window)
- return
- vcard_ = self.make_vcard()
- nick = ''
- if 'NICKNAME' in vcard_:
- nick = vcard_['NICKNAME']
- gajim.connections[self.account].send_nickname(nick)
- if nick == '':
- nick = gajim.config.get_per('accounts', self.account, 'name')
- gajim.nicks[self.account] = nick
- gajim.connections[self.account].send_vcard(vcard_)
- self.message_id = self.statusbar.push(self.context_id,
- _('Sending profile…'))
- self.progressbar.show()
- self.update_progressbar_timeout_id = GLib.timeout_add(100,
- self.update_progressbar)
-
- def _nec_vcard_published(self, obj):
- if obj.conn.name != self.account:
- return
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- self.update_progressbar_timeout_id = None
- self.window.destroy()
-
- def _nec_vcard_not_published(self, obj):
- if obj.conn.name != self.account:
- return
- if self.message_id:
- self.statusbar.remove(self.context_id, self.message_id)
- self.message_id = self.statusbar.push(self.context_id,
- _('Information NOT published'))
- self.remove_statusbar_timeout_id = GLib.timeout_add_seconds(3,
- self.remove_statusbar, self.message_id)
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- self.progressbar.set_fraction(0)
- self.update_progressbar_timeout_id = None
- dialogs.InformationDialog(_('vCard publication failed'),
- _('There was an error while publishing your personal information, '
- 'try again later.'), transient_for=self.window)
-
- def on_cancel_button_clicked(self, widget):
- self.window.destroy()
diff --git a/src/pylint.rc b/src/pylint.rc
deleted file mode 100644
index 7816d3659..000000000
--- a/src/pylint.rc
+++ /dev/null
@@ -1,310 +0,0 @@
-# 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=' '
diff --git a/src/remote_control.py b/src/remote_control.py
deleted file mode 100644
index 7209b967e..000000000
--- a/src/remote_control.py
+++ /dev/null
@@ -1,948 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/remote_control.py
-##
-## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com>
-## Dimitur Kirov <dkirov AT gmail.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import GLib
-from gi.repository import Gtk
-import os
-import base64
-import mimetypes
-
-from common import gajim
-from common import helpers
-from time import time
-from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
-from common import ged
-from common.connection_handlers_events import MessageOutgoingEvent
-from common.connection_handlers_events import GcMessageOutgoingEvent
-
-
-from common import dbus_support
-if dbus_support.supported:
- import dbus
- if dbus_support:
- import dbus.service
-
-INTERFACE = 'org.gajim.dbus.RemoteInterface'
-OBJ_PATH = '/org/gajim/dbus/RemoteObject'
-SERVICE = 'org.gajim.dbus'
-
-# 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, 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 Remote:
- def __init__(self):
- self.signal_object = None
- session_bus = dbus_support.session_bus.SessionBus()
-
- 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)
- gajim.ged.register_event_handler('roster-info', ged.POSTGUI,
- self.on_roster_info)
- gajim.ged.register_event_handler('presence-received', ged.POSTGUI,
- self.on_presence_received)
- gajim.ged.register_event_handler('subscribe-presence-received',
- ged.POSTGUI, self.on_subscribe_presence_received)
- gajim.ged.register_event_handler('subscribed-presence-received',
- ged.POSTGUI, self.on_subscribed_presence_received)
- gajim.ged.register_event_handler('unsubscribed-presence-received',
- ged.POSTGUI, self.on_unsubscribed_presence_received)
- gajim.ged.register_event_handler('gc-message-received',
- ged.POSTGUI, self.on_gc_message_received)
- gajim.ged.register_event_handler('our-show', ged.POSTGUI,
- self.on_our_status)
- gajim.ged.register_event_handler('account-created', ged.POSTGUI,
- self.on_account_created)
- gajim.ged.register_event_handler('vcard-received', ged.POSTGUI,
- self.on_vcard_received)
- gajim.ged.register_event_handler('chatstate-received', ged.POSTGUI,
- self.on_chatstate_received)
- gajim.ged.register_event_handler('message-sent', ged.POSTGUI,
- self.on_message_sent)
-
- def on_chatstate_received(self, obj):
- self.raise_signal('ChatState', (obj.conn.name, [
- obj.jid, obj.fjid, obj.stanza, obj.resource, obj.chatstate]))
-
- def on_message_sent(self, obj):
- try:
- chatstate = obj.chatstate
- except AttributeError:
- chatstate = ""
- self.raise_signal('MessageSent', (obj.conn.name, [
- obj.jid, obj.message, obj.keyID, chatstate]))
-
- 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 on_roster_info(self, obj):
- self.raise_signal('RosterInfo', (obj.conn.name, [obj.jid, obj.nickname,
- obj.sub, obj.ask, obj.groups]))
-
- def on_presence_received(self, obj):
- event = None
- if obj.old_show < 2 and obj.new_show > 1:
- event = 'ContactPresence'
- elif obj.old_show > 1 and obj.new_show < 2:
- event = 'ContactAbsence'
- elif obj.new_show > 1:
- event = 'ContactStatus'
- if event:
- self.raise_signal(event, (obj.conn.name, [obj.jid, obj.show,
- obj.status, obj.resource, obj.prio, obj.keyID, obj.timestamp,
- obj.contact_nickname]))
-
- def on_subscribe_presence_received(self, obj):
- self.raise_signal('Subscribe', (obj.conn.name, [obj.jid, obj.status,
- obj.user_nick]))
-
- def on_subscribed_presence_received(self, obj):
- self.raise_signal('Subscribed', (obj.conn.name, [obj.jid,
- obj.resource]))
-
- def on_unsubscribed_presence_received(self, obj):
- self.raise_signal('Unsubscribed', (obj.conn.name, obj.jid))
-
- def on_gc_message_received(self, obj):
- if not hasattr(obj, 'needs_highlight'):
- # event has not been handled at GUI level
- return
- self.raise_signal('GCMessage', (obj.conn.name, [obj.fjid, obj.msgtxt,
- obj.timestamp, obj.has_timestamp, obj.xhtml_msgtxt, obj.status_code,
- obj.displaymarking, obj.captcha_form, obj.needs_highlight]))
-
- def on_our_status(self, obj):
- self.raise_signal('AccountPresence', (obj.show, obj.conn.name))
-
- def on_account_created(self, obj):
- self.raise_signal('NewAccount', (obj.conn.name, obj.account_info))
-
- def on_vcard_received(self, obj):
- self.raise_signal('VcardInfo', (obj.conn.name, obj.vcard_dict))
-
- def raise_signal(self, signal, arg):
- if self.signal_object:
- try:
- getattr(self.signal_object, signal)(get_dbus_struct(arg))
- except UnicodeDecodeError:
- pass # ignore error when we fail to announce on dbus
-
-
-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 EntityTime(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
-
- @dbus.service.signal(INTERFACE, signature='av')
- def ChatState(self, account_and_array):
- pass
-
- @dbus.service.signal(INTERFACE, signature='av')
- def MessageSent(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):
- """
- Return 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):
- """
- Return 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.startswith('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]
- sessions = connection.get_sessions(jid)
- if sessions:
- session = sessions[0]
- else:
- session = connection.make_new_session(jid)
- ctrl = gajim.interface.msg_win_mgr.search_control(jid,
- connected_account)
- if ctrl:
- ctrl.send_message(message)
- else:
- gajim.nec.push_outgoing_event(MessageOutgoingEvent(None, account=connected_account, jid=jid, message=message, keyID=keyID, type_=type_, control=ctrl))
-
- 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, 'normal', 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]
- gajim.nec.push_outgoing_event(GcMessageOutgoingEvent(None,
- account=connected_account, jid=room_jid, message=message))
- return DBUS_BOOLEAN(True)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
- def open_chat(self, jid, account, message):
- """
- Shows the tabbed window for new message to 'jid', using account (optional)
- 'account'
- """
- if not jid:
- raise dbus_support.MissingArgument()
- jid = self._get_real_jid(jid, account)
- try:
- jid = helpers.parse_jid(jid)
- except Exception:
- # Jid is not conform, ignore it
- return DBUS_BOOLEAN(False)
-
- minimized_control = None
- 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:
- minimized_control = \
- jid in gajim.interface.minimized_controls[acct]
- 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 minimized_control:
- gajim.interface.roster.on_groupchat_maximized(None, jid,
- connected_account)
-
- if connected_account:
- gajim.interface.new_chat_from_jid(connected_account, jid, message)
- # 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(Gtk.get_current_event_time())
- 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'):
- status = ''
- if account:
- if not status:
- if account not in gajim.connections:
- return DBUS_BOOLEAN(False)
- status = gajim.SHOW_LIST[gajim.connections[account].connected]
- GLib.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
- if status:
- status_ = status
- else:
- if acc not in gajim.connections:
- continue
- status_ = gajim.SHOW_LIST[gajim.connections[acc].connected]
- GLib.idle_add(gajim.interface.roster.send_status, acc, status_,
- message)
- return DBUS_BOOLEAN(False)
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
- def set_priority(self, prio, account):
- """
- set_priority(prio, account). Account is optional - if not specified
- priority is changed for all accounts. That are synced with global status
- """
- if account:
- gajim.config.set_per('accounts', account, 'priority', prio)
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- status = gajim.connections[account].status
- GLib.idle_add(gajim.connections[account].change_status, show,
- status)
- else:
- # account not specified, so change prio of all accounts
- for acc in gajim.contacts.get_accounts():
- if not gajim.account_is_connected(acc):
- continue
- if not gajim.config.get_per('accounts', acc,
- 'sync_with_global_status'):
- continue
- gajim.config.set_per('accounts', acc, 'priority', prio)
- show = gajim.SHOW_LIST[gajim.connections[acc].connected]
- status = gajim.connections[acc].status
- GLib.idle_add(gajim.connections[acc].change_status, show,
- status)
-
- @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():
- account, jid, event = gajim.events.get_first_systray_event()
- if not event:
- return
- gajim.interface.handle_event(account, jid, event.type_)
-
- @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, str):
- jid = str(jid)
- if not jid:
- raise dbus_support.MissingArgument()
- jid = self._get_real_jid(jid)
-
- cached_vcard = list(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 account in gajim.connections:
- # 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(str(con.priority))
- result['resource'] = DBUS_STRING(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):
- """
- Show/hide the roster window
- """
- win = gajim.interface.roster.window
- if win.get_property('visible'):
- GLib.idle_add(win.hide)
- else:
- win.present()
- # preserve the 'steal focus preservation'
- if self._is_first():
- win.window.focus(Gtk.get_current_event_time())
- else:
- win.window.focus(int(time()))
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='')
- def show_roster(self):
- """
- Show the roster window
- """
- win = gajim.interface.roster.window
- win.present()
- # preserve the 'steal focus preservation'
- if self._is_first():
- win.window.focus(Gtk.get_current_event_time())
- else:
- win.window.focus(int(time()))
-
- @dbus.service.method(INTERFACE, in_signature='', out_signature='')
- def toggle_ipython(self):
- """
- Show/hide the ipython window
- """
- win = gajim.ipython_window
- if win:
- if win.window.is_visible():
- GLib.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)
- 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:
- 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(str(xml))
- else:
- for acc in gajim.contacts.get_accounts():
- gajim.connections[acc].send_stanza(str(xml))
-
- @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
- def change_avatar(self, picture, account):
- filesize = os.path.getsize(picture)
- invalid_file = False
- if os.path.isfile(picture):
- stat = os.stat(picture)
- if stat[6] == 0:
- invalid_file = True
- else:
- invalid_file = True
- if not invalid_file and filesize < 16384:
- with open(picture, 'rb') as fd:
- data = fd.read()
- avatar = base64.b64encode(data).decode('utf-8')
- avatar_mime_type = mimetypes.guess_type(picture)[0]
- vcard={}
- vcard['PHOTO'] = {'BINVAL': avatar}
- if avatar_mime_type:
- vcard['PHOTO']['TYPE'] = avatar_mime_type
- if account:
- gajim.connections[account].send_vcard(vcard)
- else:
- for acc in gajim.connections:
- gajim.connections[acc].send_vcard(vcard)
-
- @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):
- if not gajim.connections[acct].is_zeroconf:
- account = acct
- break
- if not account:
- return
-
- if gajim.connections[account].is_zeroconf:
- # zeroconf not support groupchats
- 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)
diff --git a/src/roster_window.py b/src/roster_window.py
deleted file mode 100644
index 9c0a2a823..000000000
--- a/src/roster_window.py
+++ /dev/null
@@ -1,6091 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/roster_window.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
-## Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## James Newton <redshodan AT gmail.com>
-## Tomasz Melcer <liori AT exroot.org>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GdkPixbuf
-from gi.repository import Pango
-from gi.repository import GObject
-from gi.repository import GLib
-from gi.repository import Gio
-import os
-import time
-import locale
-
-from enum import IntEnum, unique
-
-import common.sleepy
-import history_window
-import dialogs
-import vcard
-import config
-import disco
-import gtkgui_helpers
-import gui_menu_builder
-import cell_renderer_image
-import tooltips
-import message_control
-import adhoc_commands
-
-from common import gajim
-from common import helpers
-from common.exceptions import GajimGeneralException
-from common import i18n
-from common import location_listener
-from common import ged
-from common import dbus_support
-from message_window import MessageWindowMgr
-from nbxmpp.protocol import NS_FILE, NS_ROSTERX, NS_CONFERENCE
-
-
-@unique
-class Column(IntEnum):
- IMG = 0 # image to show state (online, new message etc)
- NAME = 1 # cellrenderer text that holds contact nickame
- TYPE = 2 # account, group or contact?
- JID = 3 # the jid of the row
- ACCOUNT = 4 # cellrenderer text that holds account name
- MOOD_PIXBUF = 5
- ACTIVITY_PIXBUF = 6
- TUNE_PIXBUF = 7
- LOCATION_PIXBUF = 8
- AVATAR_PIXBUF = 9 # avatar_pixbuf
- PADLOCK_PIXBUF = 10 # use for account row only
-
-empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
-empty_pixbuf.fill(0xffffff00)
-
-
-class RosterWindow:
- """
- Class for main window of the GTK+ interface
- """
-
- def _get_account_iter(self, name, model=None):
- """
- Return the Gtk.TreeIter of the given account or None if not found
-
- Keyword arguments:
- name -- the account name
- model -- the data model (default TreeFilterModel)
- """
- if model is None:
- model = self.modelfilter
- if model is None:
- return
-
- if self.regroup:
- name = 'MERGED'
- if name not in self._iters:
- return None
- it = self._iters[name]['account']
-
- if model == self.model or it is None:
- return it
- try:
- (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
- if ok:
- return it
- return None
- except RuntimeError:
- return None
-
-
- def _get_group_iter(self, name, account, model=None):
- """
- Return the Gtk.TreeIter of the given group or None if not found
-
- Keyword arguments:
- name -- the group name
- account -- the account name
- model -- the data model (default TreeFilterModel)
- """
- if model is None:
- model = self.modelfilter
- if model is None:
- return
-
- if self.regroup:
- account = 'MERGED'
-
- if account not in self._iters:
- return None
- if name not in self._iters[account]['groups']:
- return None
-
- it = self._iters[account]['groups'][name]
- if model == self.model or it is None:
- return it
- try:
- (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
- if ok:
- return it
- return None
- except RuntimeError:
- return None
-
-
- def _get_self_contact_iter(self, account, model=None):
- """
- Return the Gtk.TreeIter of SelfContact or None if not found
-
- Keyword arguments:
- account -- the account of SelfContact
- model -- the data model (default TreeFilterModel)
- """
- jid = gajim.get_jid_from_account(account)
- its = self._get_contact_iter(jid, account, model=model)
- if its:
- return its[0]
- return None
-
-
- def _get_contact_iter(self, jid, account, contact=None, model=None):
- """
- Return a list of Gtk.TreeIter of the given contact
-
- Keyword arguments:
- jid -- the jid without resource
- account -- the account
- contact -- the contact (default None)
- model -- the data model (default TreeFilterModel)
- """
- if model is None:
- model = self.modelfilter
- # when closing Gajim model can be none (async pbs?)
- if model is None:
- return []
-
- if not contact:
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if not contact:
- # We don't know this contact
- return []
-
- if account not in self._iters:
- return []
-
- if jid not in self._iters[account]['contacts']:
- return []
-
- its = self._iters[account]['contacts'][jid]
-
- if not its:
- return []
-
- if model == self.model:
- return its
-
- its2 = []
- for it in its:
- try:
- (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
- if ok:
- its2.append(it)
- except RuntimeError:
- pass
- return its2
-
-
- def _iter_is_separator(self, model, titer, dummy):
- """
- Return True if the given iter is a separator
-
- Keyword arguments:
- model -- the data model
- iter -- the Gtk.TreeIter to test
- """
- if model[titer][0] == 'SEPARATOR':
- return True
- return False
-
-
-#############################################################################
-### Methods for adding and removing roster window items
-#############################################################################
-
- def add_account(self, account):
- """
- Add account to roster and draw it. Do nothing if it is already in
- """
- if self._get_account_iter(account):
- # Will happen on reconnect or for merged accounts
- return
-
- if self.regroup:
- # Merged accounts view
- show = helpers.get_global_show()
- it = self.model.append(None, [
- gajim.interface.jabber_state_images['16'][show],
- _('Merged accounts'), 'account', '', 'all', None, None, None,
- None, None, None] + [None] * self.nb_ext_renderers)
- self._iters['MERGED']['account'] = it
- else:
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- our_jid = gajim.get_jid_from_account(account)
-
- tls_pixbuf = None
- if gajim.account_is_securely_connected(account):
- tls_pixbuf = gtkgui_helpers.get_icon_pixmap('changes-prevent', 16)
- # the only way to create a pixbuf from stock
-# tls_pixbuf = self.window.render_icon_pixbuf(
-# Gtk.STOCK_DIALOG_AUTHENTICATION, Gtk.IconSize.MENU)
-
- it = self.model.append(None, [
- gajim.interface.jabber_state_images['16'][show],
- GLib.markup_escape_text(account), 'account', our_jid,
- account, None, None, None, None, None, tls_pixbuf] +
- [None] * self.nb_ext_renderers)
- self._iters[account]['account'] = it
-
- self.draw_account(account)
-
-
- def add_account_contacts(self, account, improve_speed=True,
- draw_contacts=True):
- """
- Add all contacts and groups of the given account to roster, draw them
- and account
- """
- if improve_speed:
- self._before_fill()
- jids = gajim.contacts.get_jid_list(account)
-
- for jid in jids:
- self.add_contact(jid, account)
-
- if draw_contacts:
- # Do not freeze the GUI when drawing the contacts
- if jids:
- # Overhead is big, only invoke when needed
- self._idle_draw_jids_of_account(jids, account)
-
- # Draw all known groups
- for group in gajim.groups[account]:
- self.draw_group(group, account)
- self.draw_account(account)
-
- if improve_speed:
- self._after_fill()
-
- def _add_group_iter(self, account, group):
- """
- Add a group iter in roster and return the newly created iter
- """
- if self.regroup:
- account_group = 'MERGED'
- else:
- account_group = account
- delimiter = gajim.connections[account].nested_group_delimiter
- group_splited = group.split(delimiter)
- parent_group = delimiter.join(group_splited[:-1])
- if len(group_splited) > 1 and parent_group in self._iters[account_group]['groups']:
- iter_parent = self._iters[account_group]['groups'][parent_group]
- elif parent_group:
- iter_parent = self._add_group_iter(account, parent_group)
- if parent_group not in gajim.groups[account]:
- if account + parent_group in self.collapsed_rows:
- is_expanded = False
- else:
- is_expanded = True
- gajim.groups[account][parent_group] = {'expand': is_expanded}
- else:
- iter_parent = self._get_account_iter(account, self.model)
- iter_group = self.model.append(iter_parent,
- [gajim.interface.jabber_state_images['16']['closed'],
- GLib.markup_escape_text(group), 'group', group, account, None,
- None, None, None, None, None] + [None] * self.nb_ext_renderers)
- self.draw_group(group, account)
- self._iters[account_group]['groups'][group] = iter_group
- return iter_group
-
- def _add_entity(self, contact, account, groups=None,
- big_brother_contact=None, big_brother_account=None):
- """
- Add the given contact to roster data model
-
- Contact is added regardless if he is already in roster or not. Return
- list of newly added iters.
-
- Keyword arguments:
- contact -- the contact to add
- account -- the contacts account
- groups -- list of groups to add the contact to.
- (default groups in contact.get_shown_groups()).
- Parameter ignored when big_brother_contact is specified.
- big_brother_contact -- if specified contact is added as child
- big_brother_contact. (default None)
- """
- added_iters = []
- if big_brother_contact:
- # Add contact under big brother
-
- parent_iters = self._get_contact_iter(
- big_brother_contact.jid, big_brother_account,
- big_brother_contact, self.model)
- assert len(parent_iters) > 0, 'Big brother is not yet in roster!'
-
- # Do not confuse get_contact_iter: Sync groups of family members
- contact.groups = big_brother_contact.groups[:]
-
- for child_iter in parent_iters:
- it = self.model.append(child_iter, [None,
- contact.get_shown_name(), 'contact', contact.jid, account,
- None, None, None, None, None, None] + \
- [None] * self.nb_ext_renderers)
- added_iters.append(it)
- if contact.jid in self._iters[account]['contacts']:
- self._iters[account]['contacts'][contact.jid].append(it)
- else:
- self._iters[account]['contacts'][contact.jid] = [it]
- else:
- # We are a normal contact. Add us to our groups.
- if not groups:
- groups = contact.get_shown_groups()
- for group in groups:
- child_iterG = self._get_group_iter(group, account,
- model=self.model)
- if not child_iterG:
- # Group is not yet in roster, add it!
- child_iterG = self._add_group_iter(account, group)
-
- if contact.is_transport():
- typestr = 'agent'
- elif contact.is_groupchat():
- typestr = 'groupchat'
- else:
- typestr = 'contact'
-
- # we add some values here. see draw_contact
- # for more
- i_ = self.model.append(child_iterG, [None,
- contact.get_shown_name(), typestr, contact.jid, account,
- None, None, None, None, None, None] + \
- [None] * self.nb_ext_renderers)
- added_iters.append(i_)
- if contact.jid in self._iters[account]['contacts']:
- self._iters[account]['contacts'][contact.jid].append(i_)
- else:
- self._iters[account]['contacts'][contact.jid] = [i_]
-
- # Restore the group expand state
- if account + group in self.collapsed_rows:
- is_expanded = False
- else:
- is_expanded = True
- if group not in gajim.groups[account]:
- gajim.groups[account][group] = {'expand': is_expanded}
-
- assert len(added_iters), '%s has not been added to roster!' % \
- contact.jid
- return added_iters
-
- def _remove_entity(self, contact, account, groups=None):
- """
- Remove the given contact from roster data model
-
- Empty groups after contact removal are removed too.
- Return False if contact still has children and deletion was
- not performed.
- Return True on success.
-
- Keyword arguments:
- contact -- the contact to add
- account -- the contacts account
- groups -- list of groups to remove the contact from.
- """
- iters = self._get_contact_iter(contact.jid, account, contact,
- self.model)
- assert iters, '%s shall be removed but is not in roster' % contact.jid
-
- parent_iter = self.model.iter_parent(iters[0])
- parent_type = self.model[parent_iter][Column.TYPE]
-
- if groups:
- # Only remove from specified groups
- all_iters = iters[:]
- group_iters = [self._get_group_iter(group, account)
- for group in groups]
- iters = [titer for titer in all_iters
- if self.model.iter_parent(titer) in group_iters]
-
- iter_children = self.model.iter_children(iters[0])
-
- if iter_children:
- # We have children. We cannot be removed!
- return False
- # Remove us and empty groups from the model
- for i in iters:
- assert self.model[i][Column.JID] == contact.jid and \
- self.model[i][Column.ACCOUNT] == account, \
- "Invalidated iters of %s" % contact.jid
-
- parent_i = self.model.iter_parent(i)
- parent_type = self.model[parent_i][Column.TYPE]
-
- to_be_removed = i
- while parent_type == 'group' and \
- self.model.iter_n_children(parent_i) == 1:
- if self.regroup:
- account_group = 'MERGED'
- else:
- account_group = account
- group = self.model[parent_i][Column.JID]
- if group in gajim.groups[account]:
- del gajim.groups[account][group]
- to_be_removed = parent_i
- del self._iters[account_group]['groups'][group]
- parent_i = self.model.iter_parent(parent_i)
- parent_type = self.model[parent_i][Column.TYPE]
- self.model.remove(to_be_removed)
-
- del self._iters[account]['contacts'][contact.jid]
- return True
-
- def _add_metacontact_family(self, family, account):
- """
- Add the give Metacontact family to roster data model
-
- Add Big Brother to his groups and all others under him.
- Return list of all added (contact, account) tuples with
- Big Brother as first element.
-
- Keyword arguments:
- family -- the family, see Contacts.get_metacontacts_family()
- """
-
- nearby_family, big_brother_jid, big_brother_account = \
- self._get_nearby_family_and_big_brother(family, account)
- if not big_brother_jid:
- return []
- big_brother_contact = gajim.contacts.get_first_contact_from_jid(
- big_brother_account, big_brother_jid)
-
- assert len(self._get_contact_iter(big_brother_jid,
- big_brother_account, big_brother_contact, self.model)) == 0, \
- 'Big brother %s already in roster\n Family: %s' \
- % (big_brother_jid, family)
- self._add_entity(big_brother_contact, big_brother_account)
-
- brothers = []
- # Filter family members
- for data in nearby_family:
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(
- _account, _jid)
-
- if not _contact or _contact == big_brother_contact:
- # Corresponding account is not connected
- # or brother already added
- continue
-
- assert len(self._get_contact_iter(_jid, _account,
- _contact, self.model)) == 0, \
- "%s already in roster.\n Family: %s" % (_jid, nearby_family)
- self._add_entity(_contact, _account,
- big_brother_contact = big_brother_contact,
- big_brother_account = big_brother_account)
- brothers.append((_contact, _account))
-
- brothers.insert(0, (big_brother_contact, big_brother_account))
- return brothers
-
- def _remove_metacontact_family(self, family, account):
- """
- Remove the given Metacontact family from roster data model
-
- See Contacts.get_metacontacts_family() and
- RosterWindow._remove_entity()
- """
- nearby_family = self._get_nearby_family_and_big_brother(
- family, account)[0]
-
- # Family might has changed (actual big brother not on top).
- # Remove childs first then big brother
- family_in_roster = False
- for data in nearby_family:
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)
-
- iters = self._get_contact_iter(_jid, _account, _contact, self.model)
- if not iters or not _contact:
- # Family might not be up to date.
- # Only try to remove what is actually in the roster
- continue
- assert iters, '%s shall be removed but is not in roster \
- \n Family: %s' % (_jid, family)
-
- family_in_roster = True
-
- parent_iter = self.model.iter_parent(iters[0])
- parent_type = self.model[parent_iter][Column.TYPE]
-
- if parent_type != 'contact':
- # The contact on top
- old_big_account = _account
- old_big_contact = _contact
- old_big_jid = _jid
- continue
-
- ok = self._remove_entity(_contact, _account)
- assert ok, '%s was not removed' % _jid
- assert len(self._get_contact_iter(_jid, _account, _contact,
- self.model)) == 0, '%s is removed but still in roster' % _jid
-
- if not family_in_roster:
- return False
-
- assert old_big_jid, 'No Big Brother in nearby family %s (Family: %s)' %\
- (nearby_family, family)
- iters = self._get_contact_iter(old_big_jid, old_big_account,
- old_big_contact, self.model)
- assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
- old_big_jid
- assert not self.model.iter_children(iters[0]), \
- 'Old Big Brother %s still has children' % old_big_jid
-
- ok = self._remove_entity(old_big_contact, old_big_account)
- assert ok, "Old Big Brother %s not removed" % old_big_jid
- assert len(self._get_contact_iter(old_big_jid, old_big_account,
- old_big_contact, self.model)) == 0, \
- 'Old Big Brother %s is removed but still in roster' % old_big_jid
-
- return True
-
- def _recalibrate_metacontact_family(self, family, account):
- """
- Regroup metacontact family if necessary
- """
-
- brothers = []
- nearby_family, big_brother_jid, big_brother_account = \
- self._get_nearby_family_and_big_brother(family, account)
- big_brother_contact = gajim.contacts.get_contact(big_brother_account,
- big_brother_jid)
- child_iters = self._get_contact_iter(big_brother_jid,
- big_brother_account, model=self.model)
- if child_iters:
- parent_iter = self.model.iter_parent(child_iters[0])
- parent_type = self.model[parent_iter][Column.TYPE]
-
- # Check if the current BigBrother has even been before.
- if parent_type == 'contact':
- for data in nearby_family:
- # recalibrate after remove to keep highlight
- if data['jid'] in gajim.to_be_removed[data['account']]:
- return
-
- self._remove_metacontact_family(family, account)
- brothers = self._add_metacontact_family(family, account)
-
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
-
- # Check is small brothers are under the big brother
- for child in nearby_family:
- _jid = child['jid']
- _account = child['account']
- if _account == big_brother_account and _jid == big_brother_jid:
- continue
- child_iters = self._get_contact_iter(_jid, _account,
- model=self.model)
- if not child_iters:
- continue
- parent_iter = self.model.iter_parent(child_iters[0])
- parent_type = self.model[parent_iter][Column.TYPE]
- if parent_type != 'contact':
- _contact = gajim.contacts.get_contact(_account, _jid)
- self._remove_entity(_contact, _account)
- self._add_entity(_contact, _account, groups=None,
- big_brother_contact=big_brother_contact,
- big_brother_account=big_brother_account)
-
- def _get_nearby_family_and_big_brother(self, family, account):
- return gajim.contacts.get_nearby_family_and_big_brother(family, account)
-
- def _add_self_contact(self, account):
- """
- Add account's SelfContact to roster and draw it and the account
-
- Return the SelfContact contact instance
- """
- jid = gajim.get_jid_from_account(account)
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
-
- assert len(self._get_contact_iter(jid, account, contact,
- self.model)) == 0, 'Self contact %s already in roster' % jid
-
- child_iterA = self._get_account_iter(account, self.model)
- self._iters[account]['contacts'][jid] = [self.model.append(child_iterA,
- [None, gajim.nicks[account], 'self_contact', jid, account, None,
- None, None, None, None, None] + [None] * self.nb_ext_renderers)]
-
- self.draw_completely(jid, account)
- self.draw_account(account)
-
- return contact
-
- def redraw_metacontacts(self, account):
- for family in gajim.contacts.iter_metacontacts_families(account):
- self._recalibrate_metacontact_family(family, account)
-
- def add_contact(self, jid, account):
- """
- Add contact to roster and draw him
-
- Add contact to all its group and redraw the groups, the contact and the
- account. If it's a Metacontact, add and draw the whole family.
- Do nothing if the contact is already in roster.
-
- Return the added contact instance. If it is a Metacontact return
- Big Brother.
-
- Keyword arguments:
- jid -- the contact's jid or SelfJid to add SelfContact
- account -- the corresponding account.
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if len(self._get_contact_iter(jid, account, contact, self.model)):
- # If contact already in roster, do nothing
- return
-
- if jid == gajim.get_jid_from_account(account):
- show_self_contact = gajim.config.get('show_self_contact')
- if show_self_contact == 'never':
- return
- if (contact.resource != gajim.connections[account].server_resource \
- and show_self_contact == 'when_other_resource') or \
- show_self_contact == 'always':
- return self._add_self_contact(account)
- return
-
- is_observer = contact.is_observer()
- if is_observer:
- # if he has a tag, remove it
- gajim.contacts.remove_metacontact(account, jid)
-
- # Add contact to roster
- family = gajim.contacts.get_metacontacts_family(account, jid)
- contacts = []
- if family:
- # We have a family. So we are a metacontact.
- # Add all family members that we shall be grouped with
- if self.regroup:
- # remove existing family members to regroup them
- self._remove_metacontact_family(family, account)
- contacts = self._add_metacontact_family(family, account)
- else:
- # We are a normal contact
- contacts = [(contact, account), ]
- self._add_entity(contact, account)
-
- # Draw the contact and its groups contact
- if not self.starting:
- for c, acc in contacts:
- self.draw_completely(c.jid, acc)
- for group in contact.get_shown_groups():
- self.draw_group(group, account)
- self._adjust_group_expand_collapse_state(group, account)
- self.draw_account(account)
-
- return contacts[0][0] # it's contact/big brother with highest priority
-
- def remove_contact(self, jid, account, force=False, backend=False):
- """
- Remove contact from roster
-
- Remove contact from all its group. Remove empty groups or redraw
- otherwise.
- Draw the account.
- If it's a Metacontact, remove the whole family.
- Do nothing if the contact is not in roster.
-
- Keyword arguments:
- jid -- the contact's jid or SelfJid to remove SelfContact
- account -- the corresponding account.
- force -- remove contact even it has pending evens (Default False)
- backend -- also remove contact instance (Default False)
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if not contact:
- return
-
- if not force and self.contact_has_pending_roster_events(contact,
- account):
- return False
-
- iters = self._get_contact_iter(jid, account, contact, self.model)
- if iters:
- # no more pending events
- # Remove contact from roster directly
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # We have a family. So we are a metacontact.
- self._remove_metacontact_family(family, account)
- else:
- self._remove_entity(contact, account)
-
- old_grps = []
- if backend:
- if not gajim.interface.msg_win_mgr.get_control(jid, account) or \
- force:
- # If a window is still opened: don't remove contact instance
- # Remove contact before redrawing, otherwise the old
- # numbers will still be show
- gajim.contacts.remove_jid(account, jid, remove_meta=True)
- if iters:
- rest_of_family = [data for data in family
- if account != data['account'] or jid != data['jid']]
- if rest_of_family:
- # reshow the rest of the family
- brothers = self._add_metacontact_family(rest_of_family,
- account)
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
- else:
- for c in gajim.contacts.get_contacts(account, jid):
- c.sub = 'none'
- c.show = 'not in roster'
- c.status = ''
- old_grps = c.get_shown_groups()
- c.groups = [_('Not in Roster')]
- self._add_entity(c, account)
- self.draw_contact(jid, account)
-
- if iters:
- # Draw all groups of the contact
- for group in contact.get_shown_groups() + old_grps:
- self.draw_group(group, account)
- self.draw_account(account)
-
- return True
-
- def rename_self_contact(self, old_jid, new_jid, account):
- """
- Rename the self_contact jid
-
- Keyword arguments:
- old_jid -- our old jid
- new_jid -- our new jid
- account -- the corresponding account.
- """
- gajim.contacts.change_contact_jid(old_jid, new_jid, account)
- self_iter = self._get_self_contact_iter(account, model=self.model)
- if not self_iter:
- return
- self.model[self_iter][Column.JID] = new_jid
- self.draw_contact(new_jid, account)
-
- def add_groupchat(self, jid, account, status=''):
- """
- Add groupchat to roster and draw it. Return the added contact instance
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- # Do not show gc if we are disconnected and minimize it
- if gajim.account_is_connected(account):
- show = 'online'
- else:
- show = 'offline'
- status = ''
-
- if contact is None:
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
- account)
- if gc_control:
- # there is a window that we can minimize
- gajim.interface.minimized_controls[account][jid] = gc_control
- name = gc_control.name
- elif jid in gajim.interface.minimized_controls[account]:
- name = gajim.interface.minimized_controls[account][jid].name
- else:
- name = jid.split('@')[0]
- # New groupchat
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- name=name, groups=[_('Groupchats')], show=show, status=status,
- sub='none')
- gajim.contacts.add_contact(account, contact)
- self.add_contact(jid, account)
- else:
- if jid not in gajim.interface.minimized_controls[account]:
- # there is a window that we can minimize
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
- account)
- gajim.interface.minimized_controls[account][jid] = gc_control
- contact.show = show
- contact.status = status
- self.adjust_and_draw_contact_context(jid, account)
-
- return contact
-
-
- def remove_groupchat(self, jid, account):
- """
- Remove groupchat from roster and redraw account and group
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if contact.is_groupchat():
- if jid in gajim.interface.minimized_controls[account]:
- del gajim.interface.minimized_controls[account][jid]
- self.remove_contact(jid, account, force=True, backend=True)
- return True
- else:
- return False
-
-
- # FIXME: This function is yet unused! Port to new API
- def add_transport(self, jid, account):
- """
- Add transport to roster and draw it. Return the added contact instance
- """
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if contact is None:
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- name=jid, groups=[_('Transports')], show='offline',
- status='offline', sub='from')
- gajim.contacts.add_contact(account, contact)
- self.add_contact(jid, account)
- return contact
-
- def remove_transport(self, jid, account):
- """
- Remove transport from roster and redraw account and group
- """
- self.remove_contact(jid, account, force=True, backend=True)
- return True
-
- def rename_group(self, old_name, new_name, account):
- """
- Rename a roster group
- """
- if old_name == new_name:
- return
-
- # Groups may not change name from or to a special groups
- for g in helpers.special_groups:
- if g in (new_name, old_name):
- return
-
- # update all contacts in the given group
- if self.regroup:
- accounts = gajim.connections.keys()
- else:
- accounts = [account, ]
-
- for acc in accounts:
- changed_contacts = []
- for jid in gajim.contacts.get_jid_list(acc):
- contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
- if old_name not in contact.groups:
- continue
-
- self.remove_contact(jid, acc, force=True)
-
- contact.groups.remove(old_name)
- if new_name not in contact.groups:
- contact.groups.append(new_name)
-
- changed_contacts.append({'jid': jid, 'name': contact.name,
- 'groups':contact.groups})
-
- gajim.connections[acc].update_contacts(changed_contacts)
-
- for c in changed_contacts:
- self.add_contact(c['jid'], acc)
-
- self._adjust_group_expand_collapse_state(new_name, acc)
-
- self.draw_group(old_name, acc)
- self.draw_group(new_name, acc)
-
-
- def add_contact_to_groups(self, jid, account, groups, update=True):
- """
- Add contact to given groups and redraw them
-
- Contact on server is updated too. When the contact has a family,
- the action will be performed for all members.
-
- Keyword Arguments:
- jid -- the jid
- account -- the corresponding account
- groups -- list of Groups to add the contact to.
- update -- update contact on the server
- """
- self.remove_contact(jid, account, force=True)
- for contact in gajim.contacts.get_contacts(account, jid):
- for group in groups:
- if group not in contact.groups:
- # we might be dropped from meta to group
- contact.groups.append(group)
- if update:
- gajim.connections[account].update_contact(jid, contact.name,
- contact.groups)
-
- self.add_contact(jid, account)
-
- for group in groups:
- self._adjust_group_expand_collapse_state(group, account)
-
- def remove_contact_from_groups(self, jid, account, groups, update=True):
- """
- Remove contact from given groups and redraw them
-
- Contact on server is updated too. When the contact has a family,
- the action will be performed for all members.
-
- Keyword Arguments:
- jid -- the jid
- account -- the corresponding account
- groups -- list of Groups to remove the contact from
- update -- update contact on the server
- """
- self.remove_contact(jid, account, force=True)
- for contact in gajim.contacts.get_contacts(account, jid):
- for group in groups:
- if group in contact.groups:
- # Needed when we remove from "General" or "Observers"
- contact.groups.remove(group)
- if update:
- gajim.connections[account].update_contact(jid, contact.name,
- contact.groups)
- self.add_contact(jid, account)
-
- # Also redraw old groups
- for group in groups:
- self.draw_group(group, account)
-
- # FIXME: maybe move to gajim.py
- def remove_newly_added(self, jid, account):
- if account not in gajim.newly_added:
- # Account has been deleted during the timeout that called us
- return
- if jid in gajim.newly_added[account]:
- gajim.newly_added[account].remove(jid)
- self.draw_contact(jid, account)
-
- # FIXME: maybe move to gajim.py
- def remove_to_be_removed(self, jid, account):
- if account not in gajim.interface.instances:
- # Account has been deleted during the timeout that called us
- return
- if jid in gajim.newly_added[account]:
- return
- if jid in gajim.to_be_removed[account]:
- gajim.to_be_removed[account].remove(jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # Peform delayed recalibration
- self._recalibrate_metacontact_family(family, account)
- self.draw_contact(jid, account)
-
- # FIXME: integrate into add_contact()
- def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- contact = gajim.contacts.create_not_in_roster_contact(jid=jid,
- account=account, resource=resource, name=nick, keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- self.add_contact(contact.jid, account)
- return contact
-
-
-################################################################################
-### Methods for adding and removing roster window items
-################################################################################
-
- def _really_draw_account(self, account):
- child_iter = self._get_account_iter(account, self.model)
- if not child_iter:
- assert False, 'Account iter of %s could not be found.' % account
- return
-
- num_of_accounts = gajim.get_number_of_connected_accounts()
- num_of_secured = gajim.get_number_of_securely_connected_accounts()
-
- if gajim.account_is_securely_connected(account) and not self.regroup or\
- self.regroup and num_of_secured and num_of_secured == num_of_accounts:
- tls_pixbuf = gtkgui_helpers.get_icon_pixmap('changes-prevent', 16)
- # the only way to create a pixbuf from stock
-# tls_pixbuf = self.window.render_icon_pixbuf(
-# Gtk.STOCK_DIALOG_AUTHENTICATION, Gtk.IconSize.MENU)
- self.model[child_iter][Column.PADLOCK_PIXBUF] = tls_pixbuf
- else:
- self.model[child_iter][Column.PADLOCK_PIXBUF] = empty_pixbuf
-
- if self.regroup:
- account_name = _('Merged accounts')
- accounts = []
- else:
- account_name = account
- accounts = [account]
-
- if account in self.collapsed_rows and \
- self.model.iter_has_child(child_iter):
- account_name = '[%s]' % account_name
-
- if (gajim.account_is_connected(account) or (self.regroup and \
- gajim.get_number_of_connected_accounts())) and gajim.config.get(
- 'show_contacts_number'):
- nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
- accounts = accounts)
- account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
-
- self.model[child_iter][Column.NAME] = account_name
-
- pep_dict = gajim.connections[account].pep
- if gajim.config.get('show_mood_in_roster') and 'mood' in pep_dict:
- self.model[child_iter][Column.MOOD_PIXBUF] = \
- gtkgui_helpers.get_pep_as_pixbuf(pep_dict['mood'])
- else:
- self.model[child_iter][Column.MOOD_PIXBUF] = empty_pixbuf
-
- if gajim.config.get('show_activity_in_roster') and 'activity' in \
- pep_dict:
- self.model[child_iter][Column.ACTIVITY_PIXBUF] = \
- gtkgui_helpers.get_pep_as_pixbuf(pep_dict['activity'])
- else:
- self.model[child_iter][Column.ACTIVITY_PIXBUF] = empty_pixbuf
-
- if gajim.config.get('show_tunes_in_roster') and 'tune' in pep_dict:
- self.model[child_iter][Column.TUNE_PIXBUF] = \
- gtkgui_helpers.get_pep_as_pixbuf(pep_dict['tune'])
- else:
- self.model[child_iter][Column.TUNE_PIXBUF] = empty_pixbuf
-
- if gajim.config.get('show_location_in_roster') and 'location' in \
- pep_dict:
- self.model[child_iter][Column.LOCATION_PIXBUF] = \
- gtkgui_helpers.get_pep_as_pixbuf(pep_dict['location'])
- else:
- self.model[child_iter][Column.LOCATION_PIXBUF] = empty_pixbuf
-
- def _really_draw_accounts(self):
- for acct in self.accounts_to_draw:
- self._really_draw_account(acct)
- self.accounts_to_draw = []
- return False
-
- def draw_account(self, account):
- if account in self.accounts_to_draw:
- return
- self.accounts_to_draw.append(account)
- if len(self.accounts_to_draw) == 1:
- GLib.timeout_add(200, self._really_draw_accounts)
-
- def _really_draw_group(self, group, account):
- child_iter = self._get_group_iter(group, account, model=self.model)
- if not child_iter:
- # Eg. We redraw groups after we removed a entitiy
- # and its empty groups
- return
- if self.regroup:
- accounts = []
- else:
- accounts = [account]
- text = GLib.markup_escape_text(group)
- if helpers.group_is_blocked(account, group):
- text = '<span strikethrough="true">%s</span>' % text
- if gajim.config.get('show_contacts_number'):
- nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts(
- accounts = accounts, groups = [group])
- text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))
-
- self.model[child_iter][Column.NAME] = text
-
- def _really_draw_groups(self):
- for ag in self.groups_to_draw.values():
- acct = ag['account']
- grp = ag['group']
- self._really_draw_group(grp, acct)
- self.groups_to_draw = {}
- return False
-
- def draw_group(self, group, account):
- ag = account + group
- if ag in self.groups_to_draw:
- return
- self.groups_to_draw[ag] = {'group': group, 'account': account}
- if len(self.groups_to_draw) == 1:
- GLib.timeout_add(200, self._really_draw_groups)
-
- def draw_parent_contact(self, jid, account):
- child_iters = self._get_contact_iter(jid, account, model=self.model)
- if not child_iters:
- return False
- parent_iter = self.model.iter_parent(child_iters[0])
- if self.model[parent_iter][Column.TYPE] != 'contact':
- # parent is not a contact
- return
- parent_jid = self.model[parent_iter][Column.JID]
- parent_account = self.model[parent_iter][Column.ACCOUNT]
- self.draw_contact(parent_jid, parent_account)
- return False
-
- def draw_contact(self, jid, account, selected=False, focus=False,
- contact_instances=None, contact=None):
- """
- Draw the correct state image, name BUT not avatar
- """
- # focus is about if the roster window has toplevel-focus or not
- # FIXME: We really need a custom cell_renderer
-
- if not contact_instances:
- contact_instances = gajim.contacts.get_contacts(account, jid)
- if not contact:
- contact = gajim.contacts.get_highest_prio_contact_from_contacts(
- contact_instances)
- if not contact:
- return False
-
- child_iters = self._get_contact_iter(jid, account, contact, self.model)
- if not child_iters:
- return False
-
- name = GLib.markup_escape_text(contact.get_shown_name())
-
- # gets number of unread gc marked messages
- if jid in gajim.interface.minimized_controls[account] and \
- gajim.interface.minimized_controls[account][jid]:
- nb_unread = len(gajim.events.get_events(account, jid,
- ['printed_marked_gc_msg']))
- nb_unread += gajim.interface.minimized_controls \
- [account][jid].get_nb_unread_pm()
-
- if nb_unread == 1:
- name = '%s *' % name
- elif nb_unread > 1:
- name = '%s [%s]' % (name, str(nb_unread))
-
- # Strike name if blocked
- strike = False
- if helpers.jid_is_blocked(account, jid):
- strike = True
- else:
- for group in contact.get_shown_groups():
- if helpers.group_is_blocked(account, group):
- strike = True
- break
- if strike:
- name = '<span strikethrough="true">%s</span>' % name
-
- # Show resource counter
- nb_connected_contact = 0
- for c in contact_instances:
- if c.show not in ('error', 'offline'):
- nb_connected_contact += 1
- if nb_connected_contact > 1:
- # switch back to default writing direction
- name += i18n.paragraph_direction_mark(name)
- name += ' (%d)' % nb_connected_contact
-
- # add status msg, if not empty, under contact name in
- # the treeview
- if contact.status and gajim.config.get('show_status_msgs_in_roster'):
- status = contact.status.strip()
- if status != '':
- status = helpers.reduce_chars_newlines(status,
- max_lines = 1)
- # escape markup entities and make them small
- # italic and fg color color is calcuted to be
- # always readable
- color = gtkgui_helpers.get_fade_color(self.tree, selected,
- focus)
- colorstring = '#%04x%04x%04x' % (int(color.red * 65535),
- int(color.green * 65535), int(color.blue * 65535))
- name += '\n<span size="small" style="italic" ' \
- 'foreground="%s">%s</span>' % (colorstring,
- GLib.markup_escape_text(status))
-
- icon_name = helpers.get_icon_name_to_show(contact, account)
- # look if another resource has awaiting events
- for c in contact_instances:
- c_icon_name = helpers.get_icon_name_to_show(c, account)
- if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
- icon_name = c_icon_name
- break
-
- # Check for events of collapsed (hidden) brothers
- family = gajim.contacts.get_metacontacts_family(account, jid)
- is_big_brother = False
- have_visible_children = False
- if family:
- bb_jid, bb_account = \
- self._get_nearby_family_and_big_brother(family, account)[1:]
- is_big_brother = (jid, account) == (bb_jid, bb_account)
- iters = self._get_contact_iter(jid, account)
- have_visible_children = iters and \
- self.modelfilter.iter_has_child(iters[0])
-
- if have_visible_children:
- # We are the big brother and have a visible family
- for child_iter in child_iters:
- child_path = self.model.get_path(child_iter)
- path = self.modelfilter.convert_child_path_to_path(child_path)
-
- if not path:
- continue
-
- if not self.tree.row_expanded(path) and icon_name != 'event':
- iterC = self.model.iter_children(child_iter)
- while iterC:
- # a child has awaiting messages?
- jidC = self.model[iterC][Column.JID]
- accountC = self.model[iterC][Column.ACCOUNT]
- if len(gajim.events.get_events(accountC, jidC)):
- icon_name = 'event'
- break
- iterC = self.model.iter_next(iterC)
-
- if self.tree.row_expanded(path):
- state_images = self.get_appropriate_state_images(
- jid, size = 'opened',
- icon_name = icon_name)
- else:
- state_images = self.get_appropriate_state_images(
- jid, size = 'closed',
- icon_name = icon_name)
-
- # Expand/collapse icon might differ per iter
- # (group)
- img = state_images[icon_name]
- self.model[child_iter][Column.IMG] = img
- self.model[child_iter][Column.NAME] = name
- else:
- # A normal contact or little brother
- state_images = self.get_appropriate_state_images(jid,
- icon_name = icon_name)
-
- # All iters have the same icon (no expand/collapse)
- img = state_images[icon_name]
- for child_iter in child_iters:
- self.model[child_iter][Column.IMG] = img
- self.model[child_iter][Column.NAME] = name
-
- # We are a little brother
- if family and not is_big_brother and not self.starting:
- self.draw_parent_contact(jid, account)
-
- delimiter = gajim.connections[account].nested_group_delimiter
- for group in contact.get_shown_groups():
- # We need to make sure that _visible_func is called for
- # our groups otherwise we might not be shown
- group_splited = group.split(delimiter)
- i = 1
- while i < len(group_splited) + 1:
- g = delimiter.join(group_splited[:i])
- iterG = self._get_group_iter(g, account, model=self.model)
- if iterG:
- # it's not self contact
- self.model[iterG][Column.JID] = self.model[iterG][Column.JID]
- i += 1
-
- gajim.plugin_manager.gui_extension_point('roster_draw_contact', self,
- jid, account, contact)
-
- return False
-
- def _is_pep_shown_in_roster(self, pep_type):
- if pep_type == 'mood':
- return gajim.config.get('show_mood_in_roster')
- elif pep_type == 'activity':
- return gajim.config.get('show_activity_in_roster')
- elif pep_type == 'tune':
- return gajim.config.get('show_tunes_in_roster')
- elif pep_type == 'location':
- return gajim.config.get('show_location_in_roster')
- else:
- return False
-
- def draw_all_pep_types(self, jid, account, contact=None):
- for pep_type in self._pep_type_to_model_column:
- self.draw_pep(jid, account, pep_type, contact=contact)
-
- def draw_pep(self, jid, account, pep_type, contact=None):
- if pep_type not in self._pep_type_to_model_column:
- return
- if not self._is_pep_shown_in_roster(pep_type):
- return
-
- model_column = self._pep_type_to_model_column[pep_type]
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters:
- return
- if not contact:
- contact = gajim.contacts.get_contact(account, jid)
- if pep_type in contact.pep:
- pixbuf = gtkgui_helpers.get_pep_as_pixbuf(contact.pep[pep_type])
- else:
- pixbuf = empty_pixbuf
- for child_iter in iters:
- self.model[child_iter][model_column] = pixbuf
-
- def draw_avatar(self, jid, account):
- iters = self._get_contact_iter(jid, account, model=self.model)
- if not iters or not gajim.config.get('show_avatars_in_roster'):
- return
- jid = self.model[iters[0]][Column.JID]
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid)
- if pixbuf in (None, 'ask'):
- scaled_pixbuf = empty_pixbuf
- else:
- scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster')
- for child_iter in iters:
- self.model[child_iter][Column.AVATAR_PIXBUF] = scaled_pixbuf
- return False
-
- def draw_completely(self, jid, account):
- contact_instances = gajim.contacts.get_contacts(account, jid)
- contact = gajim.contacts.get_highest_prio_contact_from_contacts(
- contact_instances)
- self.draw_contact(jid, account, contact_instances=contact_instances,
- contact=contact)
- self.draw_all_pep_types(jid, account, contact=contact)
- self.draw_avatar(jid, account)
-
- def adjust_and_draw_contact_context(self, jid, account):
- """
- Draw contact, account and groups of given jid Show contact if it has
- pending events
- """
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if not contact:
- # idle draw or just removed SelfContact
- return
-
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # There might be a new big brother
- self._recalibrate_metacontact_family(family, account)
- self.draw_contact(jid, account)
- self.draw_account(account)
-
- for group in contact.get_shown_groups():
- self.draw_group(group, account)
- self._adjust_group_expand_collapse_state(group, account)
-
- def _idle_draw_jids_of_account(self, jids, account):
- """
- Draw given contacts and their avatars in a lazy fashion
-
- Keyword arguments:
- jids -- a list of jids to draw
- account -- the corresponding account
- """
- def _draw_all_contacts(jids, account):
- for jid in jids:
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- # For metacontacts over several accounts:
- # When we connect a new account existing brothers
- # must be redrawn (got removed and readded)
- for data in family:
- self.draw_completely(data['jid'], data['account'])
- else:
- self.draw_completely(jid, account)
- yield True
- self.refilter_shown_roster_items()
- yield False
-
- task = _draw_all_contacts(jids, account)
- GLib.idle_add(next, task)
-
- def _before_fill(self):
- self.tree.freeze_child_notify()
- self.tree.set_model(None)
- # disable sorting
- self.model.set_sort_column_id(-2, Gtk.SortType.ASCENDING)
- self.starting = True
- self.starting_filtering = True
-
- def _after_fill(self):
- self.starting = False
- accounts_list = gajim.contacts.get_accounts()
- for account in gajim.connections:
- if account not in accounts_list:
- continue
-
- jids = gajim.contacts.get_jid_list(account)
- for jid in jids:
- self.draw_completely(jid, account)
-
- # Draw all known groups
- for group in gajim.groups[account]:
- self.draw_group(group, account)
- self.draw_account(account)
-
- self.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
- self.tree.set_model(self.modelfilter)
- self.tree.thaw_child_notify()
- self.starting_filtering = False
- self.refilter_shown_roster_items()
-
- def setup_and_draw_roster(self):
- """
- Create new empty model and draw roster
- """
- self.modelfilter = None
- self.model = Gtk.TreeStore(*self.columns)
-
- self.model.set_sort_func(1, self._compareIters)
- self.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
- self.modelfilter = self.model.filter_new()
- self.modelfilter.set_visible_func(self._visible_func)
- self.modelfilter.connect('row-has-child-toggled',
- self.on_modelfilter_row_has_child_toggled)
- self.tree.set_model(self.modelfilter)
-
- self._iters = {}
- # for merged mode
- self._iters['MERGED'] = {'account': None, 'groups': {}}
- for acct in gajim.contacts.get_accounts():
- self._iters[acct] = {'account': None, 'groups': {}, 'contacts': {}}
-
- for acct in gajim.contacts.get_accounts():
- self.add_account(acct)
- self.add_account_contacts(acct, improve_speed=True,
- draw_contacts=False)
-
- # Recalculate column width for ellipsizing
- self.tree.columns_autosize()
-
-
- def select_contact(self, jid, account):
- """
- Select contact in roster. If contact is hidden but has events, show him
- """
- # Refiltering SHOULD NOT be needed:
- # When a contact gets a new event he will be redrawn and his
- # icon changes, so _visible_func WILL be called on him anyway
- iters = self._get_contact_iter(jid, account)
- if not iters:
- # Not visible in roster
- return
- path = self.modelfilter.get_path(iters[0])
- if self.dragging or not gajim.config.get(
- 'scroll_roster_to_last_message'):
- # do not change selection while DND'ing
- return
- # Expand his parent, so this path is visible, don't expand it.
- path.up()
- self.tree.expand_to_path(path)
- self.tree.scroll_to_cell(path)
- self.tree.set_cursor(path)
-
- def _readjust_expand_collapse_state(self):
- def func(model, path, iter_, param):
- type_ = model[iter_][Column.TYPE]
- acct = model[iter_][Column.ACCOUNT]
- jid = model[iter_][Column.JID]
- key = None
- if type_ == 'account':
- key = acct
- elif type_ == 'group':
- key = acct + jid
- elif type_ == 'contact':
- parent_iter = model.iter_parent(iter_)
- ptype = model[parent_iter][Column.TYPE]
- if ptype == 'group':
- grp = model[parent_iter][Column.JID]
- key = acct + grp + jid
- if key:
- if key in self.collapsed_rows:
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- self.modelfilter.foreach(func, None)
-
- def _adjust_account_expand_collapse_state(self, account):
- """
- Expand/collapse account row based on self.collapsed_rows
- """
- if not self.tree.get_model():
- return
- iterA = self._get_account_iter(account)
- if not iterA:
- # thank you modelfilter
- return
- path = self.modelfilter.get_path(iterA)
- if account in self.collapsed_rows:
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return False
-
-
- def _adjust_group_expand_collapse_state(self, group, account):
- """
- Expand/collapse group row based on self.collapsed_rows
- """
- if not self.tree.get_model():
- return
- if account not in gajim.connections:
- return
- delimiter = gajim.connections[account].nested_group_delimiter
- group_splited = group.split(delimiter)
- i = 1
- while i < len(group_splited) + 1:
- g = delimiter.join(group_splited[:i])
- iterG = self._get_group_iter(g, account)
- if not iterG:
- # Group not visible
- return
- path = self.modelfilter.get_path(iterG)
- if account + g in self.collapsed_rows:
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- i += 1
-
-##############################################################################
-### Roster and Modelfilter handling
-##############################################################################
-
- def refilter_shown_roster_items(self):
- if self.filtering:
- return
- self.filtering = True
- self.modelfilter.refilter()
- self.filtering = False
-
- def contact_has_pending_roster_events(self, contact, account):
- """
- Return True if the contact or one if it resources has pending events
- """
- # jid has pending events
- if gajim.events.get_nb_roster_events(account, contact.jid) > 0:
- return True
- # check events of all resources
- for contact_ in gajim.contacts.get_contacts(account, contact.jid):
- if contact_.resource and gajim.events.get_nb_roster_events(account,
- contact_.get_full_jid()) > 0:
- return True
- return False
-
- def contact_is_visible(self, contact, account):
- if self.rfilter_enabled:
- return self.rfilter_string in contact.get_shown_name().lower()
- if self.contact_has_pending_roster_events(contact, account):
- return True
-
- if contact.show in ('offline', 'error'):
- if contact.jid in gajim.to_be_removed[account]:
- return True
- return False
- if gajim.config.get('show_only_chat_and_online') and contact.show in (
- 'away', 'xa', 'busy'):
- return False
- return True
-
- def _visible_func(self, model, titer, dummy):
- """
- Determine whether iter should be visible in the treeview
- """
- if self.starting_filtering:
- return False
- type_ = model[titer][Column.TYPE]
- if not type_:
- return False
- if type_ == 'account':
- # Always show account
- return True
-
- account = model[titer][Column.ACCOUNT]
- if not account:
- return False
-
- jid = model[titer][Column.JID]
- if not jid:
- return False
-
- if type_ == 'group':
- group = jid
- if group == _('Transports'):
- if self.regroup:
- accounts = gajim.contacts.get_accounts()
- else:
- accounts = [account]
- for _acc in accounts:
- for contact in gajim.contacts.iter_contacts(_acc):
- if group in contact.get_shown_groups():
- if self.rfilter_enabled:
- if self.rfilter_string in \
- contact.get_shown_name().lower():
- return True
- elif self.contact_has_pending_roster_events(contact,
- _acc):
- return True
- if self.rfilter_enabled:
- # No transport has been found
- return False
- return gajim.config.get('show_transports_group') and \
- (gajim.account_is_connected(account) or \
- gajim.config.get('showoffline'))
- if gajim.config.get('showoffline'):
- return True
-
- if self.regroup:
- # Column.ACCOUNT for groups depends on the order
- # accounts were connected
- # Check all accounts for online group contacts
- accounts = gajim.contacts.get_accounts()
- else:
- accounts = [account]
- for _acc in accounts:
- delimiter = gajim.connections[_acc].nested_group_delimiter
- for contact in gajim.contacts.iter_contacts(_acc):
- if not self.contact_is_visible(contact, _acc):
- continue
- # Is this contact in this group?
- for grp in contact.get_shown_groups():
- while grp:
- if group == grp:
- return True
- grp = delimiter.join(grp.split(delimiter)[:-1])
- return False
- if type_ == 'contact':
- if self.rfilter_enabled:
- if model.iter_has_child(titer):
- iter_c = model.iter_children(titer)
- while iter_c:
- if self.rfilter_string in model[iter_c][Column.NAME].lower():
- return True
- iter_c = model.iter_next(iter_c)
- return self.rfilter_string in model[titer][Column.NAME].lower()
- if gajim.config.get('showoffline'):
- return True
- bb_jid = None
- bb_account = None
- family = gajim.contacts.get_metacontacts_family(account, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- self._get_nearby_family_and_big_brother(family, account)
- if (bb_jid, bb_account) == (jid, account):
- # Show the big brother if a child has pending events
- for data in nearby_family:
- jid = data['jid']
- account = data['account']
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- if contact and self.contact_is_visible(contact, account):
- return True
- return False
- else:
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- return self.contact_is_visible(contact, account)
- if type_ == 'agent':
- if self.rfilter_enabled:
- return self.rfilter_string in model[titer][Column.NAME].lower()
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- return self.contact_has_pending_roster_events(contact, account) or \
- (gajim.config.get('show_transports_group') and \
- (gajim.account_is_connected(account) or \
- gajim.config.get('showoffline')))
- if type_ == 'groupchat' and self.rfilter_enabled:
- return self.rfilter_string in model[titer][Column.NAME].lower()
- return True
-
- def _compareIters(self, model, iter1, iter2, data=None):
- """
- Compare two iters to sort them
- """
- name1 = model[iter1][Column.NAME]
- name2 = model[iter2][Column.NAME]
- if not name1 or not name2:
- return 0
- name1 = name1
- name2 = name2
- type1 = model[iter1][Column.TYPE]
- type2 = model[iter2][Column.TYPE]
- if type1 == 'self_contact':
- return -1
- if type2 == 'self_contact':
- return 1
- if type1 == 'group':
- name1 = model[iter1][Column.JID]
- if name1:
- name1 = name1
- name2 = model[iter2][Column.JID]
- if name2:
- name2 = name2
- if name1 == _('Transports'):
- return 1
- if name2 == _('Transports'):
- return -1
- if name1 == _('Not in Roster'):
- return 1
- if name2 == _('Not in Roster'):
- return -1
- if name1 == _('Groupchats'):
- return 1
- if name2 == _('Groupchats'):
- return -1
- account1 = model[iter1][Column.ACCOUNT]
- account2 = model[iter2][Column.ACCOUNT]
- if not account1 or not account2:
- return 0
- account1 = account1
- account2 = account2
- if type1 == 'account':
- return locale.strcoll(account1, account2)
- jid1 = model[iter1][Column.JID]
- jid2 = model[iter2][Column.JID]
- if type1 == 'contact':
- lcontact1 = gajim.contacts.get_contacts(account1, jid1)
- contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1)
- if not contact1:
- return 0
- name1 = contact1.get_shown_name()
- if type2 == 'contact':
- lcontact2 = gajim.contacts.get_contacts(account2, jid2)
- contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2)
- if not contact2:
- return 0
- name2 = contact2.get_shown_name()
- # We first compare by show if sort_by_show_in_roster is True or if it's
- # a child contact
- if type1 == 'contact' and type2 == 'contact' and \
- gajim.config.get('sort_by_show_in_roster'):
- cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
- 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8}
- s = self.get_show(lcontact1)
- show1 = cshow.get(s, 9)
- s = self.get_show(lcontact2)
- show2 = cshow.get(s, 9)
- removing1 = False
- removing2 = False
- if show1 == 6 and jid1 in gajim.to_be_removed[account1]:
- removing1 = True
- if show2 == 6 and jid2 in gajim.to_be_removed[account2]:
- removing2 = True
- if removing1 and not removing2:
- return 1
- if removing2 and not removing1:
- return -1
- sub1 = contact1.sub
- sub2 = contact2.sub
- # none and from goes after
- if sub1 not in ['none', 'from'] and sub2 in ['none', 'from']:
- return -1
- if sub1 in ['none', 'from'] and sub2 not in ['none', 'from']:
- return 1
- if show1 < show2:
- return -1
- elif show1 > show2:
- return 1
- # We compare names
- cmp_result = locale.strcoll(name1.lower(), name2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- if type1 == 'contact' and type2 == 'contact':
- # We compare account names
- cmp_result = locale.strcoll(account1.lower(), account2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- # We compare jids
- cmp_result = locale.strcoll(jid1.lower(), jid2.lower())
- if cmp_result < 0:
- return -1
- if cmp_result > 0:
- return 1
- return 0
-
-################################################################################
-### FIXME: Methods that don't belong to roster window...
-### ... atleast not in there current form
-################################################################################
-
- def fire_up_unread_messages_events(self, account):
- """
- Read from db the unread messages, and fire them up, and if we find very
- old unread messages, delete them from unread table
- """
- results = gajim.logger.get_unread_msgs()
- for result in results:
- jid = result[4]
- additional_data = result[5]
- shown = result[6]
- if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
- shown:
- # We have this jid in our contacts list
- # XXX unread messages should probably have their session saved
- # with them
- session = gajim.connections[account].make_new_session(jid)
-
- tim = float(result[2])
- session.roster_message(jid, result[1], tim, msg_type='chat',
- msg_log_id=result[0], additional_data=additional_data)
- gajim.logger.set_shown_unread_msgs(result[0])
-
- elif (time.time() - result[2]) > 2592000:
- # ok, here we see that we have a message in unread messages
- # table that is older than a month. It is probably from someone
- # not in our roster for accounts we usually launch, so we will
- # delete this id from unread message tables.
- gajim.logger.set_read_messages([result[0]])
-
- def fill_contacts_and_groups_dicts(self, array, account):
- """
- Fill gajim.contacts and gajim.groups
- """
- # FIXME: This function needs to be splitted
- # Most of the logic SHOULD NOT be done at GUI level
- if account not in gajim.contacts.get_accounts():
- gajim.contacts.add_account(account)
- if not account in self._iters:
- self._iters[account] = {'account': None, 'groups': {},
- 'contacts': {}}
- if account not in gajim.groups:
- gajim.groups[account] = {}
- if gajim.config.get('show_self_contact') == 'always':
- self_jid = gajim.get_jid_from_account(account)
- if gajim.connections[account].server_resource:
- self_jid += '/' + gajim.connections[account].server_resource
- array[self_jid] = {'name': gajim.nicks[account],
- 'groups': ['self_contact'], 'subscription': 'both',
- 'ask': 'none'}
- # .keys() is needed
- for jid in list(array.keys()):
- # Remove the contact in roster. It might has changed
- self.remove_contact(jid, account, force=True)
- # Remove old Contact instances
- gajim.contacts.remove_jid(account, jid, remove_meta=False)
- jids = jid.split('/')
- # get jid
- ji = jids[0]
- # get resource
- resource = ''
- if len(jids) > 1:
- resource = '/'.join(jids[1:])
- # get name
- name = array[jid]['name'] or ''
- show = 'offline' # show is offline by default
- status = '' # no status message by default
-
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
-
- if gajim.jid_is_transport(jid):
- array[jid]['groups'] = [_('Transports')]
- #TRANSP - potential
- contact1 = gajim.contacts.create_contact(jid=ji, account=account,
- name=name, groups=array[jid]['groups'], show=show,
- status=status, sub=array[jid]['subscription'],
- ask=array[jid]['ask'], resource=resource, keyID=keyID)
- gajim.contacts.add_contact(account, contact1)
-
- if gajim.config.get('ask_avatars_on_startup'):
- pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji)
- if pixbuf == 'ask':
- transport = gajim.get_transport_name_from_jid(contact1.jid)
- if not transport or gajim.jid_is_transport(contact1.jid):
- jid_with_resource = contact1.jid
- if contact1.resource:
- jid_with_resource += '/' + contact1.resource
- gajim.connections[account].request_vcard(
- jid_with_resource)
- else:
- host = gajim.get_server_from_jid(contact1.jid)
- if host not in gajim.transport_avatar[account]:
- gajim.transport_avatar[account][host] = \
- [contact1.jid]
- else:
- gajim.transport_avatar[account][host].append(
- contact1.jid)
-
- # If we already have chat windows opened, update them with new
- # contact instance
- chat_control = gajim.interface.msg_win_mgr.get_control(ji, account)
- if chat_control:
- chat_control.contact = contact1
-
- def connected_rooms(self, account):
- if account in list(gajim.gc_connected[account].values()):
- return True
- return False
-
- def on_event_removed(self, event_list):
- """
- Remove contacts on last events removed
-
- Only performed if removal was requested before but the contact still had
- pending events
- """
-
- msg_log_ids = []
- for ev in event_list:
- if ev.type_ != 'printed_chat':
- continue
- if ev.msg_log_id:
- # There is a msg_log_id
- msg_log_ids.append(ev.msg_log_id)
-
- if msg_log_ids:
- gajim.logger.set_read_messages(msg_log_ids)
-
- contact_list = ((event.jid.split('/')[0], event.account) for event in \
- event_list)
-
- for jid, account in contact_list:
- self.draw_contact(jid, account)
- # Remove contacts in roster if removal was requested
- key = (jid, account)
- if key in list(self.contacts_to_be_removed.keys()):
- backend = self.contacts_to_be_removed[key]['backend']
- del self.contacts_to_be_removed[key]
- # Remove contact will delay removal if there are more events
- # pending
- self.remove_contact(jid, account, backend=backend)
- self.show_title()
-
- def open_event(self, account, jid, event):
- """
- If an event was handled, return True, else return False
- """
- ft = gajim.interface.instances['file_transfers']
- event = gajim.events.get_first_event(account, jid, event.type_)
- if event.type_ == 'normal':
- dialogs.SingleMessageWindow(account, jid,
- action='receive', from_whom=jid, subject=event.subject,
- message=event.message, resource=event.resource,
- session=event.session, form_node=event.form_node)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'file-request':
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- ft.show_file_request(account, contact, event.file_props)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ in ('file-request-error', 'file-send-error'):
- ft.show_send_error(event.file_props)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ in ('file-error', 'file-stopped'):
- msg_err = ''
- if event.file_props.error == -1:
- msg_err = _('Remote contact stopped transfer')
- elif event.file_props.error == -6:
- msg_err = _('Error opening file')
- ft.show_stopped(jid, event.file_props, error_msg=msg_err)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'file-hash-error':
- ft.show_hash_error(jid, event.file_props, account)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'file-completed':
- ft.show_completed(jid, event.file_props)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'gc-invitation':
- dialogs.InvitationReceivedDialog(account, event.room_jid,
- event.jid_from, event.password, event.reason,
- is_continued=event.is_continued)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'subscription_request':
- dialogs.SubscriptionRequestWindow(jid, event.text, account,
- event.nick)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'unsubscribed':
- gajim.interface.show_unsubscribed_dialog(account, event.contact)
- gajim.events.remove_events(account, jid, event)
- return True
- elif event.type_ == 'jingle-incoming':
- dialogs.VoIPCallReceivedDialog(account, event.peerjid, event.sid,
- event.content_types)
- gajim.events.remove_events(account, jid, event)
- return True
- return False
-
-################################################################################
-### This and that... random.
-################################################################################
-
- def show_roster_vbox(self, active):
- vb = self.xml.get_object('roster_vbox2')
- if active:
- vb.set_no_show_all(False)
- vb.show()
- else:
- vb.hide()
- vb.set_no_show_all(True)
-
- def authorize(self, widget, jid, account):
- """
- Authorize a contact (by re-sending auth menuitem)
- """
- gajim.connections[account].send_authorization(jid)
- dialogs.InformationDialog(_('Authorization sent'),
- _('"%s" will now see your status.') %jid)
-
- def req_sub(self, widget, jid, txt, account, groups=None, nickname=None,
- auto_auth=False):
- """
- Request subscription to a contact
- """
- groups_list = groups or []
- gajim.connections[account].request_subscription(jid, txt, nickname,
- groups_list, auto_auth, gajim.nicks[account])
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- if not contact:
- keyID = ''
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- if jid in attached_keys:
- keyID = attached_keys[attached_keys.index(jid) + 1]
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- name=nickname, groups=groups_list, show='requested', status='',
- ask='none', sub='subscribe', keyID=keyID)
- gajim.contacts.add_contact(account, contact)
- else:
- if not _('Not in Roster') in contact.get_shown_groups():
- dialogs.InformationDialog(_('Subscription request has been '
- 'sent'), _('If "%s" accepts this request you will know his '
- 'or her status.') % jid)
- return
- self.remove_contact(contact.jid, account, force=True)
- contact.groups = groups_list
- if nickname:
- contact.name = nickname
- self.add_contact(jid, account)
-
- def revoke_auth(self, widget, jid, account):
- """
- Revoke a contact's authorization
- """
- gajim.connections[account].refuse_authorization(jid)
- dialogs.InformationDialog(_('Authorization removed'),
- _('Now "%s" will always see you as offline.') %jid)
-
- def set_state(self, account, state):
- child_iterA = self._get_account_iter(account, self.model)
- if child_iterA:
- self.model[child_iterA][0] = \
- gajim.interface.jabber_state_images['16'][state]
- if gajim.interface.systray_enabled:
- gajim.interface.systray.change_status(state)
-
- def set_connecting_state(self, account):
- self.set_state(account, 'connecting')
-
- def send_status(self, account, status, txt, auto=False, to=None):
- if status != 'offline':
- if to is None:
- if status == gajim.connections[account].get_status() and \
- txt == gajim.connections[account].status:
- return
- gajim.config.set_per('accounts', account, 'last_status', status)
- gajim.config.set_per('accounts', account, 'last_status_msg',
- helpers.to_one_line(txt))
- if gajim.connections[account].connected < 2:
- self.set_connecting_state(account)
-
- keyid = gajim.config.get_per('accounts', account, 'keyid')
- if keyid and not gajim.connections[account].gpg:
- dialogs.WarningDialog(_('OpenPGP is not usable'),
- _('Gajim needs python-gnupg >= 0.3.8\n'
- 'Beware there is an incompatible python package called gnupg.\n'
- 'You will be connected to %s without OpenPGP.') % account)
-
- self.send_status_continue(account, status, txt, auto, to)
-
- def send_pep(self, account, pep_dict):
- connection = gajim.connections[account]
-
- if 'activity' in pep_dict:
- activity = pep_dict['activity']
- subactivity = pep_dict.get('subactivity', None)
- activity_text = pep_dict.get('activity_text', None)
- connection.send_activity(activity, subactivity, activity_text)
- else:
- connection.retract_activity()
-
- if 'mood' in pep_dict:
- mood = pep_dict['mood']
- mood_text = pep_dict.get('mood_text', None)
- connection.send_mood(mood, mood_text)
- else:
- connection.retract_mood()
-
- def delete_pep(self, jid, account):
- if jid == gajim.get_jid_from_account(account):
- gajim.connections[account].pep = {}
- self.draw_account(account)
-
- for contact in gajim.contacts.get_contacts(account, jid):
- contact.pep = {}
-
- self.draw_all_pep_types(jid, account)
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.update_all_pep_types()
-
- def send_status_continue(self, account, status, txt, auto, to):
- if gajim.account_is_connected(account) and not to:
- if status == 'online' and gajim.interface.sleeper.getState() != \
- common.sleepy.STATE_UNKNOWN:
- gajim.sleeper_state[account] = 'online'
- elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa') or \
- status == 'offline':
- gajim.sleeper_state[account] = 'off'
-
- if to:
- gajim.connections[account].send_custom_status(status, txt, to)
- else:
- if status in ('invisible', 'offline'):
- self.delete_pep(gajim.get_jid_from_account(account), account)
- was_invisible = gajim.connections[account].connected == \
- gajim.SHOW_LIST.index('invisible')
- gajim.connections[account].change_status(status, txt, auto)
-
- if account in gajim.interface.status_sent_to_users:
- gajim.interface.status_sent_to_users[account] = {}
- if account in gajim.interface.status_sent_to_groups:
- gajim.interface.status_sent_to_groups[account] = {}
- for gc_control in gajim.interface.msg_win_mgr.get_controls(
- message_control.TYPE_GC) + \
- list(gajim.interface.minimized_controls[account].values()):
- if gc_control.account == account:
- if gajim.gc_connected[account][gc_control.room_jid]:
- gajim.connections[account].send_gc_status(
- gc_control.nick, gc_control.room_jid, status, txt)
- if was_invisible and status != 'offline':
- # We come back from invisible, join bookmarks
- gajim.interface.auto_join_bookmarks(account)
-
-
- def chg_contact_status(self, contact, show, status, account):
- """
- When a contact changes his or her status
- """
- contact_instances = gajim.contacts.get_contacts(account, contact.jid)
- contact.show = show
- contact.status = status
- # name is to show in conversation window
- name = contact.get_shown_name()
- fjid = contact.get_full_jid()
-
- # The contact has several resources
- if len(contact_instances) > 1:
- if contact.resource != '':
- name += '/' + contact.resource
-
- # Remove resource when going offline
- if show in ('offline', 'error') and \
- not self.contact_has_pending_roster_events(contact, account):
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
- if ctrl:
- ctrl.update_ui()
- ctrl.parent_win.redraw_tab(ctrl)
- # keep the contact around, since it's
- # already attached to the control
- else:
- gajim.contacts.remove_contact(account, contact)
-
- elif contact.jid == gajim.get_jid_from_account(account) and \
- show in ('offline', 'error'):
- if gajim.config.get('show_self_contact') != 'never':
- # SelfContact went offline. Remove him when last pending
- # message was read
- self.remove_contact(contact.jid, account, backend=True)
-
- uf_show = helpers.get_uf_show(show)
-
- # print status in chat window and update status/GPG image
- ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
- if ctrl and ctrl.type_id != message_control.TYPE_GC:
- ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
- account, contact.jid)
- ctrl.update_status_display(name, uf_show, status)
-
- if contact.resource:
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account)
- if ctrl:
- ctrl.update_status_display(name, uf_show, status)
-
- # Delete pep if needed
- keep_pep = any(c.show not in ('error', 'offline') for c in
- contact_instances)
- if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
- and not contact.is_groupchat():
- self.delete_pep(contact.jid, account)
-
- # Redraw everything and select the sender
- self.adjust_and_draw_contact_context(contact.jid, account)
-
-
- def on_status_changed(self, account, show):
- """
- The core tells us that our status has changed
- """
- if account not in gajim.contacts.get_accounts():
- return
- child_iterA = self._get_account_iter(account, self.model)
- if gajim.config.get('show_self_contact') == 'always':
- self_resource = gajim.connections[account].server_resource
- self_contact = gajim.contacts.get_contact(account,
- gajim.get_jid_from_account(account), resource=self_resource)
- if self_contact:
- status = gajim.connections[account].status
- self.chg_contact_status(self_contact, show, status, account)
- self.set_account_status_icon(account)
- if show == 'offline':
- if self.quit_on_next_offline > -1:
- # we want to quit, we are waiting for all accounts to be offline
- self.quit_on_next_offline -= 1
- if self.quit_on_next_offline < 1:
- # all accounts offline, quit
- self.quit_gtkgui_interface()
- else:
- # No need to redraw contacts if we're quitting
- if child_iterA:
- self.model[child_iterA][Column.AVATAR_PIXBUF] = empty_pixbuf
- if account in gajim.con_types:
- gajim.con_types[account] = None
- for jid in list(gajim.contacts.get_jid_list(account)):
- lcontact = gajim.contacts.get_contacts(account, jid)
- ctrl = gajim.interface.msg_win_mgr.get_gc_control(jid,
- account)
- for contact in [c for c in lcontact if (
- (c.show != 'offline' or c.is_transport()) and not ctrl)]:
- self.chg_contact_status(contact, 'offline', '', account)
- self.update_status_combobox()
-
- def get_status_message(self, show, on_response, show_pep=True,
- always_ask=False):
- """
- Get the status message by:
-
- 1/ looking in default status message
- 2/ asking to user if needed depending on ask_on(ff)line_status and
- always_ask
- show_pep can be False to hide pep things from status message or True
- """
- empty_pep = {'activity': '', 'subactivity': '', 'activity_text': '',
- 'mood': '', 'mood_text': ''}
- if show in gajim.config.get_per('defaultstatusmsg'):
- if gajim.config.get_per('defaultstatusmsg', show, 'enabled'):
- msg = gajim.config.get_per('defaultstatusmsg', show, 'message')
- msg = helpers.from_one_line(msg)
- on_response(msg, empty_pep)
- return
- if not always_ask and ((show == 'online' and not gajim.config.get(
- 'ask_online_status')) or (show in ('offline', 'invisible') and not \
- gajim.config.get('ask_offline_status'))):
- on_response('', empty_pep)
- return
-
- dlg = dialogs.ChangeStatusMessageDialog(on_response, show, show_pep)
- dlg.dialog.present() # show it on current workspace
-
- def change_status(self, widget, account, status):
- def change(account, status):
- def on_response(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- return
- self.send_status(account, status, message)
- self.send_pep(account, pep_dict)
- self.get_status_message(status, on_response)
-
- if status == 'invisible' and self.connected_rooms(account):
- dialogs.ConfirmationDialog(
- _('You are participating in one or more group chats'),
- _('Changing your status to invisible will result in '
- 'disconnection from those group chats. Are you sure you want '
- 'to go invisible?'), on_response_ok = (change, account, status))
- else:
- change(account, status)
-
- def update_status_combobox(self):
- # table to change index in connection.connected to index in combobox
- table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2,
- 'xa':3, 'dnd':4, 'invisible':5}
-
- liststore = self.status_combobox.get_model()
- # we check if there are more options in the combobox that it should
- # if yes, we remove the first ones
- while len(liststore) > len(table)+2:
- titer = liststore.get_iter_first()
- liststore.remove(titer)
-
- show = helpers.get_global_show()
- # temporarily block signal in order not to send status that we show
- # in the combobox
- self.combobox_callback_active = False
- if helpers.statuses_unified():
- self.status_combobox.set_active(table[show])
- else:
- uf_show = helpers.get_uf_show(show)
- liststore.prepend(['SEPARATOR', None, '', True])
- status_combobox_text = uf_show + ' (' + _("desync'ed") +')'
- liststore.prepend([status_combobox_text,
- gajim.interface.jabber_state_images['16'][show], show, False])
- self.status_combobox.set_active(0)
- gajim.interface.change_awn_icon_status(show)
- self.combobox_callback_active = True
- if gajim.interface.systray_enabled:
- gajim.interface.systray.change_status(show)
-
- def get_show(self, lcontact):
- prio = lcontact[0].priority
- show = lcontact[0].show
- for u in lcontact:
- if u.priority > prio:
- prio = u.priority
- show = u.show
- return show
-
- def on_message_window_delete(self, win_mgr, msg_win):
- if gajim.config.get('one_message_window') == 'always_with_roster':
- self.show_roster_vbox(True)
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
-
- def close_all_from_dict(self, dic):
- """
- Close all the windows in the given dictionary
- """
- for w in list(dic.values()):
- if isinstance(w, dict):
- self.close_all_from_dict(w)
- else:
- w.window.destroy()
-
- def close_all(self, account, force=False):
- """
- Close all the windows from an account. If force is True, do not ask
- confirmation before closing chat/gc windows
- """
- if account in gajim.interface.instances:
- self.close_all_from_dict(gajim.interface.instances[account])
- for ctrl in gajim.interface.msg_win_mgr.get_controls(acct=account):
- ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON,
- force=force)
-
- def on_roster_window_delete_event(self, widget, event):
- """
- Main window X button was clicked
- """
- if not gajim.config.get('quit_on_roster_x_button') and (
- (gajim.interface.systray_enabled and gajim.config.get('trayicon') != \
- 'on_event') or gajim.config.get('allow_hide_roster')):
- if gajim.config.get('save-roster-position'):
- x, y = self.window.get_position()
- gajim.config.set('roster_x-position', x)
- gajim.config.set('roster_y-position', y)
- self.window.iconify()
- elif gajim.config.get('quit_on_roster_x_button'):
- self.on_quit_request()
- else:
- def on_ok(checked):
- if checked:
- gajim.config.set('quit_on_roster_x_button', True)
- self.on_quit_request()
- dialogs.ConfirmationDialogCheck(_('Really quit Gajim?'),
- _('Are you sure you want to quit Gajim?'),
- _('Always close Gajim'), on_response_ok=on_ok)
- return True # do NOT destroy the window
-
- def prepare_quit(self):
- if self.save_done:
- return
- msgwin_width_adjust = 0
-
- # in case show_roster_on_start is False and roster is never shown
- # window.window is None
- if self.window.get_window() is not None:
- if gajim.config.get('save-roster-position'):
- x, y = self.window.get_window().get_root_origin()
- gajim.config.set('roster_x-position', x)
- gajim.config.set('roster_y-position', y)
- width, height = self.window.get_size()
- gajim.config.set('roster_width', width)
- gajim.config.set('roster_height', height)
- if not self.xml.get_object('roster_vbox2').get_property('visible'):
- # The roster vbox is hidden, so the message window is larger
- # then we want to save (i.e. the window will grow every startup)
- # so adjust.
- msgwin_width_adjust = -1 * width
- gajim.config.set('last_roster_visible',
- self.window.get_property('visible'))
- gajim.interface.msg_win_mgr.save_opened_controls()
- gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust)
-
- gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows))
- gajim.interface.save_config()
- for account in gajim.connections:
- gajim.connections[account].quit(True)
- self.close_all(account)
- if gajim.interface.systray_enabled:
- gajim.interface.hide_systray()
- self.save_done = True
-
- def quit_gtkgui_interface(self):
- """
- When we quit the gtk interface - exit gtk
- """
- self.prepare_quit()
- self.application.quit()
-
- def on_quit_request(self, widget=None):
- """
- User wants to quit. Check if he should be warned about messages pending.
- Terminate all sessions and send offline to all connected account. We do
- NOT really quit gajim here
- """
- accounts = list(gajim.connections.keys())
- get_msg = False
- for acct in accounts:
- if gajim.connections[acct].connected:
- get_msg = True
- break
-
- def on_continue3(message, pep_dict):
- self.quit_on_next_offline = 0
- accounts_to_disconnect = []
- for acct in accounts:
- if gajim.connections[acct].connected > 1:
- self.quit_on_next_offline += 1
- accounts_to_disconnect.append(acct)
-
- if not self.quit_on_next_offline:
- # all accounts offline, quit
- self.quit_gtkgui_interface()
- return
-
- for acct in accounts_to_disconnect:
- self.send_status(acct, 'offline', message)
- self.send_pep(acct, pep_dict)
-
- def on_continue2(message, pep_dict):
- # check if there is an active file transfer
- from common.protocol.bytestream import (is_transfer_active)
- files_props = gajim.interface.instances['file_transfers'].\
- files_props
- transfer_active = False
- for x in files_props:
- for y in files_props[x]:
- if is_transfer_active(files_props[x][y]):
- transfer_active = True
- break
-
- if transfer_active:
- dialogs.ConfirmationDialog(_('You have running file transfers'),
- _('If you quit now, the file(s) being transferred will '
- 'be stopped. Do you still want to quit?'),
- on_response_ok=(on_continue3, message, pep_dict))
- return
- on_continue3(message, pep_dict)
-
- def on_continue(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- return
- # check if we have unread messages
- unread = gajim.events.get_nb_events()
- if not gajim.config.get('notify_on_all_muc_messages'):
- unread_not_to_notify = gajim.events.get_nb_events(
- ['printed_gc_msg'])
- unread -= unread_not_to_notify
-
- # check if we have recent messages
- recent = False
- for win in gajim.interface.msg_win_mgr.windows():
- for ctrl in win.controls():
- fjid = ctrl.get_full_jid()
- if fjid in gajim.last_message_time[ctrl.account]:
- if time.time() - gajim.last_message_time[ctrl.account][
- fjid] < 2:
- recent = True
- break
- if recent:
- break
-
- if unread or recent:
- dialogs.ConfirmationDialog(_('You have unread messages'),
- _('Messages will only be available for reading them later '
- 'if you have history enabled and contact is in your '
- 'roster.'), on_response_ok=(on_continue2,
- message, pep_dict))
- return
- on_continue2(message, pep_dict)
-
- if get_msg:
- self.get_status_message('offline', on_continue, show_pep=False)
- else:
- on_continue('', None)
-
- def _nec_presence_received(self, obj):
- account = obj.conn.name
- jid = obj.jid
-
- if obj.need_add_in_roster:
- self.add_contact(jid, account)
-
- jid_list = gajim.contacts.get_jid_list(account)
- if jid in jid_list or jid == gajim.get_jid_from_account(account):
- if not gajim.jid_is_transport(jid) and len(obj.contact_list) == 1:
- if obj.old_show == 0 and obj.new_show > 1:
- GLib.timeout_add_seconds(5, self.remove_newly_added, jid,
- account)
- elif obj.old_show > 1 and obj.new_show == 0 and \
- obj.conn.connected > 1:
- GLib.timeout_add_seconds(5, self.remove_to_be_removed,
- jid, account)
-
- if obj.need_redraw:
- self.draw_contact(jid, account)
-
- if gajim.jid_is_transport(jid) and jid in jid_list:
- # It must be an agent
- # Update existing iter and group counting
- self.draw_contact(jid, account)
- self.draw_group(_('Transports'), account)
- if obj.new_show > 1 and jid in gajim.transport_avatar[account]:
- # transport just signed in.
- # request avatars
- for jid_ in gajim.transport_avatar[account][jid]:
- obj.conn.request_vcard(jid_)
-
- if obj.contact:
- self.chg_contact_status(obj.contact, obj.show, obj.status, account)
-
- if obj.popup:
- ctrl = gajim.interface.msg_win_mgr.search_control(jid, account)
- if ctrl:
- GLib.idle_add(ctrl.parent_win.set_active_tab, ctrl)
- else:
- ctrl = gajim.interface.new_chat(obj.contact, account)
- if len(gajim.events.get_events(account, obj.jid)):
- ctrl.read_queue()
-
- def _nec_gc_presence_received(self, obj):
- account = obj.conn.name
- if obj.room_jid in gajim.interface.minimized_controls[account]:
- gc_ctrl = gajim.interface.minimized_controls[account][obj.room_jid]
- else:
- return
-
- if obj.nick == gc_ctrl.nick:
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- obj.room_jid)
- if contact:
- contact.show = obj.show
- self.draw_contact(obj.room_jid, account)
- self.draw_group(_('Groupchats'), account)
-
- def _nec_roster_received(self, obj):
- if obj.received_from_server:
- self.fill_contacts_and_groups_dicts(obj.roster, obj.conn.name)
- self.add_account_contacts(obj.conn.name, improve_speed=False)
- self.fire_up_unread_messages_events(obj.conn.name)
- else:
- # add self contact
- if gajim.config.get('show_self_contact') == 'always':
- account = obj.conn.name
- self_jid = gajim.get_jid_from_account(account)
- if self_jid not in gajim.contacts.get_jid_list(account):
- resource = ''
- if gajim.connections[account].server_resource:
- resource = gajim.connections[account].server_resource
- contact = gajim.contacts.create_contact(jid=self_jid,
- account=account, name=gajim.nicks[account],
- groups=['self_contact'], show='offline', sub='both',
- ask='none', resource=resource)
- gajim.contacts.add_contact(account, contact)
- self.add_contact(self_jid, account)
- if gajim.config.get('remember_opened_chat_controls'):
- account = obj.conn.name
- controls = gajim.config.get_per('accounts', account,
- 'opened_chat_controls')
- if controls:
- for jid in controls.split(','):
- contact = \
- gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- if not contact:
- contact = self.add_to_not_in_the_roster(account,
- jid)
- gajim.interface.on_open_chat_window(None, contact,
- account)
- gajim.config.set_per('accounts', account,
- 'opened_chat_controls', '')
- GLib.idle_add(self.refilter_shown_roster_items)
-
- def _nec_anonymous_auth(self, obj):
- """
- This event is raised when our JID changed (most probably because we use
- anonymous account. We update contact and roster entry in this case
- """
- self.rename_self_contact(obj.old_jid, obj.new_jid, obj.conn.name)
-
- def _nec_our_show(self, obj):
- model = self.status_combobox.get_model()
- if obj.show == 'offline':
- # sensitivity for this menuitem
- if gajim.get_number_of_connected_accounts() == 0:
- model[self.status_message_menuitem_iter][3] = False
- self.application.set_account_actions_state(obj.conn.name)
- else:
- # sensitivity for this menuitem
- model[self.status_message_menuitem_iter][3] = True
- self.on_status_changed(obj.conn.name, obj.show)
-
- def _nec_connection_type(self, obj):
- self.draw_account(obj.conn.name)
-
- def _nec_agent_removed(self, obj):
- for jid in obj.jid_list:
- self.remove_contact(jid, obj.conn.name, backend=True)
-
- def _nec_pep_received(self, obj):
- if obj.jid == common.gajim.get_jid_from_account(obj.conn.name):
- self.draw_account(obj.conn.name)
-
- if obj.pep_type == 'nickname':
- self.draw_contact(obj.jid, obj.conn.name)
- else:
- self.draw_pep(obj.jid, obj.conn.name, obj.pep_type)
-
- def _nec_vcard_received(self, obj):
- if obj.resource:
- # it's a muc occupant vcard
- return
- self.draw_avatar(obj.jid, obj.conn.name)
-
- def _nec_gc_subject_received(self, obj):
- contact = gajim.contacts.get_contact_with_highest_priority(
- obj.conn.name, obj.room_jid)
- if contact:
- contact.status = obj.subject
- self.draw_contact(obj.room_jid, obj.conn.name)
-
- def _nec_metacontacts_received(self, obj):
- self.redraw_metacontacts(obj.conn.name)
-
- def _nec_signed_in(self, obj):
- self.application.set_account_actions_state(obj.conn.name, True)
- self.draw_account(obj.conn.name)
-
- def _nec_decrypted_message_received(self, obj):
- if not obj.msgtxt: # empty message text
- return True
- if obj.mtype not in ('normal', 'chat'):
- return
- if obj.mtype == 'normal' and obj.popup:
- # it's single message to be autopopuped
- dialogs.SingleMessageWindow(obj.conn.name, obj.jid,
- action='receive', from_whom=obj.jid, subject=obj.subject,
- message=obj.msgtxt, resource=obj.resource, session=obj.session,
- form_node=obj.form_node)
- return
- if obj.session.control and obj.mtype == 'chat':
- typ = ''
- xep0184_id = None
- if obj.mtype == 'error':
- typ = 'error'
- if obj.forwarded and obj.sent:
- typ = 'out'
- if obj.jid != gajim.get_jid_from_account(obj.conn.name):
- xep0184_id = obj.id_
-
- obj.session.control.print_conversation(obj.msgtxt, typ,
- tim=obj.timestamp, encrypted=obj.encrypted, subject=obj.subject,
- xhtml=obj.xhtml, displaymarking=obj.displaymarking,
- msg_log_id=obj.msg_log_id, msg_stanza_id=obj.id_, correct_id=obj.correct_id,
- xep0184_id=xep0184_id)
- if obj.msg_log_id:
- pw = obj.session.control.parent_win
- end = obj.session.control.was_at_the_end
- if not pw or (pw.get_active_control() and obj.session.control \
- == pw.get_active_control() and pw.is_active() and end):
- gajim.logger.set_read_messages([obj.msg_log_id])
- elif obj.popup and obj.mtype == 'chat':
- contact = gajim.contacts.get_contact(obj.conn.name, obj.jid)
- obj.session.control = gajim.interface.new_chat(contact,
- obj.conn.name, session=obj.session)
- if len(gajim.events.get_events(obj.conn.name, obj.fjid)):
- obj.session.control.read_queue()
-
- if obj.show_in_roster:
- self.draw_contact(obj.jid, obj.conn.name)
- self.show_title() # we show the * or [n]
- # Select the big brother contact in roster, it's visible because it
- # has events.
- family = gajim.contacts.get_metacontacts_family(obj.conn.name,
- obj.jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- gajim.contacts.get_nearby_family_and_big_brother(family,
- obj.conn.name)
- else:
- bb_jid, bb_account = obj.jid, obj.conn.name
- self.select_contact(bb_jid, bb_account)
-
-################################################################################
-### Menu and GUI callbacks
-### FIXME: order callbacks in itself...
-################################################################################
-
- def on_bookmark_menuitem_activate(self, widget, account, bookmark):
- gajim.interface.join_gc_room(account, bookmark['jid'], bookmark['nick'],
- bookmark['password'])
-
- def on_info(self, widget, contact, account):
- """
- Call vcard_information_window class to display contact's information
- """
- if gajim.connections[account].is_zeroconf:
- self.on_info_zeroconf(widget, contact, account)
- return
-
- info = gajim.interface.instances[account]['infos']
- if contact.jid in info:
- info[contact.jid].window.present()
- else:
- info[contact.jid] = vcard.VcardWindow(contact, account)
-
- def on_info_zeroconf(self, widget, contact, account):
- info = gajim.interface.instances[account]['infos']
- if contact.jid in info:
- info[contact.jid].window.present()
- else:
- contact = gajim.contacts.get_first_contact_from_jid(account,
- contact.jid)
- if contact.show in ('offline', 'error'):
- # don't show info on offline contacts
- return
- info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account)
-
- def on_agent_logging(self, widget, jid, state, account):
- """
- When an agent is requested to log in or off
- """
- gajim.connections[account].send_agent_status(jid, state)
-
- def on_edit_agent(self, widget, contact, account):
- """
- When we want to modify the agent registration
- """
- gajim.connections[account].request_register_agent_info(contact.jid)
-
- def on_remove_agent(self, widget, list_):
- """
- When an agent is requested to be removed. list_ is a list of (contact,
- account) tuple
- """
- for (contact, account) in list_:
- if gajim.config.get_per('accounts', account, 'hostname') == \
- contact.jid:
- # We remove the server contact
- # remove it from treeview
- gajim.connections[account].unsubscribe(contact.jid)
- self.remove_contact(contact.jid, account, backend=True)
- return
-
- def remove(list_):
- for (contact, account) in list_:
- full_jid = contact.get_full_jid()
- gajim.connections[account].unsubscribe_agent(full_jid)
- # remove transport from treeview
- self.remove_contact(contact.jid, account, backend=True)
-
- # Check if there are unread events from some contacts
- has_unread_events = False
- for (contact, account) in list_:
- for jid in gajim.events.get_events(account):
- if jid.endswith(contact.jid):
- has_unread_events = True
- break
- if has_unread_events:
- dialogs.ErrorDialog(_('You have unread messages'),
- _('You must read them before removing this transport.'))
- return
- if len(list_) == 1:
- pritext = _('Transport "%s" will be removed') % list_[0][0].jid
- sectext = _('You will no longer be able to send and receive '
- 'messages from contacts using this transport.')
- else:
- pritext = _('Transports will be removed')
- jids = ''
- for (contact, account) in list_:
- jids += '\n ' + contact.get_shown_name() + ','
- jids = jids[:-1] + '.'
- sectext = _('You will no longer be able to send and receive '
- 'messages to contacts from these transports: %s') % jids
- dialogs.ConfirmationDialog(pritext, sectext,
- on_response_ok = (remove, list_))
-
- def _nec_blocking(self, obj):
- if obj.unblock_all:
- jids = gajim.contacts.get_jid_list(obj.conn.name)
- self._idle_draw_jids_of_account(jids, obj.conn.name)
- else:
- for jid in obj.blocked_jids + obj.unblocked_jids:
- self.draw_contact(jid, obj.conn.name)
-
- def on_block(self, widget, list_, group=None):
- """
- When clicked on the 'block' button in context menu. list_ is a list of
- (contact, account)
- """
- def on_continue(msg, pep_dict):
- if msg is None:
- # user pressed Cancel to change status message dialog
- return
- accounts = set(i[1] for i in list_ if (gajim.connections[i[1]].\
- privacy_rules_supported or (group is None and gajim.\
- connections[i[1]].blocking_supported)))
- if group is None:
- for acct in accounts:
- l_ = [i[0] for i in list_ if i[1] == acct]
- gajim.connections[acct].block_contacts(l_, msg)
- for contact in l_:
- self.draw_contact(contact.jid, acct)
- else:
- for acct in accounts:
- l_ = [i[0] for i in list_ if i[1] == acct]
- gajim.connections[acct].block_group(group, l_, msg)
- self.draw_group(group, acct)
- for contact in l_:
- self.draw_contact(contact.jid, acct)
-
- def _block_it(is_checked=None):
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_block', 'no')
- else:
- gajim.config.set('confirm_block', 'yes')
- self.get_status_message('offline', on_continue, show_pep=False)
-
- confirm_block = gajim.config.get('confirm_block')
- if confirm_block == 'no':
- _block_it()
- return
- pritext = _('You are about to block a contact. Are you sure you want'
- ' to continue?')
- sectext = _('This contact will see you offline and you will not '
- 'receive messages it sends you.')
- dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('_Do not ask me again'), on_response_ok=_block_it)
-
- def on_unblock(self, widget, list_, group=None):
- """
- When clicked on the 'unblock' button in context menu.
- """
- accounts = set(i[1] for i in list_ if (gajim.connections[i[1]].\
- privacy_rules_supported or (group is None and gajim.\
- connections[i[1]].blocking_supported)))
- if group is None:
- for acct in accounts:
- l_ = [i[0] for i in list_ if i[1] == acct]
- gajim.connections[acct].unblock_contacts(l_)
- for contact in l_:
- self.draw_contact(contact.jid, acct)
- else:
- for acct in accounts:
- l_ = [i[0] for i in list_ if i[1] == acct]
- gajim.connections[acct].unblock_group(group, l_)
- self.draw_group(group, acct)
- for contact in l_:
- self.draw_contact(contact.jid, acct)
- for acct in accounts:
- if 'privacy_list_block' in gajim.interface.instances[acct]:
- del gajim.interface.instances[acct]['privacy_list_block']
-
- def on_rename(self, widget, row_type, jid, account):
- # this function is called either by F2 or by Rename menuitem
- if 'rename' in gajim.interface.instances:
- gajim.interface.instances['rename'].dialog.present()
- return
-
- # account is offline, don't allow to rename
- if gajim.connections[account].connected < 2:
- return
- if row_type in ('contact', 'agent'):
- # it's jid
- title = _('Rename Contact')
- message = _('Enter a new nickname for contact %s') % jid
- old_text = gajim.contacts.get_contact_with_highest_priority(account,
- jid).name
- elif row_type == 'group':
- if jid in helpers.special_groups + (_('General'),):
- return
- old_text = jid
- title = _('Rename Group')
- message = _('Enter a new name for group %s') % \
- GLib.markup_escape_text(jid)
-
- def on_renamed(new_text, account, row_type, jid, old_text):
- if 'rename' in gajim.interface.instances:
- del gajim.interface.instances['rename']
- if row_type in ('contact', 'agent'):
- if old_text == new_text:
- return
- contacts = gajim.contacts.get_contacts(account, jid)
- for contact in contacts:
- contact.name = new_text
- gajim.connections[account].update_contact(jid, new_text, \
- contacts[0].groups)
- self.draw_contact(jid, account)
- # Update opened chats
- for ctrl in gajim.interface.msg_win_mgr.get_controls(jid,
- account):
- ctrl.update_ui()
- win = gajim.interface.msg_win_mgr.get_window(jid, account)
- win.redraw_tab(ctrl)
- win.show_title()
- elif row_type == 'group':
- # in Column.JID column, we hold the group name (which is not escaped)
- self.rename_group(old_text, new_text, account)
-
- def on_canceled():
- if 'rename' in gajim.interface.instances:
- del gajim.interface.instances['rename']
-
- gajim.interface.instances['rename'] = dialogs.InputDialog(title,
- message, old_text, False, (on_renamed, account, row_type, jid,
- old_text), on_canceled, transient_for=self.window)
-
- def on_remove_group_item_activated(self, widget, group, account):
- def on_ok(checked):
- for contact in gajim.contacts.get_contacts_from_group(account,
- group):
- if not checked:
- self.remove_contact_from_groups(contact.jid, account,
- [group])
- else:
- gajim.connections[account].unsubscribe(contact.jid)
- self.remove_contact(contact.jid, account, backend=True)
-
- dialogs.ConfirmationDialogCheck(_('Remove Group'),
- _('Do you want to remove group %s from the roster?') % group,
- _('Also remove all contacts in this group from your roster'),
- on_response_ok=on_ok)
-
- def on_assign_pgp_key(self, widget, contact, account):
- attached_keys = gajim.config.get_per('accounts', account,
- 'attached_gpg_keys').split()
- keys = {}
- keyID = _('None')
- for i in range(len(attached_keys) // 2):
- keys[attached_keys[2*i]] = attached_keys[2*i+1]
- if attached_keys[2*i] == contact.jid:
- keyID = attached_keys[2*i+1]
- public_keys = gajim.connections[account].ask_gpg_keys()
- public_keys[_('None')] = _('None')
-
- def on_key_selected(keyID):
- if keyID is None:
- return
- if keyID[0] == _('None'):
- if contact.jid in keys:
- del keys[contact.jid]
- keyID = ''
- else:
- keyID = keyID[0]
- keys[contact.jid] = keyID
-
- ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
- if ctrl:
- ctrl.update_ui()
-
- keys_str = ''
- for jid in keys:
- keys_str += jid + ' ' + keys[jid] + ' '
- gajim.config.set_per('accounts', account, 'attached_gpg_keys',
- keys_str)
- for u in gajim.contacts.get_contacts(account, contact.jid):
- u.keyID = helpers.prepare_and_validate_gpg_keyID(account,
- contact.jid, keyID)
-
- dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'),
- _('Select a key to apply to the contact'), public_keys,
- on_key_selected, selected=keyID, transient_for=self.window)
-
- def on_set_custom_avatar_activate(self, widget, contact, account):
- def on_ok(widget, path_to_file):
- filesize = os.path.getsize(path_to_file) # in bytes
- invalid_file = False
- msg = ''
- if os.path.isfile(path_to_file):
- stat = os.stat(path_to_file)
- if stat[6] == 0:
- invalid_file = True
- msg = _('File is empty')
- else:
- invalid_file = True
- msg = _('File does not exist')
- if invalid_file:
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(path_to_file)
- if filesize > 16384: # 16 kb
- # get the image at 'tooltip size'
- # and hope that user did not specify in ACE crazy size
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip')
- except GObject.GError as msg: # unknown format
- # msg should be string, not object instance
- msg = str(msg)
- dialogs.ErrorDialog(_('Could not load image'), msg)
- return
- gajim.interface.save_avatar_files(contact.jid, pixbuf, local=True)
- dlg.destroy()
- self.update_avatar_in_gui(contact.jid, account)
-
- def on_clear(widget):
- dlg.destroy()
- # Delete file:
- gajim.interface.remove_avatar_files(contact.jid, local=True)
- self.update_avatar_in_gui(contact.jid, account)
-
- dlg = dialogs.AvatarChooserDialog(on_response_ok=on_ok,
- on_response_clear=on_clear)
-
- def on_edit_groups(self, widget, list_):
- dialogs.EditGroupsDialog(list_)
-
- def on_history(self, widget, contact, account):
- """
- When history menuitem is activated: call log window
- """
- if 'logs' in gajim.interface.instances:
- gajim.interface.instances['logs'].window.present()
- gajim.interface.instances['logs'].open_history(contact.jid, account)
- else:
- gajim.interface.instances['logs'] = history_window.\
- HistoryWindow(contact.jid, account)
-
- def on_disconnect(self, widget, jid, account):
- """
- When disconnect menuitem is activated: disconect from room
- """
- if jid in gajim.interface.minimized_controls[account]:
- ctrl = gajim.interface.minimized_controls[account][jid]
- ctrl.shutdown()
- ctrl.got_disconnected()
- self.remove_groupchat(jid, account)
-
- def on_reconnect(self, widget, jid, account):
- """
- When reconnect menuitem is activated: join the room
- """
- if jid in gajim.interface.minimized_controls[account]:
- ctrl = gajim.interface.minimized_controls[account][jid]
- gajim.interface.join_gc_room(account, jid, ctrl.nick,
- gajim.gc_passwords.get(jid, ''))
-
- def on_send_single_message_menuitem_activate(self, widget, account,
- contact=None):
- if contact is None:
- dialogs.SingleMessageWindow(account, action='send')
- elif isinstance(contact, list):
- dialogs.SingleMessageWindow(account, contact, 'send')
- else:
- jid = contact.jid
- if contact.jid == gajim.get_jid_from_account(account):
- jid += '/' + contact.resource
- dialogs.SingleMessageWindow(account, jid, 'send')
-
- def on_send_file_menuitem_activate(self, widget, contact, account,
- resource=None):
- gajim.interface.instances['file_transfers'].show_file_send_request(
- account, contact)
-
- def on_add_special_notification_menuitem_activate(self, widget, jid):
- dialogs.AddSpecialNotificationDialog(jid)
-
- def on_invite_to_new_room(self, widget, list_, resource=None):
- """
- Resource parameter MUST NOT be used if more than one contact in list
- """
- account_list = []
- jid_list = []
- for (contact, account) in list_:
- if contact.jid not in jid_list:
- if resource: # we MUST have one contact only in list_
- fjid = contact.jid + '/' + resource
- jid_list.append(fjid)
- else:
- jid_list.append(contact.jid)
- if account not in account_list:
- account_list.append(account)
- # transform None in 'jabber'
- type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber'
- for account in account_list:
- if gajim.connections[account].muc_jid[type_]:
- # create the room on this muc server
- if 'join_gc' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['join_gc'].window.\
- destroy()
- try:
- gajim.interface.instances[account]['join_gc'] = \
- dialogs.JoinGroupchatWindow(account,
- gajim.connections[account].muc_jid[type_],
- automatic = {'invities': jid_list})
- except GajimGeneralException:
- continue
- break
-
- def on_invite_to_room(self, widget, list_, room_jid, room_account,
- resource=None):
- """
- Resource parameter MUST NOT be used if more than one contact in list
- """
- for e in list_:
- contact = e[0]
- contact_jid = contact.jid
- if resource: # we MUST have one contact only in list_
- contact_jid += '/' + resource
- gajim.connections[room_account].send_invite(room_jid, contact_jid)
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
- room_account)
- if gc_control:
- gc_control.print_conversation(
- _('%(jid)s has been invited in this room') % {
- 'jid': contact_jid}, graphics=False)
-
- def on_all_groupchat_maximized(self, widget, group_list):
- for (contact, account) in group_list:
- self.on_groupchat_maximized(widget, contact.jid, account)
-
- def on_groupchat_maximized(self, widget, jid, account):
- """
- When a groupchat is maximized
- """
- if not jid in gajim.interface.minimized_controls[account]:
- # Already opened?
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
- account)
- if gc_control:
- mw = gajim.interface.msg_win_mgr.get_window(jid, account)
- mw.set_active_tab(gc_control)
- mw.window.get_window().focus(Gtk.get_current_event_time())
- return
- ctrl = gajim.interface.minimized_controls[account][jid]
- mw = gajim.interface.msg_win_mgr.get_window(jid, account)
- if not mw:
- mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact,
- ctrl.account, ctrl.type_id)
- id_ = mw.window.connect('motion-notify-event',
- ctrl._on_window_motion_notify)
- ctrl.handlers[id_] = mw.window
- ctrl.parent_win = mw
- ctrl.on_groupchat_maximize()
- mw.new_tab(ctrl)
- mw.set_active_tab(ctrl)
- mw.window.get_window().focus(Gtk.get_current_event_time())
- self.remove_groupchat(jid, account)
-
- def on_edit_account(self, widget, account):
- if 'accounts' in gajim.interface.instances:
- gajim.interface.instances['accounts'].window.present()
- else:
- gajim.interface.instances['accounts'] = config.AccountsWindow()
- gajim.interface.instances['accounts'].select_account(account)
-
- def on_open_gmail_inbox(self, widget, account):
- url = gajim.connections[account].gmail_url
- if url:
- helpers.launch_browser_mailer('url', url)
-
- def on_change_status_message_activate(self, widget, account):
- show = gajim.SHOW_LIST[gajim.connections[account].connected]
- def on_response(message, pep_dict):
- if message is None: # None is if user pressed Cancel
- return
- self.send_status(account, show, message)
- self.send_pep(account, pep_dict)
- dialogs.ChangeStatusMessageDialog(on_response, show)
-
- def on_add_to_roster(self, widget, contact, account):
- dialogs.AddNewContactWindow(account, contact.jid, contact.name)
-
- def on_roster_treeview_key_press_event(self, widget, event):
- """
- When a key is pressed in the treeviews
- """
- if event.keyval == Gdk.KEY_Escape:
- if self.rfilter_enabled:
- self.disable_rfilter()
- else:
- self.tree.get_selection().unselect_all()
- elif event.keyval == Gdk.KEY_F2:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- type_ = model[path][Column.TYPE]
- if type_ in ('contact', 'group', 'agent'):
- jid = model[path][Column.JID]
- account = model[path][Column.ACCOUNT]
- self.on_rename(widget, type_, jid, account)
-
- elif event.keyval == Gdk.KEY_Delete:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if not len(list_of_paths):
- return
- type_ = model[list_of_paths[0]][Column.TYPE]
- account = model[list_of_paths[0]][Column.ACCOUNT]
- if type_ in ('account', 'group', 'self_contact') or \
- account == gajim.ZEROCONF_ACC_NAME:
- return
- list_ = []
- for path in list_of_paths:
- if model[path][Column.TYPE] != type_:
- return
- jid = model[path][Column.JID]
- account = model[path][Column.ACCOUNT]
- if not gajim.account_is_connected(account):
- continue
- contact = gajim.contacts.get_contact_with_highest_priority(
- account, jid)
- list_.append((contact, account))
- if not list_:
- return
- if type_ == 'contact':
- self.on_req_usub(widget, list_)
- elif type_ == 'agent':
- self.on_remove_agent(widget, list_)
-
- elif not (event.get_state() & (Gdk.ModifierType.CONTROL_MASK | \
- Gdk.ModifierType.MOD1_MASK)):
- num = Gdk.keyval_to_unicode(event.keyval)
- if num and num > 31:
- # if we got unicode symbol without ctrl / alt
- self.enable_rfilter(chr(num))
-
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
- event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
- event.keyval == Gdk.KEY_U:
- self.enable_rfilter('')
- self.rfilter_entry.event(event)
-
- elif event.keyval == Gdk.KEY_Left:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- iter_ = model.get_iter(path)
- if model.iter_has_child(iter_) and self.tree.row_expanded(path):
- self.tree.collapse_row(path)
- return True
- elif path.get_depth() > 1:
- self.tree.set_cursor(path[:-1])
- return True
- elif event.keyval == Gdk.KEY_Right:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- iter_ = model.get_iter(path)
- if model.iter_has_child(iter_):
- self.tree.expand_row(path, False)
- return True
-
- def on_roster_treeview_button_release_event(self, widget, event):
- try:
- path = self.tree.get_path_at_pos(int(event.x), int(event.y))[0]
- except TypeError:
- return False
-
- if event.button == 1: # Left click
- if gajim.single_click and not event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
- not event.get_state() & Gdk.ModifierType.CONTROL_MASK:
- # Check if button has been pressed on the same row
- if self.clicked_path == path:
- self.on_row_activated(widget, path)
- self.clicked_path = None
-
- def accel_group_func(self, accel_group, acceleratable, keyval, modifier):
- # CTRL mask
- if modifier & Gdk.ModifierType.CONTROL_MASK:
- if keyval == Gdk.KEY_s: # CTRL + s
- model = self.status_combobox.get_model()
- accounts = list(gajim.connections.keys())
- status = model[self.previous_status_combobox_active][2]
- def on_response(message, pep_dict):
- if message is not None: # None if user pressed Cancel
- for account in accounts:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- current_show = gajim.SHOW_LIST[
- gajim.connections[account].connected]
- self.send_status(account, current_show, message)
- self.send_pep(account, pep_dict)
- dialogs.ChangeStatusMessageDialog(on_response, status)
- return True
- elif keyval == Gdk.KEY_k: # CTRL + k
- self.enable_rfilter('')
-
- def on_roster_treeview_button_press_event(self, widget, event):
- try:
- pos = self.tree.get_path_at_pos(int(event.x), int(event.y))
- path, x = pos[0], pos[2]
- except TypeError:
- self.tree.get_selection().unselect_all()
- return False
-
- if event.button == 3: # Right click
- try:
- model, list_of_paths = self.tree.get_selection().\
- get_selected_rows()
- except TypeError:
- list_of_paths = []
- if path not in list_of_paths:
- self.tree.get_selection().unselect_all()
- self.tree.get_selection().select_path(path)
- return self.show_treeview_menu(event)
-
- elif event.button == 2: # Middle click
- try:
- model, list_of_paths = self.tree.get_selection().\
- get_selected_rows()
- except TypeError:
- list_of_paths = []
- if list_of_paths != [path]:
- self.tree.get_selection().unselect_all()
- self.tree.get_selection().select_path(path)
- type_ = model[path][Column.TYPE]
- if type_ in ('agent', 'contact', 'self_contact', 'groupchat'):
- self.on_row_activated(widget, path)
- elif type_ == 'account':
- account = model[path][Column.ACCOUNT]
- if account != 'all':
- show = gajim.connections[account].connected
- if show > 1: # We are connected
- self.on_change_status_message_activate(widget, account)
- return True
- show = helpers.get_global_show()
- if show == 'offline':
- return True
- def on_response(message, pep_dict):
- if message is None:
- return True
- for acct in gajim.connections:
- if not gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- continue
- current_show = gajim.SHOW_LIST[gajim.connections[acct].\
- connected]
- self.send_status(acct, current_show, message)
- self.send_pep(acct, pep_dict)
- dialogs.ChangeStatusMessageDialog(on_response, show)
- return True
-
- elif event.button == 1: # Left click
- model = self.modelfilter
- type_ = model[path][Column.TYPE]
- # x_min is the x start position of status icon column
- if gajim.config.get('avatar_position_in_roster') == 'left':
- x_min = gajim.config.get('roster_avatar_width')
- else:
- x_min = 0
- if gajim.single_click and not event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
- not event.get_state() & Gdk.ModifierType.CONTROL_MASK:
- # Don't handle double click if we press icon of a metacontact
- titer = model.get_iter(path)
- if x > x_min and x < x_min + 27 and type_ == 'contact' and \
- model.iter_has_child(titer):
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return
- # We just save on which row we press button, and open chat
- # window on button release to be able to do DND without opening
- # chat window
- self.clicked_path = path
- return
- else:
- if type_ == 'group' and x < 27:
- # first cell in 1st column (the arrow SINGLE clicked)
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.expand_group_row(path)
-
- elif type_ == 'contact' and x > x_min and x < x_min + 27:
- if (self.tree.row_expanded(path)):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
-
- def expand_group_row(self, path):
- self.tree.expand_row(path, False)
- iter = self.modelfilter.get_iter(path)
- child_iter = self.modelfilter.iter_children(iter)
- while child_iter:
- type_ = self.modelfilter[child_iter][Column.TYPE]
- account = self.modelfilter[child_iter][Column.ACCOUNT]
- group = self.modelfilter[child_iter][Column.JID]
- if type_ == 'group' and account + group not in self.collapsed_rows:
- self.expand_group_row(self.modelfilter.get_path(child_iter))
- child_iter = self.modelfilter.iter_next(child_iter)
-
- def on_req_usub(self, widget, list_):
- """
- Remove a contact. list_ is a list of (contact, account) tuples
- """
- def on_ok(is_checked, list_):
- remove_auth = True
- if len(list_) == 1:
- contact = list_[0][0]
- if contact.sub != 'to' and is_checked:
- remove_auth = False
- for (contact, account) in list_:
- if _('Not in Roster') not in contact.get_shown_groups():
- gajim.connections[account].unsubscribe(contact.jid,
- remove_auth)
- self.remove_contact(contact.jid, account, backend=True)
- if not remove_auth and contact.sub == 'both':
- contact.name = ''
- contact.groups = []
- contact.sub = 'from'
- # we can't see him, but have to set it manually in contact
- contact.show = 'offline'
- gajim.contacts.add_contact(account, contact)
- self.add_contact(contact.jid, account)
- def on_ok2(list_):
- on_ok(False, list_)
-
- if len(list_) == 1:
- contact = list_[0][0]
- pritext = _('Contact "%s" will be removed from your roster') % \
- contact.get_shown_name()
- sectext = _('You are about to remove "%(name)s" (%(jid)s) from '
- 'your roster.\n') % {'name': contact.get_shown_name(),
- 'jid': contact.jid}
- if contact.sub == 'to':
- dialogs.ConfirmationDialog(pritext, sectext + \
- _('By removing this contact you also remove authorization '
- 'resulting in them always seeing you as offline.'),
- on_response_ok=(on_ok2, list_))
- elif _('Not in Roster') in contact.get_shown_groups():
- # Contact is not in roster
- dialogs.ConfirmationDialog(pritext, sectext + \
- _('Do you want to continue?'), on_response_ok=(on_ok2,
- list_))
- else:
- dialogs.ConfirmationDialogCheck(pritext, sectext + \
- _('By removing this contact you also by default remove '
- 'authorization resulting in them always seeing you as'
- ' offline.'),
- _('I want this contact to know my status after removal'),
- on_response_ok=(on_ok, list_))
- else:
- # several contact to remove at the same time
- pritext = _('Contacts will be removed from your roster')
- jids = ''
- for (contact, account) in list_:
- jids += '\n ' + contact.get_shown_name() + ' (%s)' % \
- contact.jid + ','
- sectext = _('By removing these contacts:%s\nyou also remove '
- 'authorization resulting in them always seeing you as '
- 'offline.') % jids
- dialogs.ConfirmationDialog(pritext, sectext,
- on_response_ok=(on_ok2, list_))
-
- def on_send_custom_status(self, widget, contact_list, show, group=None):
- """
- Send custom status
- """
- # contact_list has only one element except if group != None
- def on_response(message, pep_dict):
- if message is None: # None if user pressed Cancel
- return
- account_list = []
- for (contact, account) in contact_list:
- if account not in account_list:
- account_list.append(account)
- # 1. update status_sent_to_[groups|users] list
- if group:
- for account in account_list:
- if account not in gajim.interface.status_sent_to_groups:
- gajim.interface.status_sent_to_groups[account] = {}
- gajim.interface.status_sent_to_groups[account][group] = show
- else:
- for (contact, account) in contact_list:
- if account not in gajim.interface.status_sent_to_users:
- gajim.interface.status_sent_to_users[account] = {}
- gajim.interface.status_sent_to_users[account][contact.jid] \
- = show
-
- # 2. update privacy lists if main status is invisible
- for account in account_list:
- if gajim.SHOW_LIST[gajim.connections[account].connected] == \
- 'invisible':
- gajim.connections[account].set_invisible_rule()
-
- # 3. send directed presence
- for (contact, account) in contact_list:
- our_jid = gajim.get_jid_from_account(account)
- jid = contact.jid
- if jid == our_jid:
- jid += '/' + contact.resource
- self.send_status(account, show, message, to=jid)
-
- def send_it(is_checked=None):
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_custom_status', 'no')
- else:
- gajim.config.set('confirm_custom_status', 'yes')
- self.get_status_message(show, on_response, show_pep=False,
- always_ask=True)
-
- confirm_custom_status = gajim.config.get('confirm_custom_status')
- if confirm_custom_status == 'no':
- send_it()
- return
- pritext = _('You are about to send a custom status. Are you sure you '
- 'want to continue?')
- sectext = _('This contact will temporarily see you as %(status)s, '
- 'but only until you change your status. Then they will see '
- 'your global status.') % {'status': show}
- dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('_Do not ask me again'), on_response_ok=send_it)
-
- def on_status_combobox_changed(self, widget):
- """
- When we change our status via the combobox
- """
- model = self.status_combobox.get_model()
- active = self.status_combobox.get_active()
- if active == -1: # no active item
- return
- if not self.combobox_callback_active:
- self.previous_status_combobox_active = active
- return
- accounts = list(gajim.connections.keys())
- if len(accounts) == 0:
- dialogs.ErrorDialog(_('No account available'),
- _('You must create an account before you can chat with other '
- 'contacts.'))
- self.update_status_combobox()
- return
- status = model[active][2]
- # status "desync'ed" or not
- statuses_unified = helpers.statuses_unified()
- if (active == 7 and statuses_unified) or (active == 9 and \
- not statuses_unified):
- # 'Change status message' selected:
- # do not change show, just show change status dialog
- status = model[self.previous_status_combobox_active][2]
- def on_response(message, pep_dict):
- if message is not None: # None if user pressed Cancel
- for account in accounts:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- current_show = gajim.SHOW_LIST[
- gajim.connections[account].connected]
- self.send_status(account, current_show, message)
- self.send_pep(account, pep_dict)
- self.combobox_callback_active = False
- self.status_combobox.set_active(
- self.previous_status_combobox_active)
- self.combobox_callback_active = True
- dialogs.ChangeStatusMessageDialog(on_response, status)
- return
- # we are about to change show, so save this new show so in case
- # after user chooses "Change status message" menuitem
- # we can return to this show
- self.previous_status_combobox_active = active
- connected_accounts = gajim.get_number_of_connected_accounts()
-
- def on_continue(message, pep_dict):
- if message is None:
- # user pressed Cancel to change status message dialog
- self.update_status_combobox()
- return
- global_sync_accounts = []
- for acct in accounts:
- if gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- global_sync_accounts.append(acct)
- global_sync_connected_accounts = \
- gajim.get_number_of_connected_accounts(global_sync_accounts)
- for account in accounts:
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- # we are connected (so we wanna change show and status)
- # or no account is connected and we want to connect with new
- # show and status
-
- if not global_sync_connected_accounts > 0 or \
- gajim.connections[account].connected > 0:
- self.send_status(account, status, message)
- self.send_pep(account, pep_dict)
- self.update_status_combobox()
-
- if status == 'invisible':
- bug_user = False
- for account in accounts:
- if connected_accounts < 1 or gajim.account_is_connected(
- account):
- if not gajim.config.get_per('accounts', account,
- 'sync_with_global_status'):
- continue
- # We're going to change our status to invisible
- if self.connected_rooms(account):
- bug_user = True
- break
- if bug_user:
- def on_ok():
- self.get_status_message(status, on_continue, show_pep=False)
-
- def on_cancel():
- self.update_status_combobox()
-
- dialogs.ConfirmationDialog(
- _('You are participating in one or more group chats'),
- _('Changing your status to invisible will result in '
- 'disconnection from those group chats. Are you sure you '
- 'want to go invisible?'), on_reponse_ok=on_ok,
- on_response_cancel=on_cancel)
- return
-
- self.get_status_message(status, on_continue)
-
- def on_publish_tune_toggled(self, widget, account):
- active = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_tune', active)
- if active:
- gajim.interface.enable_music_listener()
- else:
- gajim.connections[account].retract_tune()
- # disable music listener only if no other account uses it
- for acc in gajim.connections:
- if gajim.config.get_per('accounts', acc, 'publish_tune'):
- break
- else:
- gajim.interface.disable_music_listener()
-
- helpers.update_optional_features(account)
-
- def on_publish_location_toggled(self, widget, account):
- active = widget.get_active()
- gajim.config.set_per('accounts', account, 'publish_location', active)
- if active:
- location_listener.enable()
- else:
- gajim.connections[account].retract_location()
- # disable music listener only if no other account uses it
- for acc in gajim.connections:
- if gajim.config.get_per('accounts', acc, 'publish_location'):
- break
- else:
- location_listener.disable()
-
- helpers.update_optional_features(account)
-
- def on_pep_services_menuitem_activate(self, widget, account):
- if 'pep_services' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['pep_services'].window.present()
- else:
- gajim.interface.instances[account]['pep_services'] = \
- config.ManagePEPServicesWindow(account)
-
- def on_add_new_contact(self, widget, account):
- dialogs.AddNewContactWindow(account)
-
- def on_join_gc_activate(self, widget, account):
- """
- When the join gc menuitem is clicked, show the join gc window
- """
- invisible_show = gajim.SHOW_LIST.index('invisible')
- if gajim.connections[account].connected == invisible_show:
- dialogs.ErrorDialog(_('You cannot join a group chat while you are '
- 'invisible'))
- return
- if 'join_gc' in gajim.interface.instances[account]:
- gajim.interface.instances[account]['join_gc'].window.present()
- else:
- try:
- gajim.interface.instances[account]['join_gc'] = \
- dialogs.JoinGroupchatWindow(account)
- except GajimGeneralException:
- pass
-
- def on_new_chat_menuitem_activate(self, widget, account):
- dialogs.NewChatDialog(account)
-
- def on_show_transports_action(self, action, param):
- gajim.config.set('show_transports_group', param.get_boolean())
- action.set_state(param)
- self.refilter_shown_roster_items()
-
- def on_manage_bookmarks_menuitem_activate(self, widget):
- config.ManageBookmarksWindow()
-
- def on_execute_command(self, widget, contact, account, resource=None):
- """
- Execute command. Full JID needed; if it is other contact, resource is
- necessary. Widget is unnecessary, only to be able to make this a
- callback
- """
- jid = contact.jid
- if resource is not None:
- jid = jid + '/' + resource
- adhoc_commands.CommandWindow(account, jid)
-
- def on_roster_window_focus_in_event(self, widget, event):
- # roster received focus, so if we had urgency REMOVE IT
- # NOTE: we do not have to read the message to remove urgency
- # so this functions does that
- gtkgui_helpers.set_unset_urgency_hint(widget, False)
-
- # if a contact row is selected, update colors (eg. for status msg)
- # because gtk engines may differ in bg when window is selected
- # or not
- if len(self._last_selected_contact):
- for (jid, account) in self._last_selected_contact:
- self.draw_contact(jid, account, selected=True, focus=True)
-
- def on_roster_window_focus_out_event(self, widget, event):
- # if a contact row is selected, update colors (eg. for status msg)
- # because gtk engines may differ in bg when window is selected
- # or not
- if len(self._last_selected_contact):
- for (jid, account) in self._last_selected_contact:
- self.draw_contact(jid, account, selected=True, focus=False)
-
- def on_roster_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- if self.rfilter_enabled:
- self.disable_rfilter()
- return True
- if gajim.interface.msg_win_mgr.mode == \
- MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \
- gajim.interface.msg_win_mgr.one_window_opened():
- # let message window close the tab
- return
- list_of_paths = self.tree.get_selection().get_selected_rows()[1]
- if not len(list_of_paths) and not gajim.config.get(
- 'quit_on_roster_x_button') and ((gajim.interface.systray_enabled and\
- gajim.config.get('trayicon') == 'always') or gajim.config.get(
- 'allow_hide_roster')):
- self.window.hide()
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == \
- Gdk.KEY_i:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- for path in list_of_paths:
- type_ = model[path][Column.TYPE]
- if type_ in ('contact', 'agent'):
- jid = model[path][Column.JID]
- account = model[path][Column.ACCOUNT]
- contact = gajim.contacts.get_first_contact_from_jid(account,
- jid)
- self.on_info(widget, contact, account)
- elif event.get_state() & Gdk.ModifierType.CONTROL_MASK and event.keyval == \
- Gdk.KEY_h:
- treeselection = self.tree.get_selection()
- model, list_of_paths = treeselection.get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- type_ = model[path][Column.TYPE]
- if type_ in ('contact', 'agent'):
- jid = model[path][Column.JID]
- account = model[path][Column.ACCOUNT]
- contact = gajim.contacts.get_first_contact_from_jid(account,
- jid)
- self.on_history(widget, contact, account)
-
- def on_roster_window_popup_menu(self, widget):
- event = Gdk.Event.new(Gdk.EventType.KEY_PRESS)
- self.show_treeview_menu(event)
-
- def on_row_activated(self, widget, path):
- """
- When an iter is activated (double-click or single click if gnome is set
- this way)
- """
- model = self.modelfilter
- account = model[path][Column.ACCOUNT]
- type_ = model[path][Column.TYPE]
- if type_ in ('group', 'account'):
- if self.tree.row_expanded(path):
- self.tree.collapse_row(path)
- else:
- self.tree.expand_row(path, False)
- return
- if self.rfilter_enabled:
- GObject.idle_add(self.disable_rfilter)
- jid = model[path][Column.JID]
- resource = None
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- titer = model.get_iter(path)
- if contact.is_groupchat():
- first_ev = gajim.events.get_first_event(account, jid)
- if first_ev and self.open_event(account, jid, first_ev):
- # We are invited to a GC
- # open event cares about connecting to it
- self.remove_groupchat(jid, account)
- else:
- self.on_groupchat_maximized(None, jid, account)
- return
-
- # else
- first_ev = gajim.events.get_first_event(account, jid)
- if not first_ev:
- # look in other resources
- for c in gajim.contacts.get_contacts(account, jid):
- fjid = c.get_full_jid()
- first_ev = gajim.events.get_first_event(account, fjid)
- if first_ev:
- resource = c.resource
- break
- if not first_ev and model.iter_has_child(titer):
- child_iter = model.iter_children(titer)
- while not first_ev and child_iter:
- child_jid = model[child_iter][Column.JID]
- first_ev = gajim.events.get_first_event(account, child_jid)
- if first_ev:
- jid = child_jid
- else:
- child_iter = model.iter_next(child_iter)
- session = None
- if first_ev:
- if first_ev.type_ in ('chat', 'normal'):
- session = first_ev.session
- fjid = jid
- if resource:
- fjid += '/' + resource
- if self.open_event(account, fjid, first_ev):
- return
- # else
- contact = gajim.contacts.get_contact(account, jid, resource)
- if not contact or isinstance(contact, list):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if jid == gajim.get_jid_from_account(account):
- resource = None
-
- gajim.interface.on_open_chat_window(None, contact, account, \
- resource=resource, session=session)
-
- def on_roster_treeview_row_activated(self, widget, path, col=0):
- """
- When an iter is double clicked: open the first event window
- """
- if not gajim.single_click:
- self.on_row_activated(widget, path)
-
- def on_roster_treeview_row_expanded(self, widget, titer, path):
- """
- When a row is expanded change the icon of the arrow
- """
- self._toggeling_row = True
- model = widget.get_model()
- child_model = model.get_model()
- child_iter = model.convert_iter_to_child_iter(titer)
-
- if self.regroup: # merged accounts
- accounts = list(gajim.connections.keys())
- else:
- accounts = [model[titer][Column.ACCOUNT]]
-
- type_ = model[titer][Column.TYPE]
- if type_ == 'group':
- group = model[titer][Column.JID]
- child_model[child_iter][Column.IMG] = \
- gajim.interface.jabber_state_images['16']['opened']
- if self.rfilter_enabled:
- return
- for account in accounts:
- if group in gajim.groups[account]: # This account has this group
- gajim.groups[account][group]['expand'] = True
- if account + group in self.collapsed_rows:
- self.collapsed_rows.remove(account + group)
- for contact in gajim.contacts.iter_contacts(account):
- jid = contact.jid
- if group in contact.groups and \
- gajim.contacts.is_big_brother(account, jid, accounts) and \
- account + group + jid not in self.collapsed_rows:
- titers = self._get_contact_iter(jid, account)
- for titer in titers:
- path = model.get_path(titer)
- self.tree.expand_row(path, False)
- elif type_ == 'account':
- account = list(accounts)[0] # There is only one cause we don't use merge
- if account in self.collapsed_rows:
- self.collapsed_rows.remove(account)
- self.draw_account(account)
- # When we expand, groups are collapsed. Restore expand state
- for group in gajim.groups[account]:
- if gajim.groups[account][group]['expand']:
- titer = self._get_group_iter(group, account)
- if titer:
- path = model.get_path(titer)
- self.tree.expand_row(path, False)
- elif type_ == 'contact':
- # Metacontact got toggled, update icon
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- contact = gajim.contacts.get_contact(account, jid)
- for group in contact.groups:
- if account + group + jid in self.collapsed_rows:
- self.collapsed_rows.remove(account + group + jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- nearby_family = \
- self._get_nearby_family_and_big_brother(family, account)[0]
- # Redraw all brothers to show pending events
- for data in nearby_family:
- self.draw_contact(data['jid'], data['account'])
-
- self._toggeling_row = False
-
- def on_roster_treeview_row_collapsed(self, widget, titer, path):
- """
- When a row is collapsed change the icon of the arrow
- """
- self._toggeling_row = True
- model = widget.get_model()
- child_model = model.get_model()
- child_iter = model.convert_iter_to_child_iter(titer)
-
- if self.regroup: # merged accounts
- accounts = list(gajim.connections.keys())
- else:
- accounts = [model[titer][Column.ACCOUNT]]
-
- type_ = model[titer][Column.TYPE]
- if type_ == 'group':
- child_model[child_iter][Column.IMG] = gajim.interface.\
- jabber_state_images['16']['closed']
- if self.rfilter_enabled:
- return
- group = model[titer][Column.JID]
- for account in accounts:
- if group in gajim.groups[account]: # This account has this group
- gajim.groups[account][group]['expand'] = False
- if account + group not in self.collapsed_rows:
- self.collapsed_rows.append(account + group)
- elif type_ == 'account':
- account = accounts[0] # There is only one cause we don't use merge
- if account not in self.collapsed_rows:
- self.collapsed_rows.append(account)
- self.draw_account(account)
- elif type_ == 'contact':
- # Metacontact got toggled, update icon
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- contact = gajim.contacts.get_contact(account, jid)
- groups = contact.groups
- if not groups:
- groups = [_('General')]
- for group in groups:
- if account + group + jid not in self.collapsed_rows:
- self.collapsed_rows.append(account + group + jid)
- family = gajim.contacts.get_metacontacts_family(account, jid)
- nearby_family = \
- self._get_nearby_family_and_big_brother(family, account)[0]
- # Redraw all brothers to show pending events
- for data in nearby_family:
- self.draw_contact(data['jid'], data['account'])
-
- self._toggeling_row = False
-
- def on_modelfilter_row_has_child_toggled(self, model, path, titer):
- """
- Called when a row has gotten the first or lost its last child row
-
- Expand Parent if necessary.
- """
- if self._toggeling_row:
- # Signal is emitted when we write to our model
- return
-
- type_ = model[titer][Column.TYPE]
- account = model[titer][Column.ACCOUNT]
- if not account:
- return
-
- if type_ == 'contact':
- child_iter = model.convert_iter_to_child_iter(titer)
- if self.model.iter_has_child(child_iter):
- # we are a bigbrother metacontact
- # redraw us to show/hide expand icon
- if self.filtering:
- # Prevent endless loops
- jid = model[titer][Column.JID]
- GLib.idle_add(self.draw_contact, jid, account)
- elif type_ == 'group':
- group = model[titer][Column.JID]
- GLib.idle_add(self._adjust_group_expand_collapse_state, group, account)
- elif type_ == 'account':
- GLib.idle_add(self._adjust_account_expand_collapse_state, account)
-
-# Selection can change when the model is filtered
-# Only write to the model when filtering is finished!
-#
-# FIXME: When we are filtering our custom colors are somehow lost
-#
-# def on_treeview_selection_changed(self, selection):
-# '''Called when selection in TreeView has changed.
-#
-# Redraw unselected rows to make status message readable
-# on all possible backgrounds.
-# '''
-# model, list_of_paths = selection.get_selected_rows()
-# if len(self._last_selected_contact):
-# # update unselected rows
-# for (jid, account) in self._last_selected_contact:
-# GLib.idle_add(self.draw_contact, jid,
-# account)
-# self._last_selected_contact = []
-# if len(list_of_paths) == 0:
-# return
-# for path in list_of_paths:
-# row = model[path]
-# if row[Column.TYPE] != 'contact':
-# self._last_selected_contact = []
-# return
-# jid = row[Column.JID]
-# account = row[Column.ACCOUNT]
-# self._last_selected_contact.append((jid, account))
-# GLib.idle_add(self.draw_contact, jid, account, True)
-
-
- def on_service_disco_menuitem_activate(self, widget, account):
- server_jid = gajim.config.get_per('accounts', account, 'hostname')
- if server_jid in gajim.interface.instances[account]['disco']:
- gajim.interface.instances[account]['disco'][server_jid].\
- window.present()
- else:
- try:
- # Object will add itself to the window dict
- disco.ServiceDiscoveryWindow(account, address_entry=True)
- except GajimGeneralException:
- pass
-
- def on_show_offline_contacts_action(self, action, param):
- """
- When show offline option is changed: redraw the treeview
- """
- action.set_state(param)
- gajim.config.set('showoffline', param.get_boolean())
- self.refilter_shown_roster_items()
- if param.get_boolean():
- # We need to filter twice to show groups with no contacts inside
- # in the correct expand state
- self.refilter_shown_roster_items()
- self.window.lookup_action('show-active').set_enabled(False)
- else:
- self.window.lookup_action('show-active').set_enabled(True)
-
- def on_show_active_contacts_action(self, action, param):
- """
- When show only active contact option is changed: redraw the treeview
- """
- action.set_state(param)
- gajim.config.set('show_only_chat_and_online', param.get_boolean())
- self.refilter_shown_roster_items()
-
- if param.get_boolean():
- # We need to filter twice to show groups with no contacts inside
- # in the correct expand state
- self.refilter_shown_roster_items()
- self.window.lookup_action('show-offline').set_enabled(False)
- else:
- self.window.lookup_action('show-offline').set_enabled(True)
-
- def on_show_roster_action(self, action, param):
- # when num controls is 0 this menuitem is hidden, but still need to
- # disable keybinding
- action.set_state(param)
- if self.hpaned.get_child2() is not None:
- self.show_roster_vbox(param.get_boolean())
-
- def on_rfilter_entry_changed(self, widget):
- """ When we update the content of the filter """
- self.rfilter_string = widget.get_text().lower()
- if self.rfilter_string == '':
- self.disable_rfilter()
- self.refilter_shown_roster_items()
- # select first row
- self.tree.get_selection().unselect_all()
- def _func(model, path, iter_, param):
- if model[iter_][Column.TYPE] == 'contact' and self.rfilter_string in \
- model[iter_][Column.NAME].lower():
- col = self.tree.get_column(0)
- self.tree.set_cursor_on_cell(path, col, None, False)
- return True
- self.modelfilter.foreach(_func, None)
-
- def on_rfilter_entry_icon_press(self, widget, icon, event):
- """
- Disable the roster filtering by clicking the icon in the textEntry
- """
- self.disable_rfilter()
-
- def on_rfilter_entry_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.disable_rfilter()
- elif event.keyval == Gdk.KEY_Return:
- self.tree.grab_focus()
- self.tree.event(event)
- self.disable_rfilter()
- elif event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down):
- self.tree.grab_focus()
- self.tree.event(event)
- elif event.keyval == Gdk.KEY_BackSpace:
- if widget.get_text() == '':
- self.disable_rfilter()
-
- def enable_rfilter(self, search_string):
- self.rfilter_entry.set_visible(True)
- self.rfilter_entry.set_editable(True)
- self.rfilter_entry.grab_focus()
- if self.rfilter_enabled:
- self.rfilter_entry.set_text(self.rfilter_entry.get_text() + \
- search_string)
- else:
- self.rfilter_enabled = True
- self.rfilter_entry.set_text(search_string)
- self.tree.expand_all()
- self.rfilter_entry.set_position(-1)
-
- # If roster is hidden, let's temporarily show it. This can happen if user
- # enables rfilter via keyboard shortcut.
- self.show_roster_vbox(True)
-
- def disable_rfilter(self):
- self.rfilter_enabled = False
- self.rfilter_entry.set_text('')
- self.rfilter_entry.set_visible(False)
- self.rfilter_entry.set_editable(False)
- self.refilter_shown_roster_items()
- self.tree.grab_focus()
- self._readjust_expand_collapse_state()
-
- # If roster was hidden before enable_rfilter was called, hide it back.
- state = self.window.lookup_action('show-roster').get_state().get_boolean()
- if state is False and self.hpaned.get_child2() is not None:
- self.show_roster_vbox(False)
-
- def on_roster_hpaned_notify(self, pane, gparamspec):
- """
- Keep changing the width of the roster
- (when a Gtk.Paned widget handle is dragged)
- """
- if gparamspec and gparamspec.name == 'position':
- roster_width = pane.get_child1().get_allocation().width
- gajim.config.set('roster_width', roster_width)
- gajim.config.set('roster_hpaned_position', pane.get_position())
-
-################################################################################
-### Drag and Drop handling
-################################################################################
-
- def drag_data_get_data(self, treeview, context, selection, target_id,
- etime):
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- if len(list_of_paths) != 1:
- return
- path = list_of_paths[0]
- data = ''
- if path.get_depth() >= 2:
- data = model[path][Column.JID]
- selection.set_text(data, -1)
-
- def drag_begin(self, treeview, context):
- self.dragging = True
-
- def drag_end(self, treeview, context):
- self.dragging = False
-
- def on_drop_rosterx(self, widget, account_source, c_source, account_dest,
- c_dest, was_big_brother, context, etime):
- type_ = 'message'
- if c_dest.show not in ('offline', 'error') and c_dest.supports(
- NS_ROSTERX):
- type_ = 'iq'
- gajim.connections[account_dest].send_contacts([c_source],
- c_dest.get_full_jid(), type_=type_)
-
- def on_drop_in_contact(self, widget, account_source, c_source, account_dest,
- c_dest, was_big_brother, context, etime):
-
- if not gajim.connections[account_source].private_storage_supported or \
- not gajim.connections[account_dest].private_storage_supported:
- dialogs.WarningDialog(_('Metacontacts storage not supported by '
- 'your server'),
- _('Your server does not support storing metacontacts '
- 'information. So this information will not be saved on next '
- 'reconnection.'))
-
- def merge_contacts(is_checked=None):
- contacts = 0
- if is_checked is not None: # dialog has been shown
- if is_checked: # user does not want to be asked again
- gajim.config.set('confirm_metacontacts', 'no')
- else:
- gajim.config.set('confirm_metacontacts', 'yes')
-
- # We might have dropped on a metacontact.
- # Remove it and readd later with updated family info
- dest_family = gajim.contacts.get_metacontacts_family(account_dest,
- c_dest.jid)
- if dest_family:
- self._remove_metacontact_family(dest_family, account_dest)
- source_family = gajim.contacts.get_metacontacts_family(
- account_source, c_source.jid)
- if dest_family == source_family:
- n = contacts = len(dest_family)
- for tag in source_family:
- if tag['jid'] == c_source.jid:
- tag['order'] = contacts
- continue
- if 'order' in tag:
- n -= 1
- tag['order'] = n
- else:
- self._remove_entity(c_dest, account_dest)
-
- old_family = gajim.contacts.get_metacontacts_family(account_source,
- c_source.jid)
- old_groups = c_source.groups
-
- # Remove old source contact(s)
- if was_big_brother:
- # We have got little brothers. Readd them all
- self._remove_metacontact_family(old_family, account_source)
- else:
- # We are only a litle brother. Simply remove us from our big
- # brother
- if self._get_contact_iter(c_source.jid, account_source):
- # When we have been in the group before.
- # Do not try to remove us again
- self._remove_entity(c_source, account_source)
-
- own_data = {}
- own_data['jid'] = c_source.jid
- own_data['account'] = account_source
- # Don't touch the rest of the family
- old_family = [own_data]
-
- # Apply new tag and update contact
- for data in old_family:
- if account_source != data['account'] and not self.regroup:
- continue
-
- _account = data['account']
- _jid = data['jid']
- _contact = gajim.contacts.get_first_contact_from_jid(_account,
- _jid)
- if not _contact:
- # One of the metacontacts may be not connected.
- continue
-
- _contact.groups = c_dest.groups[:]
- gajim.contacts.add_metacontact(account_dest, c_dest.jid,
- _account, _contact.jid, contacts)
- gajim.connections[account_source].update_contact(_contact.jid,
- _contact.name, _contact.groups)
-
- # Re-add all and update GUI
- new_family = gajim.contacts.get_metacontacts_family(account_source,
- c_source.jid)
- brothers = self._add_metacontact_family(new_family, account_source)
-
- for c, acc in brothers:
- self.draw_completely(c.jid, acc)
-
- old_groups.extend(c_dest.groups)
- for g in old_groups:
- self.draw_group(g, account_source)
-
- self.draw_account(account_source)
- context.finish(True, True, etime)
-
- dest_family = gajim.contacts.get_metacontacts_family(account_dest,
- c_dest.jid)
- source_family = gajim.contacts.get_metacontacts_family(account_source,
- c_source.jid)
- confirm_metacontacts = gajim.config.get('confirm_metacontacts')
- if confirm_metacontacts == 'no' or dest_family == source_family:
- merge_contacts()
- return
- pritext = _('You are about to create a metacontact. Are you sure you '
- 'want to continue?')
- sectext = _('Metacontacts are a way to regroup several contacts in one '
- 'line. Generally it is used when the same person has several '
- 'XMPP accounts or transport accounts.')
- dlg = dialogs.ConfirmationDialogCheck(pritext, sectext,
- _('_Do not ask me again'), on_response_ok=merge_contacts)
- if not confirm_metacontacts: # First time we see this window
- dlg.checkbutton.set_active(True)
-
- def on_drop_in_group(self, widget, account, c_source, grp_dest,
- is_big_brother, context, etime, grp_source = None):
- if is_big_brother:
- # add whole metacontact to new group
- self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
- # remove afterwards so the contact is not moved to General in the
- # meantime
- if grp_dest != grp_source:
- self.remove_contact_from_groups(c_source.jid, account,
- [grp_source])
- else:
- # Normal contact or little brother
- family = gajim.contacts.get_metacontacts_family(account,
- c_source.jid)
- if family:
- # Little brother
- # Remove whole family. Remove us from the family.
- # Then re-add other family members.
- self._remove_metacontact_family(family, account)
- gajim.contacts.remove_metacontact(account, c_source.jid)
- for data in family:
- if account != data['account'] and not self.regroup:
- continue
- if data['jid'] == c_source.jid and\
- data['account'] == account:
- continue
- self.add_contact(data['jid'], data['account'])
- break
-
- self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
-
- else:
- # Normal contact
- self.add_contact_to_groups(c_source.jid, account, [grp_dest, ])
- # remove afterwards so the contact is not moved to General in
- # the meantime
- if grp_dest != grp_source:
- self.remove_contact_from_groups(c_source.jid, account,
- [grp_source])
-
- if context.get_actions() in (Gdk.DragAction.MOVE, Gdk.DragAction.COPY):
- context.finish(True, True, etime)
-
- def drag_drop(self, treeview, context, x, y, timestamp):
- target_list = treeview.drag_dest_get_target_list()
- target = treeview.drag_dest_find_target(context, target_list)
- treeview.drag_get_data(context, target, 0)
- context.finish(False, True, 0)
- return True
-
- def move_group(self, old_name, new_name, account):
- for group in list(gajim.groups[account].keys()):
- if group.startswith(old_name):
- self.rename_group(group, group.replace(old_name, new_name),
- account)
-
- def drag_data_received_data(self, treeview, context, x, y, selection, info,
- etime):
- treeview.stop_emission_by_name('drag_data_received')
- drop_info = treeview.get_dest_row_at_pos(x, y)
- if not drop_info:
- return
- data = selection.get_data().decode()
- if not data:
- return # prevents tb when several entrys are dragged
- model = treeview.get_model()
-
- path_dest, position = drop_info
-
- if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 2 \
- and path_dest[1] == 0: # dropped before the first group
- return
- if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 2:
- # dropped before a group: we drop it in the previous group every
- # time
- path_dest = (path_dest[0], path_dest[1]-1)
- # destination: the row something got dropped on
- iter_dest = model.get_iter(path_dest)
- type_dest = model[iter_dest][Column.TYPE]
- jid_dest = model[iter_dest][Column.JID]
- account_dest = model[iter_dest][Column.ACCOUNT]
-
- # drop on account row in merged mode, we cannot know the desired account
- if account_dest == 'all':
- return
- # nothing can be done, if destination account is offline
- if gajim.connections[account_dest].connected < 2:
- return
-
- # A file got dropped on the roster
- if info == self.TARGET_TYPE_URI_LIST:
- if len(path_dest) < 3:
- return
- if type_dest != 'contact':
- return
- c_dest = gajim.contacts.get_contact_with_highest_priority(
- account_dest, jid_dest)
- if not c_dest.supports(NS_FILE):
- return
- uri = data.strip()
- uri_splitted = uri.split() # we may have more than one file dropped
- try:
- # This is always the last element in windows
- uri_splitted.remove('\0')
- except ValueError:
- pass
- nb_uri = len(uri_splitted)
- # Check the URIs
- bad_uris = []
- for a_uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(a_uri)
- if not os.path.isfile(path):
- bad_uris.append(a_uri)
- if len(bad_uris):
- dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris))
- return
- def _on_send_files(account, jid, uris):
- c = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- for uri in uris:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- if os.path.isfile(path): # is it file?
- gajim.interface.instances['file_transfers'].send_file(
- account, c, path)
- # Popup dialog to confirm sending
- prim_text = 'Send file?'
- sec_text = i18n.ngettext('Do you want to send this file to %s:',
- 'Do you want to send these files to %s:', nb_uri) %\
- c_dest.get_shown_name()
- for uri in uri_splitted:
- path = helpers.get_file_path_from_dnd_dropped_uri(uri)
- sec_text += '\n' + os.path.basename(path)
- dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text,
- on_response_ok=(_on_send_files, account_dest, jid_dest,
- uri_splitted))
- dialog.popup()
- return
-
- # a roster entry was dragged and dropped somewhere in the roster
-
- # source: the row that was dragged
- path_source = treeview.get_selection().get_selected_rows()[1][0]
- iter_source = model.get_iter(path_source)
- type_source = model[iter_source][Column.TYPE]
- account_source = model[iter_source][Column.ACCOUNT]
-
- if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
- return
-
- if type_dest == 'self_contact':
- # drop on self contact row
- return
-
- if type_dest == 'groupchat':
- # drop on a minimized groupchat
- # TODO: Invite to groupchat if type_dest = contact
- return
-
- if type_source == 'group':
- if account_source != account_dest:
- # drop on another account
- return
- grp_source = model[iter_source][Column.JID]
- delimiter = gajim.connections[account_source].nested_group_delimiter
- grp_source_list = grp_source.split(delimiter)
- new_grp = None
- if type_dest == 'account':
- new_grp = grp_source_list[-1]
- elif type_dest == 'group':
- grp_dest = model[iter_dest][Column.JID]
- grp_dest_list = grp_dest.split(delimiter)
- # Do not allow to drop on a subgroup of source group
- if grp_source_list[0] != grp_dest_list[0]:
- new_grp = model[iter_dest][Column.JID] + delimiter + \
- grp_source_list[-1]
- if new_grp:
- self.move_group(grp_source, new_grp, account_source)
-
- # Only normal contacts and group can be dragged
- if type_source != 'contact':
- return
-
- # A contact was dropped
- if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
- # drop on zeroconf account, adding not possible
- return
-
- if type_dest == 'account' and account_source == account_dest:
- # drop on the account it was dragged from
- return
-
- # Get valid source group, jid and contact
- it = iter_source
- while model[it][Column.TYPE] == 'contact':
- it = model.iter_parent(it)
- grp_source = model[it][Column.JID]
- if grp_source in helpers.special_groups and \
- grp_source not in ('Not in Roster', 'Observers'):
- # a transport or a minimized groupchat was dragged
- # we can add it to other accounts but not move it to another group,
- # see below
- return
- jid_source = data
- c_source = gajim.contacts.get_contact_with_highest_priority(
- account_source, jid_source)
-
- # Get destination group
- grp_dest = None
- if type_dest == 'group':
- grp_dest = model[iter_dest][Column.JID]
- elif type_dest in ('contact', 'agent'):
- it = iter_dest
- while model[it][Column.TYPE] != 'group':
- it = model.iter_parent(it)
- grp_dest = model[it][Column.JID]
- if grp_dest in helpers.special_groups:
- return
-
- if jid_source == jid_dest:
- if grp_source == grp_dest and account_source == account_dest:
- # Drop on self
- return
-
- # contact drop somewhere in or on a foreign account
- if (type_dest == 'account' or not self.regroup) and \
- account_source != account_dest:
- # add to account in specified group
- dialogs.AddNewContactWindow(account=account_dest, jid=jid_source,
- user_nick=c_source.name, group=grp_dest)
- return
-
- # we may not add contacts from special_groups
- if grp_source in helpers.special_groups :
- return
-
- # Is the contact we drag a meta contact?
- accounts = (self.regroup and gajim.contacts.get_accounts()) or \
- account_source
- is_big_brother = gajim.contacts.is_big_brother(account_source,
- jid_source, accounts)
-
- drop_in_middle_of_meta = False
- if type_dest == 'contact':
- if position == Gtk.TreeViewDropPosition.BEFORE and len(path_dest) == 4:
- drop_in_middle_of_meta = True
- if position == Gtk.TreeViewDropPosition.AFTER and (len(path_dest) == 4 or\
- self.modelfilter.iter_has_child(iter_dest)):
- drop_in_middle_of_meta = True
- # Contact drop on group row or between two contacts that are
- # not metacontacts
- if (type_dest == 'group' or position in (Gtk.TreeViewDropPosition.BEFORE,
- Gtk.TreeViewDropPosition.AFTER)) and not drop_in_middle_of_meta:
- self.on_drop_in_group(None, account_source, c_source, grp_dest,
- is_big_brother, context, etime, grp_source)
- return
-
- # Contact drop on another contact, make meta contacts
- if position == Gtk.TreeViewDropPosition.INTO_OR_AFTER or \
- position == Gtk.TreeViewDropPosition.INTO_OR_BEFORE or drop_in_middle_of_meta:
- c_dest = gajim.contacts.get_contact_with_highest_priority(
- account_dest, jid_dest)
- if not c_dest:
- # c_dest is None if jid_dest doesn't belong to account
- return
- menu = Gtk.Menu()
- item = Gtk.MenuItem.new_with_label(_('Send %s to %s') % (
- c_source.get_shown_name(), c_dest.get_shown_name()))
- item.set_use_underline(False)
- item.connect('activate', self.on_drop_rosterx, account_source,
- c_source, account_dest, c_dest, is_big_brother, context, etime)
- menu.append(item)
-
- dest_family = gajim.contacts.get_metacontacts_family(account_dest,
- c_dest.jid)
- source_family = gajim.contacts.get_metacontacts_family(
- account_source, c_source.jid)
- if dest_family == source_family and dest_family:
- item = Gtk.MenuItem.new_with_label(
- _('Make %s first contact') % (
- c_source.get_shown_name()))
- item.set_use_underline(False)
- else:
- item = Gtk.MenuItem.new_with_label(
- _('Make %s and %s metacontacts') % (
- c_source.get_shown_name(), c_dest.get_shown_name()))
- item.set_use_underline(False)
-
- item.connect('activate', self.on_drop_in_contact, account_source,
- c_source, account_dest, c_dest, is_big_brother, context, etime)
-
- menu.append(item)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, None, 1, etime)
-
-################################################################################
-### Everything about images and icons....
-### Cleanup assigned to Jim++ :-)
-################################################################################
-
- def get_appropriate_state_images(self, jid, size='16', icon_name='online'):
- """
- Check jid and return the appropriate state images dict for the demanded
- size. icon_name is taken into account when jid is from transport:
- transport iconset doesn't contain all icons, so we fall back to jabber
- one
- """
- transport = gajim.get_transport_name_from_jid(jid)
- if transport and size in self.transports_state_images:
- if transport not in self.transports_state_images[size]:
- # we don't have iconset for this transport loaded yet. Let's do
- # it
- self.make_transport_state_images(transport)
- if transport in self.transports_state_images[size] and \
- icon_name in self.transports_state_images[size][transport]:
- return self.transports_state_images[size][transport]
- return gajim.interface.jabber_state_images[size]
-
- def make_transport_state_images(self, transport):
- """
- Initialize opened and closed 'transport' iconset dict
- """
- if not gajim.config.get('use_transports_iconsets'):
- return
-
- folder = os.path.join(helpers.get_transport_path(transport), '32x32')
- self.transports_state_images['32'][transport] = \
- gtkgui_helpers.load_iconset(folder, transport=True)
- folder = os.path.join(helpers.get_transport_path(transport), '16x16')
- self.transports_state_images['16'][transport] = \
- gtkgui_helpers.load_iconset(folder, transport=True)
-
- pixo, pixc = gtkgui_helpers.load_icons_meta()
- self.transports_state_images['opened'][transport] = \
- gtkgui_helpers.load_iconset(folder, pixo, transport=True)
- self.transports_state_images['closed'][transport] = \
- gtkgui_helpers.load_iconset(folder, pixc, transport=True)
-
- def update_jabber_state_images(self):
- # Update the roster
- self.setup_and_draw_roster()
- # Update the status combobox
- model = self.status_combobox.get_model()
- titer = model.get_iter_first()
- while titer:
- if model[titer][2] != '':
- # If it's not change status message iter
- # eg. if it has show parameter not ''
- model[titer][1] = gajim.interface.jabber_state_images['16'][
- model[titer][2]]
- titer = model.iter_next(titer)
- # Update the systray
- if gajim.interface.systray_enabled:
- gajim.interface.systray.set_img()
-
- for win in gajim.interface.msg_win_mgr.windows():
- for ctrl in win.controls():
- ctrl.update_ui()
- win.redraw_tab(ctrl)
-
- self.update_status_combobox()
-
- def set_account_status_icon(self, account):
- status = gajim.connections[account].connected
- child_iterA = self._get_account_iter(account, self.model)
- if not child_iterA:
- return
- if not self.regroup:
- show = gajim.SHOW_LIST[status]
- else: # accounts merged
- show = helpers.get_global_show()
- self.model[child_iterA][Column.IMG] = gajim.interface.jabber_state_images[
- '16'][show]
-
-################################################################################
-### Style and theme related methods
-################################################################################
-
- def show_title(self):
- change_title_allowed = gajim.config.get('change_roster_title')
- if not change_title_allowed:
- return
-
- nb_unread = 0
- for account in gajim.connections:
- # Count events in roster title only if we don't auto open them
- if not helpers.allow_popup_window(account):
- nb_unread += gajim.events.get_nb_events(['chat', 'normal',
- 'file-request', 'file-error', 'file-completed',
- 'file-request-error', 'file-send-error', 'file-stopped',
- 'printed_chat'], account)
-
-
- if gajim.config.get('one_message_window') == 'always_with_roster':
- # always_with_roster mode defers to the MessageWindow
- if not gajim.interface.msg_win_mgr.one_window_opened():
- # No MessageWindow to defer to
- self.window.set_title('Gajim')
- gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
- return
-
- start = ''
- if nb_unread > 1:
- start = '[' + str(nb_unread) + '] '
- elif nb_unread == 1:
- start = '* '
-
- self.window.set_title(start + 'Gajim')
-
- gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread)
-
- def _change_style(self, model, path, titer, option):
- if option is None or model[titer][Column.TYPE] == option:
- # We changed style for this type of row
- model[titer][Column.NAME] = model[titer][Column.NAME]
-
- def change_roster_style(self, option):
- self.model.foreach(self._change_style, option)
- for win in gajim.interface.msg_win_mgr.windows():
- win.repaint_themed_widgets()
-
- def repaint_themed_widgets(self):
- """
- Notify windows that contain themed widgets to repaint them
- """
- for win in gajim.interface.msg_win_mgr.windows():
- win.repaint_themed_widgets()
- for account in gajim.connections:
- for ctrl in list(gajim.interface.minimized_controls[account].values()):
- ctrl.repaint_themed_widgets()
-
- def update_avatar_in_gui(self, jid, account):
- # Update roster
- self.draw_avatar(jid, account)
- # Update chat window
-
- ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
- if ctrl:
- ctrl.show_avatar()
-
- def _iconCellDataFunc(self, column, renderer, model, titer, data=None):
- """
- When a row is added, set properties for icon renderer
- """
- try:
- type_ = model[titer][Column.TYPE]
- except TypeError:
- return
- if type_ == 'account':
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 0)
- elif type_ == 'group':
- self._set_group_row_background_color(renderer)
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][Column.TYPE] == 'group':
- renderer.set_property('xalign', 0.4)
- else:
- renderer.set_property('xalign', 0.2)
- elif type_:
- # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]:
- # This can append when at the moment we add the row
- return
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- self._set_contact_row_background_color(renderer, jid, account)
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][Column.TYPE] == 'contact':
- renderer.set_property('xalign', 1)
- else:
- renderer.set_property('xalign', 0.6)
- renderer.set_property('width', 26)
-
- def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
- """
- When a row is added, set properties for name renderer
- """
- try:
- type_ = model[titer][Column.TYPE]
- except TypeError:
- return
- theme = gajim.config.get('roster_theme')
- if type_ == 'account':
- color = gajim.config.get_per('themes', theme, 'accounttextcolor')
- renderer.set_property('foreground', color or None)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont'))
- renderer.set_property('xpad', 0)
- renderer.set_property('width', 3)
- self._set_account_row_background_color(renderer)
- elif type_ == 'group':
- color = gajim.config.get_per('themes', theme, 'grouptextcolor')
- renderer.set_property('foreground', color or None)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][Column.TYPE] == 'group':
- renderer.set_property('xpad', 8)
- else:
- renderer.set_property('xpad', 4)
- self._set_group_row_background_color(renderer)
- elif type_:
- # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]:
- # This can append when at the moment we add the row
- return
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- color = None
- if type_ == 'groupchat':
- ctrl = gajim.interface.minimized_controls[account].get(jid,
- None)
- if ctrl and ctrl.attention_flag:
- color = gajim.config.get_per('themes', theme,
- 'state_muc_directed_msg_color')
- renderer.set_property('foreground', 'red')
- if not color:
- color = gajim.config.get_per('themes', theme,
- 'contacttextcolor')
- if color:
- renderer.set_property('foreground', color)
- else:
- renderer.set_property('foreground', None)
- self._set_contact_row_background_color(renderer, jid, account)
- renderer.set_property('font',
- gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
- parent_iter = model.iter_parent(titer)
- if model[parent_iter][Column.TYPE] == 'contact':
- renderer.set_property('xpad', 16)
- else:
- renderer.set_property('xpad', 12)
-
- def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
- data=None):
- """
- When a row is added, draw the respective pep icon
- """
- try:
- type_ = model[titer][Column.TYPE]
- except TypeError:
- return
-
- # allocate space for the icon only if needed
- if not model[titer][data] or model[titer][data] == empty_pixbuf:
- renderer.set_property('visible', False)
- else:
- renderer.set_property('visible', True)
-
- if type_ == 'account':
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 1)
- elif type_:
- if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- self._set_contact_row_background_color(renderer, jid, account)
-
- def _fill_avatar_pixbuf_renderer(self, column, renderer, model, titer,
- data=None):
- """
- When a row is added, set properties for avatar renderer
- """
- try:
- type_ = model[titer][Column.TYPE]
- except TypeError:
- return
-
- if type_ in ('group', 'account'):
- renderer.set_property('visible', False)
- return
-
- # allocate space for the icon only if needed
- if model[titer][Column.AVATAR_PIXBUF] or \
- gajim.config.get('avatar_position_in_roster') == 'left':
- renderer.set_property('visible', True)
- if type_:
- # prevent type_ = None, see http://trac.gajim.org/ticket/2534
- if not model[titer][Column.JID] or not model[titer][Column.ACCOUNT]:
- # This can append at the moment we add the row
- return
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- self._set_contact_row_background_color(renderer, jid, account)
- else:
- renderer.set_property('visible', False)
- if model[titer][Column.AVATAR_PIXBUF] == empty_pixbuf and \
- gajim.config.get('avatar_position_in_roster') != 'left':
- renderer.set_property('visible', False)
-
- if gajim.config.get('avatar_position_in_roster') == 'left':
- renderer.set_property('width', gajim.config.get(
- 'roster_avatar_width'))
- renderer.set_property('xalign', 0.5)
- else:
- renderer.set_property('xalign', 1) # align pixbuf to the right
-
- def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
- data=None):
- """
- When a row is added, set properties for padlock renderer
- """
- try:
- type_ = model[titer][Column.TYPE]
- except TypeError:
- return
-
- # allocate space for the icon only if needed
- if type_ == 'account' and model[titer][Column.PADLOCK_PIXBUF]:
- renderer.set_property('visible', True)
- self._set_account_row_background_color(renderer)
- renderer.set_property('xalign', 1) # align pixbuf to the right
- else:
- renderer.set_property('visible', False)
-
- def _set_account_row_background_color(self, renderer):
- theme = gajim.config.get('roster_theme')
- color = gajim.config.get_per('themes', theme, 'accountbgcolor')
- renderer.set_property('cell-background', color or None)
-
- def _set_contact_row_background_color(self, renderer, jid, account):
- theme = gajim.config.get('roster_theme')
- if jid in gajim.newly_added[account]:
- renderer.set_property('cell-background', gajim.config.get(
- 'just_connected_bg_color'))
- elif jid in gajim.to_be_removed[account]:
- renderer.set_property('cell-background', gajim.config.get(
- 'just_disconnected_bg_color'))
- else:
- color = gajim.config.get_per('themes', theme, 'contactbgcolor')
- renderer.set_property('cell-background', color or None)
-
- def _set_group_row_background_color(self, renderer):
- theme = gajim.config.get('roster_theme')
- color = gajim.config.get_per('themes', theme, 'groupbgcolor')
- renderer.set_property('cell-background', color or None)
-
-################################################################################
-### Everything about building menus
-### FIXME: We really need to make it simpler! 1465 lines are a few to much....
-################################################################################
-
- def build_account_menu(self, account):
- # we have to create our own set of icons for the menu
- # using self.jabber_status_images is poopoo
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
-
- if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- xml = gtkgui_helpers.get_gtk_builder('account_context_menu.ui')
- account_context_menu = xml.get_object('account_context_menu')
-
- status_menuitem = xml.get_object('status_menuitem')
- start_chat_menuitem = xml.get_object('start_chat_menuitem')
- join_group_chat_menuitem = xml.get_object(
- 'join_group_chat_menuitem')
- open_gmail_inbox_menuitem = xml.get_object(
- 'open_gmail_inbox_menuitem')
- add_contact_menuitem = xml.get_object('add_contact_menuitem')
- service_discovery_menuitem = xml.get_object(
- 'service_discovery_menuitem')
- execute_command_menuitem = xml.get_object(
- 'execute_command_menuitem')
- edit_account_menuitem = xml.get_object('edit_account_menuitem')
- sub_menu = Gtk.Menu()
- status_menuitem.set_submenu(sub_menu)
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- con = gajim.connections[account]
- if show == 'invisible' and con.connected > 1 and \
- not con.privacy_rules_supported:
- item.set_sensitive(False)
- else:
- item.connect('activate', self.change_status, account, show)
-
- item = Gtk.SeparatorMenuItem.new()
- sub_menu.append(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('_Change Status Message'))
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate,
- account)
- if gajim.connections[account].connected < 2:
- item.set_sensitive(False)
-
- item = Gtk.SeparatorMenuItem.new()
- sub_menu.append(item)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, 'offline')
-
- pep_menuitem = xml.get_object('pep_menuitem')
- if gajim.connections[account].pep_supported:
- pep_submenu = Gtk.Menu()
- pep_menuitem.set_submenu(pep_submenu)
- def add_item(label, opt_name, func):
- item = Gtk.CheckMenuItem.new_with_label(label)
- pep_submenu.append(item)
- if not dbus_support.supported:
- item.set_sensitive(False)
- else:
- activ = gajim.config.get_per('accounts', account,
- opt_name)
- item.set_active(activ)
- item.connect('toggled', func, account)
-
- add_item(_('Publish Tune'), 'publish_tune',
- self.on_publish_tune_toggled)
- add_item(_('Publish Location'), 'publish_location',
- self.on_publish_location_toggled)
-
- pep_config = Gtk.MenuItem.new_with_label(
- _('Configure Services…'))
- item = Gtk.SeparatorMenuItem.new()
- pep_submenu.append(item)
- pep_config.set_sensitive(True)
- pep_submenu.append(pep_config)
- pep_config.connect('activate',
- self.on_pep_services_menuitem_activate, account)
-
- else:
- pep_menuitem.set_sensitive(False)
-
- if not gajim.connections[account].gmail_url:
- open_gmail_inbox_menuitem.set_no_show_all(True)
- open_gmail_inbox_menuitem.hide()
- else:
- open_gmail_inbox_menuitem.connect('activate',
- self.on_open_gmail_inbox, account)
-
- edit_account_menuitem.connect('activate', self.on_edit_account,
- account)
- if gajim.connections[account].roster_supported:
- add_contact_menuitem.connect('activate',
- self.on_add_new_contact, account)
- else:
- add_contact_menuitem.set_sensitive(False)
- service_discovery_menuitem.connect('activate',
- self.on_service_disco_menuitem_activate, account)
- hostname = gajim.config.get_per('accounts', account, 'hostname')
- contact = gajim.contacts.create_contact(jid=hostname,
- account=account) # Fake contact
- execute_command_menuitem.connect('activate',
- self.on_execute_command, contact, account)
-
- start_chat_menuitem.connect('activate',
- self.on_new_chat_menuitem_activate, account)
-
- gc_sub_menu = Gtk.Menu() # gc is always a submenu
- join_group_chat_menuitem.set_submenu(gc_sub_menu)
- self.add_bookmarks_list(gc_sub_menu, account)
-
- # make some items insensitive if account is offline
- if gajim.connections[account].connected < 2:
- for widget in (add_contact_menuitem, service_discovery_menuitem,
- join_group_chat_menuitem, execute_command_menuitem,
- pep_menuitem, start_chat_menuitem):
- widget.set_sensitive(False)
- else:
- xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui')
- account_context_menu = xml.get_object('zeroconf_context_menu')
-
- status_menuitem = xml.get_object('status_menuitem')
- zeroconf_properties_menuitem = xml.get_object(
- 'zeroconf_properties_menuitem')
- sub_menu = Gtk.Menu()
- status_menuitem.set_submenu(sub_menu)
-
- for show in ('online', 'away', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, show)
-
- item = Gtk.SeparatorMenuItem.new()
- sub_menu.append(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('_Change Status Message'))
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate,
- account)
- if gajim.connections[account].connected < 2:
- item.set_sensitive(False)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- item.connect('activate', self.change_status, account, 'offline')
-
- zeroconf_properties_menuitem.connect('activate',
- self.on_edit_account, account)
-
- return account_context_menu
-
- def make_account_menu(self, event, titer):
- """
- Make account's popup menu
- """
- model = self.modelfilter
- account = model[titer][Column.ACCOUNT]
-
- if account != 'all': # not in merged mode
- menu = self.build_account_menu(account)
- else:
- menu = Gtk.Menu()
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- accounts = [] # Put accounts in a list to sort them
- for account in gajim.connections:
- accounts.append(account)
- accounts.sort()
- for account in accounts:
- item = Gtk.MenuItem.new_with_label(account)
- account_menu = self.build_account_menu(account)
- item.set_submenu(account_menu)
- menu.append(item)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, None, event_button, event.time)
-
- def make_group_menu(self, event, titer):
- """
- Make group's popup menu
- """
- model = self.modelfilter
- path = model.get_path(titer)
- group = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
-
- list_ = [] # list of (contact, account) tuples
- list_online = [] # list of (contact, account) tuples
-
- show_bookmarked = True
- group = model[titer][Column.JID]
- for jid in gajim.contacts.get_jid_list(account):
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if group in contact.get_shown_groups():
- if contact.show not in ('offline', 'error'):
- list_online.append((contact, account))
- # Check that all contacts support direct NUC invite
- if not contact.supports(NS_CONFERENCE):
- show_bookmarked = False
- list_.append((contact, account))
- menu = Gtk.Menu()
-
- # Make special context menu if group is Groupchats
- if group == _('Groupchats'):
- maximize_menuitem = Gtk.MenuItem.new_with_mnemonic(_(
- '_Maximize All'))
- maximize_menuitem.connect('activate',
- self.on_all_groupchat_maximized, list_)
- menu.append(maximize_menuitem)
- else:
- # Send Group Message
- send_group_message_item = Gtk.MenuItem.new_with_mnemonic(
- _('Send Group M_essage'))
-
- send_group_message_submenu = Gtk.Menu()
- send_group_message_item.set_submenu(send_group_message_submenu)
- menu.append(send_group_message_item)
-
- group_message_to_all_item = Gtk.MenuItem.new_with_label(_(
- 'To all users'))
- send_group_message_submenu.append(group_message_to_all_item)
-
- group_message_to_all_online_item = Gtk.MenuItem.new_with_label(
- _('To all online users'))
- send_group_message_submenu.append(group_message_to_all_online_item)
-
- group_message_to_all_online_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account,
- list_online)
- group_message_to_all_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, list_)
-
- # Invite to
- if group != _('Transports'):
- invite_menuitem = Gtk.MenuItem.new_with_mnemonic(
- _('In_vite to'))
-
- gui_menu_builder.build_invite_submenu(invite_menuitem,
- list_online, show_bookmarked=show_bookmarked)
- menu.append(invite_menuitem)
-
- # Send Custom Status
- send_custom_status_menuitem = Gtk.MenuItem.new_with_mnemonic(
- _('Send Cus_tom Status'))
- if helpers.group_is_blocked(account, group):
- send_custom_status_menuitem.set_sensitive(False)
- status_menuitems = Gtk.Menu()
- send_custom_status_menuitem.set_submenu(status_menuitems)
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
- status_menuitem = Gtk.MenuItem.new_with_label(
- helpers.get_uf_show(s))
- status_menuitem.connect('activate', self.on_send_custom_status,
- list_, s, group)
- status_menuitems.append(status_menuitem)
- menu.append(send_custom_status_menuitem)
-
- # there is no singlemessage and custom status for zeroconf
- if gajim.config.get_per('accounts', account, 'is_zeroconf'):
- send_custom_status_menuitem.set_sensitive(False)
- send_group_message_item.set_sensitive(False)
-
- if gajim.connections[account].connected < 2:
- send_group_message_item.set_sensitive(False)
- invite_menuitem.set_sensitive(False)
- send_custom_status_menuitem.set_sensitive(False)
-
- if not group in helpers.special_groups:
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- # Rename
- rename_item = Gtk.MenuItem.new_with_mnemonic(_('_Rename…'))
- menu.append(rename_item)
- rename_item.connect('activate', self.on_rename, 'group', group,
- account)
-
- # Block group
- is_blocked = False
- if self.regroup:
- for g_account in gajim.connections:
- if helpers.group_is_blocked(g_account, group):
- is_blocked = True
- else:
- if helpers.group_is_blocked(account, group):
- is_blocked = True
-
- if is_blocked and gajim.connections[account].\
- privacy_rules_supported:
- unblock_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Unblock'))
- unblock_menuitem.connect('activate', self.on_unblock, list_,
- group)
- menu.append(unblock_menuitem)
- else:
- block_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Block'))
- block_menuitem.connect('activate', self.on_block, list_, group)
- menu.append(block_menuitem)
- if not gajim.connections[account].privacy_rules_supported:
- block_menuitem.set_sensitive(False)
-
- # Remove group
- remove_item = Gtk.MenuItem.new_with_mnemonic(_('Remo_ve'))
- menu.append(remove_item)
- remove_item.connect('activate', self.on_remove_group_item_activated,
- group, account)
-
- # unsensitive if account is not connected
- if gajim.connections[account].connected < 2:
- rename_item.set_sensitive(False)
-
- # General group cannot be changed
- if group == _('General'):
- rename_item.set_sensitive(False)
- remove_item.set_sensitive(False)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, None, event_button, event.time)
-
- def make_contact_menu(self, event, titer):
- """
- Make contact's popup menu
- """
- model = self.modelfilter
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = gui_menu_builder.get_contact_menu(contact, account)
- event_button = gtkgui_helpers.get_possible_button_event(event)
- menu.attach_to_widget(self.tree, None)
- menu.popup(None, None, None, None, event_button, event.time)
-
- def make_multiple_contact_menu(self, event, iters):
- """
- Make group's popup menu
- """
- model = self.modelfilter
- list_ = [] # list of (jid, account) tuples
- one_account_offline = False
- is_blocked = True
- privacy_rules_supported = True
- for titer in iters:
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- if gajim.connections[account].connected < 2:
- one_account_offline = True
- if not gajim.connections[account].privacy_rules_supported:
- privacy_rules_supported = False
- contact = gajim.contacts.get_contact_with_highest_priority(account,
- jid)
- if not helpers.jid_is_blocked(account, jid):
- is_blocked = False
- list_.append((contact, account))
-
- menu = Gtk.Menu()
- account = None
- for (contact, current_account) in list_:
- # check that we use the same account for every sender
- if account is not None and account != current_account:
- account = None
- break
- account = current_account
- show_bookmarked = True
- for (contact, current_account) in list_:
- # Check that all contacts support direct NUC invite
- if not contact.supports(NS_CONFERENCE):
- show_bookmarked = False
- break
- if account is not None:
- send_group_message_item = Gtk.MenuItem.new_with_mnemonic(
- _('Send Group M_essage'))
- menu.append(send_group_message_item)
- send_group_message_item.connect('activate',
- self.on_send_single_message_menuitem_activate, account, list_)
-
- # Invite to Groupchat
- invite_item = Gtk.MenuItem.new_with_mnemonic(_('In_vite to'))
-
- gui_menu_builder.build_invite_submenu(invite_item, list_,
- show_bookmarked=show_bookmarked)
- menu.append(invite_item)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- # Manage Transport submenu
- item = Gtk.MenuItem.new_with_mnemonic(_('_Manage Contacts'))
- manage_contacts_submenu = Gtk.Menu()
- item.set_submenu(manage_contacts_submenu)
- menu.append(item)
-
- # Edit Groups
- edit_groups_item = Gtk.MenuItem.new_with_mnemonic(_('Edit _Groups…'))
- manage_contacts_submenu.append(edit_groups_item)
- edit_groups_item.connect('activate', self.on_edit_groups, list_)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- manage_contacts_submenu.append(item)
-
- # Block
- if is_blocked and privacy_rules_supported:
- unblock_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Unblock'))
- unblock_menuitem.connect('activate', self.on_unblock, list_)
- manage_contacts_submenu.append(unblock_menuitem)
- else:
- block_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Block'))
- block_menuitem.connect('activate', self.on_block, list_)
- manage_contacts_submenu.append(block_menuitem)
-
- if not privacy_rules_supported:
- block_menuitem.set_sensitive(False)
-
- # Remove
- remove_item = Gtk.MenuItem.new_with_mnemonic(_('_Remove'))
- manage_contacts_submenu.append(remove_item)
- remove_item.connect('activate', self.on_req_usub, list_)
- # unsensitive remove if one account is not connected
- if one_account_offline:
- remove_item.set_sensitive(False)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, None, event_button, event.time)
-
- def make_transport_menu(self, event, titer):
- """
- Make transport's popup menu
- """
- model = self.modelfilter
- jid = model[titer][Column.JID]
- path = model.get_path(titer)
- account = model[titer][Column.ACCOUNT]
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = gui_menu_builder.get_transport_menu(contact, account)
- event_button = gtkgui_helpers.get_possible_button_event(event)
- menu.attach_to_widget(self.tree, None)
- menu.popup(None, None, None, None, event_button, event.time)
-
- def make_groupchat_menu(self, event, titer):
- model = self.modelfilter
-
- jid = model[titer][Column.JID]
- account = model[titer][Column.ACCOUNT]
- contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
- menu = Gtk.Menu()
-
- if jid in gajim.interface.minimized_controls[account]:
- maximize_menuitem = Gtk.MenuItem.new_with_mnemonic(_(
- '_Maximize'))
- maximize_menuitem.connect('activate', self.on_groupchat_maximized, \
- jid, account)
- menu.append(maximize_menuitem)
-
- if not gajim.gc_connected[account].get(jid, False):
- connect_menuitem = Gtk.MenuItem.new_with_mnemonic(_(
- '_Reconnect'))
- connect_menuitem.connect('activate', self.on_reconnect, jid,
- account)
- menu.append(connect_menuitem)
- disconnect_menuitem = Gtk.MenuItem.new_with_mnemonic(_(
- '_Disconnect'))
- disconnect_menuitem.connect('activate', self.on_disconnect, jid,
- account)
- menu.append(disconnect_menuitem)
-
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- history_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_History'))
- history_menuitem .connect('activate', self.on_history, contact, account)
- menu.append(history_menuitem)
-
- event_button = gtkgui_helpers.get_possible_button_event(event)
-
- menu.attach_to_widget(self.tree, None)
- menu.connect('selection-done', gtkgui_helpers.destroy_widget)
- menu.show_all()
- menu.popup(None, None, None, None, event_button, event.time)
-
- def get_and_connect_advanced_menuitem_menu(self, account):
- """
- Add FOR ACCOUNT options
- """
- xml = gtkgui_helpers.get_gtk_builder('advanced_menuitem_menu.ui')
- advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
-
- xml_console_menuitem = xml.get_object('xml_console_menuitem')
- archiving_preferences_menuitem = xml.get_object(
- 'archiving_preferences_menuitem')
- privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
- administrator_menuitem = xml.get_object('administrator_menuitem')
- send_server_message_menuitem = xml.get_object(
- 'send_server_message_menuitem')
- set_motd_menuitem = xml.get_object('set_motd_menuitem')
- update_motd_menuitem = xml.get_object('update_motd_menuitem')
- delete_motd_menuitem = xml.get_object('delete_motd_menuitem')
-
- xml_console_menuitem.connect('activate',
- self.on_xml_console_menuitem_activate, account)
-
- if gajim.connections[account]:
- if gajim.connections[account].privacy_rules_supported:
- privacy_lists_menuitem.connect('activate',
- self.on_privacy_lists_menuitem_activate, account)
- else:
- privacy_lists_menuitem.set_sensitive(False)
- if gajim.connections[account].archive_pref_supported or \
- gajim.connections[account].archiving_313_supported:
- archiving_preferences_menuitem.connect(
- 'activate',
- self.on_archiving_preferences_menuitem_activate, account)
- else:
- archiving_preferences_menuitem.set_sensitive(False)
-
- if gajim.connections[account].is_zeroconf:
- administrator_menuitem.set_sensitive(False)
- send_server_message_menuitem.set_sensitive(False)
- set_motd_menuitem.set_sensitive(False)
- update_motd_menuitem.set_sensitive(False)
- delete_motd_menuitem.set_sensitive(False)
- else:
- send_server_message_menuitem.connect('activate',
- self.on_send_server_message_menuitem_activate, account)
-
- set_motd_menuitem.connect('activate',
- self.on_set_motd_menuitem_activate, account)
-
- update_motd_menuitem.connect('activate',
- self.on_update_motd_menuitem_activate, account)
-
- delete_motd_menuitem.connect('activate',
- self.on_delete_motd_menuitem_activate, account)
-
- advanced_menuitem_menu.show_all()
-
- return advanced_menuitem_menu
-
- def add_history_manager_menuitem(self, menu):
- """
- Add a seperator and History Manager menuitem BELOW for account menuitems
- """
- item = Gtk.SeparatorMenuItem.new() # separator
- menu.append(item)
-
- # History manager
- item = Gtk.MenuItem.new_with_mnemonic(_('History Manager'))
- menu.append(item)
- item.connect('activate', self.on_history_manager_menuitem_activate)
-
- def add_bookmarks_list(self, gc_sub_menu, account):
- """
- Show join new group chat item and bookmarks list for an account
- """
- item = Gtk.MenuItem.new_with_mnemonic(_('_Join New Group Chat'))
- item.connect('activate', self.on_join_gc_activate, account)
-
- gc_sub_menu.append(item)
-
- # User has at least one bookmark.
- if gajim.connections[account].bookmarks:
- item = Gtk.SeparatorMenuItem.new()
- gc_sub_menu.append(item)
-
- for bookmark in gajim.connections[account].bookmarks:
- name = bookmark['name']
- if not name:
- # No name was given for this bookmark.
- # Use the first part of JID instead...
- name = bookmark['jid'].split("@")[0]
-
- # Shorten long names
- name = (name[:42] + '..') if len(name) > 42 else name
-
- # Do not use underline.
- item = Gtk.MenuItem.new_with_label(name)
- item.set_use_underline(False)
- item.connect('activate', self.on_bookmark_menuitem_activate,
- account, bookmark)
- gc_sub_menu.append(item)
-
- def show_appropriate_context_menu(self, event, iters):
- # iters must be all of the same type
- model = self.modelfilter
- type_ = model[iters[0]][Column.TYPE]
- for titer in iters[1:]:
- if model[titer][Column.TYPE] != type_:
- return
- if type_ == 'group' and len(iters) == 1:
- self.make_group_menu(event, iters[0])
- if type_ == 'groupchat' and len(iters) == 1:
- self.make_groupchat_menu(event, iters[0])
- elif type_ == 'agent' and len(iters) == 1:
- self.make_transport_menu(event, iters[0])
- elif type_ in ('contact', 'self_contact') and len(iters) == 1:
- self.make_contact_menu(event, iters[0])
- elif type_ == 'contact':
- self.make_multiple_contact_menu(event, iters)
- elif type_ == 'account' and len(iters) == 1:
- self.make_account_menu(event, iters[0])
-
- def show_treeview_menu(self, event):
- try:
- model, list_of_paths = self.tree.get_selection().get_selected_rows()
- except TypeError:
- self.tree.get_selection().unselect_all()
- return
- if not len(list_of_paths):
- # no row is selected
- return
- if len(list_of_paths) > 1:
- iters = []
- for path in list_of_paths:
- iters.append(model.get_iter(path))
- else:
- path = list_of_paths[0]
- iters = [model.get_iter(path)]
- self.show_appropriate_context_menu(event, iters)
-
- return True
-
- def on_ctrl_j(self, accel_group, acceleratable, keyval, modifier):
- """
- Bring up the conference join dialog, when CTRL+J accelerator is being
- activated
- """
- # find a connected account:
- for account in gajim.connections:
- if gajim.account_is_connected(account):
- break
- self.on_join_gc_activate(None, account)
- return True
-
- def fill_column(self, col):
- for rend in self.renderers_list:
- col.pack_start(rend[1], rend[2])
- col.add_attribute(rend[1], rend[3], rend[4])
- col.set_cell_data_func(rend[1], rend[5], rend[6])
- # set renderers propertys
- for renderer in self.renderers_propertys.keys():
- renderer.set_property(self.renderers_propertys[renderer][0],
- self.renderers_propertys[renderer][1])
-
- def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
- try:
- row = widget.get_path_at_pos(x_pos, y_pos)[0]
- except TypeError:
- return False
- if not row:
- return False
-
- iter_ = None
- try:
- model = widget.get_model()
- iter_ = model.get_iter(row)
- except Exception:
- return False
-
- typ = model[iter_][Column.TYPE]
- account = model[iter_][Column.ACCOUNT]
- jid = model[iter_][Column.JID]
- connected_contacts = []
-
- if typ in ('contact', 'self_contact'):
- contacts = gajim.contacts.get_contacts(account, jid)
-
- for c in contacts:
- if c.show not in ('offline', 'error'):
- connected_contacts.append(c)
- if not connected_contacts:
- # no connected contacts, show the offline one
- connected_contacts = contacts
- elif typ == 'groupchat':
- connected_contacts = gajim.contacts.get_contacts(account, jid)
- elif typ != 'account':
- return False
-
- if self.current_tooltip != row:
- # If the row changes we hide the current tooltip
- self.current_tooltip = row
- return False
-
- tooltip = widget.get_tooltip_window()
-
- if tooltip.row == row:
- # We already populated the window with the row data
- return True
- tooltip.row = row
- tooltip.populate(connected_contacts, account, typ)
- return True
-
- def add_actions(self):
- action = Gio.SimpleAction.new_stateful(
- "show-roster", None,
- GLib.Variant.new_boolean(
- not self.xml.get_object('roster_vbox2').get_no_show_all()))
- action.connect("change-state",
- self.on_show_roster_action)
- self.window.add_action(action)
-
- action = Gio.SimpleAction.new_stateful(
- "show-offline", None,
- GLib.Variant.new_boolean(gajim.config.get('showoffline')))
- action.connect("change-state",
- self.on_show_offline_contacts_action)
- self.window.add_action(action)
-
- action = Gio.SimpleAction.new_stateful(
- "show-active", None,
- GLib.Variant.new_boolean(
- gajim.config.get('show_only_chat_and_online')))
- action.connect("change-state",
- self.on_show_active_contacts_action)
- self.window.add_action(action)
-
- action = Gio.SimpleAction.new_stateful(
- "show-transports", None,
- GLib.Variant.new_boolean(gajim.config.get('show_transports_group')))
- action.connect("change-state", self.on_show_transports_action)
- self.window.add_action(action)
-
-################################################################################
-###
-################################################################################
-
- def __init__(self, app: Gtk.Application):
- self.application = app
- self.filtering = False
- self.starting = False
- self.starting_filtering = False
- # Number of renderers plugins added
- self.nb_ext_renderers = 0
- # When we quit, rememver if we already saved config once
- self.save_done = False
- # [icon, name, type, jid, account, editable, mood_pixbuf,
- # activity_pixbuf, tune_pixbuf, location_pixbuf, avatar_pixbuf,
- # padlock_pixbuf]
- self.columns = [Gtk.Image, str, str, str, str,
- GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf,
- GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf]
- self.xml = gtkgui_helpers.get_gtk_builder('roster_window.ui')
- self.window = self.xml.get_object('roster_window')
- app.add_window(self.window)
- self.add_actions()
- self.hpaned = self.xml.get_object('roster_hpaned')
- gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned)
- gajim.interface.msg_win_mgr.connect('window-delete',
- self.on_message_window_delete)
- self.advanced_menus = [] # We keep them to destroy them
- if gajim.config.get('roster_window_skip_taskbar'):
- self.window.set_property('skip-taskbar-hint', True)
- self.tree = self.xml.get_object('roster_treeview')
- sel = self.tree.get_selection()
- sel.set_mode(Gtk.SelectionMode.MULTIPLE)
- # sel.connect('changed',
- # self.on_treeview_selection_changed)
-
- self._iters = {}
- # for merged mode
- self._iters['MERGED'] = {'account': None, 'groups': {}}
- # holds a list of (jid, account) tuples
- self._last_selected_contact = []
- self.transports_state_images = {'16': {}, '32': {}, 'opened': {},
- 'closed': {}}
-
- self.last_save_dir = None
- self.editing_path = None # path of row with cell in edit mode
- self.add_new_contact_handler_id = False
- self.service_disco_handler_id = False
- self.new_chat_menuitem_handler_id = False
- self.single_message_menuitem_handler_id = False
- self.profile_avatar_menuitem_handler_id = False
- #FIXME: When list_accel_closures will be wrapped in pygtk
- # no need of this variable
- self.have_new_chat_accel = False # Is the "Ctrl+N" shown ?
- self.regroup = gajim.config.get('mergeaccounts')
- self.clicked_path = None # Used remember on wich row we clicked
- if len(gajim.connections) < 2:
- # Do not merge accounts if only one exists
- self.regroup = False
- gtkgui_helpers.resize_window(self.window,
- gajim.config.get('roster_width'),
- gajim.config.get('roster_height'))
- if gajim.config.get('save-roster-position'):
- gtkgui_helpers.move_window(self.window,
- gajim.config.get('roster_x-position'),
- gajim.config.get('roster_y-position'))
-
- self.popups_notification_height = 0
- self.popup_notification_windows = []
-
- # Remove contact from roster when last event opened
- # { (contact, account): { backend: boolean }
- self.contacts_to_be_removed = {}
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- # when this value become 0 we quit main application. If it's more than 0
- # it means we are waiting for this number of accounts to disconnect
- # before quitting
- self.quit_on_next_offline = -1
-
- # groups to draw next time we draw groups.
- self.groups_to_draw = {}
- # accounts to draw next time we draw accounts.
- self.accounts_to_draw = []
-
- # uf_show, img, show, sensitive
- liststore = Gtk.ListStore(str, Gtk.Image, str, bool)
- self.status_combobox = self.xml.get_object('status_combobox')
-
- cell = cell_renderer_image.CellRendererImage(0, 1)
- self.status_combobox.pack_start(cell, False)
-
- # img to show is in in 2nd column of liststore
- self.status_combobox.add_attribute(cell, 'image', 1)
- # if it will be sensitive or not it is in the fourth column
- # all items in the 'row' must have sensitive to False
- # if we want False (so we add it for img_cell too)
- self.status_combobox.add_attribute(cell, 'sensitive', 3)
-
- cell = Gtk.CellRendererText()
- cell.set_property('ellipsize', Pango.EllipsizeMode.END)
- cell.set_property('xpad', 5) # padding for status text
- self.status_combobox.pack_start(cell, True)
- # text to show is in in first column of liststore
- self.status_combobox.add_attribute(cell, 'text', 0)
- # if it will be sensitive or not it is in the fourth column
- self.status_combobox.add_attribute(cell, 'sensitive', 3)
-
- self.status_combobox.set_row_separator_func(self._iter_is_separator, None)
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show)
- liststore.append([uf_show,
- gajim.interface.jabber_state_images['16'][show], show, True])
- # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
- liststore.append(['SEPARATOR', None, '', True])
-
- path = gtkgui_helpers.get_icon_path('gajim-kbd_input')
- img = Gtk.Image()
- img.set_from_file(path)
- # sensitivity to False because by default we're offline
- self.status_message_menuitem_iter = liststore.append(
- [_('Change Status Message…'), img, '', False])
- # Add a Separator (self._iter_is_separator() checks on string SEPARATOR)
- liststore.append(['SEPARATOR', None, '', True])
-
- uf_show = helpers.get_uf_show('offline')
- liststore.append([uf_show, gajim.interface.jabber_state_images['16'][
- 'offline'], 'offline', True])
-
- status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd',
- 'invisible', 'separator1', 'change_status_msg', 'separator2',
- 'offline']
- self.status_combobox.set_model(liststore)
-
- # default to offline
- number_of_menuitem = status_combobox_items.index('offline')
- self.status_combobox.set_active(number_of_menuitem)
-
- # holds index to previously selected item so if
- # "change status message..." is selected we can fallback to previously
- # selected item and not stay with that item selected
- self.previous_status_combobox_active = number_of_menuitem
-
- # Enable/Disable checkboxes at start
- if gajim.config.get('showoffline'):
- self.window.lookup_action('show-active').set_enabled(False)
-
- if gajim.config.get('show_only_chat_and_online'):
- self.window.lookup_action('show-offline').set_enabled(False)
-
- if self.hpaned.get_child2() is None:
- self.window.lookup_action('show-roster').set_enabled(False)
-
- # columns
- col = Gtk.TreeViewColumn()
- # list of renderers with attributes / properties in the form:
- # (name, renderer_object, expand?, attribute_name, attribute_value,
- # cell_data_func, func_arg)
- self.renderers_list = []
- self.renderers_propertys ={}
- self._pep_type_to_model_column = {'mood': Column.MOOD_PIXBUF,
- 'activity': Column.ACTIVITY_PIXBUF, 'tune': Column.TUNE_PIXBUF,
- 'location': Column.LOCATION_PIXBUF}
-
- renderer_text = Gtk.CellRendererText()
- self.renderers_propertys[renderer_text] = ('ellipsize',
- Pango.EllipsizeMode.END)
-
- def add_avatar_renderer():
- self.renderers_list.append(('avatar', Gtk.CellRendererPixbuf(),
- False, 'pixbuf', Column.AVATAR_PIXBUF,
- self._fill_avatar_pixbuf_renderer, None))
-
- if gajim.config.get('avatar_position_in_roster') == 'left':
- add_avatar_renderer()
-
- self.renderers_list += (
- ('icon', cell_renderer_image.CellRendererImage(0, 0), False,
- 'image', Column.IMG, self._iconCellDataFunc, None),
-
- ('name', renderer_text, True,
- 'markup', Column.NAME, self._nameCellDataFunc, None),
-
- ('mood', Gtk.CellRendererPixbuf(), False,
- 'pixbuf', Column.MOOD_PIXBUF,
- self._fill_pep_pixbuf_renderer, Column.MOOD_PIXBUF),
-
- ('activity', Gtk.CellRendererPixbuf(), False,
- 'pixbuf', Column.ACTIVITY_PIXBUF,
- self._fill_pep_pixbuf_renderer, Column.ACTIVITY_PIXBUF),
-
- ('tune', Gtk.CellRendererPixbuf(), False,
- 'pixbuf', Column.TUNE_PIXBUF,
- self._fill_pep_pixbuf_renderer, Column.TUNE_PIXBUF),
-
- ('location', Gtk.CellRendererPixbuf(), False,
- 'pixbuf', Column.LOCATION_PIXBUF,
- self._fill_pep_pixbuf_renderer, Column.LOCATION_PIXBUF))
-
- if gajim.config.get('avatar_position_in_roster') == 'right':
- add_avatar_renderer()
-
- self.renderers_list.append(('padlock', Gtk.CellRendererPixbuf(), False,
- 'pixbuf', Column.PADLOCK_PIXBUF,
- self._fill_padlock_pixbuf_renderer, None))
-
- # fill and append column
- self.fill_column(col)
- self.tree.append_column(col)
-
- # do not show gtk arrows workaround
- col = Gtk.TreeViewColumn()
- render_pixbuf = Gtk.CellRendererPixbuf()
- col.pack_start(render_pixbuf, False)
- self.tree.append_column(col)
- col.set_visible(False)
- self.tree.set_expander_column(col)
-
- # signals
- self.TARGET_TYPE_URI_LIST = 80
- self.tree.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
- [], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE | \
- Gdk.DragAction.COPY)
- self.tree.drag_source_add_text_targets()
- self.tree.enable_model_drag_dest([], Gdk.DragAction.DEFAULT)
- dst_targets = Gtk.TargetList.new([])
- dst_targets.add_text_targets(0)
- dst_targets.add_uri_targets(self.TARGET_TYPE_URI_LIST)
- self.tree.drag_dest_set_target_list(dst_targets)
- self.tree.connect('drag_begin', self.drag_begin)
- self.tree.connect('drag_end', self.drag_end)
- self.tree.connect('drag_drop', self.drag_drop)
- self.tree.connect('drag_data_get', self.drag_data_get_data)
- self.tree.connect('drag_data_received', self.drag_data_received_data)
- self.dragging = False
- self.xml.connect_signals(self)
- self.combobox_callback_active = True
-
- self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t')
- self.tree.set_has_tooltip(True)
- self.tree.set_tooltip_window(tooltips.RosterTooltip(self.window))
- self.current_tooltip = None
- self.tree.connect('query-tooltip', self.query_tooltip)
- # Workaroung: For strange reasons signal is behaving like row-changed
- self._toggeling_row = False
- self.setup_and_draw_roster()
-
- if gajim.config.get('show_roster_on_startup') == 'always':
- self.window.show_all()
- elif gajim.config.get('show_roster_on_startup') == 'never':
- if gajim.config.get('trayicon') != 'always':
- # Without trayicon, user should see the roster!
- self.window.show_all()
- gajim.config.set('last_roster_visible', True)
- else:
- if gajim.config.get('last_roster_visible') or \
- gajim.config.get('trayicon') != 'always':
- self.window.show_all()
-
- if not gajim.config.get_per('accounts') or \
- gajim.config.get_per('accounts') == ['Local'] and not \
- gajim.config.get_per('accounts', 'Local', 'active'):
- # if we have no account configured or only Local account but not enabled
- def _open_wizard():
- gajim.interface.instances['account_creation_wizard'] = \
- config.AccountCreationWizardWindow()
- # Open wizard only after roster is created, so we can make it
- # transient for the roster window
- GLib.idle_add(_open_wizard)
- if not gajim.ZEROCONF_ACC_NAME in gajim.config.get_per('accounts'):
- # Create zeroconf in config file
- from common.zeroconf import connection_zeroconf
- connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
-
- # Setting CTRL+J to be the shortcut for bringing up the dialog to join a
- # conference.
- accel_group = Gtk.accel_groups_from_object(self.window)[0]
- accel_group.connect(Gdk.KEY_j, Gdk.ModifierType.CONTROL_MASK,
- Gtk.AccelFlags.MASK, self.on_ctrl_j)
-
- # Setting CTRL+S to be the shortcut to change status message
- accel_group = Gtk.AccelGroup()
- keyval, mod = Gtk.accelerator_parse('<Control>s')
- accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE,
- self.accel_group_func)
-
- # Setting CTRL+k to focus rfilter_entry
- keyval, mod = Gtk.accelerator_parse('<Control>k')
- accel_group.connect(keyval, mod, Gtk.AccelFlags.VISIBLE,
- self.accel_group_func)
- self.window.add_accel_group(accel_group)
-
- # Setting the search stuff
- self.rfilter_entry = self.xml.get_object('rfilter_entry')
- self.rfilter_string = ''
- self.rfilter_enabled = False
- self.rfilter_entry.connect('key-press-event',
- self.on_rfilter_entry_key_press_event)
-
- gajim.ged.register_event_handler('presence-received', ged.GUI1,
- self._nec_presence_received)
- # presence has to be fully handled so that contact is added to occupant
- # list before roster can be correctly updated
- gajim.ged.register_event_handler('gc-presence-received', ged.GUI2,
- self._nec_gc_presence_received)
- gajim.ged.register_event_handler('roster-received', ged.GUI1,
- self._nec_roster_received)
- gajim.ged.register_event_handler('anonymous-auth', ged.GUI1,
- self._nec_anonymous_auth)
- gajim.ged.register_event_handler('our-show', ged.GUI1,
- self._nec_our_show)
- gajim.ged.register_event_handler('connection-type', ged.GUI1,
- self._nec_connection_type)
- gajim.ged.register_event_handler('agent-removed', ged.GUI1,
- self._nec_agent_removed)
- gajim.ged.register_event_handler('pep-received', ged.GUI1,
- self._nec_pep_received)
- gajim.ged.register_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
- gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
- self._nec_gc_subject_received)
- gajim.ged.register_event_handler('metacontacts-received', ged.GUI2,
- self._nec_metacontacts_received)
- gajim.ged.register_event_handler('signed-in', ged.GUI1,
- self._nec_signed_in)
- gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
- self._nec_decrypted_message_received)
- gajim.ged.register_event_handler('blocking', ged.GUI1,
- self._nec_blocking)
diff --git a/src/search_window.py b/src/search_window.py
deleted file mode 100644
index 59037b406..000000000
--- a/src/search_window.py
+++ /dev/null
@@ -1,247 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/search_window.py
-##
-## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import GLib
-from gi.repository import Gtk
-from gi.repository import Gdk
-
-from common import gajim
-from common import dataforms
-from common import ged
-
-import gtkgui_helpers
-import dialogs
-import vcard
-import config
-import dataforms_widget
-
-class SearchWindow:
- def __init__(self, account, jid):
- """
- Create new window
- """
- # an account object
- self.account = account
- self.jid = jid
-
- # retrieving widgets from xml
- self.xml = gtkgui_helpers.get_gtk_builder('search_window.ui')
- self.window = self.xml.get_object('search_window')
- for name in ('label', 'progressbar', 'search_vbox', 'search_button',
- 'add_contact_button', 'information_button'):
- self.__dict__[name] = self.xml.get_object(name)
-
- self.search_button.set_sensitive(False)
-
- # displaying the window
- self.xml.connect_signals(self)
- self.window.show_all()
- self.request_form()
- self.pulse_id = GLib.timeout_add(80, self.pulse_callback)
-
- self.is_form = None
-
- # Is there a jid column in results ? if -1: no, else column number
- self.jid_column = -1
-
- gajim.ged.register_event_handler('search-form-received', ged.GUI1,
- self._nec_search_form_received)
- gajim.ged.register_event_handler('search-result-received', ged.GUI1,
- self._nec_search_result_received)
-
- def request_form(self):
- gajim.connections[self.account].request_search_fields(self.jid)
-
- def pulse_callback(self):
- self.progressbar.pulse()
- return True
-
- def on_search_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_search_window_destroy(self, widget):
- if self.pulse_id:
- GLib.source_remove(self.pulse_id)
- del gajim.interface.instances[self.account]['search'][self.jid]
- gajim.ged.remove_event_handler('search-form-received', ged.GUI1,
- self._nec_search_form_received)
- gajim.ged.remove_event_handler('search-result-received', ged.GUI1,
- self._nec_search_result_received)
-
- def on_close_button_clicked(self, button):
- self.window.destroy()
-
- def on_search_button_clicked(self, button):
- if self.is_form:
- self.data_form_widget.data_form.type_ = 'submit'
- gajim.connections[self.account].send_search_form(self.jid,
- self.data_form_widget.data_form.get_purged(), True)
- else:
- infos = self.data_form_widget.get_infos()
- if 'instructions' in infos:
- del infos['instructions']
- gajim.connections[self.account].send_search_form(self.jid, infos,
- False)
-
- self.search_vbox.remove(self.data_form_widget)
-
- self.progressbar.show()
- self.label.set_text(_('Waiting for results'))
- self.label.show()
- self.pulse_id = GLib.timeout_add(80, self.pulse_callback)
- self.search_button.hide()
-
- def on_add_contact_button_clicked(self, widget):
- (model, iter_) = self.result_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][self.jid_column]
- dialogs.AddNewContactWindow(self.account, jid)
-
- def on_information_button_clicked(self, widget):
- (model, iter_) = self.result_treeview.get_selection().get_selected()
- if not iter_:
- return
- jid = model[iter_][self.jid_column]
- if jid in gajim.interface.instances[self.account]['infos']:
- gajim.interface.instances[self.account]['infos'][jid].window.present()
- else:
- contact = gajim.contacts.create_contact(jid=jid, account=self.account)
- gajim.interface.instances[self.account]['infos'][jid] = \
- vcard.VcardWindow(contact, self.account)
-
- def _nec_search_form_received(self, obj):
- if self.pulse_id:
- GLib.source_remove(self.pulse_id)
- self.progressbar.hide()
- self.label.hide()
-
- if obj.is_dataform:
- self.is_form = True
- self.data_form_widget = dataforms_widget.DataFormWidget()
- self.dataform = dataforms.ExtendForm(node=obj.data)
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.data_form = self.dataform
- except dataforms.Error:
- self.label.set_text(_('Error in received dataform'))
- self.label.show()
- return
- if self.data_form_widget.title:
- self.window.set_title('%s - Search - Gajim' % \
- self.data_form_widget.title)
- else:
- self.is_form = False
- self.data_form_widget = config.FakeDataForm(obj.data)
-
- self.data_form_widget.show_all()
- self.search_vbox.pack_start(self.data_form_widget, True, True, 0)
- self.search_button.set_sensitive(True)
-
- def on_result_treeview_cursor_changed(self, treeview):
- if self.jid_column == -1:
- return
- (model, iter_) = treeview.get_selection().get_selected()
- if not iter_:
- return
- if model[iter_][self.jid_column]:
- self.add_contact_button.set_sensitive(True)
- self.information_button.set_sensitive(True)
- else:
- self.add_contact_button.set_sensitive(False)
- self.information_button.set_sensitive(False)
-
- def _nec_search_result_received(self, obj):
- if self.pulse_id:
- GLib.source_remove(self.pulse_id)
- self.progressbar.hide()
- self.label.hide()
-
- if not obj.is_dataform:
- if not obj.data:
- self.label.set_text(_('No result'))
- self.label.show()
- return
- # We suppose all items have the same fields
- sw = Gtk.ScrolledWindow()
- sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- self.result_treeview = Gtk.TreeView()
- self.result_treeview.connect('cursor-changed',
- self.on_result_treeview_cursor_changed)
- sw.add(self.result_treeview)
- # Create model
- fieldtypes = [str]*len(obj.data[0])
- model = Gtk.ListStore(*fieldtypes)
- # Copy data to model
- for item in obj.data:
- model.append(item.values())
- # Create columns
- counter = 0
- for field in obj.data[0].keys():
- self.result_treeview.append_column(Gtk.TreeViewColumn(field,
- Gtk.CellRendererText(), text=counter))
- if field == 'jid':
- self.jid_column = counter
- counter += 1
- self.result_treeview.set_model(model)
- sw.show_all()
- self.search_vbox.pack_start(sw, True, True, 0)
- if self.jid_column > -1:
- self.add_contact_button.show()
- self.information_button.show()
- return
-
- self.dataform = dataforms.ExtendForm(node=obj.data)
- if len(self.dataform.items) == 0:
- # No result
- self.label.set_text(_('No result'))
- self.label.show()
- return
-
- self.data_form_widget.set_sensitive(True)
- try:
- self.data_form_widget.data_form = self.dataform
- except dataforms.Error:
- self.label.set_text(_('Error in received dataform'))
- self.label.show()
- return
-
- self.result_treeview = self.data_form_widget.records_treeview
- selection = self.result_treeview.get_selection()
- selection.set_mode(Gtk.SelectionMode.SINGLE)
- self.result_treeview.connect('cursor-changed',
- self.on_result_treeview_cursor_changed)
-
- counter = 0
- for field in self.dataform.reported.iter_fields():
- if field.var == 'jid':
- self.jid_column = counter
- break
- counter += 1
- self.search_vbox.pack_start(self.data_form_widget, True, True, 0)
- self.data_form_widget.show()
- if self.jid_column > -1:
- self.add_contact_button.show()
- self.information_button.show()
- if self.data_form_widget.title:
- self.window.set_title('%s - Search - Gajim' % \
- self.data_form_widget.title)
diff --git a/src/secrets.py b/src/secrets.py
deleted file mode 100644
index f6e235a9a..000000000
--- a/src/secrets.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/secrets.py
-##
-## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from common.configpaths import gajimpaths
-
-import Crypto
-from common import crypto
-from common import exceptions
-
-import os
-import pickle
-
-secrets_filename = gajimpaths['SECRETS_FILE']
-secrets_cache = None
-
-class Secrets():
- def __init__(self, filename):
- self.filename = filename
- self.srs = {}
- self.pubkeys = {}
- self.privkeys = {}
-
- def cancel(self):
- raise exceptions.Cancelled
-
- def save(self):
- f = open(secrets_filename, 'wb')
- pickle.dump(self, f, protocol=2)
- f.close()
-
- def retained_secrets(self, account, bare_jid):
- try:
- return self.srs[account][bare_jid]
- except KeyError:
- return []
-
- # retained secrets are stored as a tuple of the secret and whether the user
- # has verified it
- def save_new_srs(self, account, jid, secret, verified):
- if not account in self.srs:
- self.srs[account] = {}
-
- if not jid in self.srs[account]:
- self.srs[account][jid] = []
-
- self.srs[account][jid].append((secret, verified))
-
- self.save()
-
- def find_srs(self, account, jid, srs):
- our_secrets = self.srs[account][jid]
- return [(x, y) for x, y in our_secrets if x == srs][0]
-
- # has the user verified this retained secret?
- def srs_verified(self, account, jid, srs):
- return self.find_srs(account, jid, srs)[1]
-
- def replace_srs(self, account, jid, old_secret, new_secret, verified):
- our_secrets = self.srs[account][jid]
-
- idx = our_secrets.index(self.find_srs(account, jid, old_secret))
-
- our_secrets[idx] = (new_secret, verified)
-
- self.save()
-
- # the public key associated with 'account'
- def my_pubkey(self, account):
- try:
- pk = self.privkeys[account]
- except KeyError:
- pk = Crypto.PublicKey.RSA.generate(2048, crypto.random_bytes)
-
- self.privkeys[account] = pk
- self.save()
-
- return pk
-
-def load_secrets(filename):
- f = open(filename, 'rb')
-
- try:
- secrets = pickle.load(f, encoding='latin1')
- # We do that to be able to read files written in py2
- for acct in secrets.srs:
- for jid in secrets.srs[acct]:
- for (secret, verified) in list(secrets.srs[acct][jid]):
- if type(secret) is str:
- secrets.srs[acct][jid].remove((secret, verified))
- secrets.srs[acct][jid].append((secret.encode('latin1'), verified))
- except (KeyError, EOFError):
- f.close()
- secrets = Secrets(filename)
-
- f.close()
- return secrets
-
-def secrets():
- global secrets_cache
-
- if secrets_cache:
- return secrets_cache
-
- if os.path.exists(secrets_filename):
- secrets_cache = load_secrets(secrets_filename)
- else:
- secrets_cache = Secrets(secrets_filename)
-
- return secrets_cache
diff --git a/src/session.py b/src/session.py
deleted file mode 100644
index fcc1aee80..000000000
--- a/src/session.py
+++ /dev/null
@@ -1,554 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/session.py
-##
-## Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-from common import helpers
-
-from common import events
-from common import exceptions
-from common import gajim
-from common import stanza_session
-from common import contacts
-from common import ged
-from common.connection_handlers_events import ChatstateReceivedEvent, \
- InformationEvent
-
-import message_control
-
-import notify
-
-import dialogs
-import negotiation
-
-class ChatControlSession(stanza_session.EncryptedStanzaSession):
- def __init__(self, conn, jid, thread_id, type_='chat'):
- stanza_session.EncryptedStanzaSession.__init__(self, conn, jid, thread_id,
- type_='chat')
- gajim.ged.register_event_handler('decrypted-message-received', ged.GUI1,
- self._nec_decrypted_message_received)
-
- self.control = None
-
- def detach_from_control(self):
- if self.control:
- self.control.set_session(None)
-
- def acknowledge_termination(self):
- self.detach_from_control()
- stanza_session.EncryptedStanzaSession.acknowledge_termination(self)
-
- def terminate(self, send_termination = True):
- stanza_session.EncryptedStanzaSession.terminate(self, send_termination)
- self.detach_from_control()
-
- def _nec_decrypted_message_received(self, obj):
- """
- Dispatch a received <message> stanza
- """
- if obj.session != self:
- return
- contact = gajim.contacts.get_contact(self.conn.name, obj.jid,
- obj.resource)
- if not contact:
- contact = gajim.contacts.get_gc_contact(self.conn.name, obj.jid,
- obj.resource)
- if self.resource != obj.resource:
- self.resource = obj.resource
- if self.control:
- if isinstance(contact, contacts.GC_Contact):
- self.control.gc_contact = contact
- self.control.contact = contact.as_contact()
- else:
- self.control.contact = contact
- if self.control.resource:
- self.control.change_resource(self.resource)
-
- if obj.mtype == 'chat':
- if not obj.msgtxt and obj.chatstate is None:
- return
-
- log_type = 'chat_msg'
- else:
- log_type = 'single_msg'
- end = '_recv'
- if obj.forwarded and obj.sent:
- end = '_sent'
- log_type += end
-
- if self.is_loggable() and obj.msgtxt:
- if obj.xhtml and gajim.config.get('log_xhtml_messages'):
- msg_to_log = obj.xhtml
- else:
- msg_to_log = obj.msgtxt
- obj.msg_log_id = gajim.logger.write(log_type, obj.fjid,
- msg_to_log, tim=obj.timestamp, subject=obj.subject, additional_data=obj.additional_data)
-
- treat_as = gajim.config.get('treat_incoming_messages')
- if treat_as:
- obj.mtype = treat_as
- pm = False
- if obj.gc_control and obj.resource:
- # It's a Private message
- pm = True
- obj.mtype = 'pm'
-
- # Handle chat states
- if contact and (not obj.forwarded or not obj.sent):
- if self.control and self.control.type_id == \
- message_control.TYPE_CHAT:
- if obj.chatstate is not None:
- # other peer sent us reply, so he supports jep85 or jep22
- contact.chatstate = obj.chatstate
- if contact.our_chatstate == 'ask': # we were jep85 disco?
- contact.our_chatstate = 'active' # no more
- gajim.nec.push_incoming_event(ChatstateReceivedEvent(None,
- conn=obj.conn, msg_obj=obj))
- elif contact.chatstate != 'active':
- # got no valid jep85 answer, peer does not support it
- contact.chatstate = False
- elif obj.chatstate == 'active':
- # Brand new message, incoming.
- contact.our_chatstate = obj.chatstate
- contact.chatstate = obj.chatstate
- if obj.msg_log_id: # Do not overwrite an existing msg_log_id with None
- contact.msg_log_id = obj.msg_log_id
-
- # THIS MUST BE AFTER chatstates handling
- # AND BEFORE playsound (else we hear sounding on chatstates!)
- if not obj.msgtxt: # empty message text
- return True
-
- if gajim.config.get_per('accounts', self.conn.name,
- 'ignore_unknown_contacts') and not gajim.contacts.get_contacts(
- self.conn.name, obj.jid) and not pm:
- return True
-
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- self.conn.name, obj.jid)
-
- # does this resource have the highest priority of any available?
- is_highest = not highest_contact or not highest_contact.resource or \
- obj.resource == highest_contact.resource or highest_contact.show ==\
- 'offline'
-
- if not self.control:
- ctrl = gajim.interface.msg_win_mgr.search_control(obj.jid,
- obj.conn.name, obj.resource)
- if ctrl:
- self.control = ctrl
- self.control.set_session(self)
- if isinstance(contact, contacts.GC_Contact):
- self.control.gc_contact = contact
- self.control.contact = contact.as_contact()
- else:
- self.control.contact = contact
-
- if not pm:
- self.roster_message2(obj)
-
- if gajim.interface.remote_ctrl:
- gajim.interface.remote_ctrl.raise_signal('NewMessage', (
- self.conn.name, [obj.fjid, obj.msgtxt, obj.timestamp,
- obj.encrypted, obj.mtype, obj.subject, obj.chatstate,
- obj.msg_log_id, obj.user_nick, obj.xhtml, obj.form_node]))
-
- def roster_message2(self, obj):
- """
- Display the message or show notification in the roster
- """
- contact = None
- jid = obj.jid
- resource = obj.resource
-
- fjid = jid
-
- # Try to catch the contact with correct resource
- if resource:
- fjid = jid + '/' + resource
- contact = gajim.contacts.get_contact(obj.conn.name, jid, resource)
-
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- obj.conn.name, jid)
- if not contact:
- # If there is another resource, it may be a message from an
- # invisible resource
- lcontact = gajim.contacts.get_contacts(obj.conn.name, jid)
- if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
- lcontact[0].show != 'offline')) and jid.find('@') > 0:
- contact = gajim.contacts.copy_contact(highest_contact)
- contact.resource = resource
- contact.priority = 0
- contact.show = 'offline'
- contact.status = ''
- gajim.contacts.add_contact(obj.conn.name, contact)
-
- else:
- # Default to highest prio
- fjid = jid
- contact = highest_contact
-
- if not contact:
- # contact is not in roster
- contact = gajim.interface.roster.add_to_not_in_the_roster(
- obj.conn.name, jid, obj.user_nick)
-
- if not self.control:
- ctrl = gajim.interface.msg_win_mgr.search_control(obj.jid,
- obj.conn.name, obj.resource)
- if ctrl:
- self.control = ctrl
- self.control.set_session(self)
- else:
- fjid = jid
-
- obj.popup = helpers.allow_popup_window(self.conn.name)
-
- event_t = events.ChatEvent
- event_type = 'message_received'
-
- if obj.mtype == 'normal':
- event_t = events.NormalEvent
- event_type = 'single_message_received'
-
- if self.control and obj.mtype != 'normal':
- # We have a ChatControl open
- obj.show_in_roster = False
- obj.show_in_systray = False
- elif obj.forwarded and obj.sent:
- # Its a Carbon Copied Message we sent
- obj.show_in_roster = False
- obj.show_in_systray = False
- gajim.events.remove_events(self.conn.name, fjid, types=['chat'])
- else:
- # Everything else
- obj.show_in_roster = notify.get_show_in_roster(event_type,
- self.conn.name, contact, self)
- obj.show_in_systray = notify.get_show_in_systray(event_type,
- self.conn.name, contact)
-
- event = event_t(obj.msgtxt, obj.subject, obj.mtype, obj.timestamp,
- obj.encrypted, obj.resource, obj.msg_log_id,
- correct_id=(obj.id_, obj.correct_id), xhtml=obj.xhtml,
- session=self, form_node=obj.form_node,
- displaymarking=obj.displaymarking,
- sent_forwarded=obj.forwarded and obj.sent,
- show_in_roster=obj.show_in_roster,
- show_in_systray=obj.show_in_systray)
-
- gajim.events.add_event(self.conn.name, fjid, event)
-
- def roster_message(self, jid, msg, tim, encrypted=False, msg_type='',
- subject=None, resource='', msg_log_id=None, user_nick='', xhtml=None,
- form_node=None, displaymarking=None, additional_data=None):
- """
- Display the message or show notification in the roster
- """
- contact = None
- fjid = jid
-
- if additional_data is None:
- additional_data = {}
-
- # Try to catch the contact with correct resource
- if resource:
- fjid = jid + '/' + resource
- contact = gajim.contacts.get_contact(self.conn.name, jid, resource)
-
- highest_contact = gajim.contacts.get_contact_with_highest_priority(
- self.conn.name, jid)
- if not contact:
- # If there is another resource, it may be a message from an invisible
- # resource
- lcontact = gajim.contacts.get_contacts(self.conn.name, jid)
- if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \
- lcontact[0].show != 'offline')) and jid.find('@') > 0:
- contact = gajim.contacts.copy_contact(highest_contact)
- contact.resource = resource
- if resource:
- fjid = jid + '/' + resource
- contact.priority = 0
- contact.show = 'offline'
- contact.status = ''
- gajim.contacts.add_contact(self.conn.name, contact)
-
- else:
- # Default to highest prio
- fjid = jid
- contact = highest_contact
-
- if not contact:
- # contact is not in roster
- contact = gajim.interface.roster.add_to_not_in_the_roster(
- self.conn.name, jid, user_nick)
-
- if not self.control:
- ctrl = gajim.interface.msg_win_mgr.get_control(fjid, self.conn.name)
- if ctrl:
- self.control = ctrl
- self.control.set_session(self)
- else:
- fjid = jid
-
- # Do we have a queue?
- no_queue = len(gajim.events.get_events(self.conn.name, fjid)) == 0
-
- popup = helpers.allow_popup_window(self.conn.name)
-
- if msg_type == 'normal' and popup: # it's single message to be autopopuped
- dialogs.SingleMessageWindow(self.conn.name, contact.jid,
- action='receive', from_whom=jid, subject=subject, message=msg,
- resource=resource, session=self, form_node=form_node)
- return
-
- # We print if window is opened and it's not a single message
- if self.control and msg_type != 'normal':
- typ = ''
-
- if msg_type == 'error':
- typ = 'error'
-
- self.control.print_conversation(msg, typ, tim=tim, encrypted=encrypted,
- subject=subject, xhtml=xhtml, displaymarking=displaymarking,
- additional_data=additional_data)
-
- if msg_log_id:
- gajim.logger.set_read_messages([msg_log_id])
-
- return
-
- # We save it in a queue
- event_t = events.ChatEvent
- event_type = 'message_received'
-
- if msg_type == 'normal':
- event_t = events.NormalEvent
- event_type = 'single_message_received'
-
- show_in_roster = notify.get_show_in_roster(event_type, self.conn.name,
- contact, self)
- show_in_systray = notify.get_show_in_systray(event_type, self.conn.name,
- contact)
-
- event = event_t(msg, subject, msg_type, tim, encrypted, resource,
- msg_log_id, xhtml=xhtml, session=self, form_node=form_node,
- displaymarking=displaymarking, sent_forwarded=False,
- show_in_roster=show_in_roster, show_in_systray=show_in_systray,
- additional_data=additional_data)
-
- gajim.events.add_event(self.conn.name, fjid, event)
-
- if popup:
- if not self.control:
- self.control = gajim.interface.new_chat(contact,
- self.conn.name, session=self)
-
- if len(gajim.events.get_events(self.conn.name, fjid)):
- self.control.read_queue()
- else:
- if no_queue: # We didn't have a queue: we change icons
- gajim.interface.roster.draw_contact(jid, self.conn.name)
-
- gajim.interface.roster.show_title() # we show the * or [n]
- # Select the big brother contact in roster, it's visible because it has
- # events.
- family = gajim.contacts.get_metacontacts_family(self.conn.name, jid)
- if family:
- nearby_family, bb_jid, bb_account = \
- gajim.contacts.get_nearby_family_and_big_brother(family,
- self.conn.name)
- else:
- bb_jid, bb_account = jid, self.conn.name
- gajim.interface.roster.select_contact(bb_jid, bb_account)
-
- # ---- ESessions stuff ---
-
- def handle_negotiation(self, form):
- if form.getField('accept') and not form['accept'] in ('1', 'true'):
- self.cancelled_negotiation()
- return
-
- # encrypted session states. these are described in stanza_session.py
-
- try:
- if form.getType() == 'form' and 'security' in form.asDict():
- security_options = [x[1] for x in form.getField('security').\
- getOptions()]
- if security_options == ['none']:
- self.respond_archiving(form)
- else:
- # bob responds
-
- # we don't support 3-message negotiation as the responder
- if 'dhkeys' in form.asDict():
- self.fail_bad_negotiation('3 message negotiation not '
- 'supported when responding', ('dhkeys',))
- return
-
- negotiated, not_acceptable, ask_user = \
- self.verify_options_bob(form)
-
- if ask_user:
- def accept_nondefault_options(is_checked):
- self.dialog.destroy()
- negotiated.update(ask_user)
- self.respond_e2e_bob(form, negotiated,
- not_acceptable)
-
- def reject_nondefault_options():
- self.dialog.destroy()
- for key in ask_user.keys():
- not_acceptable.append(key)
- self.respond_e2e_bob(form, negotiated,
- not_acceptable)
-
- self.dialog = dialogs.YesNoDialog(_('Confirm these '
- 'session options'),
- _('The remote client wants to negotiate a session '
- 'with these features:\n\n%s\n\nAre these options '
- 'acceptable?''') % (
- negotiation.describe_features(ask_user)),
- on_response_yes=accept_nondefault_options,
- on_response_no=reject_nondefault_options,
- transient_for=self.control.parent_win.window)
- else:
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- return
-
- elif self.status == 'requested-archiving' and form.getType() == \
- 'submit':
- try:
- self.archiving_accepted(form)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- return
-
- # alice accepts
- elif self.status == 'requested-e2e' and form.getType() == 'submit':
- negotiated, not_acceptable, ask_user = self.verify_options_alice(
- form)
-
- if ask_user:
- def accept_nondefault_options(is_checked):
- if dialog:
- dialog.destroy()
-
- if is_checked:
- allow_no_log_for = gajim.config.get_per(
- 'accounts', self.conn.name,
- 'allow_no_log_for').split()
- jid = str(self.jid)
- if jid not in allow_no_log_for:
- allow_no_log_for.append(jid)
- gajim.config.set_per('accounts', self.conn.name,
- 'allow_no_log_for', ' '.join(allow_no_log_for))
-
- negotiated.update(ask_user)
-
- try:
- self.accept_e2e_alice(form, negotiated)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- def reject_nondefault_options():
- self.reject_negotiation()
- dialog.destroy()
-
- allow_no_log_for = gajim.config.get_per('accounts',
- self.conn.name, 'allow_no_log_for').split()
- if str(self.jid) in allow_no_log_for:
- dialog = None
- accept_nondefault_options(False)
- else:
- dialog = dialogs.YesNoDialog(_('Confirm these session '
- 'options'),
- _('The remote client selected these options:\n\n%s'
- '\n\nContinue with the session?') % (
- negotiation.describe_features(ask_user)),
- _('Always accept for this contact'),
- on_response_yes = accept_nondefault_options,
- on_response_no = reject_nondefault_options,
- transient_for=self.control.parent_win.window)
- else:
- try:
- self.accept_e2e_alice(form, negotiated)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- return
- elif self.status == 'responded-archiving' and form.getType() == \
- 'result':
- try:
- self.we_accept_archiving(form)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- return
- elif self.status == 'responded-e2e' and form.getType() == 'result':
- try:
- self.accept_e2e_bob(form)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- return
- elif self.status == 'identified-alice' and form.getType() == 'result':
- try:
- self.final_steps_alice(form)
- except exceptions.NegotiationError as details:
- self.fail_bad_negotiation(details)
-
- return
- except exceptions.Cancelled:
- # user cancelled the negotiation
-
- self.reject_negotiation()
-
- return
-
- if form.getField('terminate') and\
- form.getField('terminate').getValue() in ('1', 'true'):
- self.acknowledge_termination()
-
- self.conn.delete_session(str(self.jid), self.thread_id)
-
- return
-
- # non-esession negotiation. this isn't very useful, but i'm keeping it
- # around to test my test suite.
- if form.getType() == 'form':
- if not self.control:
- jid, resource = gajim.get_room_and_nick_from_fjid(str(self.jid))
-
- account = self.conn.name
- contact = gajim.contacts.get_contact(account, str(self.jid),
- resource)
-
- if not contact:
- contact = gajim.contacts.create_contact(jid=jid, account=account,
- resource=resource, show=self.conn.get_status())
-
- gajim.interface.new_chat(contact, account, resource=resource,
- session=self)
-
- negotiation.FeatureNegotiationWindow(account, str(self.jid), self,
- form)
diff --git a/src/shortcuts_window.py b/src/shortcuts_window.py
deleted file mode 100644
index dd0bb1f14..000000000
--- a/src/shortcuts_window.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/shortcuts_window.py
-##
-## 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/>.
-##
-
-from gi.repository import Gtk
-
-import gtkgui_helpers
-
-from common import helpers
-
-__all__ = ['show']
-
-class ShortcutsWindow:
- def __init__(self):
- self.window = None
-
- def show(self, parent=None):
- if self.window is None:
- builder = gtkgui_helpers.get_gtk_builder('shortcuts_window.ui')
- self.window = builder.get_object('shortcuts_window')
- self.window.connect('destroy', self._on_window_destroy)
- self.window.set_transient_for(parent)
- self.window.show_all()
- self.window.present()
-
- def _on_window_destroy(self, widget):
- self.window = None
-
-def show_shortcuts_webpage(self, parent=None):
- helpers.launch_browser_mailer('url',
- 'https://dev.gajim.org/gajim/gajim/wikis/help/keyboardshortcuts')
-
-if (3, 19) <= (Gtk.get_major_version(), Gtk.get_minor_version()):
- show = ShortcutsWindow().show
-else:
- show = show_shortcuts_webpage
diff --git a/src/statusicon.py b/src/statusicon.py
deleted file mode 100644
index 4544cb2df..000000000
--- a/src/statusicon.py
+++ /dev/null
@@ -1,484 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/statusicon.py
-##
-## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-import os
-
-import dialogs
-import config
-import tooltips
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-
-class StatusIcon:
- """
- Class for the notification area icon
- """
-
- def __init__(self):
- self.single_message_handler_id = None
- self.show_roster_handler_id = None
- self.new_chat_handler_id = None
- # click somewhere else does not popdown menu. workaround this.
- self.added_hide_menuitem = False
- self.status = 'offline'
- self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui')
- self.systray_context_menu = self.xml.get_object('systray_context_menu')
- self.xml.connect_signals(self)
- self.popup_menus = []
- self.status_icon = None
- self.tooltip = tooltips.NotificationAreaTooltip()
-
- def subscribe_events(self):
- """
- Register listeners to the events class
- """
- gajim.events.event_added_subscribe(self.on_event_added)
- gajim.events.event_removed_subscribe(self.on_event_removed)
-
- def unsubscribe_events(self):
- """
- Unregister listeners to the events class
- """
- gajim.events.event_added_unsubscribe(self.on_event_added)
- gajim.events.event_removed_unsubscribe(self.on_event_removed)
-
- def on_event_added(self, event):
- """
- Called when an event is added to the event list
- """
- if event.show_in_systray:
- self.set_img()
-
- def on_event_removed(self, event_list):
- """
- Called when one or more events are removed from the event list
- """
- self.set_img()
-
- def show_icon(self):
- if not self.status_icon:
- self.status_icon = Gtk.StatusIcon()
- self.statusicon_size = '16'
- self.status_icon.set_property('has-tooltip', True)
- self.status_icon.connect('activate', self.on_status_icon_left_clicked)
- self.status_icon.connect('popup-menu',
- self.on_status_icon_right_clicked)
- self.status_icon.connect('query-tooltip',
- self.on_status_icon_query_tooltip)
- self.status_icon.connect('size-changed',
- self.on_status_icon_size_changed)
-
- self.set_img()
- self.subscribe_events()
-
- def on_status_icon_right_clicked(self, widget, event_button, event_time):
- self.make_menu(event_button, event_time)
-
- def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
- self.tooltip.populate()
- tooltip.set_custom(self.tooltip.hbox)
- return True
-
- def hide_icon(self):
- self.status_icon.set_visible(False)
- self.unsubscribe_events()
-
- def on_status_icon_left_clicked(self, widget):
- self.on_left_click()
-
- def on_status_icon_size_changed(self, statusicon, size):
- if size > 31:
- self.statusicon_size = '32'
- elif size > 23:
- self.statusicon_size = '24'
- else:
- self.statusicon_size = '16'
- if os.environ.get('KDE_FULL_SESSION') == 'true':
- # detect KDE session. see #5476
- self.statusicon_size = '32'
- if os.environ.get('MATE_DESKTOP_SESSION_ID'):
- # detect MATE session.
- self.statusicon_size = '16'
- self.set_img()
-
- def set_img(self):
- """
- Apart from image, we also update tooltip text here
- """
- def really_set_img():
- if image.get_storage_type() == Gtk.ImageType.PIXBUF:
- self.status_icon.set_from_pixbuf(image.get_pixbuf())
- # FIXME: oops they forgot to support GIF animation?
- # or they were lazy to get it to work under Windows! WTF!
- elif image.get_storage_type() == Gtk.ImageType.ANIMATION:
- self.status_icon.set_from_pixbuf(
- image.get_animation().get_static_image())
- # self.status_icon.set_from_animation(image.get_animation())
-
- if not gajim.interface.systray_enabled:
- return
- if gajim.config.get('trayicon') == 'always':
- self.status_icon.set_visible(True)
- if gajim.events.get_nb_systray_events():
- self.status_icon.set_visible(True)
-# if gajim.config.get('trayicon_blink'):
-# self.status_icon.set_blinking(True)
-# else:
- image = gtkgui_helpers.load_icon('event')
- really_set_img()
- return
- else:
- if gajim.config.get('trayicon') == 'on_event':
- self.status_icon.set_visible(False)
-# self.status_icon.set_blinking(False)
-
- image = gajim.interface.jabber_state_images[self.statusicon_size][
- self.status]
- really_set_img()
-
- def change_status(self, global_status):
- """
- Set tray image to 'global_status'
- """
- # change image and status, only if it is different
- if global_status is not None and self.status != global_status:
- self.status = global_status
- self.set_img()
-
- def start_chat(self, widget, account, jid):
- contact = gajim.contacts.get_first_contact_from_jid(account, jid)
- if gajim.interface.msg_win_mgr.has_window(jid, account):
- gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
- jid, account)
- elif contact:
- gajim.interface.new_chat(contact, account)
- gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
- jid, account)
-
- def on_single_message_menuitem_activate(self, widget, account):
- dialogs.SingleMessageWindow(account, action='send')
-
- def on_new_chat(self, widget, account):
- dialogs.NewChatDialog(account)
-
- def make_menu(self, event_button, event_time):
- """
- Create chat with and new message (sub) menus/menuitems
- """
- for m in self.popup_menus:
- m.destroy()
-
- chat_with_menuitem = self.xml.get_object('chat_with_menuitem')
- single_message_menuitem = self.xml.get_object(
- 'single_message_menuitem')
- status_menuitem = self.xml.get_object('status_menu')
- join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
- sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem')
- show_roster_menuitem = self.xml.get_object('show_roster_menuitem')
-
- if self.single_message_handler_id:
- single_message_menuitem.handler_disconnect(
- self.single_message_handler_id)
- self.single_message_handler_id = None
- if self.new_chat_handler_id:
- chat_with_menuitem.disconnect(self.new_chat_handler_id)
- self.new_chat_handler_id = None
-
- sub_menu = Gtk.Menu()
- self.popup_menus.append(sub_menu)
- status_menuitem.set_submenu(sub_menu)
-
- gc_sub_menu = Gtk.Menu() # gc is always a submenu
- join_gc_menuitem.set_submenu(gc_sub_menu)
-
- # We need our own set of status icons, let's make 'em!
- iconset = gajim.config.get('iconset')
- path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
-
- for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
- uf_show = helpers.get_uf_show(show, use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, show)
-
- item = Gtk.SeparatorMenuItem.new()
- sub_menu.append(item)
-
- item = Gtk.MenuItem.new_with_mnemonic(_('_Change Status Message…'))
- sub_menu.append(item)
- item.connect('activate', self.on_change_status_message_activate)
-
- connected_accounts = gajim.get_number_of_connected_accounts()
- if connected_accounts < 1:
- item.set_sensitive(False)
-
- connected_accounts_with_private_storage = 0
-
- item = Gtk.SeparatorMenuItem.new()
- sub_menu.append(item)
-
- uf_show = helpers.get_uf_show('offline', use_mnemonic=True)
- item = Gtk.MenuItem.new_with_mnemonic(uf_show)
- sub_menu.append(item)
- item.connect('activate', self.on_show_menuitem_activate, 'offline')
-
- iskey = connected_accounts > 0 and not (connected_accounts == 1 and
- gajim.zeroconf_is_connected())
- chat_with_menuitem.set_sensitive(iskey)
- single_message_menuitem.set_sensitive(iskey)
- join_gc_menuitem.set_sensitive(iskey)
-
- accounts_list = sorted(gajim.contacts.get_accounts())
- # items that get shown whether an account is zeroconf or not
- if connected_accounts > 1: # 2 or more connections? make submenus
- account_menu_for_chat_with = Gtk.Menu()
- chat_with_menuitem.set_submenu(account_menu_for_chat_with)
- self.popup_menus.append(account_menu_for_chat_with)
-
- for account in accounts_list:
- if gajim.account_is_connected(account):
- # for chat_with
- item = Gtk.MenuItem.new_with_label(
- _('using account %s') % account)
- account_menu_for_chat_with.append(item)
- item.connect('activate', self.on_new_chat, account)
-
- elif connected_accounts == 1: # one account
- # one account connected, no need to show 'as jid'
- for account in gajim.connections:
- if gajim.connections[account].connected > 1:
- # for start chat
- self.new_chat_handler_id = chat_with_menuitem.connect(
- 'activate', self.on_new_chat, account)
- break # No other connected account
-
- # menu items that don't apply to zeroconf connections
- if connected_accounts == 1 or (connected_accounts == 2 and \
- gajim.zeroconf_is_connected()):
- # only one 'real' (non-zeroconf) account is connected, don't need
- # submenus
- for account in gajim.connections:
- if gajim.account_is_connected(account) and \
- not gajim.config.get_per('accounts', account, 'is_zeroconf'):
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
-
- # for single message
- single_message_menuitem.set_submenu(None)
- self.single_message_handler_id = single_message_menuitem.\
- connect('activate',
- self.on_single_message_menuitem_activate, account)
- # join gc
- gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
- account)
- break # No other account connected
- else:
- # 2 or more 'real' accounts are connected, make submenus
- account_menu_for_single_message = Gtk.Menu()
- single_message_menuitem.set_submenu(
- account_menu_for_single_message)
- self.popup_menus.append(account_menu_for_single_message)
-
- for account in accounts_list:
- if gajim.connections[account].is_zeroconf or \
- not gajim.account_is_connected(account):
- continue
- if gajim.connections[account].private_storage_supported:
- connected_accounts_with_private_storage += 1
- # for single message
- item = Gtk.MenuItem.new_with_label(
- _('using account %s') % account)
- item.connect('activate',
- self.on_single_message_menuitem_activate, account)
- account_menu_for_single_message.append(item)
-
- # join gc
- gc_item = Gtk.MenuItem.new_with_label(
- _('using account %s') % account)
- gc_sub_menu.append(gc_item)
- gc_menuitem_menu = Gtk.Menu()
- gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
- account)
- gc_item.set_submenu(gc_menuitem_menu)
- gc_sub_menu.show_all()
-
- newitem = Gtk.SeparatorMenuItem.new() # separator
- gc_sub_menu.append(newitem)
- newitem = Gtk.MenuItem.new_with_mnemonic(_('_Manage Bookmarks…'))
- newitem.connect('activate',
- gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
- gc_sub_menu.append(newitem)
- if connected_accounts_with_private_storage == 0:
- newitem.set_sensitive(False)
-
- sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
-
- win = gajim.interface.roster.window
- if self.show_roster_handler_id:
- show_roster_menuitem.handler_disconnect(self.show_roster_handler_id)
- if win.get_property('has-toplevel-focus'):
- show_roster_menuitem.get_children()[0].set_label(_('Hide _Roster'))
- self.show_roster_handler_id = show_roster_menuitem.connect(
- 'activate', self.on_hide_roster_menuitem_activate)
- else:
- show_roster_menuitem.get_children()[0].set_label(_('Show _Roster'))
- self.show_roster_handler_id = show_roster_menuitem.connect(
- 'activate', self.on_show_roster_menuitem_activate)
-
- if os.name == 'nt':
- if self.added_hide_menuitem is False:
- self.systray_context_menu.prepend(Gtk.SeparatorMenuItem.new())
- item = Gtk.MenuItem.new_with_label(
- _('Hide this menu'))
- self.systray_context_menu.prepend(item)
- self.added_hide_menuitem = True
-
- self.systray_context_menu.show_all()
- self.systray_context_menu.popup(None, None, None, None, 0, event_time)
-
- def on_show_all_events_menuitem_activate(self, widget):
- events = gajim.events.get_systray_events()
- for account in events:
- for jid in events[account]:
- for event in events[account][jid]:
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_sounds_mute_menuitem_activate(self, widget):
- gajim.config.set('sounds_on', not widget.get_active())
-
- def on_show_roster_menuitem_activate(self, widget):
- win = gajim.interface.roster.window
- win.present()
-
- def on_hide_roster_menuitem_activate(self, widget):
- win = gajim.interface.roster.window
- win.hide()
-
- def on_preferences_menuitem_activate(self, widget):
- if 'preferences' in gajim.interface.instances:
- gajim.interface.instances['preferences'].window.present()
- else:
- gajim.interface.instances['preferences'] = config.PreferencesWindow()
-
- def on_quit_menuitem_activate(self, widget):
- gajim.interface.roster.on_quit_request()
-
- def on_left_click(self):
- win = gajim.interface.roster.window
- if len(gajim.events.get_systray_events()) == 0:
- # No pending events, so toggle visible/hidden for roster window
- if win.get_property('visible') and (win.get_property(
- 'has-toplevel-focus') or os.name == 'nt'):
- # visible in ANY virtual desktop?
-
- # we could be in another VD right now. eg vd2
- # and we want to show it in vd2
- if not gtkgui_helpers.possibly_move_window_in_current_desktop(
- win) and gajim.config.get('save-roster-position'):
- x, y = win.get_position()
- gajim.config.set('roster_x-position', x)
- gajim.config.set('roster_y-position', y)
- win.hide() # else we hide it from VD that was visible in
- else:
- if not win.get_property('visible'):
- win.show_all()
- if gajim.config.get('save-roster-position'):
- gtkgui_helpers.move_window(win,
- gajim.config.get('roster_x-position'),
- gajim.config.get('roster_y-position'))
- if not gajim.config.get('roster_window_skip_taskbar'):
- win.set_property('skip-taskbar-hint', False)
- win.present_with_time(Gtk.get_current_event_time())
- else:
- self.handle_first_event()
-
- def handle_first_event(self):
- account, jid, event = gajim.events.get_first_systray_event()
- if not event:
- return
- win = gajim.interface.roster.window
- if not win.get_property('visible') and gajim.config.get(
- 'save-roster-position'):
- gtkgui_helpers.move_window(win,
- gajim.config.get('roster_x-position'),
- gajim.config.get('roster_y-position'))
- gajim.interface.handle_event(account, jid, event.type_)
-
- def on_middle_click(self):
- """
- Middle click raises window to have complete focus (fe. get kbd events)
- but if already raised, it hides it
- """
- win = gajim.interface.roster.window
- if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
- win.hide()
- else:
- win.present()
-
- def on_clicked(self, widget, event):
- self.on_tray_leave_notify_event(widget, None)
- if event.type_ != Gdk.EventType.BUTTON_PRESS:
- return
- if event.button == 1: # Left click
- self.on_left_click()
- elif event.button == 2: # middle click
- self.on_middle_click()
- elif event.button == 3: # right click
- self.make_menu(event.button, event.time)
-
- def on_show_menuitem_activate(self, widget, show):
- # we all add some fake (we cannot select those nor have them as show)
- # but this helps to align with roster's status_combobox index positions
- l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
- 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
- index = l.index(show)
- if not helpers.statuses_unified():
- gajim.interface.roster.status_combobox.set_active(index + 2)
- return
- current = gajim.interface.roster.status_combobox.get_active()
- if index != current:
- gajim.interface.roster.status_combobox.set_active(index)
-
- def on_change_status_message_activate(self, widget):
- model = gajim.interface.roster.status_combobox.get_model()
- active = gajim.interface.roster.status_combobox.get_active()
- status = model[active][2]
- def on_response(message, pep_dict):
- if message is None: # None if user press Cancel
- return
- accounts = gajim.connections.keys()
- for acct in accounts:
- if not gajim.config.get_per('accounts', acct,
- 'sync_with_global_status'):
- continue
- show = gajim.SHOW_LIST[gajim.connections[acct].connected]
- gajim.interface.roster.send_status(acct, show, message)
- gajim.interface.roster.send_pep(acct, pep_dict)
- dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
- dlg.dialog.present()
diff --git a/src/tooltips.py b/src/tooltips.py
deleted file mode 100644
index 468bc336f..000000000
--- a/src/tooltips.py
+++ /dev/null
@@ -1,924 +0,0 @@
-# -*- coding: utf-8 -*-
-## src/tooltips.py
-##
-## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
-## Stéphan Kochen <stephan AT kochen.nl>
-## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
-## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2006 Travis Shirk <travis AT pobox.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
-## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
-## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-from gi.repository import Gtk
-from gi.repository import Gdk
-from gi.repository import GLib
-from gi.repository import Pango
-import os
-import time
-from datetime import datetime
-from datetime import timedelta
-
-import gtkgui_helpers
-
-from common import gajim
-from common import helpers
-from common.i18n import Q_
-
-class BaseTooltip:
- """
- Base Tooltip class
-
- Usage:
- tooltip = BaseTooltip()
- ....
- tooltip.show_tooltip(data, widget_height, widget_y_position)
- ....
- if tooltip.timeout != 0:
- tooltip.hide_tooltip()
-
- * data - the text to be displayed (extenders override this argument and
- display more complex contents)
- * widget_height - the height of the widget on which we want to show tooltip
- * widget_y_position - the vertical position of the widget on the screen
-
- Tooltip is displayed aligned centered to the mouse poiner and 4px below the widget.
- In case tooltip goes below the visible area it is shown above the widget.
- """
-
- def __init__(self):
- self.timeout = 0
- self.preferred_position = [0, 0]
- self.win = None
- self.id = None
- self.cur_data = None
- self.check_last_time = None
- self.shown = False
- self.position_computed = False
-
- def populate(self, data):
- """
- This method must be overriden by all extenders. This is the most simple
- implementation: show data as value of a label
- """
- self.create_window()
- self.win.add(Gtk.Label(label=data))
-
- def create_window(self):
- """
- Create a popup window each time tooltip is requested
- """
- self.win = Gtk.Window.new(Gtk.WindowType.POPUP)
- self.win.set_title('tooltip')
- self.win.set_border_width(3)
- self.win.set_resizable(False)
- self.win.set_name('gtk-tooltips')
- self.win.set_type_hint(Gdk.WindowTypeHint.TOOLTIP)
-
- self.win.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
- self.win.connect('size-allocate', self.on_size_allocate)
- self.win.connect('motion-notify-event', self.motion_notify_event)
- self.screen = self.win.get_screen()
-
- def _get_icon_name_for_tooltip(self, contact):
- """
- Helper function used for tooltip contacts/acounts
-
- Tooltip on account has fake contact with sub == '', in this case we show
- real status of the account
- """
- if contact.ask == 'subscribe':
- return 'requested'
- elif contact.sub in ('both', 'to', ''):
- return contact.show
- return 'not in roster'
-
- def motion_notify_event(self, widget, event):
- GLib.idle_add(self.hide_tooltip)
-
- def on_size_allocate(self, widget, rect):
- if not self.position_computed:
- half_width = rect.width / 2 + 1
- if self.preferred_position[1] + rect.height > \
- self.screen.get_height():
- # flip tooltip up
- self.preferred_position[1] -= rect.height + self.widget_height \
- + 8
- if self.preferred_position[1] < 0:
- self.preferred_position[1] = self.screen.get_height() - \
- rect.height - 2
-
- if self.preferred_position[0] + rect.width + 7 < \
- self.screen.get_width():
- self.preferred_position[0] = self.preferred_position[0]\
- + 7
- else:
- self.preferred_position[0] = self.preferred_position[0]\
- - rect.width - 7
- self.win.move(self.preferred_position[0],
- self.preferred_position[1])
- return
- if self.preferred_position[0] < half_width:
- self.preferred_position[0] = 0
- elif self.preferred_position[0] + rect.width > \
- self.screen.get_width() + half_width:
- self.preferred_position[0] = self.screen.get_width() - \
- rect.width
- elif not self.check_last_time:
- self.preferred_position[0] -= half_width
- self.position_computed = True
- self.win.move(self.preferred_position[0], self.preferred_position[1])
-
- def show_tooltip(self, data, widget_height, widget_y_position):
- """
- Show tooltip on widget
-
- Data contains needed data for tooltip contents.
- widget_height is the height of the widget on which we show the tooltip.
- widget_y_position is vertical position of the widget on the screen.
- """
- if self.shown:
- return
- self.position_computed = False
- self.cur_data = data
- # set tooltip contents
- self.populate(data)
-
- # get the X position of mouse pointer on the screen
- pointer_x = self.screen.get_display().get_device_manager().\
- get_client_pointer().get_position()[1]
-
- # get the prefered X position of the tooltip on the screen in case this position is >
- # than the height of the screen, tooltip will be shown above the widget
- preferred_y = widget_y_position + widget_height + 4
-
- self.preferred_position = [pointer_x, preferred_y]
- self.widget_height = widget_height
- self.win.show_all()
- self.shown = True
-
- def hide_tooltip(self):
- if self.timeout > 0:
- GLib.source_remove(self.timeout)
- self.timeout = 0
- if self.win:
- self.win.destroy()
- self.win = None
- self.id = None
- self.cur_data = None
- self.check_last_time = None
- self.shown = False
-
-class StatusTable:
- """
- Contains methods for creating status table. This is used in Roster and
- NotificationArea tooltips
- """
-
- def __init__(self):
- self.current_row = 0
- self.table = None
- self.text_label = None
- self.spacer_label = ' '
-
- def create_table(self):
- self.table = Gtk.Grid()
- self.table.insert_column(0)
- self.table.set_property('column-spacing', 2)
-
- def add_text_row(self, text, col_inc=0):
- self.table.insert_row(self.current_row)
- self.text_label = Gtk.Label()
- self.text_label.set_line_wrap(True)
- self.text_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
- self.text_label.set_max_width_chars(35)
- self.text_label.set_halign(Gtk.Align.START)
- self.text_label.set_valign(Gtk.Align.START)
- self.text_label.set_selectable(False)
- self.text_label.set_markup(text)
- self.table.attach(self.text_label, 1 + col_inc, self.current_row,
- 3 - col_inc, 1)
- self.current_row += 1
-
- def get_status_info(self, resource, priority, show, status):
- str_status = resource + ' (' + str(priority) + ')'
- if status:
- status = status.strip()
- if status != '':
- # reduce to 100 chars, 1 line
- status = helpers.reduce_chars_newlines(status, 100, 1)
- str_status = GLib.markup_escape_text(str_status)
- status = GLib.markup_escape_text(status)
- str_status += ' - <i>' + status + '</i>'
- return str_status
-
- def add_status_row(self, file_path, show, str_status, status_time=None,
- show_lock=False, indent=True):
- """
- Append a new row with status icon to the table
- """
- self.table.insert_row(self.current_row)
- state_file = show.replace(' ', '_')
- files = []
- files.append(os.path.join(file_path, state_file + '.png'))
- files.append(os.path.join(file_path, state_file + '.gif'))
- image = Gtk.Image()
- image.set_from_pixbuf(None)
- for f in files:
- if os.path.exists(f):
- image.set_from_file(f)
- break
- spacer = Gtk.Label(label=self.spacer_label)
- image.set_halign(Gtk.Align.START)
- image.set_valign(Gtk.Align.CENTER)
- if indent:
- self.table.attach(spacer, 1, self.current_row, 1, 1)
- self.table.attach(image, 2, self.current_row, 1, 1)
- status_label = Gtk.Label()
- status_label.set_markup(str_status)
- status_label.set_halign(Gtk.Align.START)
- status_label.set_valign(Gtk.Align.START)
- status_label.set_line_wrap(True)
- self.table.attach(status_label, 3, self.current_row, 1, 1)
- if show_lock:
- lock_image = Gtk.Image()
- lock_image.set_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION,
- Gtk.IconSize.MENU)
- self.table.attach(lock_image, 4, self.current_row, 1, 1)
- self.current_row += 1
-
-class NotificationAreaTooltip(BaseTooltip, StatusTable):
- """
- Tooltip that is shown in the notification area
- """
-
- def __init__(self):
- BaseTooltip.__init__(self)
- StatusTable.__init__(self)
-
- def fill_table_with_accounts(self, accounts):
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = 'dcraven'
- file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for acct in accounts:
- message = acct['message']
- message = helpers.reduce_chars_newlines(message, 100, 1)
- message = GLib.markup_escape_text(message)
- if acct['name'] in gajim.con_types and \
- gajim.con_types[acct['name']] in ('tls', 'ssl'):
- show_lock = True
- else:
- show_lock = False
- if message:
- self.add_status_row(file_path, acct['show'],
- GLib.markup_escape_text(acct['name']) + ' - ' + message,
- show_lock=show_lock, indent=False)
- else:
- self.add_status_row(file_path, acct['show'],
- GLib.markup_escape_text(acct['name']), show_lock=show_lock,
- indent=False)
- for line in acct['event_lines']:
- self.add_text_row(' ' + line, 1)
-
- def populate(self, data=''):
- self.create_window()
- self.create_table()
-
- accounts = helpers.get_notification_icon_tooltip_dict()
- self.fill_table_with_accounts(accounts)
- self.hbox = Gtk.HBox()
- self.table.set_property('column-spacing', 1)
-
- self.hbox.add(self.table)
- self.hbox.show_all()
-
-class GCTooltip(Gtk.Window):
- # pylint: disable=E1101
- def __init__(self, parent):
- Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP, transient_for=parent)
- self.row = None
- self.set_title('tooltip')
- self.set_border_width(3)
- self.set_resizable(False)
- self.set_name('gtk-tooltips')
- self.set_type_hint(Gdk.WindowTypeHint.TOOLTIP)
-
- self.xml = gtkgui_helpers.get_gtk_builder('tooltip_gc_contact.ui')
- for name in ('nick', 'status', 'jid', 'user_show', 'fillelement',
- 'resource', 'affiliation', 'avatar', 'resource_label',
- 'jid_label', 'tooltip_grid'):
- setattr(self, name, self.xml.get_object(name))
-
- self.add(self.tooltip_grid)
- self.tooltip_grid.show()
-
- def clear_tooltip(self):
- """
- Hide all Elements of the Tooltip Grid
- """
- for child in self.tooltip_grid.get_children():
- child.hide()
-
- def populate(self, contact):
- """
- Populate the Tooltip Grid with data of from the contact
- """
- self.clear_tooltip()
-
- self.nick.set_text(contact.get_shown_name())
- self.nick.show()
-
- # Status Message
- if contact.status:
- status = contact.status.strip()
- if status != '':
- self.status.set_text(status)
- self.status.show()
-
- # Status
- show = helpers.get_uf_show(contact.show)
- self.user_show.set_markup(colorize_status(show))
- self.user_show.show()
-
- # JID
- if contact.jid.strip():
- self.jid.set_text(contact.jid)
- self.jid.show()
- self.jid_label.show()
- # Resource
- if hasattr(contact, 'resource') and contact.resource.strip():
- self.resource.set_text(contact.resource)
- self.resource.show()
- self.resource_label.show()
-
- # Affiliation
- if contact.affiliation != 'none':
- uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
- uf_affiliation = \
- _('%(owner_or_admin_or_member)s of this group chat') \
- % {'owner_or_admin_or_member': uf_affiliation}
- uf_affiliation = self.colorize_affiliation(uf_affiliation)
- self.affiliation.set_markup(uf_affiliation)
- self.affiliation.show()
-
- # Avatar
- puny_name = helpers.sanitize_filename(contact.name)
- puny_room = helpers.sanitize_filename(contact.room_jid)
- file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH,
- puny_room, puny_name))
- if file_:
- with open(file_, 'rb') as file_data:
- pix = gtkgui_helpers.get_pixbuf_from_data(file_data.read())
- pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
- self.avatar.set_from_pixbuf(pix)
- self.avatar.show()
- self.fillelement.show()
-
- @staticmethod
- def colorize_affiliation(affiliation):
- """
- Color the affiliation of a MUC participant inside the tooltip by
- it's semantics. Color palette is the Tango.
- """
- formatted = "<span foreground='%s'>%s</span>"
- color = None
- if affiliation.startswith(Q_("?Group Chat Contact Affiliation:None")):
- color = gajim.config.get('tooltip_affiliation_none_color')
- elif affiliation.startswith(_("Member")):
- color = gajim.config.get('tooltip_affiliation_member_color')
- elif affiliation.startswith(_("Administrator")):
- color = gajim.config.get('tooltip_affiliation_administrator_color')
- elif affiliation.startswith(_("Owner")):
- color = gajim.config.get('tooltip_affiliation_owner_color')
- if color:
- affiliation = formatted % (color, affiliation)
- return affiliation
-
-class RosterTooltip(Gtk.Window, StatusTable):
- # pylint: disable=E1101
- def __init__(self, parent):
- Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP, transient_for=parent)
- StatusTable.__init__(self)
- self.create_table()
- self.row = None
- self.check_last_time = {}
- self.contact_jid = None
- self.last_widget = None
- self.num_resources = 0
- self.set_title('tooltip')
- self.set_border_width(3)
- self.set_resizable(False)
- self.set_name('gtk-tooltips')
- self.set_type_hint(Gdk.WindowTypeHint.TOOLTIP)
-
- self.xml = gtkgui_helpers.get_gtk_builder('tooltip_roster_contact.ui')
- for name in ('name', 'status', 'jid', 'user_show', 'fillelement',
- 'resource', 'avatar', 'resource_label', 'pgp', 'pgp_label',
- 'jid_label', 'tooltip_grid', 'idle_since', 'idle_for',
- 'idle_since_label', 'idle_for_label', 'mood', 'tune',
- 'activity', 'location', 'tune_label', 'location_label',
- 'activity_label', 'mood_label', 'sub_label', 'sub',
- 'status_label'):
- setattr(self, name, self.xml.get_object(name))
-
- self.add(self.tooltip_grid)
- self.tooltip_grid.show()
-
- def clear_tooltip(self):
- """
- Hide all Elements of the Tooltip Grid
- """
- for child in self.tooltip_grid.get_children():
- child.hide()
- status_table = self.tooltip_grid.get_child_at(0, 3)
- if status_table:
- status_table.destroy()
- self.create_table()
-
- def fill_table_with_accounts(self, accounts):
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = 'dcraven'
- file_path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
- for acct in accounts:
- message = acct['message']
- message = helpers.reduce_chars_newlines(message, 100, 1)
- message = GLib.markup_escape_text(message)
- if acct['name'] in gajim.con_types and \
- gajim.con_types[acct['name']] in ('tls', 'ssl'):
- show_lock = True
- else:
- show_lock = False
- if message:
- self.add_status_row(file_path, acct['show'],
- GLib.markup_escape_text(acct['name']) + ' - ' + message,
- show_lock=show_lock, indent=False)
- else:
- self.add_status_row(file_path, acct['show'],
- GLib.markup_escape_text(acct['name']), show_lock=show_lock,
- indent=False)
- for line in acct['event_lines']:
- self.add_text_row(' ' + line, 1)
-
- def populate(self, contacts, account, typ):
- """
- Populate the Tooltip Grid with data of from the contact
- """
- self.current_row = 0
- self.account = account
- if self.last_widget:
- self.last_widget.set_vexpand(False)
-
- self.clear_tooltip()
-
- if account == 'all':
- # Tooltip for merged accounts row
- accounts = helpers.get_notification_icon_tooltip_dict()
- self.spacer_label = ''
- self.fill_table_with_accounts(accounts)
- self.tooltip_grid.attach(self.table, 0, 3, 2, 1)
- self.table.show_all()
- return
-
- if typ == 'account':
- jid = gajim.get_jid_from_account(account)
- contacts = []
- connection = gajim.connections[account]
- # get our current contact info
-
- nbr_on, nbr_total = gajim.\
- contacts.get_nb_online_total_contacts(
- accounts=[account])
- account_name = account
- if gajim.account_is_connected(account):
- account_name += ' (%s/%s)' % (repr(nbr_on),
- repr(nbr_total))
- contact = gajim.contacts.create_self_contact(jid=jid,
- account=account, name=account_name,
- show=connection.get_status(), status=connection.status,
- resource=connection.server_resource,
- priority=connection.priority)
- if gajim.connections[account].gpg:
- contact.keyID = gajim.config.get_per('accounts',
- connection.name, 'keyid')
- contacts.append(contact)
- # if we're online ...
- if connection.connection:
- roster = connection.connection.getRoster()
- # in threadless connection when no roster stanza is sent
- # 'roster' is None
- if roster and roster.getItem(jid):
- resources = roster.getResources(jid)
- # ...get the contact info for our other online
- # resources
- for resource in resources:
- # Check if we already have this resource
- found = False
- for contact_ in contacts:
- if contact_.resource == resource:
- found = True
- break
- if found:
- continue
- show = roster.getShow(jid + '/' + resource)
- if not show:
- show = 'online'
- contact = gajim.contacts.create_self_contact(
- jid=jid, account=account, show=show,
- status=roster.getStatus(
- jid + '/' + resource),
- priority=roster.getPriority(
- jid + '/' + resource), resource=resource)
- contacts.append(contact)
-
- # Username/Account/Groupchat
- self.prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
- contacts)
- self.contact_jid = self.prim_contact.jid
- name = GLib.markup_escape_text(self.prim_contact.get_shown_name())
- name_markup = '<b>{}</b>'.format(name)
- if gajim.config.get('mergeaccounts'):
- color = gajim.config.get('tooltip_account_name_color')
- account_name = GLib.markup_escape_text(self.prim_contact.account.name)
- name_markup += " <span foreground='{}'>({})</span>".format(
- color, account_name)
-
- if account and helpers.jid_is_blocked(account, self.prim_contact.jid):
- name_markup += _(' [blocked]')
-
- try:
- if self.prim_contact.jid in gajim.interface.minimized_controls[account]:
- name_markup += _(' [minimized]')
- except KeyError:
- pass
-
- self.name.set_markup(name_markup)
- self.name.show()
-
- self.num_resources = 0
- # put contacts in dict, where key is priority
- contacts_dict = {}
- for contact in contacts:
- if contact.resource:
- self.num_resources += 1
- priority = int(contact.priority)
- if priority in contacts_dict:
- contacts_dict[priority].append(contact)
- else:
- contacts_dict[priority] = [contact]
- if self.num_resources > 1:
- self.status_label.show()
- transport = gajim.get_transport_name_from_jid(self.prim_contact.jid)
- if transport:
- file_path = os.path.join(helpers.get_transport_path(transport),
- '16x16')
- else:
- iconset = gajim.config.get('iconset')
- if not iconset:
- iconset = 'dcraven'
- file_path = os.path.join(helpers.get_iconset_path(iconset),
- '16x16')
-
- contact_keys = sorted(contacts_dict.keys())
- contact_keys.reverse()
- for priority in contact_keys:
- for acontact in contacts_dict[priority]:
- icon_name = self._get_icon_name_for_tooltip(acontact)
- if acontact.status and len(acontact.status) > 25:
- status = ''
- add_text = True
- else:
- status = acontact.status
- add_text = False
-
- status_line = self.get_status_info(acontact.resource,
- acontact.priority, acontact.show, status)
- self.add_status_row(file_path, icon_name, status_line,
- acontact.last_status_time)
- if add_text:
- self.add_text_row(acontact.status, 2)
-
- self.tooltip_grid.attach(self.table, 0, 3, 2, 1)
- self.table.show_all()
-
- else: # only one resource
- if contact.show:
- request_time = False
- try:
- last_time = self.check_last_time[contact]
- if isinstance(last_time, float) and last_time < time.time() - 60:
- request_time = True
- except KeyError:
- request_time = True
-
- if request_time:
- if contact.show == 'offline':
- gajim.connections[account].\
- request_last_status_time(contact.jid, '')
- elif contact.resource:
- gajim.connections[account].\
- request_last_status_time(
- contact.jid, contact.resource)
- self.check_last_time[contact] = time.time()
-
- if contact.status:
- status = contact.status.strip()
- if status:
- self.status.set_text(status)
- self.status.show()
- self.status_label.show()
-
- # PEP Info
- self._append_pep_info(contact)
-
- # JID
- self.jid.set_text(self.prim_contact.jid)
- self.jid.show()
- self.jid_label.show()
-
- # contact has only one ressource
- if self.num_resources == 1 and contact.resource:
- res = GLib.markup_escape_text(contact.resource)
- prio = str(contact.priority)
- self.resource.set_text("{} ({})".format(res, prio))
- self.resource.show()
- self.resource_label.show()
-
- if self.prim_contact.jid not in gajim.gc_connected[account]:
- if (account and
- self.prim_contact.sub and
- self.prim_contact.sub != 'both'):
- # ('both' is the normal sub so we don't show it)
- self.sub.set_text(helpers.get_uf_sub(self.prim_contact.sub))
- self.sub.show()
- self.sub_label.show()
-
- if self.prim_contact.keyID:
- keyID = None
- if len(self.prim_contact.keyID) == 8:
- keyID = self.prim_contact.keyID
- elif len(self.prim_contact.keyID) == 16:
- keyID = self.prim_contact.keyID[8:]
- if keyID:
- self.pgp.set_text(keyID)
- self.pgp.show()
- self.pgp_label.show()
-
- self._set_idle_time(contact)
-
- # Avatar
- puny_jid = helpers.sanitize_filename(self.prim_contact.jid)
- file_ = helpers.get_avatar_path(os.path.join(gajim.AVATAR_PATH,
- puny_jid))
- if file_:
- with open(file_, 'rb') as file_data:
- pix = gtkgui_helpers.get_pixbuf_from_data(file_data.read())
- pix = gtkgui_helpers.get_scaled_pixbuf(pix, 'tooltip')
- self.avatar.set_from_pixbuf(pix)
- self.avatar.show()
-
- # Sets the Widget that is at the bottom to expand.
- # This is needed in case the Picture takes more Space then the Labels
- i = 1
- while i < 15:
- if self.tooltip_grid.get_child_at(0, i):
- if self.tooltip_grid.get_child_at(0, i).get_visible():
- self.last_widget = self.tooltip_grid.get_child_at(0, i)
- i += 1
- self.last_widget.set_vexpand(True)
-
- def _append_pep_info(self, contact):
- """
- Append Tune, Mood, Activity, Location information of the specified contact
- to the given property list.
- """
- if 'mood' in contact.pep:
- mood = contact.pep['mood'].asMarkupText()
- self.mood.set_markup(mood)
- self.mood.show()
- self.mood_label.show()
-
- if 'activity' in contact.pep:
- activity = contact.pep['activity'].asMarkupText()
- self.activity.set_markup(activity)
- self.activity.show()
- self.activity_label.show()
-
- if 'tune' in contact.pep:
- tune = contact.pep['tune'].asMarkupText()
- self.tune.set_markup(tune)
- self.tune.show()
- self.tune_label.show()
-
- if 'location' in contact.pep:
- location = contact.pep['location'].asMarkupText()
- self.location.set_markup(location)
- self.location.show()
- self.location_label.show()
-
- def _set_idle_time(self, contact):
- if contact.last_activity_time:
- last_active = datetime(*contact.last_activity_time[:6])
- current = datetime.now()
-
- diff = current - last_active
- diff = timedelta(diff.days, diff.seconds)
-
- if last_active.date() == current.date():
- formatted = last_active.strftime("%X")
- else:
- formatted = last_active.strftime("%c")
-
- # Do not show the "Idle since" and "Idle for" items if there
- # is no meaningful difference between last activity time and
- # current time.
- if diff.days > 0 or diff.seconds > 0:
- idle_color = gajim.config.get('tooltip_idle_color')
- idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, formatted)
- self.idle_since.set_markup(idle_markup)
- self.idle_since.show()
- self.idle_since_label.show()
- idle_markup = "<span foreground='{}'>{}</span>".format(idle_color, str(diff))
- self.idle_for.set_markup(idle_markup)
- self.idle_for_label.show()
- self.idle_for.show()
-
- if contact.show and self.num_resources < 2:
- show = helpers.get_uf_show(contact.show)
- if contact.last_status_time:
- if contact.show == 'offline':
- text = ' - ' + _('Last status: %s')
- else:
- text = _(' since %s')
-
- if time.strftime('%j', time.localtime()) == \
- time.strftime('%j', contact.last_status_time):
- # it's today, show only the locale hour representation
- local_time = time.strftime('%X', contact.last_status_time)
- else:
- # time.strftime returns locale encoded string
- local_time = time.strftime('%c', contact.last_status_time)
-
- text = text % local_time
- show += text
-
- # Contact is Groupchat
- if (self.account and
- self.prim_contact.jid in gajim.gc_connected[self.account]):
- if gajim.gc_connected[self.account][self.prim_contact.jid]:
- show = _('Connected')
- else:
- show = _('Disconnected')
-
- self.user_show.set_markup(colorize_status(show))
- self.user_show.show()
-
- def _get_icon_name_for_tooltip(self, contact):
- """
- Helper function used for tooltip contacts/acounts
-
- Tooltip on account has fake contact with sub == '', in this case we show
- real status of the account
- """
- if contact.ask == 'subscribe':
- return 'requested'
- elif contact.sub in ('both', 'to', ''):
- return contact.show
- return 'not in roster'
-
- def update_last_time(self, contact, error=False):
- if not contact:
- return
- if error:
- self.check_last_time[contact] = 'error'
- return
- if contact.jid == self.contact_jid:
- self._set_idle_time(contact)
-
-
-class FileTransfersTooltip(BaseTooltip):
- """
- Tooltip that is shown in the notification area
- """
-
- def __init__(self):
- BaseTooltip.__init__(self)
-
- def populate(self, file_props):
- ft_table = Gtk.Table(2, 1)
- ft_table.set_property('column-spacing', 2)
- current_row = 1
- self.create_window()
- properties = []
- name = file_props.name
- if file_props.type_ == 'r':
- file_name = os.path.split(file_props.file_name)[1]
- else:
- file_name = file_props.name
- properties.append((_('Name: '), GLib.markup_escape_text(file_name)))
- if file_props.type_ == 'r':
- type_ = _('?Noun:Download')
- actor = _('Sender: ')
- sender = file_props.sender.split('/')[0]
- name = gajim.contacts.get_first_contact_from_jid(
- file_props.tt_account, sender).get_shown_name()
- else:
- type_ = _('?Noun:Upload')
- actor = _('Recipient: ')
- receiver = file_props.receiver
- if hasattr(receiver, 'name'):
- name = receiver.get_shown_name()
- else:
- name = receiver.split('/')[0]
- properties.append((_('Type: '), type_))
- properties.append((actor, GLib.markup_escape_text(name)))
-
- transfered_len = file_props.received_len
- if not transfered_len:
- transfered_len = 0
- properties.append((_('Transferred: '), helpers.convert_bytes(transfered_len)))
- status = ''
- if file_props.started:
- status = _('Not started')
- if file_props.stopped == True:
- status = _('Stopped')
- elif file_props.completed:
- status = _('Completed')
- elif file_props.connected == False:
- if file_props.completed:
- status = _('Completed')
- else:
- if file_props.paused == True:
- status = _('?transfer status:Paused')
- elif file_props.stalled == True:
- #stalled is not paused. it is like 'frozen' it stopped alone
- status = _('Stalled')
- else:
- status = _('Transferring')
- else:
- status = _('Not started')
- properties.append((_('Status: '), status))
- file_desc = file_props.desc or ''
- properties.append((_('Description: '), GLib.markup_escape_text(
- file_desc)))
- while properties:
- property_ = properties.pop(0)
- current_row += 1
- label = Gtk.Label()
- label.set_halign(Gtk.Align.START)
- label.set_valign(Gtk.Align.START)
- label.set_markup(property_[0])
- ft_table.attach(label, 1, 2, current_row, current_row + 1,
- Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, 0, 0)
- label = Gtk.Label()
- label.set_halign(Gtk.Align.START)
- label.set_valign(Gtk.Align.START)
- label.set_line_wrap(True)
- label.set_markup(property_[1])
- ft_table.attach(label, 2, 3, current_row, current_row + 1,
- Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, 0, 0)
-
- self.win.add(ft_table)
-
-
-def colorize_status(status):
- """
- Colorize the status message inside the tooltip by it's
- semantics. Color palette is the Tango.
- """
- formatted = "<span foreground='%s'>%s</span>"
- color = None
- if status.startswith(Q_("?user status:Available")):
- color = gajim.config.get('tooltip_status_online_color')
- elif status.startswith(_("Free for Chat")):
- color = gajim.config.get('tooltip_status_free_for_chat_color')
- elif status.startswith(_("Away")):
- color = gajim.config.get('tooltip_status_away_color')
- elif status.startswith(_("Busy")):
- color = gajim.config.get('tooltip_status_busy_color')
- elif status.startswith(_("Not Available")):
- color = gajim.config.get('tooltip_status_na_color')
- elif status.startswith(_("Offline")):
- color = gajim.config.get('tooltip_status_offline_color')
- if color:
- status = formatted % (color, status)
- return status
diff --git a/src/upower_listener.py b/src/upower_listener.py
deleted file mode 100644
index 744399689..000000000
--- a/src/upower_listener.py
+++ /dev/null
@@ -1,47 +0,0 @@
-## src/upower_listener.py
-##
-## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
-##
-## This file is part of Gajim.
-##
-## Gajim is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## Gajim is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-##
-## You should have received a copy of the GNU General Public License
-## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
-##
-
-
-supported = False
-
-from common import dbus_support
-from common import gajim
-
-def on_suspend(*args, **kwargs):
- for name, conn in gajim.connections.items():
- if gajim.account_is_connected(name):
- conn.old_show = gajim.SHOW_LIST[conn.connected]
- st = conn.status
- conn.change_status('offline', _('Machine going to sleep'))
- conn.status = st
- conn.time_to_reconnect = 5
-
-if dbus_support.supported:
- try:
- from common.dbus_support import system_bus
- bus = system_bus.bus()
- if 'org.freedesktop.UPower' in bus.list_names():
- up_object = bus.get_object('org.freedesktop.UPower',
- '/org/freedesktop/UPower')
- bus.add_signal_receiver(on_suspend, 'Sleeping',
- 'org.freedesktop.UPower', 'org.freedesktop.UPower',
- '/org/freedesktop/UPower')
- supported = True
- except Exception:
- pass
diff --git a/src/vcard.py b/src/vcard.py
deleted file mode 100644
index be3daf0f3..000000000
--- a/src/vcard.py
+++ /dev/null
@@ -1,646 +0,0 @@
-# -*- coding:utf-8 -*-
-## src/vcard.py
-##
-## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
-## Copyright (C) 2005 Vincent Hanquez <tab AT snarc.org>
-## Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com>
-## Copyright (C) 2006 Junglecow J <junglecow AT gmail.com>
-## Dimitur Kirov <dkirov AT gmail.com>
-## Travis Shirk <travis AT pobox.com>
-## Stefan Bethge <stefan AT lanpartei.de>
-## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
-## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
-## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
-## Jonathan Schleifer <js-gajim AT webkeks.org>
-## Stephan Erb <steve-e AT h3c.de>
-##
-## 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/>.
-##
-
-# THIS FILE IS FOR **OTHERS'** PROFILE (when we VIEW their INFO)
-
-from gi.repository import Gtk
-from gi.repository import GLib
-from gi.repository import Gdk
-import base64
-import time
-import locale
-import os
-# import logging
-
-import gtkgui_helpers
-
-from common import helpers
-from common import gajim
-from common import ged
-from common.i18n import Q_
-
-# log = logging.getLogger('gajim.vcard')
-
-def get_avatar_pixbuf_encoded_mime(photo):
- """
- Return the pixbuf of the image
-
- Photo is a dictionary containing PHOTO information.
- """
- if not isinstance(photo, dict):
- return None, None, None
- img_decoded = None
- avatar_encoded = None
- avatar_mime_type = None
- if 'BINVAL' in photo:
- img_encoded = photo['BINVAL']
- avatar_encoded = img_encoded
- try:
- img_decoded = base64.b64decode(img_encoded.encode('utf-8'))
- except Exception:
- pass
- if img_decoded:
- if 'TYPE' in photo:
- avatar_mime_type = photo['TYPE']
- pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
- else:
- pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
- img_decoded, want_type=True)
- else:
- pixbuf = None
- return pixbuf, avatar_encoded, avatar_mime_type
-
-class VcardWindow:
- """
- Class for contact's information window
- """
-
- def __init__(self, contact, account, gc_contact = None):
- # the contact variable is the jid if vcard is true
- self.xml = gtkgui_helpers.get_gtk_builder('vcard_information_window.ui')
- self.window = self.xml.get_object('vcard_information_window')
- self.progressbar = self.xml.get_object('progressbar')
-
- self.contact = contact
- self.account = account
- self.gc_contact = gc_contact
-
- # Get real jid
- if gc_contact:
- # Don't use real jid if room is (semi-)anonymous
- gc_control = gajim.interface.msg_win_mgr.get_gc_control(
- gc_contact.room_jid, account)
- if gc_contact.jid and not gc_control.is_anonymous:
- self.real_jid = gc_contact.jid
- self.real_jid_for_vcard = gc_contact.jid
- if gc_contact.resource:
- self.real_jid += '/' + gc_contact.resource
- else:
- self.real_jid = gc_contact.get_full_jid()
- self.real_jid_for_vcard = self.real_jid
- self.real_resource = gc_contact.name
- else:
- self.real_jid = contact.get_full_jid()
- self.real_resource = contact.resource
-
- puny_jid = helpers.sanitize_filename(contact.jid)
- local_avatar_basepath = os.path.join(gajim.AVATAR_PATH, puny_jid) + \
- '_local'
- for extension in ('.png', '.jpeg'):
- local_avatar_path = local_avatar_basepath + extension
- if os.path.isfile(local_avatar_path):
- image = self.xml.get_object('custom_avatar_image')
- image.set_from_file(local_avatar_path)
- image.show()
- self.xml.get_object('custom_avatar_label').show()
- break
- self.avatar_mime_type = None
- self.avatar_encoded = None
- self.vcard_arrived = False
- self.os_info_arrived = False
- self.entity_time_arrived = False
- self.time = 0
- self.update_intervall = 100 # Milliseconds
- self.update_progressbar_timeout_id = GLib.timeout_add(self.update_intervall,
- 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)
- gajim.ged.register_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
-
- self.fill_jabber_page()
- annotations = gajim.connections[self.account].annotations
- if self.contact.jid in annotations:
- buffer_ = self.xml.get_object('textview_annotation').get_buffer()
- buffer_.set_text(annotations[self.contact.jid])
-
- for widget_name in ('URL_label',
- 'EMAIL_WORK_USERID_label',
- 'EMAIL_HOME_USERID_label'):
- widget = self.xml.get_object(widget_name)
- widget.hide()
-
- self.xml.connect_signals(self)
- self.xml.get_object('close_button').grab_focus()
- self.window.show_all()
-
- def update_progressbar(self):
- self.progressbar.pulse()
- self.time += self.update_intervall
- # Timeout in Milliseconds
- if (self.vcard_arrived and self.os_info_arrived and
- self.entity_time_arrived) or self.time == 10000:
- self.progressbar.hide()
- self.update_progressbar_timeout_id = None
- return False
- return True
-
- def on_vcard_information_window_destroy(self, widget):
- if self.update_progressbar_timeout_id is not None:
- GLib.source_remove(self.update_progressbar_timeout_id)
- del gajim.interface.instances[self.account]['infos'][self.contact.jid]
- buffer_ = self.xml.get_object('textview_annotation').get_buffer()
- annotation = buffer_.get_text(buffer_.get_start_iter(),
- buffer_.get_end_iter(), True)
- connection = gajim.connections[self.account]
- 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)
- gajim.ged.remove_event_handler('vcard-received', ged.GUI1,
- self._nec_vcard_received)
-
- def on_vcard_information_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_information_notebook_switch_page(self, widget, page, page_num):
- GLib.idle_add(self.xml.get_object('close_button').grab_focus)
-
- def on_PHOTO_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = Gtk.Menu()
- menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.contact.get_shown_name())
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.attach_to_widget(widget, None)
- menu.popup(None, None, None, None, event.button, event.time)
-
- def set_value(self, entry_name, value):
- try:
- widget = self.xml.get_object(entry_name)
- if entry_name in ('URL_label',
- 'EMAIL_WORK_USERID_label',
- 'EMAIL_HOME_USERID_label'):
- widget.set_uri('mailto:' + value)
- widget.set_label(value)
- self.xml.get_object(entry_name).show()
- else:
- val = widget.get_text()
- if val:
- value = val + ' / ' + value
- widget.set_text(value)
- except AttributeError:
- pass
-
- def set_values(self, vcard):
- for i in vcard.keys():
- if i == 'PHOTO' and self.xml.get_object('information_notebook').\
- get_n_pages() > 4:
- pixbuf, self.avatar_encoded, self.avatar_mime_type = \
- get_avatar_pixbuf_encoded_mime(vcard[i])
- image = self.xml.get_object('PHOTO_image')
- image.show()
- self.xml.get_object('user_avatar_label').show()
- if not pixbuf:
- image.set_from_icon_name('stock_person',
- Gtk.IconSize.DIALOG)
- continue
- pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
- image.set_from_pixbuf(pixbuf)
- continue
- if i in ('ADR', 'TEL', 'EMAIL'):
- for entry in vcard[i]:
- add_on = '_HOME'
- if 'WORK' in entry:
- add_on = '_WORK'
- for j in entry.keys():
- self.set_value(i + add_on + '_' + j + '_label', entry[j])
- if isinstance(vcard[i], dict):
- for j in vcard[i].keys():
- self.set_value(i + '_' + j + '_label', vcard[i][j])
- else:
- if i == 'DESC':
- self.xml.get_object('DESC_textview').get_buffer().set_text(
- vcard[i], len(vcard[i].encode('utf-8')))
- elif i != 'jid': # Do not override jid_label
- self.set_value(i + '_label', vcard[i])
- self.vcard_arrived = True
-
- def clear_values(self):
- for l in ('FN', 'NICKNAME', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE',
- 'N_PREFIX', 'N_SUFFIX', 'EMAIL_HOME_USERID', 'TEL_HOME_NUMBER', 'BDAY',
- 'ORG_ORGNAME', 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'EMAIL_WORK_USERID',
- 'TEL_WORK_NUMBER', 'URL'):
- widget = self.xml.get_object(l + '_label')
- if l in ('EMAIL_HOME_USERID', 'EMAIL_WORK_USERID', 'URL'):
- widget.hide()
- else:
- widget.set_text('')
- for pref in ('ADR_HOME', 'ADR_WORK'):
- for l in ('STREET', 'EXTADR', 'LOCALITY', 'PCODE', 'REGION',
- 'CTRY'):
- widget = self.xml.get_object(pref + '_' + l + '_label')
- widget.set_text('')
- self.xml.get_object('DESC_textview').get_buffer().set_text('')
-
-
- def _nec_vcard_received(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.resource:
- # It's a muc occupant vcard
- if obj.fjid != self.contact.jid:
- return
- else:
- if obj.jid != self.contact.jid:
- return
- self.clear_values()
- self.set_values(obj.vcard_dict)
-
- def set_last_status_time(self, obj):
- if obj.conn.name != self.account:
- return
- if obj.fjid != self.real_jid:
- return
- self.fill_status_label()
-
- def set_os_info(self, obj):
- if obj.conn.name != self.account:
- return
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- if self.gc_contact:
- if obj.fjid != self.contact.jid:
- return
- elif gajim.get_jid_without_resource(obj.fjid) != self.contact.jid:
- return
- i = 0
- client = ''
- os = ''
- while i in self.os_info:
- if self.os_info[i]['resource'] == obj.resource:
- if obj.client_info:
- self.os_info[i]['client'] = obj.client_info
- else:
- self.os_info[i]['client'] = Q_('?Client:Unknown')
- if obj.os_info:
- self.os_info[i]['os'] = obj.os_info
- else:
- self.os_info[i]['os'] = Q_('?OS:Unknown')
- else:
- if not self.os_info[i]['client']:
- self.os_info[i]['client'] = Q_('?Client:Unknown')
- if not self.os_info[i]['os']:
- self.os_info[i]['os'] = Q_('?OS:Unknown')
- if i > 0:
- client += '\n'
- os += '\n'
- client += self.os_info[i]['client']
- os += self.os_info[i]['os']
- i += 1
-
- self.xml.get_object('client_name_version_label').set_text(client)
- self.xml.get_object('os_label').set_text(os)
- self.os_info_arrived = True
-
- def set_entity_time(self, obj):
- if obj.conn.name != self.account:
- return
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- if self.gc_contact:
- if obj.fjid != self.contact.jid:
- return
- elif gajim.get_jid_without_resource(obj.fjid) != self.contact.jid:
- return
- i = 0
- time_s = ''
- while i in self.time_info:
- if self.time_info[i]['resource'] == obj.resource:
- if obj.time_info:
- self.time_info[i]['time'] = obj.time_info
- else:
- self.time_info[i]['time'] = Q_('?Time:Unknown')
- else:
- if not self.time_info[i]['time']:
- self.time_info[i]['time'] = Q_('?Time:Unknown')
- if i > 0:
- time_s += '\n'
- time_s += self.time_info[i]['time']
- i += 1
-
- self.xml.get_object('time_label').set_text(time_s)
- self.entity_time_arrived = True
-
- def fill_status_label(self):
- if self.xml.get_object('information_notebook').get_n_pages() < 5:
- return
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- connected_contact_list = []
- for c in contact_list:
- if c.show not in ('offline', 'error'):
- connected_contact_list.append(c)
- if not connected_contact_list:
- # no connected contact, get the offline one
- connected_contact_list = contact_list
- # stats holds show and status message
- stats = ''
- if connected_contact_list:
- # Start with self.contact, as with resources
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- if self.contact.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- self.contact.last_status_time)
- for c in connected_contact_list:
- if c.resource != self.contact.resource:
- stats += '\n'
- stats += helpers.get_uf_show(c.show)
- if c.status:
- stats += ': ' + c.status
- if c.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- c.last_status_time)
- else: # Maybe gc_vcard ?
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- status_label = self.xml.get_object('status_label')
- status_label.set_text(stats)
- status_label.set_tooltip_text(stats)
-
- def fill_jabber_page(self):
- self.xml.get_object('nickname_label').set_markup(
- '<b><span size="x-large">' +
- self.contact.get_shown_name() +
- '</span></b>')
- self.xml.get_object('jid_label').set_text(self.contact.jid)
-
- subscription_label = self.xml.get_object('subscription_label')
- ask_label = self.xml.get_object('ask_label')
- if self.gc_contact:
- self.xml.get_object('subscription_title_label').set_markup(Q_("?Role in Group Chat:<b>Role:</b>"))
- uf_role = helpers.get_uf_role(self.gc_contact.role)
- subscription_label.set_text(uf_role)
-
- self.xml.get_object('ask_title_label').set_markup(_("<b>Affiliation:</b>"))
- uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation)
- ask_label.set_text(uf_affiliation)
- else:
- uf_sub = helpers.get_uf_sub(self.contact.sub)
- subscription_label.set_text(uf_sub)
- if self.contact.sub == 'from':
- tt_text = _("This contact is interested in your presence information, but you are not interested in their presence")
- elif self.contact.sub == 'to':
- tt_text = _("You are interested in the contact's presence information, but it is not mutual")
- elif self.contact.sub == 'both':
- tt_text = _("The contact and you want to exchange presence information")
- else: # None
- tt_text = _("You and the contact have a mutual disinterest in each-others presence information")
- subscription_label.set_tooltip_text(tt_text)
-
- uf_ask = helpers.get_uf_ask(self.contact.ask)
- ask_label.set_text(uf_ask)
- if self.contact.ask == 'subscribe':
- tt_text = _("You are waiting contact's answer about your subscription request")
- else:
- tt_text = _("There is no pending subscription request.")
- ask_label.set_tooltip_text(tt_text)
-
- resources = '%s (%s)' % (self.contact.resource, str(
- self.contact.priority))
- uf_resources = self.contact.resource + _(' resource with priority ')\
- + str(self.contact.priority)
- if not self.contact.status:
- self.contact.status = ''
-
- # Request list time status only if contact is offline
- if self.contact.show == 'offline':
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- gajim.connections[self.account].request_last_status_time(j, r,
- self.contact.jid)
- else:
- gajim.connections[self.account].request_last_status_time(
- self.contact.jid, '')
-
- # do not wait for os_info if contact is not connected or has error
- # additional check for observer is needed, as show is offline for him
- if self.contact.show in ('offline', 'error')\
- and not self.contact.is_observer():
- self.os_info_arrived = True
- else: # Request os info if contact is connected
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- GLib.idle_add(gajim.connections[self.account].request_os_info,
- j, r, self.contact.jid)
- else:
- GLib.idle_add(gajim.connections[self.account].request_os_info,
- self.contact.jid, self.contact.resource)
-
- # do not wait for entity_time if contact is not connected or has error
- # additional check for observer is needed, as show is offline for him
- if self.contact.show in ('offline', 'error')\
- and not self.contact.is_observer():
- self.entity_time_arrived = True
- else: # Request entity time if contact is connected
- if self.gc_contact:
- j, r = gajim.get_room_and_nick_from_fjid(self.real_jid)
- GLib.idle_add(gajim.connections[self.account].\
- request_entity_time, j, r, self.contact.jid)
- else:
- GLib.idle_add(gajim.connections[self.account].\
- request_entity_time, self.contact.jid, self.contact.resource)
-
- self.os_info = {0: {'resource': self.real_resource, 'client': '',
- 'os': ''}}
- self.time_info = {0: {'resource': self.real_resource, 'time': ''}}
- i = 1
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- if contact_list:
- for c in contact_list:
- if c.resource != self.contact.resource:
- resources += '\n%s (%s)' % (c.resource,
- str(c.priority))
- uf_resources += '\n' + c.resource + \
- _(' resource with priority ') + str(c.priority)
- if c.show not in ('offline', 'error'):
- GLib.idle_add(gajim.connections[self.account].\
- request_os_info, c.jid, c.resource)
- GLib.idle_add(gajim.connections[self.account].\
- request_entity_time, c.jid, c.resource)
- self.os_info[i] = {'resource': c.resource, 'client': '',
- 'os': ''}
- self.time_info[i] = {'resource': c.resource, 'time': ''}
- i += 1
-
- self.xml.get_object('resource_prio_label').set_text(resources)
- resource_prio_label_eventbox = self.xml.get_object(
- 'resource_prio_label_eventbox')
- resource_prio_label_eventbox.set_tooltip_text(uf_resources)
-
- self.fill_status_label()
-
- if self.gc_contact:
- # If we know the real jid, remove the resource from vcard request
- gajim.connections[self.account].request_vcard(self.real_jid_for_vcard,
- self.gc_contact.get_full_jid())
- else:
- gajim.connections[self.account].request_vcard(self.contact.jid)
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()
-
-
-class ZeroconfVcardWindow:
- def __init__(self, contact, account, is_fake = False):
- # the contact variable is the jid if vcard is true
- self.xml = gtkgui_helpers.get_gtk_builder('zeroconf_information_window.ui')
- self.window = self.xml.get_object('zeroconf_information_window')
-
- self.contact = contact
- self.account = account
- self.is_fake = is_fake
-
- # self.avatar_mime_type = None
- # self.avatar_encoded = None
-
- self.fill_contact_page()
- self.fill_personal_page()
-
- self.xml.connect_signals(self)
- self.window.show_all()
-
- def on_zeroconf_information_window_destroy(self, widget):
- del gajim.interface.instances[self.account]['infos'][self.contact.jid]
-
- def on_zeroconf_information_window_key_press_event(self, widget, event):
- if event.keyval == Gdk.KEY_Escape:
- self.window.destroy()
-
- def on_PHOTO_eventbox_button_press_event(self, widget, event):
- """
- If right-clicked, show popup
- """
- if event.button == 3: # right click
- menu = Gtk.Menu()
- menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As'))
- menuitem.connect('activate',
- gtkgui_helpers.on_avatar_save_as_menuitem_activate,
- self.contact.jid, self.contact.get_shown_name())
- menu.append(menuitem)
- menu.connect('selection-done', lambda w:w.destroy())
- # show the menu
- menu.show_all()
- menu.attach_to_widget(widget, None)
- menu.popup(None, None, None, None, event.button, event.time)
-
- def set_value(self, entry_name, value):
- try:
- if value and entry_name == 'URL_label':
- widget = Gtk.LinkButton(uri=value, label=value)
- widget.set_alignment(0, 0)
- table = self.xml.get_object('personal_info_table')
- table.attach(widget, 1, 3, 2, 1)
- else:
- self.xml.get_object(entry_name).set_text(value)
- except AttributeError:
- pass
-
- def fill_status_label(self):
- if self.xml.get_object('information_notebook').get_n_pages() < 2:
- return
- contact_list = gajim.contacts.get_contacts(self.account, self.contact.jid)
- # stats holds show and status message
- stats = ''
- one = True # Are we adding the first line ?
- if contact_list:
- for c in contact_list:
- if not one:
- stats += '\n'
- stats += helpers.get_uf_show(c.show)
- if c.status:
- stats += ': ' + c.status
- if c.last_status_time:
- stats += '\n' + _('since %s') % time.strftime('%c',
- c.last_status_time).decode(locale.getpreferredencoding())
- one = False
- else: # Maybe gc_vcard ?
- stats = helpers.get_uf_show(self.contact.show)
- if self.contact.status:
- stats += ': ' + self.contact.status
- status_label = self.xml.get_object('status_label')
- status_label.set_text(stats)
- status_label.set_tooltip_text(stats)
-
- def fill_contact_page(self):
- self.xml.get_object('nickname_label').set_markup(
- '<b><span size="x-large">' +
- self.contact.get_shown_name() +
- '</span></b>')
- self.xml.get_object('local_jid_label').set_text(self.contact.jid)
-
- resources = '%s (%s)' % (self.contact.resource, str(
- self.contact.priority))
- uf_resources = self.contact.resource + _(' resource with priority ')\
- + str(self.contact.priority)
- if not self.contact.status:
- self.contact.status = ''
-
- self.xml.get_object('resource_prio_label').set_text(resources)
- resource_prio_label_eventbox = self.xml.get_object(
- 'resource_prio_label_eventbox')
- resource_prio_label_eventbox.set_tooltip_text(uf_resources)
-
- self.fill_status_label()
-
- def fill_personal_page(self):
- contact = gajim.connections[gajim.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid)
- for key in ('1st', 'last', 'jid', 'email'):
- if key not in contact['txt_dict']:
- contact['txt_dict'][key] = ''
- self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st'])
- self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last'])
- self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid'])
- self.xml.get_object('email_label').set_text(contact['txt_dict']['email'])
-
- def on_close_button_clicked(self, widget):
- self.window.destroy()