Merging changes from trunk (6774:7465)
This commit is contained in:
parent
755964c3f1
commit
175a792cfe
69 changed files with 8647 additions and 2680 deletions
35
src/Makefile
35
src/Makefile
|
@ -1,35 +0,0 @@
|
|||
# Set the C flags to include the GTK+ and Python libraries
|
||||
PYTHON ?= python
|
||||
PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
|
||||
gtk_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fPIC -I/usr/include/python$(PYTHONVER) -I.
|
||||
gtk_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -lpython$(PYTHONVER)
|
||||
|
||||
all: trayicon.so gtkspell.so
|
||||
|
||||
# Build the shared objects
|
||||
trayicon.so: trayicon.o eggtrayicon.o trayiconmodule.o
|
||||
$(CC) -shared $^ -o $@ $(LDFLAGS) $(gtk_LDFLAGS)
|
||||
|
||||
gtkspell.so:
|
||||
$(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_CFLAGS) $(gtk_LDFLAGS) `pkg-config --libs --cflags gtkspell-2.0` -shared gtkspellmodule.c $^ -o $@
|
||||
|
||||
# The path to the GTK+ python types
|
||||
DEFS=`pkg-config --variable=defsdir pygtk-2.0`
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -o $@ -c $< $(CFLAGS) $(gtk_CFLAGS)
|
||||
|
||||
# Generate the C wrapper from the defs and our override file
|
||||
trayicon.c: trayicon.defs trayicon.override
|
||||
pygtk-codegen-2.0 --prefix trayicon \
|
||||
--register $(DEFS)/gdk-types.defs \
|
||||
--register $(DEFS)/gtk-types.defs \
|
||||
--override trayicon.override \
|
||||
trayicon.defs > $@
|
||||
|
||||
|
||||
# A rule to clean the generated files
|
||||
clean:
|
||||
rm -f trayicon.so *.o trayicon.c gtkspell.so *~
|
||||
|
||||
.PHONY: clean
|
73
src/Makefile.am
Normal file
73
src/Makefile.am
Normal file
|
@ -0,0 +1,73 @@
|
|||
SUBDIRS = common
|
||||
|
||||
CLEANFILES = \
|
||||
trayicon.c
|
||||
INCLUDES = \
|
||||
$(PYTHON_INCLUDES)
|
||||
|
||||
if BUILD_GTKSPELL
|
||||
gtkspelllib_LTLIBRARIES = gtkspell.la
|
||||
gtkspelllibdir = $(libdir)/gajim
|
||||
|
||||
gtkspell_la_LIBADD = \
|
||||
$(GTKSPELL_LIBS) $(PYGTK_LIBS)
|
||||
|
||||
gtkspell_la_SOURCES = \
|
||||
gtkspellmodule.c
|
||||
|
||||
gtkspell_la_LDFLAGS = \
|
||||
-module -avoid-version
|
||||
|
||||
gtkspell_la_CFLAGS = $(GTKSPELL_CFLAGS) $(PYGTK_CFLAGS)
|
||||
endif
|
||||
if BUILD_TRAYICON
|
||||
trayiconlib_LTLIBRARIES = trayicon.la
|
||||
trayiconlibdir = $(libdir)/gajim
|
||||
trayicon_la_LIBADD = $(PYGTK_LIBS)
|
||||
trayicon_la_SOURCES = \
|
||||
eggtrayicon.c \
|
||||
trayiconmodule.c
|
||||
|
||||
nodist_trayicon_la_SOURCES = \
|
||||
trayicon.c
|
||||
|
||||
trayicon_la_LDFLAGS = \
|
||||
-module -avoid-version
|
||||
trayicon_la_CFLAGS = $(PYGTK_CFLAGS)
|
||||
|
||||
trayicon.c:
|
||||
pygtk-codegen-2.0 --prefix trayicon \
|
||||
--register $(PYGTK_DEFS)/gdk-types.defs \
|
||||
--register $(PYGTK_DEFS)/gtk-types.defs \
|
||||
--override $(srcdir)/trayicon.override \
|
||||
$(srcdir)/trayicon.defs > $@
|
||||
endif
|
||||
gajimsrcdir = $(pkgdatadir)/src
|
||||
gajimsrc_DATA = $(srcdir)/*.py
|
||||
|
||||
gajimsrc1dir = $(pkgdatadir)/src/common
|
||||
gajimsrc1_DATA = \
|
||||
$(srcdir)/common/*.py
|
||||
|
||||
gajimsrc2dir = $(pkgdatadir)/src/common/xmpp
|
||||
gajimsrc2_DATA = \
|
||||
$(srcdir)/common/xmpp/*.py
|
||||
|
||||
gajimsrc3dir = $(pkgdatadir)/src/common/zeroconf
|
||||
gajimsrc3_DATA = \
|
||||
$(srcdir)/common/zeroconf/*.py
|
||||
|
||||
DISTCLEANFILES =
|
||||
|
||||
EXTRA_DIST = $(gajimsrc_DATA) \
|
||||
$(gajimsrc1_DATA) \
|
||||
$(gajimsrc2_DATA) \
|
||||
$(gajimsrc3_DATA) \
|
||||
gtkspellmodule.c \
|
||||
eggtrayicon.c \
|
||||
trayiconmodule.c \
|
||||
eggtrayicon.h \
|
||||
trayicon.defs \
|
||||
trayicon.override
|
||||
|
||||
MAINTAINERCLEANFILES = Makefile.in
|
|
@ -1,18 +1,8 @@
|
|||
## advanced.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Vincent Hanquez <tab@snarc.org>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005 Vincent Hanquez <tab@snarc.org>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -42,7 +32,7 @@ C_TYPE
|
|||
|
||||
GTKGUI_GLADE = 'manage_accounts_window.glade'
|
||||
|
||||
class AdvancedConfigurationWindow:
|
||||
class AdvancedConfigurationWindow(object):
|
||||
def __init__(self):
|
||||
self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade')
|
||||
self.window = self.xml.get_widget('advanced_configuration_window')
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
|
|
@ -34,6 +34,8 @@ from message_textview import MessageTextView
|
|||
from common.contacts import GC_Contact
|
||||
from common.logger import Constants
|
||||
constants = Constants()
|
||||
from common.rst_xhtml_generator import create_xhtml
|
||||
from common.xmpp.protocol import NS_XHTML
|
||||
|
||||
try:
|
||||
import gtkspell
|
||||
|
@ -112,7 +114,7 @@ class ChatControlBase(MessageControl):
|
|||
acct, resource = None):
|
||||
MessageControl.__init__(self, type_id, parent_win, widget_name,
|
||||
display_names, contact, acct, resource = resource);
|
||||
# when/if we do XHTML we will but formatting buttons back
|
||||
# when/if we do XHTML we will put formatting buttons back
|
||||
widget = self.xml.get_widget('emoticons_button')
|
||||
id = widget.connect('clicked', self.on_emoticons_button_clicked)
|
||||
self.handlers[id] = widget
|
||||
|
@ -196,7 +198,6 @@ class ChatControlBase(MessageControl):
|
|||
self.msg_textview.lang = lang
|
||||
spell.set_language(lang)
|
||||
except (gobject.GError, RuntimeError), msg:
|
||||
#FIXME: add a ui for this use spell.set_language()
|
||||
dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
|
||||
'for which you want to highlight misspelled words, then please '
|
||||
'set your $LANG as appropriate. Eg. for French do export '
|
||||
|
@ -229,6 +230,11 @@ class ChatControlBase(MessageControl):
|
|||
item = gtk.SeparatorMenuItem()
|
||||
menu.prepend(item)
|
||||
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
|
||||
menu.prepend(item)
|
||||
id = item.connect('activate', self.msg_textview.clear)
|
||||
self.handlers[id] = item
|
||||
|
||||
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
|
||||
item = gtk.MenuItem(_('Spelling language'))
|
||||
menu.prepend(item)
|
||||
|
@ -242,11 +248,6 @@ class ChatControlBase(MessageControl):
|
|||
id = item.connect('activate', _on_select_dictionary, langs[lang])
|
||||
self.handlers[id] = item
|
||||
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
|
||||
menu.prepend(item)
|
||||
id = item.connect('activate', self.msg_textview.clear)
|
||||
self.handlers[id] = item
|
||||
|
||||
menu.show_all()
|
||||
|
||||
# moved from ChatControl
|
||||
|
@ -425,7 +426,8 @@ class ChatControlBase(MessageControl):
|
|||
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).decode('utf-8')
|
||||
message = message_buffer.get_text(start_iter, end_iter, False).decode(
|
||||
'utf-8')
|
||||
|
||||
# construct event instance from binding
|
||||
event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
|
||||
|
@ -471,10 +473,11 @@ class ChatControlBase(MessageControl):
|
|||
self.send_message(message) # send the message
|
||||
else:
|
||||
# Give the control itself a chance to process
|
||||
self.handle_message_textview_mykey_press(widget, event_keyval, event_keymod)
|
||||
self.handle_message_textview_mykey_press(widget, event_keyval,
|
||||
event_keymod)
|
||||
|
||||
def _process_command(self, message):
|
||||
if not message:
|
||||
if not message or message[0] != '/':
|
||||
return False
|
||||
|
||||
message = message[1:]
|
||||
|
@ -534,7 +537,7 @@ class ChatControlBase(MessageControl):
|
|||
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):
|
||||
subject = None, old_kind = None, xhtml = None):
|
||||
'''prints 'chat' type messages'''
|
||||
jid = self.contact.jid
|
||||
full_jid = self.get_full_jid()
|
||||
|
@ -544,20 +547,26 @@ class ChatControlBase(MessageControl):
|
|||
end = True
|
||||
textview.print_conversation_line(text, jid, kind, name, tim,
|
||||
other_tags_for_name, other_tags_for_time, other_tags_for_text,
|
||||
subject, old_kind)
|
||||
subject, old_kind, xhtml)
|
||||
|
||||
if not count_as_new:
|
||||
return
|
||||
if kind == 'incoming':
|
||||
gajim.last_message_time[self.account][full_jid] = time.time()
|
||||
urgent = True
|
||||
if (not self.parent_win.get_active_jid() or \
|
||||
full_jid != self.parent_win.get_active_jid() or \
|
||||
not self.parent_win.is_active() or not end) and \
|
||||
kind in ('incoming', 'incoming_queue'):
|
||||
if self.notify_on_new_messages():
|
||||
gc_message = False
|
||||
if self.type_id == message_control.TYPE_GC:
|
||||
gc_message = True
|
||||
if not gc_message or \
|
||||
(gc_message and (other_tags_for_text == ['marked'] or \
|
||||
gajim.config.get('notify_on_all_muc_messages'))):
|
||||
# we want to have save this message in events list
|
||||
# other_tags_for_text == ['marked'] --> highlighted gc message
|
||||
type_ = 'printed_' + self.type_id
|
||||
if self.type_id == message_control.TYPE_GC:
|
||||
if gc_message:
|
||||
type_ = 'printed_gc_msg'
|
||||
show_in_roster = notify.get_show_in_roster('message_received',
|
||||
self.account, self.contact)
|
||||
|
@ -572,10 +581,11 @@ class ChatControlBase(MessageControl):
|
|||
gajim.interface.roster.draw_contact(self.contact.jid,
|
||||
self.account)
|
||||
self.parent_win.redraw_tab(self)
|
||||
ctrl = gajim.interface.msg_win_mgr.get_control(full_jid, self.account)
|
||||
if not self.parent_win.is_active():
|
||||
ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
|
||||
self.account)
|
||||
self.parent_win.show_title(urgent, ctrl)
|
||||
self.parent_win.show_title(True, ctrl) # Enabled Urgent hint
|
||||
else:
|
||||
self.parent_win.show_title(False, ctrl) # Disabled Urgent hint
|
||||
|
||||
def toggle_emoticons(self):
|
||||
'''hide show emoticons_button and make sure emoticons_menu is always there
|
||||
|
@ -647,17 +657,7 @@ class ChatControlBase(MessageControl):
|
|||
if not gajim.events.remove_events(self.account, self.get_full_jid(),
|
||||
types = [type_]):
|
||||
# There were events to remove
|
||||
self.parent_win.redraw_tab(self)
|
||||
self.parent_win.show_title()
|
||||
# redraw 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_control(
|
||||
room_jid, self.account)
|
||||
groupchat_control.draw_contact(nick)
|
||||
else:
|
||||
gajim.interface.roster.draw_contact(jid, self.account)
|
||||
gajim.interface.roster.show_title()
|
||||
self.redraw_after_event_removed(jid)
|
||||
self.msg_textview.grab_focus()
|
||||
# Note, we send None chatstate to preserve current
|
||||
self.parent_win.redraw_tab(self)
|
||||
|
@ -750,8 +750,22 @@ class ChatControlBase(MessageControl):
|
|||
if not gajim.events.remove_events(self.account, self.get_full_jid(),
|
||||
types = ['printed_' + type_, type_]):
|
||||
# There were events to remove
|
||||
self.parent_win.redraw_tab(self)
|
||||
self.parent_win.show_title()
|
||||
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_control(
|
||||
room_jid, self.account)
|
||||
groupchat_control.draw_contact(nick)
|
||||
else:
|
||||
gajim.interface.roster.draw_contact(jid, self.account)
|
||||
gajim.interface.roster.show_title()
|
||||
|
||||
def sent_messages_scroll(self, direction, conv_buf):
|
||||
size = len(self.sent_history)
|
||||
|
@ -760,7 +774,8 @@ class ChatControlBase(MessageControl):
|
|||
#whatever is already typed
|
||||
start_iter = conv_buf.get_start_iter()
|
||||
end_iter = conv_buf.get_end_iter()
|
||||
self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode('utf-8')
|
||||
self.orig_msg = conv_buf.get_text(start_iter, end_iter, 0).decode(
|
||||
'utf-8')
|
||||
self.typing_new = False
|
||||
if direction == 'up':
|
||||
if self.sent_history_pos == 0:
|
||||
|
@ -820,8 +835,8 @@ class ChatControl(ChatControlBase):
|
|||
old_msg_kind = None # last kind of the printed message
|
||||
|
||||
def __init__(self, parent_win, contact, acct, resource = None):
|
||||
ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox',
|
||||
(_('Chat'), _('Chats')), contact, acct, resource)
|
||||
ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
|
||||
'chat_child_vbox', (_('Chat'), _('Chats')), contact, acct, resource)
|
||||
|
||||
# for muc use:
|
||||
# widget = self.xml.get_widget('muc_window_actions_button')
|
||||
|
@ -829,13 +844,16 @@ class ChatControl(ChatControlBase):
|
|||
id = widget.connect('clicked', self.on_actions_button_clicked)
|
||||
self.handlers[id] = widget
|
||||
|
||||
self.hide_chat_buttons_always = gajim.config.get('always_hide_chat_buttons')
|
||||
self.hide_chat_buttons_always = gajim.config.get(
|
||||
'always_hide_chat_buttons')
|
||||
self.chat_buttons_set_visible(self.hide_chat_buttons_always)
|
||||
self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_chat_banner'))
|
||||
self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
|
||||
gajim.config.get('hide_chat_banner'))
|
||||
# Initialize drag-n-drop
|
||||
self.TARGET_TYPE_URI_LIST = 80
|
||||
self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ]
|
||||
id = self.widget.connect('drag_data_received', self._on_drag_data_received)
|
||||
id = self.widget.connect('drag_data_received',
|
||||
self._on_drag_data_received)
|
||||
self.handlers[id] = self.widget
|
||||
self.widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
|
||||
gtk.DEST_DEFAULT_HIGHLIGHT |
|
||||
|
@ -857,17 +875,21 @@ class ChatControl(ChatControlBase):
|
|||
self._on_window_motion_notify)
|
||||
self.handlers[id] = self.parent_win.window
|
||||
message_tv_buffer = self.msg_textview.get_buffer()
|
||||
id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed)
|
||||
id = message_tv_buffer.connect('changed',
|
||||
self._on_message_tv_buffer_changed)
|
||||
self.handlers[id] = message_tv_buffer
|
||||
|
||||
widget = self.xml.get_widget('avatar_eventbox')
|
||||
id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
|
||||
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)
|
||||
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)
|
||||
id = widget.connect('button-press-event',
|
||||
self.on_avatar_eventbox_button_press_event)
|
||||
self.handlers[id] = widget
|
||||
|
||||
widget = self.xml.get_widget('gpg_togglebutton')
|
||||
|
@ -881,12 +903,7 @@ class ChatControl(ChatControlBase):
|
|||
self.update_ui()
|
||||
# restore previous conversation
|
||||
self.restore_conversation()
|
||||
# is account displayed after nick in banner ?
|
||||
self.account_displayed= False
|
||||
|
||||
def notify_on_new_messages(self):
|
||||
return gajim.config.get('trayicon_notification_on_new_messages')
|
||||
|
||||
def on_avatar_eventbox_enter_notify_event(self, widget, event):
|
||||
'''we enter the eventbox area so we under conditions add a timeout
|
||||
to show a bigger avatar after 0.5 sec'''
|
||||
|
@ -924,7 +941,8 @@ class ChatControl(ChatControlBase):
|
|||
menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
id = menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.contact.jid, self.account, self.contact.name + '.jpeg')
|
||||
self.contact.jid, self.account, self.contact.get_shown_name() +
|
||||
'.jpeg')
|
||||
self.handlers[id] = menuitem
|
||||
menu.append(menuitem)
|
||||
menu.show_all()
|
||||
|
@ -1001,39 +1019,33 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
banner_name_label = self.xml.get_widget('banner_name_label')
|
||||
name = contact.get_shown_name()
|
||||
avoid_showing_account_too = False
|
||||
if self.resource:
|
||||
name += '/' + self.resource
|
||||
avoid_showing_account_too = True
|
||||
if self.TYPE_ID == message_control.TYPE_PM:
|
||||
room_jid = self.contact.jid.split('/')[0]
|
||||
room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid,
|
||||
self.account)
|
||||
name = _('%s from room %s') % (name, room_ctrl.name)
|
||||
name = _('%(nickname)s from group chat %(room_name)s') %\
|
||||
{'nickname': name, 'room_name': self.room_name}
|
||||
name = gtkgui_helpers.escape_for_pango_markup(name)
|
||||
|
||||
# We know our contacts nick, but if there are any other controls
|
||||
# with the same nick we need to also display the account
|
||||
# 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 = ''
|
||||
self.account_displayed = False
|
||||
for ctrl in self.parent_win.controls():
|
||||
if ctrl == self:
|
||||
for account in gajim.contacts.get_accounts():
|
||||
if account == self.account:
|
||||
continue
|
||||
if self.contact.get_shown_name() == ctrl.contact.get_shown_name()\
|
||||
and not avoid_showing_account_too:
|
||||
self.account_displayed = True
|
||||
if not ctrl.account_displayed:
|
||||
# do that after this instance exists
|
||||
gobject.idle_add(ctrl.draw_banner)
|
||||
acct_info = ' (%s)' % \
|
||||
gtkgui_helpers.escape_for_pango_markup(self.account)
|
||||
if acct_info: # We already found a contact with same nick
|
||||
break
|
||||
for jid in gajim.contacts.get_jid_list(account):
|
||||
contact_ = gajim.contacts.get_first_contact_from_jid(account, jid)
|
||||
if contact_.get_shown_name() == self.contact.get_shown_name():
|
||||
acct_info = ' (%s)' % \
|
||||
gtkgui_helpers.escape_for_pango_markup(self.account)
|
||||
break
|
||||
|
||||
status = contact.status
|
||||
if status is not None:
|
||||
banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
|
||||
status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 2)
|
||||
status = helpers.reduce_chars_newlines(status, max_lines = 2)
|
||||
status_escaped = gtkgui_helpers.escape_for_pango_markup(status)
|
||||
|
||||
font_attrs, font_attrs_small = self.get_font_attrs()
|
||||
|
@ -1071,8 +1083,8 @@ class ChatControl(ChatControlBase):
|
|||
banner_name_label.set_markup(label_text)
|
||||
|
||||
def on_toggle_gpg_togglebutton(self, widget):
|
||||
gajim.config.set_per('contacts', self.contact.get_full_jid(),
|
||||
'gpg_enabled', widget.get_active())
|
||||
gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled',
|
||||
widget.get_active())
|
||||
|
||||
def _update_gpg(self):
|
||||
tb = self.xml.get_widget('gpg_togglebutton')
|
||||
|
@ -1166,8 +1178,8 @@ class ChatControl(ChatControlBase):
|
|||
if current_state == 'composing':
|
||||
self.send_chatstate('paused') # 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!
|
||||
# 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
|
||||
|
||||
|
@ -1186,11 +1198,12 @@ class ChatControl(ChatControlBase):
|
|||
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:
|
||||
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!
|
||||
# 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
|
||||
|
||||
|
@ -1201,7 +1214,7 @@ class ChatControl(ChatControlBase):
|
|||
self.kbd_activity_in_last_30_secs = False
|
||||
|
||||
def print_conversation(self, text, frm = '', tim = None,
|
||||
encrypted = False, subject = None):
|
||||
encrypted = False, subject = None, xhtml = None):
|
||||
'''Print a line in the conversation:
|
||||
if contact is set to status: it's a status message
|
||||
if contact is set to another value: it's an outgoing message
|
||||
|
@ -1240,8 +1253,12 @@ class ChatControl(ChatControlBase):
|
|||
else:
|
||||
kind = 'outgoing'
|
||||
name = gajim.nicks[self.account]
|
||||
if not xhtml and not encrypted and gajim.config.get('rst_formatting_outgoing_messages'):
|
||||
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)
|
||||
subject = subject, old_kind = self.old_msg_kind, xhtml = xhtml)
|
||||
if text.startswith('/me ') or text.startswith('/me\n'):
|
||||
self.old_msg_kind = None
|
||||
else:
|
||||
|
@ -1276,9 +1293,6 @@ class ChatControl(ChatControlBase):
|
|||
elif chatstate == 'paused':
|
||||
color = gajim.config.get_per('themes', theme,
|
||||
'state_paused_color')
|
||||
else:
|
||||
color = gajim.config.get_per('themes', theme,
|
||||
'state_active_color')
|
||||
if color:
|
||||
# We set the color for when it's the current tab or not
|
||||
color = gtk.gdk.colormap_get_system().alloc_color(color)
|
||||
|
@ -1287,6 +1301,9 @@ class ChatControl(ChatControlBase):
|
|||
if chatstate in ('inactive', 'gone') and\
|
||||
self.parent_win.get_active_control() != self:
|
||||
color = self.lighten_color(color)
|
||||
elif chatstate == 'active' : # active, get color from gtk
|
||||
color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
|
||||
|
||||
|
||||
name = self.contact.get_shown_name()
|
||||
if self.resource:
|
||||
|
@ -1459,18 +1476,20 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
# prevent going paused if we we were not composing (JEP violation)
|
||||
if state == 'paused' and not contact.our_chatstate == 'composing':
|
||||
MessageControl.send_message(self, None, chatstate = 'active') # go active before
|
||||
# go active before
|
||||
MessageControl.send_message(self, None, chatstate = 'active')
|
||||
contact.our_chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
# if we're inactive prevent composing (JEP violation)
|
||||
elif contact.our_chatstate == 'inactive' and state == 'composing':
|
||||
MessageControl.send_message(self, None, chatstate = 'active') # go active before
|
||||
# go active before
|
||||
MessageControl.send_message(self, None, chatstate = 'active')
|
||||
contact.our_chatstate = 'active'
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
||||
MessageControl.send_message(self, None, chatstate = state, msg_id = contact.msg_id,
|
||||
composing_jep = contact.composing_jep)
|
||||
MessageControl.send_message(self, None, chatstate = state,
|
||||
msg_id = contact.msg_id, composing_jep = contact.composing_jep)
|
||||
contact.our_chatstate = state
|
||||
if contact.our_chatstate == 'active':
|
||||
self.reset_kbd_mouse_timeout_vars()
|
||||
|
@ -1501,7 +1520,7 @@ class ChatControl(ChatControlBase):
|
|||
self.msg_textview.destroy()
|
||||
|
||||
|
||||
def allow_shutdown(self):
|
||||
def allow_shutdown(self, method):
|
||||
if time.time() - gajim.last_message_time[self.account]\
|
||||
[self.get_full_jid()] < 2:
|
||||
# 2 seconds
|
||||
|
@ -1593,13 +1612,16 @@ class ChatControl(ChatControlBase):
|
|||
return
|
||||
timeout = gajim.config.get('restore_timeout') # in minutes
|
||||
|
||||
events = gajim.events.get_events(self.account, jid, ['chat'])
|
||||
# number of messages that are in queue and are already logged, we want
|
||||
# to avoid duplication
|
||||
pending_how_many = len(events)
|
||||
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)
|
||||
pending_how_many, timeout, self.account)
|
||||
local_old_kind = None
|
||||
for row in rows: # row[0] time, row[1] has kind, row[2] the message
|
||||
if not row[2]: # message is empty, we don't print it
|
||||
|
@ -1657,7 +1679,7 @@ class ChatControl(ChatControlBase):
|
|||
else:
|
||||
kind = 'print_queue'
|
||||
self.print_conversation(data[0], kind, tim = data[3],
|
||||
encrypted = data[4], subject = data[1])
|
||||
encrypted = data[4], subject = data[1], xhtml = data[7])
|
||||
if len(data) > 6 and isinstance(data[6], int):
|
||||
message_ids.append(data[6])
|
||||
if message_ids:
|
||||
|
@ -1665,30 +1687,19 @@ class ChatControl(ChatControlBase):
|
|||
gajim.events.remove_events(self.account, jid_with_resource,
|
||||
types = [self.type_id])
|
||||
|
||||
self.parent_win.show_title()
|
||||
self.parent_win.redraw_tab(self)
|
||||
# redraw roster
|
||||
gajim.interface.roster.show_title()
|
||||
|
||||
typ = 'chat' # Is it a normal chat or a pm ?
|
||||
# reset to status image in gc if it is a pm
|
||||
if is_pm:
|
||||
control.update_ui()
|
||||
typ = 'pm'
|
||||
|
||||
if is_pm:
|
||||
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
||||
groupchat_control = gajim.interface.msg_win_mgr.get_control(
|
||||
room_jid, self.account)
|
||||
groupchat_control.draw_contact(nick)
|
||||
else:
|
||||
gajim.interface.roster.draw_contact(jid, self.account)
|
||||
# Redraw parent too
|
||||
gajim.interface.roster.draw_parent_contact(jid, self.account)
|
||||
if (self.contact.show == 'offline' or self.contact.show == 'error'):
|
||||
showOffline = gajim.config.get('showoffline')
|
||||
if not showOffline and typ == 'chat' and \
|
||||
len(gajim.contacts.get_contact(self.account, jid)) < 2:
|
||||
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_contact(self.account, jid)) < 2):
|
||||
gajim.interface.roster.really_remove_contact(self.contact,
|
||||
self.account)
|
||||
elif typ == 'pm':
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <nkour@jabber.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# Set the C flags to include the GTK+ and Python libraries
|
||||
PYTHON ?= python
|
||||
PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
|
||||
|
||||
HAVE_XSCRNSAVER = $(shell pkg-config --exists xscrnsaver && echo 'YES')
|
||||
|
||||
ifeq ($(HAVE_XSCRNSAVER),YES)
|
||||
# We link with libXScrnsaver from modular X.Org X11
|
||||
gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0 xscrnsaver` -fpic -I/usr/include/python$(PYTHONVER) -I.
|
||||
gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0 xscrnsaver` -lpython$(PYTHONVER)
|
||||
else
|
||||
# # We link with libXScrnsaver from monolithic X.Org X11
|
||||
gtk_and_x_CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fpic -I/usr/include/python$(PYTHONVER) -I.
|
||||
gtk_and_x_LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` \
|
||||
-L/usr/X11R6$(LIBDIR) -lX11 -lXss -lXext -lpython$(PYTHONVER)
|
||||
endif
|
||||
|
||||
all: idle.so
|
||||
|
||||
idle.so:
|
||||
$(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_and_x_CFLAGS) $(gtk_and_x_LDFLAGS) -shared idle.c $^ -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.so
|
||||
rm -rf build
|
22
src/common/Makefile.am
Normal file
22
src/common/Makefile.am
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
INCLUDES = \
|
||||
$(PYTHON_INCLUDES)
|
||||
if BUILD_IDLE
|
||||
idlelib_LTLIBRARIES = idle.la
|
||||
idlelibdir = $(libdir)/gajim
|
||||
|
||||
idle_la_LIBADD = $(XSCRNSAVER_LIBS)
|
||||
|
||||
idle_la_SOURCES = idle.c
|
||||
|
||||
idle_la_LDFLAGS = \
|
||||
-module -avoid-version
|
||||
|
||||
idle_la_CFLAGS = $(XSCRNSAVER_CFLAGS) $(PYTHON_INCLUDES)
|
||||
endif
|
||||
|
||||
DISTCLEANFILES =
|
||||
|
||||
EXTRA_DIST =
|
||||
|
||||
MAINTAINERCLEANFILES = Makefile.in
|
|
@ -1,16 +1,7 @@
|
|||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Travis Shirk <travis@pobox.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.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
|
||||
|
@ -29,7 +20,14 @@ import stat
|
|||
from common import gajim
|
||||
import logger
|
||||
|
||||
from pysqlite2 import dbapi2 as sqlite # DO NOT MOVE ABOVE OF import gajim
|
||||
# DO NOT MOVE ABOVE OF import gajim
|
||||
try:
|
||||
import sqlite3 as sqlite # python 2.5
|
||||
except ImportError:
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
|
||||
def create_log_db():
|
||||
print _('creating logs database')
|
||||
|
@ -57,11 +55,13 @@ def create_log_db():
|
|||
jid_id INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id);
|
||||
|
||||
CREATE TABLE transports_cache (
|
||||
transport TEXT UNIQUE,
|
||||
type INTEGER
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE logs(
|
||||
log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
jid_id INTEGER,
|
||||
|
@ -72,6 +72,8 @@ def create_log_db():
|
|||
message TEXT,
|
||||
subject TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind);
|
||||
'''
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
|
||||
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
|
||||
##
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
|
@ -20,6 +21,7 @@
|
|||
|
||||
import sre
|
||||
import copy
|
||||
import defs
|
||||
|
||||
|
||||
(
|
||||
|
@ -37,6 +39,8 @@ opt_bool = [ 'boolean', 0 ]
|
|||
opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ]
|
||||
opt_one_window_types = ['never', 'always', 'peracct', 'pertype']
|
||||
|
||||
DEFAULT_ICONSET = 'dcraven'
|
||||
|
||||
class Config:
|
||||
|
||||
__options = {
|
||||
|
@ -50,7 +54,8 @@ class Config:
|
|||
'autopopupaway': [ opt_bool, False ],
|
||||
'use_notif_daemon': [ opt_bool, True , _('Use D-Bus and Notification-Daemon to show notifications') ],
|
||||
'ignore_unknown_contacts': [ opt_bool, False ],
|
||||
'showoffline': [ opt_bool, False, '', True ],
|
||||
'showoffline': [ opt_bool, False ],
|
||||
'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, _('Away as a result of being idle') ],
|
||||
|
@ -67,7 +72,7 @@ class Config:
|
|||
'last_status_msg_invisible': [ opt_str, '' ],
|
||||
'last_status_msg_offline': [ opt_str, '' ],
|
||||
'trayicon': [ opt_bool, True, '', True ],
|
||||
'iconset': [ opt_str, 'dcraven', '', True ],
|
||||
'iconset': [ opt_str, DEFAULT_ICONSET, '', True ],
|
||||
'use_transports_iconsets': [ opt_bool, True, '', True ],
|
||||
'inmsgcolor': [ opt_color, '#a34526', '', True ],
|
||||
'outmsgcolor': [ opt_color, '#164e6f', '', True ],
|
||||
|
@ -79,15 +84,19 @@ class Config:
|
|||
'saveposition': [ opt_bool, True ],
|
||||
'mergeaccounts': [ opt_bool, False, '', True ],
|
||||
'sort_by_show': [ opt_bool, True, '', True ],
|
||||
'enable_zeroconf': [opt_bool, False, _('Enable link-local/zeroconf messaging')],
|
||||
'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, _('Value of fuzziness from 1 to 4 or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the less precise one.') ],
|
||||
'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 less 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 for HTML, plus ascii formatting if selected. (If you want to use this, install docutils)')],
|
||||
'sounds_on': [ opt_bool, True ],
|
||||
# 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
|
||||
'soundplayer': [ opt_str, '' ],
|
||||
|
@ -125,6 +134,7 @@ class Config:
|
|||
'before_nickname': [ opt_str, '' ],
|
||||
'after_nickname': [ opt_str, ':' ],
|
||||
'send_os_info': [ opt_bool, True ],
|
||||
'set_status_msg_from_current_music_track': [ opt_bool, False ],
|
||||
'notify_on_new_gmail_email': [ opt_bool, True ],
|
||||
'notify_on_new_gmail_email_extra': [ opt_bool, False ],
|
||||
'usegpg': [ opt_bool, False, '', True ],
|
||||
|
@ -135,25 +145,26 @@ class Config:
|
|||
'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')],
|
||||
'show_roster_on_startup': [opt_bool, True],
|
||||
'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
|
||||
'version': [ opt_str, '0.10.1.3' ], # which version created the config
|
||||
'version': [ opt_str, defs.version ], # which version created the config
|
||||
'search_engine': [opt_str, 'http://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, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
|
||||
'networkmanager_support': [opt_bool, True, _('If True, listen to D-Bus signals from NetworkManager and change the status of accounts (provided they do not have listen_to_network_manager set to False and they sync with global status) based upon the status of the network connection.'), True],
|
||||
'chat_state_notifications': [opt_str, 'all'], # 'all', 'composing_only', 'disabled'
|
||||
'autodetect_browser_mailer': [opt_bool, False, '', True],
|
||||
'print_ichat_every_foo_minutes': [opt_int, 5, _('When not printing time for every message (print_time==sometimes), print it every x minutes.')],
|
||||
'confirm_close_muc': [opt_bool, True, _('Ask before closing a group chat tab/window.')],
|
||||
'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of room jids.')],
|
||||
'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of room jids.')],
|
||||
'confirm_close_muc_rooms': [opt_str, '', _('Always ask before closing group chat tab/window in this space separated list of group chat jids.')],
|
||||
'noconfirm_close_muc_rooms': [opt_str, '', _('Never ask before closing group chat tab/window in this space separated list of group chat jids.')],
|
||||
'notify_on_file_complete': [opt_bool, True],
|
||||
'file_transfers_port': [opt_int, 28011],
|
||||
'ft_override_host_to_send': [opt_str, '', _('Overrides the host we send 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_new_messages': [opt_bool, True],
|
||||
'trayicon_notification_on_events': [opt_bool, True, _('Notify of events in the system trayicon.')],
|
||||
'last_save_dir': [opt_str, ''],
|
||||
'last_send_dir': [opt_str, ''],
|
||||
'last_emoticons_dir': [opt_str, ''],
|
||||
|
@ -174,7 +185,7 @@ class Config:
|
|||
'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 multi-user chat.')],
|
||||
'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 trayicon is used.')],
|
||||
'set_xmpp://_handler_everytime': [opt_bool, False, _('If True, Gajim registers for xmpp:// on each startup.')],
|
||||
'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.')],
|
||||
|
@ -182,15 +193,15 @@ class Config:
|
|||
'show_avatars_in_roster': [opt_bool, True, '', 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, True, _('If False, Gajim will no longer print status line in chats when a contact changes his or her status and/or his or her status message.')],
|
||||
'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves room.')],
|
||||
'print_status_in_muc': [opt_str, 'in_and_out', _('can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes his or her status and/or his or her status message. If "all" Gajim will print all status messages. If "in_and_out", gajim will only print FOO enters/leaves group chat.')],
|
||||
'log_contact_status_changes': [opt_bool, False],
|
||||
'restored_messages_color': [opt_str, 'grey'],
|
||||
'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],
|
||||
'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 room. Turn this option to False to stop sending sha info in group chat presences.')],
|
||||
'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected group chat. Turn this option to False to stop sending sha info in group chat presences.')],
|
||||
'one_message_window': [opt_str, 'always',
|
||||
#always, never, peracct, pertype should not be translated
|
||||
_('Controls the window where new messages are placed.\n\'always\' - All messages are sent to a single window.\n\'never\' - All messages get their own window.\n\'peracct\' - Messages for each account are sent to a specific window.\n\'pertype\' - Each message type (e.g., chats vs. groupchats) are sent to a specific window. Note, changing this option requires restarting Gajim before the changes will take effect.')],
|
||||
|
@ -200,11 +211,12 @@ class Config:
|
|||
'always_hide_chat_buttons': [opt_bool, False, _('Hides the buttons in two persons chat window.')],
|
||||
'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 room occupants list in group chat window.')],
|
||||
'chat_merge_consecutive_nickname': [opt_bool, False, _('Merge consecutive nickname in 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 nickame.')],
|
||||
'gc_nicknames_colors': [ opt_str, '#a34526:#c000ff:#0012ff:#388a99:#38995d:#519938:#ff8a00:#94452d:#244b5a:#32645a', _('List of colors 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.')],
|
||||
}
|
||||
|
||||
__options_per_key = {
|
||||
|
@ -215,6 +227,13 @@ class Config:
|
|||
'password': [ opt_str, '' ],
|
||||
'resource': [ opt_str, 'gajim', '', 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 ],
|
||||
'autoreconnect': [ opt_bool, True ],
|
||||
'active': [ opt_bool, True],
|
||||
|
@ -246,6 +265,12 @@ class Config:
|
|||
'msgwin-y-position': [opt_int, -1], # Default is to let the wm decide
|
||||
'msgwin-width': [opt_int, 480],
|
||||
'msgwin-height': [opt_int, 440],
|
||||
'listen_to_network_manager' : [opt_bool, True],
|
||||
'is_zeroconf': [opt_bool, False],
|
||||
'zeroconf_first_name': [ opt_str, '', '', True ],
|
||||
'zeroconf_last_name': [ opt_str, '', '', True ],
|
||||
'zeroconf_jabber_id': [ opt_str, '', '', True ],
|
||||
'zeroconf_email': [ opt_str, '', '', True ],
|
||||
}, {}),
|
||||
'statusmsg': ({
|
||||
'message': [ opt_str, '' ],
|
||||
|
@ -284,8 +309,6 @@ class Config:
|
|||
'bannerfontattrs': [ opt_str, 'B', '', True ],
|
||||
|
||||
# http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
|
||||
# FIXME: not black but the default color from gtk+ theme
|
||||
'state_active_color': [ opt_color, 'black' ],
|
||||
'state_inactive_color': [ opt_color, 'grey62' ],
|
||||
'state_composing_color': [ opt_color, 'green4' ],
|
||||
'state_paused_color': [ opt_color, 'mediumblue' ],
|
||||
|
@ -327,15 +350,15 @@ class Config:
|
|||
_('Movie'): _("I'm watching a movie."),
|
||||
_('Working'): _("I'm working."),
|
||||
_('Phone'): _("I'm on the phone."),
|
||||
_('Out'): _("I'm out enjoying life"),
|
||||
_('Out'): _("I'm out enjoying life."),
|
||||
}
|
||||
|
||||
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') ],
|
||||
'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!') ],
|
||||
}
|
||||
|
@ -346,8 +369,8 @@ class Config:
|
|||
'contact_connected': [ True, '../data/sounds/connected.wav' ],
|
||||
'contact_disconnected': [ True, '../data/sounds/disconnected.wav' ],
|
||||
'message_sent': [ True, '../data/sounds/sent.wav' ],
|
||||
'muc_message_highlight': [ True, '../data/sounds/gc_message1.wav', _('Sound to play when a MUC message contains one of the words in muc_highlight_words, or when a MUC message contains your nickname.')],
|
||||
'muc_message_received': [ True, '../data/sounds/gc_message2.wav', _('Sound to play when any MUC message arrives. (This setting is taken into account only if notify_on_all_muc_messages is True)') ],
|
||||
'muc_message_highlight': [ True, '../data/sounds/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, '../data/sounds/gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
|
||||
}
|
||||
|
||||
themes_default = {
|
||||
|
|
115
src/common/configpaths.py
Normal file
115
src/common/configpaths.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# 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 fse(s):
|
||||
'''Convert from filesystem encoding if not already Unicode'''
|
||||
return unicode(s, sys.getfilesystemencoding())
|
||||
|
||||
class ConfigPaths:
|
||||
def __init__(this, root=None):
|
||||
this.root = root
|
||||
this.paths = {}
|
||||
|
||||
if this.root is None:
|
||||
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.
|
||||
this.root = os.path.join(fse(os.environ[u'appdata']), u'Gajim')
|
||||
except KeyError:
|
||||
# win9x, in cwd
|
||||
this.root = u''
|
||||
else: # Unices
|
||||
# Pass in an Unicode string, and hopefully get one back.
|
||||
this.root = os.path.expanduser(u'~/.gajim')
|
||||
|
||||
def add_from_root(this, name, path):
|
||||
this.paths[name] = (True, path)
|
||||
|
||||
def add(this, name, path):
|
||||
this.paths[name] = (False, path)
|
||||
|
||||
def __getitem__(this, key):
|
||||
relative, path = this.paths[key]
|
||||
if not relative:
|
||||
return path
|
||||
return os.path.join(this.root, path)
|
||||
|
||||
def get(this, key, default=None):
|
||||
try:
|
||||
return this[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def iteritems(this):
|
||||
for key in this.paths.iterkeys():
|
||||
yield (key, this[key])
|
||||
|
||||
def windowsify(s):
|
||||
if os.name == 'nt':
|
||||
return s.capitalize()
|
||||
return s
|
||||
|
||||
def init():
|
||||
paths = ConfigPaths()
|
||||
|
||||
# LOG is deprecated
|
||||
k = ( 'LOG', 'LOG_DB', 'VCARD', 'AVATAR', 'MY_EMOTS' )
|
||||
v = (u'logs', u'logs.db', u'vcards', u'avatars', u'emoticons')
|
||||
|
||||
if os.name == 'nt':
|
||||
v = map(lambda x: x.capitalize(), v)
|
||||
|
||||
for n, p in zip(k, v):
|
||||
paths.add_from_root(n, p)
|
||||
|
||||
paths.add('DATA', os.path.join(u'..', windowsify(u'data')))
|
||||
paths.add('HOME', os.path.expanduser(u'~'))
|
||||
paths.add('TMP', fse(tempfile.gettempdir()))
|
||||
|
||||
try:
|
||||
import svn_config
|
||||
svn_config.configure(paths)
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
#for k, v in paths.iteritems():
|
||||
# print "%s: %s" % (k, v)
|
||||
|
||||
return paths
|
||||
|
||||
gajimpaths = init()
|
||||
|
||||
def init_profile(profile, paths=gajimpaths):
|
||||
conffile = windowsify(u'config')
|
||||
pidfile = windowsify(u'gajim')
|
||||
|
||||
if len(profile) > 0:
|
||||
conffile += u'.' + profile
|
||||
pidfile += u'.' + profile
|
||||
pidfile += u'.pid'
|
||||
paths.add_from_root('CONFIG_FILE', conffile)
|
||||
paths.add_from_root('PID_FILE', pidfile)
|
|
@ -1,19 +1,11 @@
|
|||
## common/connection.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <nkour@jabber.org>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
## - Travis Shirk <travis@pobox.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.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
|
||||
|
@ -38,10 +30,13 @@ import common.xmpp
|
|||
from common import helpers
|
||||
from common import gajim
|
||||
from common import GnuPG
|
||||
from common import passwords
|
||||
|
||||
from connection_handlers import *
|
||||
USE_GPG = GnuPG.USE_GPG
|
||||
|
||||
from common.rst_xhtml_generator import create_xhtml
|
||||
|
||||
class Connection(ConnectionHandlers):
|
||||
'''Connection class'''
|
||||
def __init__(self, name):
|
||||
|
@ -51,8 +46,10 @@ class Connection(ConnectionHandlers):
|
|||
self.connection = None # xmpppy ClientCommon instance
|
||||
# this property is used to prevent double connections
|
||||
self.last_connection = None # last ClientCommon instance
|
||||
self.is_zeroconf = False
|
||||
self.gpg = None
|
||||
self.status = ''
|
||||
self.priority = gajim.get_priority(name, 'offline')
|
||||
self.old_show = ''
|
||||
# increase/decrease default timeout for server responses
|
||||
self.try_connecting_for_foo_secs = 45
|
||||
|
@ -61,11 +58,12 @@ class Connection(ConnectionHandlers):
|
|||
self.time_to_reconnect = None
|
||||
self.new_account_info = None
|
||||
self.bookmarks = []
|
||||
self.annotations = {}
|
||||
self.on_purpose = False
|
||||
self.last_io = gajim.idlequeue.current_time()
|
||||
self.last_sent = []
|
||||
self.last_history_line = {}
|
||||
self.password = gajim.config.get_per('accounts', name, 'password')
|
||||
self.password = passwords.get_password(name)
|
||||
self.server_resource = gajim.config.get_per('accounts', name, 'resource')
|
||||
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')
|
||||
|
@ -88,6 +86,7 @@ class Connection(ConnectionHandlers):
|
|||
self.available_transports = {} # list of available transports on this
|
||||
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
|
||||
self.vcard_supported = True
|
||||
self.metacontacts_supported = True
|
||||
# END __init__
|
||||
|
||||
def put_event(self, ev):
|
||||
|
@ -118,6 +117,7 @@ class Connection(ConnectionHandlers):
|
|||
self.on_purpose = on_purpose
|
||||
self.connected = 0
|
||||
self.time_to_reconnect = None
|
||||
self.privacy_rules_supported = False
|
||||
if self.connection:
|
||||
# make sure previous connection is completely closed
|
||||
gajim.proxy65_manager.disconnect(self.connection)
|
||||
|
@ -128,23 +128,22 @@ class Connection(ConnectionHandlers):
|
|||
def _disconnectedReconnCB(self):
|
||||
'''Called when we are disconnected'''
|
||||
gajim.log.debug('disconnectedReconnCB')
|
||||
if self.connected > 1:
|
||||
# we cannot change our status to offline or connectiong
|
||||
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
|
||||
self.dispatch('STATUS', 'offline')
|
||||
if not self.on_purpose:
|
||||
self.disconnect()
|
||||
if gajim.config.get_per('accounts', self.name, 'autoreconnect') \
|
||||
and self.retrycount <= 10:
|
||||
if gajim.config.get_per('accounts', self.name, 'autoreconnect'):
|
||||
self.connected = 1
|
||||
self.dispatch('STATUS', 'connecting')
|
||||
# this check has moved from _reconnect method
|
||||
if self.retrycount > 5:
|
||||
self.time_to_reconnect = 20
|
||||
self.time_to_reconnect = random.randint(15, 25)
|
||||
else:
|
||||
self.time_to_reconnect = 10
|
||||
self.time_to_reconnect = random.randint(5, 15)
|
||||
gajim.idlequeue.set_alarm(self._reconnect_alarm,
|
||||
self.time_to_reconnect)
|
||||
elif self.on_connect_failure:
|
||||
|
@ -163,7 +162,7 @@ class Connection(ConnectionHandlers):
|
|||
self.dispatch('STATUS', 'offline')
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Connection with account "%s" has been lost') % self.name,
|
||||
_('To continue sending and receiving messages, you will need to reconnect.')))
|
||||
_('Reconnect manually.')))
|
||||
|
||||
def _event_dispatcher(self, realm, event, data):
|
||||
if realm == common.xmpp.NS_REGISTER:
|
||||
|
@ -185,7 +184,6 @@ class Connection(ConnectionHandlers):
|
|||
if not common.xmpp.isResultNode(result):
|
||||
self.dispatch('ACC_NOT_OK', (result.getError()))
|
||||
return
|
||||
self.connected = 0
|
||||
self.password = self.new_account_info['password']
|
||||
if USE_GPG:
|
||||
self.gpg = GnuPG.GnuPG()
|
||||
|
@ -195,7 +193,9 @@ class Connection(ConnectionHandlers):
|
|||
gajim.connections[self.name] = self
|
||||
self.dispatch('ACC_OK', (self.new_account_info))
|
||||
self.new_account_info = None
|
||||
self.connection = None
|
||||
if self.connection:
|
||||
self.connection.UnregisterDisconnectHandler(self._on_new_account)
|
||||
self.disconnect(on_purpose=True)
|
||||
common.xmpp.features_nb.register(self.connection, data[0],
|
||||
req, _on_register_result)
|
||||
return
|
||||
|
@ -368,8 +368,7 @@ class Connection(ConnectionHandlers):
|
|||
secure = self._secure)
|
||||
return
|
||||
else:
|
||||
if not retry or self.retrycount > 10:
|
||||
self.retrycount = 0
|
||||
if not retry and self.retrycount == 0:
|
||||
self.time_to_reconnect = None
|
||||
if self.on_connect_failure:
|
||||
self.on_connect_failure()
|
||||
|
@ -404,8 +403,6 @@ class Connection(ConnectionHandlers):
|
|||
con.RegisterDisconnectHandler(self._disconnectedReconnCB)
|
||||
gajim.log.debug(_('Connected to server %s:%s with %s') % (self._current_host['host'],
|
||||
self._current_host['port'], con_type))
|
||||
# Ask metacontacts before roster
|
||||
self.get_metacontacts()
|
||||
self._register_handlers(con, con_type)
|
||||
return True
|
||||
|
||||
|
@ -459,7 +456,7 @@ class Connection(ConnectionHandlers):
|
|||
# END connect
|
||||
|
||||
def quit(self, kill_core):
|
||||
if kill_core and self.connected > 1:
|
||||
if kill_core and gajim.account_is_connected(self.name):
|
||||
self.disconnect(on_purpose = True)
|
||||
|
||||
def get_privacy_lists(self):
|
||||
|
@ -531,14 +528,15 @@ class Connection(ConnectionHandlers):
|
|||
# active the privacy rule
|
||||
self.privacy_rules_supported = True
|
||||
self.activate_privacy_rule('invisible')
|
||||
prio = unicode(gajim.config.get_per('accounts', self.name, 'priority'))
|
||||
p = common.xmpp.Presence(typ = ptype, priority = prio, show = show)
|
||||
priority = unicode(gajim.get_priority(self.name, show))
|
||||
p = common.xmpp.Presence(typ = ptype, priority = priority, show = show)
|
||||
p = self.add_sha(p, ptype != 'unavailable')
|
||||
if msg:
|
||||
p.setStatus(msg)
|
||||
if signed:
|
||||
p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
|
||||
self.connection.send(p)
|
||||
self.priority = priority
|
||||
self.dispatch('STATUS', 'invisible')
|
||||
if initial:
|
||||
#ask our VCard
|
||||
|
@ -546,6 +544,9 @@ class Connection(ConnectionHandlers):
|
|||
|
||||
#Get bookmarks from private namespace
|
||||
self.get_bookmarks()
|
||||
|
||||
#Get annotations
|
||||
self.get_annotations()
|
||||
|
||||
#Inform GUI we just signed in
|
||||
self.dispatch('SIGNED_IN', ())
|
||||
|
@ -591,8 +592,11 @@ class Connection(ConnectionHandlers):
|
|||
if self.connection:
|
||||
con.set_send_timeout(self.keepalives, self.send_keepalive)
|
||||
self.connection.onreceive(None)
|
||||
# Ask metacontacts before roster
|
||||
self.get_metacontacts()
|
||||
iq = common.xmpp.Iq('get', common.xmpp.NS_PRIVACY, xmlns = '')
|
||||
id = self.connection.getAnID()
|
||||
iq.setID(id)
|
||||
self.awaiting_answers[id] = (PRIVACY_ARRIVED, )
|
||||
self.connection.send(iq)
|
||||
|
||||
def change_status(self, show, msg, auto = False):
|
||||
if not show in STATUS_LIST:
|
||||
|
@ -646,9 +650,8 @@ class Connection(ConnectionHandlers):
|
|||
iq = self.build_privacy_rule('visible', 'allow')
|
||||
self.connection.send(iq)
|
||||
self.activate_privacy_rule('visible')
|
||||
prio = unicode(gajim.config.get_per('accounts', self.name,
|
||||
'priority'))
|
||||
p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
|
||||
priority = unicode(gajim.get_priority(self.name, sshow))
|
||||
p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
|
||||
p = self.add_sha(p)
|
||||
if msg:
|
||||
p.setStatus(msg)
|
||||
|
@ -656,6 +659,7 @@ class Connection(ConnectionHandlers):
|
|||
p.setTag(common.xmpp.NS_SIGNED + ' x').setData(signed)
|
||||
if self.connection:
|
||||
self.connection.send(p)
|
||||
self.priority = priority
|
||||
self.dispatch('STATUS', show)
|
||||
|
||||
def _on_disconnected(self):
|
||||
|
@ -666,17 +670,22 @@ class Connection(ConnectionHandlers):
|
|||
def get_status(self):
|
||||
return STATUS_LIST[self.connected]
|
||||
|
||||
def send_motd(self, jid, subject = '', msg = ''):
|
||||
|
||||
def send_motd(self, jid, subject = '', msg = '', xhtml = None):
|
||||
if not self.connection:
|
||||
return
|
||||
msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject)
|
||||
msg_iq = common.xmpp.Message(to = jid, body = msg, subject = subject,
|
||||
xhtml = xhtml)
|
||||
|
||||
self.connection.send(msg_iq)
|
||||
|
||||
def send_message(self, jid, msg, keyID, type = 'chat', subject='',
|
||||
chatstate = None, msg_id = None, composing_jep = None, resource = None,
|
||||
user_nick = None):
|
||||
user_nick = None, xhtml = None):
|
||||
if not self.connection:
|
||||
return
|
||||
if msg and not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
|
||||
xhtml = create_xhtml(msg)
|
||||
if not msg and chatstate is None:
|
||||
return
|
||||
fjid = jid
|
||||
|
@ -690,18 +699,24 @@ class Connection(ConnectionHandlers):
|
|||
if msgenc:
|
||||
msgtxt = '[This message is encrypted]'
|
||||
lang = os.getenv('LANG')
|
||||
if lang is not None or lang != 'en': # we're not english
|
||||
msgtxt = _('[This message is encrypted]') +\
|
||||
' ([This message is encrypted])' # one in locale and one en
|
||||
if lang is not None and lang != 'en': # we're not english
|
||||
# one in locale and one en
|
||||
msgtxt = _('[This message is *encrypted* (See :JEP:`27`]') +\
|
||||
' ([This message is *encrypted* (See :JEP:`27`])'
|
||||
if msgtxt and not xhtml and gajim.config.get(
|
||||
'rst_formatting_outgoing_messages'):
|
||||
# Generate a XHTML part using reStructured text markup
|
||||
xhtml = create_xhtml(msgtxt)
|
||||
if type == 'chat':
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type,
|
||||
xhtml = xhtml)
|
||||
else:
|
||||
if subject:
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
|
||||
typ = 'normal', subject = subject)
|
||||
typ = 'normal', subject = subject, xhtml = xhtml)
|
||||
else:
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
|
||||
typ = 'normal')
|
||||
typ = 'normal', xhtml = xhtml)
|
||||
if msgenc:
|
||||
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
|
||||
|
||||
|
@ -719,7 +734,8 @@ class Connection(ConnectionHandlers):
|
|||
msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
|
||||
if composing_jep == 'JEP-0022' or not composing_jep:
|
||||
# JEP-0022
|
||||
chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
|
||||
chatstate_node = msg_iq.setTag('x',
|
||||
namespace = common.xmpp.NS_EVENT)
|
||||
if not msgtxt: # when no <body>, add <id>
|
||||
if not msg_id: # avoid putting 'None' in <id> tag
|
||||
msg_id = ''
|
||||
|
@ -729,7 +745,8 @@ class Connection(ConnectionHandlers):
|
|||
chatstate_node.addChild(name = 'composing')
|
||||
|
||||
self.connection.send(msg_iq)
|
||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
|
||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
|
||||
.split()
|
||||
ji = gajim.get_jid_without_resource(jid)
|
||||
if self.name not in no_log_for and ji not in no_log_for:
|
||||
log_msg = msg
|
||||
|
@ -832,7 +849,7 @@ class Connection(ConnectionHandlers):
|
|||
if self.connection:
|
||||
self.connection.getRoster().setItem(jid = jid, name = name,
|
||||
groups = groups)
|
||||
|
||||
|
||||
def new_account(self, name, config, sync = False):
|
||||
# If a connection already exist we cannot create a new account
|
||||
if self.connection:
|
||||
|
@ -851,6 +868,8 @@ class Connection(ConnectionHandlers):
|
|||
return
|
||||
self.on_connect_failure = None
|
||||
self.connection = con
|
||||
if con:
|
||||
con.RegisterDisconnectHandler(self._on_new_account)
|
||||
common.xmpp.features_nb.getRegInfo(con, self._hostname)
|
||||
|
||||
def account_changed(self, new_name):
|
||||
|
@ -914,14 +933,39 @@ class Connection(ConnectionHandlers):
|
|||
# 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['nick']:
|
||||
if bm.get('nick', None):
|
||||
iq5 = iq4.setTagData('nick', bm['nick'])
|
||||
if bm['password']:
|
||||
if bm.get('password', None):
|
||||
iq5 = iq4.setTagData('password', bm['password'])
|
||||
if bm['print_status']:
|
||||
if bm.get('print_status', None):
|
||||
iq5 = iq4.setTagData('print_status', bm['print_status'])
|
||||
self.connection.send(iq)
|
||||
|
||||
def get_annotations(self):
|
||||
'''Get Annonations from storage as described in XEP 0048, and XEP 0145'''
|
||||
self.annotations = {}
|
||||
if not self.connection:
|
||||
return
|
||||
iq = common.xmpp.Iq(typ='get')
|
||||
iq2 = iq.addChild(name='query', namespace='jabber:iq: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 self.connection:
|
||||
return
|
||||
iq = common.xmpp.Iq(typ='set')
|
||||
iq2 = iq.addChild(name='query', namespace='jabber:iq: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_metacontacts(self):
|
||||
'''Get metacontacts list from storage as described in JEP 0049'''
|
||||
if not self.connection:
|
||||
|
@ -958,14 +1002,15 @@ class Connection(ConnectionHandlers):
|
|||
p = self.add_sha(p, ptype != 'unavailable')
|
||||
self.connection.send(p)
|
||||
|
||||
def join_gc(self, nick, room, server, password):
|
||||
def join_gc(self, nick, room_jid, password):
|
||||
# FIXME: This room JID needs to be normalized; see #1364
|
||||
if not self.connection:
|
||||
return
|
||||
show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
|
||||
if show == 'invisible':
|
||||
# Never join a room when invisible
|
||||
return
|
||||
p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick),
|
||||
p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick),
|
||||
show = show, status = self.status)
|
||||
if gajim.config.get('send_sha_in_gc_presence'):
|
||||
p = self.add_sha(p)
|
||||
|
@ -974,17 +1019,18 @@ class Connection(ConnectionHandlers):
|
|||
t.setTagData('password', password)
|
||||
self.connection.send(p)
|
||||
#last date/time in history to avoid duplicate
|
||||
# FIXME: This JID needs to be normalized; see #1364
|
||||
jid='%s@%s' % (room, server)
|
||||
last_log = gajim.logger.get_last_date_that_has_logs(jid, is_room = True)
|
||||
last_log = gajim.logger.get_last_date_that_has_logs(room_jid,
|
||||
is_room = True)
|
||||
if last_log is None:
|
||||
last_log = 0
|
||||
self.last_history_line[jid]= last_log
|
||||
self.last_history_line[room_jid]= last_log
|
||||
|
||||
def send_gc_message(self, jid, msg):
|
||||
def send_gc_message(self, jid, msg, xhtml = None):
|
||||
if not self.connection:
|
||||
return
|
||||
msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat')
|
||||
if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
|
||||
xhtml = create_xhtml(msg)
|
||||
msg_iq = common.xmpp.Message(jid, msg, typ = 'groupchat', xhtml = xhtml)
|
||||
self.connection.send(msg_iq)
|
||||
self.dispatch('MSGSENT', (jid, msg))
|
||||
|
||||
|
@ -1110,11 +1156,11 @@ class Connection(ConnectionHandlers):
|
|||
self.connection.send(iq)
|
||||
|
||||
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
|
||||
# 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 self.connected > 1:
|
||||
if gajim.account_is_connected(self.name):
|
||||
hostname = gajim.config.get_per('accounts', self.name, 'hostname')
|
||||
iq = common.xmpp.Iq(typ = 'set', to = hostname)
|
||||
q = iq.setTag(common.xmpp.NS_REGISTER + ' query').setTag('remove')
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <nkour@jabber.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
## - Travis Shirk <travis@pobox.com>
|
||||
##
|
||||
|
@ -45,15 +45,13 @@ VCARD_PUBLISHED = 'vcard_published'
|
|||
VCARD_ARRIVED = 'vcard_arrived'
|
||||
AGENT_REMOVED = 'agent_removed'
|
||||
METACONTACTS_ARRIVED = 'metacontacts_arrived'
|
||||
PRIVACY_ARRIVED = 'privacy_arrived'
|
||||
HAS_IDLE = True
|
||||
try:
|
||||
import common.idle as idle # when we launch gajim from sources
|
||||
import idle
|
||||
except:
|
||||
try:
|
||||
import idle # when Gajim is installed
|
||||
except:
|
||||
gajim.log.debug(_('Unable to load idle module'))
|
||||
HAS_IDLE = False
|
||||
gajim.log.debug(_('Unable to load idle module'))
|
||||
HAS_IDLE = False
|
||||
|
||||
class ConnectionBytestream:
|
||||
def __init__(self):
|
||||
|
@ -94,7 +92,7 @@ class ConnectionBytestream:
|
|||
if contact.jid == receiver_jid:
|
||||
file_props['error'] = -5
|
||||
self.remove_transfer(file_props)
|
||||
self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
|
||||
self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
|
||||
sender_jid = unicode(file_props['sender']).split('/')[0]
|
||||
if contact.jid == sender_jid:
|
||||
file_props['error'] = -3
|
||||
|
@ -179,11 +177,12 @@ class ConnectionBytestream:
|
|||
except socket.gaierror:
|
||||
self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
|
||||
ft_override_host_to_send = self.peerhost[0]
|
||||
listener = gajim.socks5queue.start_listener(self.peerhost[0], port,
|
||||
listener = gajim.socks5queue.start_listener(port,
|
||||
sha_str, self._result_socks5_sid, file_props['sid'])
|
||||
if listener == None:
|
||||
file_props['error'] = -5
|
||||
self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props))
|
||||
self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
|
||||
''))
|
||||
self._connect_error(unicode(receiver), file_props['sid'],
|
||||
file_props['sid'], code = 406)
|
||||
return
|
||||
|
@ -225,8 +224,8 @@ class ConnectionBytestream:
|
|||
iq = common.xmpp.Protocol(name = 'iq',
|
||||
to = unicode(file_props['sender']), typ = 'error')
|
||||
iq.setAttr('id', file_props['request-id'])
|
||||
err = common.xmpp.ErrorNode(code = '406', typ = 'auth', name =
|
||||
'not-acceptable')
|
||||
err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
|
||||
'forbidden', text = 'Offer Declined')
|
||||
iq.addChild(node=err)
|
||||
self.connection.send(iq)
|
||||
|
||||
|
@ -318,8 +317,8 @@ class ConnectionBytestream:
|
|||
if file_props is not None:
|
||||
self.disconnect_transfer(file_props)
|
||||
file_props['error'] = -3
|
||||
self.dispatch('FILE_REQUEST_ERROR', (to, file_props))
|
||||
|
||||
self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
|
||||
|
||||
def _proxy_auth_ok(self, proxy):
|
||||
'''cb, called after authentication to proxy server '''
|
||||
file_props = self.files_props[proxy['sid']]
|
||||
|
@ -348,7 +347,7 @@ class ConnectionBytestream:
|
|||
return
|
||||
file_props = self.files_props[id]
|
||||
file_props['error'] = -4
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _bytestreamSetCB(self, con, iq_obj):
|
||||
|
@ -565,7 +564,7 @@ class ConnectionBytestream:
|
|||
return
|
||||
jid = helpers.get_jid_from_iq(iq_obj)
|
||||
file_props['error'] = -3
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props))
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
class ConnectionDisco:
|
||||
|
@ -696,6 +695,13 @@ class ConnectionDisco:
|
|||
attr = {}
|
||||
for key in i.getAttrs():
|
||||
attr[key] = i.getAttrs()[key]
|
||||
if 'jid' not in attr:
|
||||
continue
|
||||
try:
|
||||
helpers.parse_jid(attr['jid'])
|
||||
except common.helpers.InvalidFormat:
|
||||
# jid is not conform
|
||||
continue
|
||||
items.append(attr)
|
||||
jid = helpers.get_full_jid_from_iq(iq_obj)
|
||||
hostname = gajim.config.get_per('accounts', self.name,
|
||||
|
@ -732,6 +738,7 @@ class ConnectionDisco:
|
|||
q.addChild('feature', attrs = {'var': common.xmpp.NS_SI})
|
||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE})
|
||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC})
|
||||
q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM})
|
||||
self.connection.send(iq)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
|
@ -839,6 +846,8 @@ class ConnectionVcard:
|
|||
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)
|
||||
|
@ -976,9 +985,7 @@ class ConnectionVcard:
|
|||
'invisible':
|
||||
self.vcard_sha = new_sha
|
||||
sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected])
|
||||
prio = unicode(gajim.config.get_per('accounts', self.name,
|
||||
'priority'))
|
||||
p = common.xmpp.Presence(typ = None, priority = prio,
|
||||
p = common.xmpp.Presence(typ = None, priority = self.priority,
|
||||
show = sshow, status = self.status)
|
||||
p = self.add_sha(p)
|
||||
self.connection.send(p)
|
||||
|
@ -1023,8 +1030,15 @@ class ConnectionVcard:
|
|||
else:
|
||||
meta_list[tag] = [data]
|
||||
self.dispatch('METACONTACTS', meta_list)
|
||||
else:
|
||||
self.metacontacts_supported = False
|
||||
# We can now continue connection by requesting the roster
|
||||
self.connection.initRoster()
|
||||
elif self.awaiting_answers[id][0] == PRIVACY_ARRIVED:
|
||||
if iq_obj.getType() != 'error':
|
||||
self.privacy_rules_supported = True
|
||||
# Ask metacontacts before roster
|
||||
self.get_metacontacts()
|
||||
|
||||
del self.awaiting_answers[id]
|
||||
|
||||
|
@ -1104,10 +1118,8 @@ class ConnectionVcard:
|
|||
if STATUS_LIST[self.connected] == 'invisible':
|
||||
return
|
||||
sshow = helpers.get_xmpp_show(STATUS_LIST[self.connected])
|
||||
prio = unicode(gajim.config.get_per('accounts', self.name,
|
||||
'priority'))
|
||||
p = common.xmpp.Presence(typ = None, priority = prio, show = sshow,
|
||||
status = self.status)
|
||||
p = common.xmpp.Presence(typ = None, priority = self.priority,
|
||||
show = sshow, status = self.status)
|
||||
p = self.add_sha(p)
|
||||
self.connection.send(p)
|
||||
else:
|
||||
|
@ -1137,11 +1149,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
|
||||
def build_http_auth_answer(self, iq_obj, answer):
|
||||
if answer == 'yes':
|
||||
iq = iq_obj.buildReply('result')
|
||||
self.connection.send(iq_obj.buildReply('result'))
|
||||
elif answer == 'no':
|
||||
iq = iq_obj.buildReply('error')
|
||||
iq.setError('not-authorized', 401)
|
||||
self.connection.send(iq)
|
||||
err = common.xmpp.Error(iq_obj,
|
||||
common.xmpp.protocol.ERR_NOT_AUTHORIZED)
|
||||
self.connection.send(err)
|
||||
|
||||
def _HttpAuthCB(self, con, iq_obj):
|
||||
gajim.log.debug('HttpAuthCB')
|
||||
|
@ -1156,7 +1168,13 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _ErrorCB(self, con, iq_obj):
|
||||
errmsg = iq_obj.getError()
|
||||
gajim.log.debug('ErrorCB')
|
||||
if iq_obj.getQueryNS() == common.xmpp.NS_VERSION:
|
||||
who = helpers.get_full_jid_from_iq(iq_obj)
|
||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
||||
self.dispatch('OS_INFO', (jid_stripped, resource, '', ''))
|
||||
return
|
||||
errmsg = iq_obj.getErrorMsg()
|
||||
errcode = iq_obj.getErrorCode()
|
||||
jid_from = helpers.get_full_jid_from_iq(iq_obj)
|
||||
id = unicode(iq_obj.getID())
|
||||
|
@ -1197,6 +1215,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
# http://www.jabber.org/jeps/jep-0049.html
|
||||
#TODO: implement this
|
||||
pass
|
||||
elif ns == 'storage:rosternotes':
|
||||
# Annotations
|
||||
# http://www.xmpp.org/extensions/xep-0145.html
|
||||
notes = storage.getTags('note')
|
||||
for note in notes:
|
||||
jid = note.getAttr('jid')
|
||||
annotation = note.getData()
|
||||
self.annotations[jid] = annotation
|
||||
|
||||
def _PrivateErrorCB(self, con, iq_obj):
|
||||
gajim.log.debug('PrivateErrorCB')
|
||||
|
@ -1205,6 +1231,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
if storage_tag:
|
||||
ns = storage_tag.getNamespace()
|
||||
if ns == 'storage:metacontacts':
|
||||
self.metacontacts_supported = False
|
||||
# Private XML Storage (JEP49) is not supported by server
|
||||
# Continue connecting
|
||||
self.connection.initRoster()
|
||||
|
@ -1312,7 +1339,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
if gm.getTag('mailbox').getTag('mail-thread-info'):
|
||||
gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
|
||||
for gmessage in gmail_messages:
|
||||
gmail_from = gmessage.getTag('senders').getTag('sender').getAttr('address')
|
||||
sender = gmessage.getTag('senders').getTag('sender')
|
||||
if not sender:
|
||||
continue
|
||||
gmail_from = sender.getAttr('address')
|
||||
gmail_subject = gmessage.getTag('subject').getData()
|
||||
gmail_snippet = gmessage.getTag('snippet').getData()
|
||||
gmail_messages_list.append({ \
|
||||
|
@ -1331,6 +1361,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
self._pubsubEventCB(con, msg)
|
||||
return
|
||||
msgtxt = msg.getBody()
|
||||
msghtml = msg.getXHTML()
|
||||
mtype = msg.getType()
|
||||
subject = msg.getSubject() # if not there, it's None
|
||||
tim = msg.getTimestamp()
|
||||
|
@ -1405,15 +1436,18 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
|
||||
tim))
|
||||
elif mtype == 'groupchat':
|
||||
has_timestamp = False
|
||||
if msg.timestamp:
|
||||
has_timestamp = True
|
||||
if subject:
|
||||
self.dispatch('GC_SUBJECT', (frm, subject, msgtxt))
|
||||
self.dispatch('GC_SUBJECT', (frm, subject, msgtxt, has_timestamp))
|
||||
else:
|
||||
if not msg.getTag('body'): #no <body>
|
||||
return
|
||||
# Ignore message from room in which we are not
|
||||
if not self.last_history_line.has_key(jid):
|
||||
return
|
||||
self.dispatch('GC_MSG', (frm, msgtxt, tim))
|
||||
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
|
||||
if self.name not in no_log_for and not int(float(time.mktime(tim))) <= \
|
||||
self.last_history_line[jid] and msgtxt:
|
||||
gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
|
||||
|
@ -1425,11 +1459,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
||||
subject = subject)
|
||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
|
||||
chatstate, msg_id, composing_jep, user_nick))
|
||||
chatstate, msg_id, composing_jep, user_nick, msghtml))
|
||||
else: # it's single message
|
||||
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
||||
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
||||
subject = subject)
|
||||
if invite is not None:
|
||||
item = invite.getTag('invite')
|
||||
jid_from = item.getAttr('from')
|
||||
|
@ -1437,9 +1468,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
item = invite.getTag('password')
|
||||
password = invite.getTagData('password')
|
||||
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
|
||||
else:
|
||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
||||
subject, chatstate, msg_id, composing_jep, user_nick))
|
||||
return
|
||||
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
||||
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
||||
subject = subject)
|
||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
||||
subject, chatstate, msg_id, composing_jep, user_nick, msghtml))
|
||||
# END messageCB
|
||||
|
||||
def _pubsubEventCB(self, con, msg):
|
||||
|
@ -1482,8 +1516,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
# one
|
||||
who = str(prs.getFrom())
|
||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
||||
self.dispatch('GC_MSG', (jid_stripped, _('Nickname not allowed: %s') % \
|
||||
resource, None))
|
||||
self.dispatch('GC_MSG', (jid_stripped,
|
||||
_('Nickname not allowed: %s') % resource, None, False, None))
|
||||
return
|
||||
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
|
||||
timestamp = None
|
||||
|
@ -1545,22 +1579,22 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
self.dispatch('NOTIFY', (jid_stripped, 'error', errmsg, resource,
|
||||
prio, keyID, timestamp))
|
||||
elif errcode == '401': # password required to join
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
_('A password is required to join this room.')))
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('A password is required to join this group chat.')))
|
||||
elif errcode == '403': # we are banned
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
_('You are banned from this room.')))
|
||||
elif errcode == '404': # room does not exist
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
_('Such room does not exist.')))
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('You are banned from this group chat.')))
|
||||
elif errcode == '404': # group chat does not exist
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('Such group chat does not exist.')))
|
||||
elif errcode == '405':
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
_('Room creation is restricted.')))
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('Group chat creation is restricted.')))
|
||||
elif errcode == '406':
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('Your registered nickname must be used.')))
|
||||
elif errcode == '407':
|
||||
self.dispatch('ERROR', (_('Unable to join room'),
|
||||
self.dispatch('ERROR', (_('Unable to join group chat'),
|
||||
_('You are not in the members list.')))
|
||||
elif errcode == '409': # nick conflict
|
||||
# the jid_from in this case is FAKE JID: room_jid/nick
|
||||
|
@ -1568,7 +1602,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
proposed_nickname = resource + \
|
||||
gajim.config.get('gc_proposed_nick_char')
|
||||
room_jid = gajim.get_room_from_fjid(who)
|
||||
self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join room'),
|
||||
self.dispatch('ASK_NEW_NICK', (room_jid, _('Unable to join group chat'),
|
||||
_('Your desired nickname is in use or registered by another occupant.\nPlease specify another nickname below:'), proposed_nickname))
|
||||
else: # print in the window the error
|
||||
self.dispatch('ERROR_ANSWER', ('', jid_stripped,
|
||||
|
@ -1839,12 +1873,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
if show == 'invisible':
|
||||
self.send_invisible_presence(msg, signed, True)
|
||||
return
|
||||
prio = unicode(gajim.config.get_per('accounts', self.name,
|
||||
'priority'))
|
||||
priority = gajim.get_priority(self.name, sshow)
|
||||
vcard = self.get_cached_vcard(jid)
|
||||
if vcard and vcard.has_key('PHOTO') and vcard['PHOTO'].has_key('SHA'):
|
||||
self.vcard_sha = vcard['PHOTO']['SHA']
|
||||
p = common.xmpp.Presence(typ = None, priority = prio, show = sshow)
|
||||
p = common.xmpp.Presence(typ = None, priority = priority, show = sshow)
|
||||
p = self.add_sha(p)
|
||||
if msg:
|
||||
p.setStatus(msg)
|
||||
|
@ -1853,6 +1886,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
|
||||
if self.connection:
|
||||
self.connection.send(p)
|
||||
self.priority = priority
|
||||
self.dispatch('STATUS', show)
|
||||
# ask our VCard
|
||||
self.request_vcard(None)
|
||||
|
@ -1860,6 +1894,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
# Get bookmarks from private namespace
|
||||
self.get_bookmarks()
|
||||
|
||||
# Get annotations from private namespace
|
||||
self.get_annotations()
|
||||
|
||||
# If it's a gmail account,
|
||||
# inform the server that we want e-mail notifications
|
||||
if gajim.get_server_from_jid(our_jid) in gajim.gmail_domains:
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
## common/contacts.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -27,8 +19,8 @@ import common.gajim
|
|||
class Contact:
|
||||
'''Information concerning each contact'''
|
||||
def __init__(self, jid='', name='', groups=[], show='', status='', sub='',
|
||||
ask='', resource='', priority=0, keyID='', our_chatstate=None,
|
||||
chatstate=None, last_status_time=None, msg_id = None, composing_jep = None):
|
||||
ask='', resource='', priority=0, keyID='', our_chatstate=None,
|
||||
chatstate=None, last_status_time=None, msg_id = None, composing_jep = None):
|
||||
self.jid = jid
|
||||
self.name = name
|
||||
self.groups = groups
|
||||
|
@ -67,10 +59,40 @@ class Contact:
|
|||
return self.name
|
||||
return self.jid.split('@')[0]
|
||||
|
||||
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_transport(self):
|
||||
# if not '@' or '@' starts the jid then contact is transport
|
||||
if self.jid.find('@') <= 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class GC_Contact:
|
||||
'''Information concerning each groupchat contact'''
|
||||
def __init__(self, room_jid='', name='', show='', status='', role='',
|
||||
affiliation='', jid = '', resource = ''):
|
||||
affiliation='', jid = '', resource = ''):
|
||||
self.room_jid = room_jid
|
||||
self.name = name
|
||||
self.show = show
|
||||
|
@ -220,6 +242,51 @@ class Contacts:
|
|||
return self._contacts[account][jid][0]
|
||||
return None
|
||||
|
||||
def get_contacts_from_group(self, account, group):
|
||||
'''Returns all contacts in the given group'''
|
||||
group_contacts = []
|
||||
for jid in self._contacts[account]:
|
||||
contacts = self.get_contacts_from_jid(account, jid)
|
||||
if group in contacts[0].groups:
|
||||
group_contacts += contacts
|
||||
return group_contacts
|
||||
|
||||
def get_nb_online_total_contacts(self, accounts = [], groups = []):
|
||||
'''Returns the number of online contacts and the total number of
|
||||
contacts'''
|
||||
if accounts == []:
|
||||
accounts = self.get_accounts()
|
||||
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 \
|
||||
common.gajim.config.get('show_transports_group'):
|
||||
# do not count transports
|
||||
continue
|
||||
contact = self.get_contact_with_highest_priority(account, jid)
|
||||
in_groups = False
|
||||
if groups == []:
|
||||
in_groups = True
|
||||
else:
|
||||
contact_groups = contact.groups
|
||||
if not contact_groups:
|
||||
# Contact is not in a group, so count it in General group
|
||||
contact_groups.append(_('General'))
|
||||
for group in groups:
|
||||
if group in contact_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 define_metacontacts(self, account, tags_list):
|
||||
self._metacontacts_tags[account] = tags_list
|
||||
|
||||
|
|
|
@ -16,32 +16,56 @@
|
|||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from common import gajim
|
||||
from common import exceptions
|
||||
|
||||
_GAJIM_ERROR_IFACE = 'org.gajim.dbus.Error'
|
||||
|
||||
try:
|
||||
import dbus
|
||||
version = getattr(dbus, 'version', (0, 20, 0))
|
||||
supported = True
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
supported = True # does use have D-Bus bindings?
|
||||
except ImportError:
|
||||
version = (0, 0, 0)
|
||||
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')
|
||||
|
||||
# dbus 0.23 leads to segfault with threads_init()
|
||||
if sys.version[:4] >= '2.4' and version[1] < 30:
|
||||
supported = False
|
||||
|
||||
if version >= (0, 41, 0):
|
||||
import dbus.service
|
||||
import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
|
||||
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.dbus_bindings.DBusException:
|
||||
self.system_bus = None
|
||||
return False
|
||||
if self.system_bus is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
system_bus = SystemBus()
|
||||
|
||||
class SessionBus:
|
||||
'''A Singleton for the DBus SessionBus'''
|
||||
'''A Singleton for the D-Bus SessionBus'''
|
||||
def __init__(self):
|
||||
self.session_bus = None
|
||||
|
||||
|
@ -102,4 +126,13 @@ def get_interface(interface, path):
|
|||
|
||||
def get_notifications_interface():
|
||||
'''Returns the notifications interface.'''
|
||||
return get_interface('org.freedesktop.Notifications','/org/freedesktop/Notifications')
|
||||
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'
|
9
src/common/defs.py
Normal file
9
src/common/defs.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
docdir = '../'
|
||||
|
||||
datadir = '../'
|
||||
|
||||
version = '0.10.1.7'
|
||||
|
||||
import sys, os.path
|
||||
for base in ('.', 'common'):
|
||||
sys.path.append(os.path.join(base, '.libs'))
|
|
@ -5,7 +5,7 @@
|
|||
##
|
||||
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -81,7 +81,7 @@ class Events:
|
|||
gajim.interface.systray.set_img()
|
||||
|
||||
def remove_events(self, account, jid, event = None, types = []):
|
||||
'''if event is not speficied, remove all events from this jid,
|
||||
'''if event is not specified, remove all events from this jid,
|
||||
optionnaly only from given type
|
||||
return True if no such event found'''
|
||||
if not self._events.has_key(account):
|
||||
|
@ -118,11 +118,11 @@ class Events:
|
|||
if gajim.interface.systray_capabilities:
|
||||
gajim.interface.systray.set_img()
|
||||
|
||||
def get_nb_events(self, types = []):
|
||||
return self._get_nb_events(types = types)
|
||||
def get_nb_events(self, types = [], account = None):
|
||||
return self._get_nb_events(types = types, account = account)
|
||||
|
||||
def get_events(self, account, jid = None, types = []):
|
||||
'''if event is not speficied, remove all events from this jid,
|
||||
'''if event is not specified, get all events from this jid,
|
||||
optionnaly only from given type'''
|
||||
if not self._events.has_key(account):
|
||||
return []
|
||||
|
@ -149,7 +149,7 @@ class Events:
|
|||
return first_event
|
||||
|
||||
def _get_nb_events(self, account = None, jid = None, attribute = None, types = []):
|
||||
'''return the number of events'''
|
||||
'''return the number of pending events'''
|
||||
nb = 0
|
||||
if account:
|
||||
accounts = [account]
|
||||
|
@ -223,7 +223,7 @@ class Events:
|
|||
return self._get_first_event_with_attribute(events)
|
||||
|
||||
def get_nb_roster_events(self, account = None, jid = None, types = []):
|
||||
'''returns the number of events displayedin roster'''
|
||||
'''returns the number of events displayed in roster'''
|
||||
return self._get_nb_events(attribute = 'roster', account = account,
|
||||
jid = jid, types = types)
|
||||
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
## exceptions.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@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
|
||||
|
@ -54,3 +44,12 @@ class SessionBusNotPresent(Exception):
|
|||
|
||||
def __str__(self):
|
||||
return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus')
|
||||
|
||||
class GajimGeneralException(Exception):
|
||||
'''This exception ir our general exception'''
|
||||
def __init__(self, text=''):
|
||||
Exception.__init__(self)
|
||||
self.text = text
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
|
|
@ -15,9 +15,7 @@
|
|||
## GNU General Public License for more details.
|
||||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import logging
|
||||
import locale
|
||||
|
||||
|
@ -25,10 +23,36 @@ import config
|
|||
from contacts import Contacts
|
||||
from events import Events
|
||||
|
||||
try:
|
||||
import defs
|
||||
except ImportError:
|
||||
print >> sys.stderr, '''defs.py is missing!
|
||||
|
||||
If you start gajim from svn:
|
||||
* Make sure you have GNU autotools installed.
|
||||
This includes the following packages:
|
||||
automake >= 1.8
|
||||
autoconf >= 2.59
|
||||
intltool-0.35
|
||||
libtool
|
||||
* Run
|
||||
$ sh autogen.sh
|
||||
* Optionally, install gajim
|
||||
$ make
|
||||
$ sudo make install
|
||||
|
||||
**** Note for translators ****
|
||||
You can get the latest string updates, by running:
|
||||
$ cd po/
|
||||
$ make update-po
|
||||
|
||||
'''
|
||||
sys.exit(1)
|
||||
|
||||
interface = None # The actual interface (the gtk one for the moment)
|
||||
config = config.Config()
|
||||
version = config.get('version')
|
||||
connections = {}
|
||||
connections = {} # 'account name': 'account (connection.Connection) instance'
|
||||
verbose = False
|
||||
|
||||
h = logging.StreamHandler()
|
||||
|
@ -40,38 +64,17 @@ log.addHandler(h)
|
|||
import logger
|
||||
logger = logger.Logger() # init the logger
|
||||
|
||||
if os.name == 'nt':
|
||||
DATA_DIR = os.path.join('..', 'data')
|
||||
try:
|
||||
# Documents and Settings\[User Name]\Application Data\Gajim
|
||||
LOGPATH = os.path.join(os.environ['appdata'], 'Gajim', 'Logs') # deprecated
|
||||
VCARD_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Vcards')
|
||||
AVATAR_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Avatars')
|
||||
MY_EMOTS_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'Emoticons')
|
||||
except KeyError:
|
||||
# win9x, in cwd
|
||||
LOGPATH = 'Logs' # deprecated
|
||||
VCARD_PATH = 'Vcards'
|
||||
AVATAR_PATH = 'Avatars'
|
||||
MY_EMOTS_PATH = 'Emoticons'
|
||||
else: # Unices
|
||||
DATA_DIR = '../data'
|
||||
LOGPATH = os.path.expanduser('~/.gajim/logs') # deprecated
|
||||
VCARD_PATH = os.path.expanduser('~/.gajim/vcards')
|
||||
AVATAR_PATH = os.path.expanduser('~/.gajim/avatars')
|
||||
MY_EMOTS_PATH = os.path.expanduser('~/.gajim/emoticons')
|
||||
import configpaths
|
||||
gajimpaths = configpaths.gajimpaths
|
||||
|
||||
HOME_DIR = os.path.expanduser('~')
|
||||
TMP = tempfile.gettempdir()
|
||||
LOGPATH = gajimpaths['LOG'] # deprecated
|
||||
VCARD_PATH = gajimpaths['VCARD']
|
||||
AVATAR_PATH = gajimpaths['AVATAR']
|
||||
MY_EMOTS_PATH = gajimpaths['MY_EMOTS']
|
||||
TMP = gajimpaths['TMP']
|
||||
DATA_DIR = gajimpaths['DATA']
|
||||
HOME_DIR = gajimpaths['HOME']
|
||||
|
||||
try:
|
||||
LOGPATH = LOGPATH.decode(sys.getfilesystemencoding())
|
||||
VCARD_PATH = VCARD_PATH.decode(sys.getfilesystemencoding())
|
||||
TMP = TMP.decode(sys.getfilesystemencoding())
|
||||
AVATAR_PATH = AVATAR_PATH.decode(sys.getfilesystemencoding())
|
||||
MY_EMOTS_PATH = MY_EMOTS_PATH.decode(sys.getfilesystemencoding())
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc..
|
||||
except (ValueError, locale.Error):
|
||||
|
@ -120,6 +123,12 @@ status_before_autoaway = {}
|
|||
SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible']
|
||||
|
||||
# zeroconf account name
|
||||
ZEROCONF_ACC_NAME = 'Local'
|
||||
priority_dict = {}
|
||||
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||
priority_dict[status] = config.get('autopriority' + status)
|
||||
|
||||
def get_nick_from_jid(jid):
|
||||
pos = jid.find('@')
|
||||
return jid[:pos]
|
||||
|
@ -133,10 +142,10 @@ def get_nick_from_fjid(jid):
|
|||
# gaim@conference.jabber.no/nick/nick-continued
|
||||
return jid.split('/', 1)[1]
|
||||
|
||||
def get_room_name_and_server_from_room_jid(jid):
|
||||
room_name = get_nick_from_jid(jid)
|
||||
def get_name_and_server_from_jid(jid):
|
||||
name = get_nick_from_jid(jid)
|
||||
server = get_server_from_jid(jid)
|
||||
return room_name, server
|
||||
return name, server
|
||||
|
||||
def get_room_and_nick_from_fjid(jid):
|
||||
# fake jid is the jid for a contact in a room
|
||||
|
@ -207,11 +216,36 @@ def get_number_of_connected_accounts(accounts_list = None):
|
|||
accounts = connections.keys()
|
||||
else:
|
||||
accounts = accounts_list
|
||||
for acct in accounts:
|
||||
if connections[acct].connected > 1:
|
||||
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 get_number_of_securely_connected_accounts():
|
||||
'''returns the number of the accounts that are SSL/TLS connected'''
|
||||
num_of_secured = 0
|
||||
for account in connections:
|
||||
if account_is_securely_connected(account):
|
||||
num_of_secured += 1
|
||||
return num_of_secured
|
||||
|
||||
def account_is_securely_connected(account):
|
||||
if 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'''
|
||||
|
@ -299,3 +333,13 @@ def get_name_from_jid(account, jid):
|
|||
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')
|
||||
|
|
|
@ -17,19 +17,21 @@
|
|||
##
|
||||
|
||||
import sre
|
||||
import locale
|
||||
import os
|
||||
import subprocess
|
||||
import urllib
|
||||
import errno
|
||||
import select
|
||||
import sys
|
||||
import sha
|
||||
from encodings.punycode import punycode_encode
|
||||
|
||||
import gajim
|
||||
from i18n import Q_
|
||||
from i18n import ngettext
|
||||
from xmpp_stringprep import nodeprep, resourceprep, nameprep
|
||||
|
||||
|
||||
try:
|
||||
import winsound # windows-only built-in module for playing wav
|
||||
import win32api
|
||||
|
@ -290,6 +292,21 @@ def get_uf_role(role, plural = False):
|
|||
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 = adict.keys()
|
||||
|
@ -362,9 +379,14 @@ def is_in_path(name_of_command, return_abs_path = False):
|
|||
return is_in_dir
|
||||
|
||||
def exec_command(command):
|
||||
'''command is a string that contain arguments'''
|
||||
# os.system(command)
|
||||
subprocess.Popen(command.split())
|
||||
subprocess.Popen(command, shell = True)
|
||||
|
||||
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 launch_browser_mailer(kind, uri):
|
||||
#kind = 'url' or 'mail'
|
||||
|
@ -382,6 +404,8 @@ def launch_browser_mailer(kind, uri):
|
|||
command = 'gnome-open'
|
||||
elif gajim.config.get('openwith') == 'kfmclient exec':
|
||||
command = 'kfmclient exec'
|
||||
elif gajim.config.get('openwith') == 'exo-open':
|
||||
command = 'exo-open'
|
||||
elif gajim.config.get('openwith') == 'custom':
|
||||
if kind == 'url':
|
||||
command = gajim.config.get('custombrowser')
|
||||
|
@ -389,7 +413,8 @@ def launch_browser_mailer(kind, uri):
|
|||
command = gajim.config.get('custommailapp')
|
||||
if command == '': # if no app is configured
|
||||
return
|
||||
command = command + ' ' + uri
|
||||
|
||||
command = build_command(command, uri)
|
||||
try:
|
||||
exec_command(command)
|
||||
except:
|
||||
|
@ -406,11 +431,13 @@ def launch_file_manager(path_to_open):
|
|||
command = 'gnome-open'
|
||||
elif gajim.config.get('openwith') == 'kfmclient exec':
|
||||
command = 'kfmclient exec'
|
||||
elif gajim.config.get('openwith') == 'exo-open':
|
||||
command = 'exo-open'
|
||||
elif gajim.config.get('openwith') == 'custom':
|
||||
command = gajim.config.get('custom_file_manager')
|
||||
if command == '': # if no app is configured
|
||||
return
|
||||
command = command + ' ' + path_to_open
|
||||
command = build_command(command, path_to_open)
|
||||
try:
|
||||
exec_command(command)
|
||||
except:
|
||||
|
@ -438,7 +465,7 @@ def play_sound_file(path_to_soundfile):
|
|||
if gajim.config.get('soundplayer') == '':
|
||||
return
|
||||
player = gajim.config.get('soundplayer')
|
||||
command = player + ' ' + path_to_soundfile
|
||||
command = build_command(player, path_to_soundfile)
|
||||
exec_command(command)
|
||||
|
||||
def get_file_path_from_dnd_dropped_uri(uri):
|
||||
|
@ -523,8 +550,10 @@ def get_icon_name_to_show(contact, account = None):
|
|||
|
||||
def decode_string(string):
|
||||
'''try to decode (to make it Unicode instance) given string'''
|
||||
if isinstance(string, unicode):
|
||||
return string
|
||||
# by the time we go to iso15 it better be the one else we show bad characters
|
||||
encodings = (sys.getfilesystemencoding(), 'utf-8', 'iso-8859-15')
|
||||
encodings = (locale.getpreferredencoding(), 'utf-8', 'iso-8859-15')
|
||||
for encoding in encodings:
|
||||
try:
|
||||
string = string.decode(encoding)
|
||||
|
@ -599,7 +628,6 @@ def get_documents_path():
|
|||
path = os.path.expanduser('~')
|
||||
return path
|
||||
|
||||
# moved from connection.py
|
||||
def get_full_jid_from_iq(iq_obj):
|
||||
'''return the full jid (with resource) from an iq as unicode'''
|
||||
return parse_jid(str(iq_obj.getFrom()))
|
||||
|
@ -674,6 +702,7 @@ def get_os_info():
|
|||
output = temp_failure_retry(child_stdout.readline).strip()
|
||||
child_stdout.close()
|
||||
child_stdin.close()
|
||||
os.wait()
|
||||
# some distros put n/a in places, so remove those
|
||||
output = output.replace('n/a', '').replace('N/A', '')
|
||||
return output
|
||||
|
@ -726,23 +755,21 @@ def sanitize_filename(filename):
|
|||
|
||||
return filename
|
||||
|
||||
def allow_showing_notification(account, type = None, advanced_notif_num = None,
|
||||
first = True):
|
||||
def allow_showing_notification(account, type = 'notify_on_new_message',
|
||||
advanced_notif_num = None, 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 ex: notify_on_signing
|
||||
first: set it to false when it's not the first message'''
|
||||
if advanced_notif_num != None:
|
||||
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 advanced_notif_num is not None:
|
||||
popup = gajim.config.get_per('notifications', str(advanced_notif_num),
|
||||
'popup')
|
||||
if popup == 'yes':
|
||||
return True
|
||||
if popup == 'no':
|
||||
return False
|
||||
if type and (not gajim.config.get(type) or not first):
|
||||
if type and (not gajim.config.get(type) or not is_first_message):
|
||||
return False
|
||||
if type and gajim.config.get(type) and first:
|
||||
return True
|
||||
if gajim.config.get('autopopupaway'): # always show notification
|
||||
return True
|
||||
if gajim.connections[account].connected in (2, 3): # we're online or chat
|
||||
|
@ -766,7 +793,7 @@ def allow_popup_window(account, advanced_notif_num = None):
|
|||
return False
|
||||
|
||||
def allow_sound_notification(sound_event, advanced_notif_num = None):
|
||||
if advanced_notif_num != None:
|
||||
if advanced_notif_num is not None:
|
||||
sound = gajim.config.get_per('notifications', str(advanced_notif_num),
|
||||
'sound')
|
||||
if sound == 'yes':
|
||||
|
@ -796,3 +823,110 @@ def get_chat_control(account, contact):
|
|||
highest_contact.resource:
|
||||
return None
|
||||
return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
|
||||
|
||||
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 isinstance(text, str):
|
||||
text = text.decode('utf-8')
|
||||
|
||||
if max_lines == 0:
|
||||
lines = text.split('\n')
|
||||
else:
|
||||
lines = text.split('\n', max_lines)[:max_lines]
|
||||
if max_chars > 0:
|
||||
if lines:
|
||||
lines = map(lambda e: _cut_if_long(e), lines)
|
||||
if lines:
|
||||
reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines)
|
||||
else:
|
||||
reduced_text = ''
|
||||
return reduced_text
|
||||
|
||||
def get_notification_icon_tooltip_text():
|
||||
text = None
|
||||
unread_chat = gajim.events.get_nb_events(types = ['printed_chat',
|
||||
'chat'])
|
||||
unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
|
||||
unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
|
||||
'gc_msg'])
|
||||
unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
|
||||
|
||||
accounts = get_accounts_info()
|
||||
|
||||
if unread_chat or unread_single_chat or unread_gc or unread_pm:
|
||||
text = 'Gajim '
|
||||
awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
|
||||
if awaiting_events == unread_chat or awaiting_events == unread_single_chat \
|
||||
or awaiting_events == unread_gc or awaiting_events == unread_pm:
|
||||
# This condition is like previous if but with xor...
|
||||
# Print in one line
|
||||
text += '-'
|
||||
else:
|
||||
# Print in multiple lines
|
||||
text += '\n '
|
||||
if unread_chat:
|
||||
text += ngettext(
|
||||
' %d unread message',
|
||||
' %d unread messages',
|
||||
unread_chat, unread_chat, unread_chat)
|
||||
text += '\n '
|
||||
if unread_single_chat:
|
||||
text += ngettext(
|
||||
' %d unread single message',
|
||||
' %d unread single messages',
|
||||
unread_single_chat, unread_single_chat, unread_single_chat)
|
||||
text += '\n '
|
||||
if unread_gc:
|
||||
text += ngettext(
|
||||
' %d unread group chat message',
|
||||
' %d unread group chat messages',
|
||||
unread_gc, unread_gc, unread_gc)
|
||||
text += '\n '
|
||||
if unread_pm:
|
||||
text += ngettext(
|
||||
' %d unread private message',
|
||||
' %d unread private messages',
|
||||
unread_pm, unread_pm, unread_pm)
|
||||
text += '\n '
|
||||
text = text[:-4] # remove latest '\n '
|
||||
elif len(accounts) > 1:
|
||||
text = _('Gajim')
|
||||
elif len(accounts) == 1:
|
||||
message = accounts[0]['status_line']
|
||||
message = reduce_chars_newlines(message, 100, 1)
|
||||
text = _('Gajim - %s') % message
|
||||
else:
|
||||
text = _('Gajim - %s') % get_uf_show('offline')
|
||||
|
||||
return text
|
||||
|
||||
def get_accounts_info():
|
||||
'''helper for notification icon tooltip'''
|
||||
accounts = []
|
||||
accounts_list = gajim.contacts.get_accounts()
|
||||
accounts_list.sort()
|
||||
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
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
## logger.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -32,24 +22,15 @@ import exceptions
|
|||
import gajim
|
||||
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
import sqlite3 as sqlite # python 2.5
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
# Documents and Settings\[User Name]\Application Data\Gajim\logs.db
|
||||
LOG_DB_PATH = os.path.join(os.environ['appdata'], 'Gajim', 'logs.db')
|
||||
except KeyError:
|
||||
# win9x, ./logs.db
|
||||
LOG_DB_PATH = 'logs.db'
|
||||
else: # Unices
|
||||
LOG_DB_PATH = os.path.expanduser('~/.gajim/logs.db')
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
|
||||
try:
|
||||
LOG_DB_PATH = LOG_DB_PATH.decode(sys.getfilesystemencoding())
|
||||
except:
|
||||
pass
|
||||
import configpaths
|
||||
LOG_DB_PATH = configpaths.gajimpaths['LOG_DB']
|
||||
|
||||
class Constants:
|
||||
def __init__(self):
|
||||
|
@ -107,15 +88,33 @@ class Logger:
|
|||
return
|
||||
self.init_vars()
|
||||
|
||||
def init_vars(self):
|
||||
# if locked, wait up to 20 sec to unlock
|
||||
# before raise (hopefully should be enough)
|
||||
def close_db(self):
|
||||
if self.con:
|
||||
self.con.close()
|
||||
self.con = None
|
||||
self.cur = None
|
||||
|
||||
def open_db(self):
|
||||
self.close_db()
|
||||
|
||||
# if locked, wait up to 20 sec to unlock
|
||||
# before raise (hopefully should be enough)
|
||||
self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
|
||||
isolation_level = 'IMMEDIATE')
|
||||
self.cur = self.con.cursor()
|
||||
self.set_synchronous(False)
|
||||
|
||||
def set_synchronous(self, sync):
|
||||
try:
|
||||
if sync:
|
||||
self.cur.execute("PRAGMA synchronous = NORMAL")
|
||||
else:
|
||||
self.cur.execute("PRAGMA synchronous = OFF")
|
||||
except sqlite.Error, e:
|
||||
gajim.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 get_jids_already_in_db(self):
|
||||
|
@ -136,9 +135,11 @@ class Logger:
|
|||
and after that all okay'''
|
||||
|
||||
possible_room_jid, possible_nick = jid.split('/', 1)
|
||||
return self.jid_is_room_jid(possible_room_jid)
|
||||
|
||||
def jid_is_room_jid(self, jid):
|
||||
self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?',
|
||||
(possible_room_jid, constants.JID_ROOM_TYPE))
|
||||
(jid, constants.JID_ROOM_TYPE))
|
||||
row = self.cur.fetchone()
|
||||
if row is None:
|
||||
return False
|
||||
|
@ -344,10 +345,8 @@ class Logger:
|
|||
ROOM_JID/nick if pm-related.'''
|
||||
|
||||
if self.jids_already_in == []: # only happens if we just created the db
|
||||
self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
|
||||
isolation_level = 'IMMEDIATE')
|
||||
self.cur = self.con.cursor()
|
||||
|
||||
self.open_db()
|
||||
|
||||
jid = jid.lower()
|
||||
contact_name_col = None # holds nickname for kinds gcstatus, gc_msg
|
||||
# message holds the message unless kind is status or gcstatus,
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@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
|
||||
|
@ -27,11 +17,21 @@ import sys
|
|||
import locale
|
||||
from common import gajim
|
||||
|
||||
import exceptions
|
||||
try:
|
||||
import sqlite3 as sqlite # python 2.5
|
||||
except ImportError:
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
import logger
|
||||
|
||||
class OptionsParser:
|
||||
def __init__(self, filename):
|
||||
self.__filename = filename
|
||||
self.old_values = {} # values that are saved in the file and maybe
|
||||
# no longer valid
|
||||
self.old_values = {} # values that are saved in the file and maybe
|
||||
# no longer valid
|
||||
|
||||
def read_line(self, line):
|
||||
index = line.find(' = ')
|
||||
|
@ -126,8 +126,7 @@ class OptionsParser:
|
|||
os.chmod(self.__filename, 0600)
|
||||
|
||||
def update_config(self, old_version, new_version):
|
||||
# Convert '0.x.y' to (0, x, y)
|
||||
old_version_list = old_version.split('.')
|
||||
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)))
|
||||
|
@ -146,7 +145,15 @@ class OptionsParser:
|
|||
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()
|
||||
|
||||
gajim.logger.init_vars()
|
||||
gajim.config.set('version', new_version)
|
||||
|
||||
|
@ -202,13 +209,6 @@ class OptionsParser:
|
|||
|
||||
def assert_unread_msgs_table_exists(self):
|
||||
'''create table unread_messages if there is no such table'''
|
||||
import exceptions
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
import logger
|
||||
|
||||
con = sqlite.connect(logger.LOG_DB_PATH)
|
||||
cur = con.cursor()
|
||||
try:
|
||||
|
@ -278,13 +278,6 @@ class OptionsParser:
|
|||
|
||||
def update_config_to_01013(self):
|
||||
'''create table transports_cache if there is no such table'''
|
||||
import exceptions
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
import logger
|
||||
|
||||
con = sqlite.connect(logger.LOG_DB_PATH)
|
||||
cur = con.cursor()
|
||||
try:
|
||||
|
@ -301,3 +294,56 @@ class OptionsParser:
|
|||
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 indeces')
|
||||
con = sqlite.connect(logger.LOG_DB_PATH)
|
||||
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:
|
||||
pass
|
||||
con.close()
|
||||
gajim.config.set('version', '0.10.1.4')
|
||||
|
||||
def update_config_to_01015(self):
|
||||
'''clean show values in logs database'''
|
||||
con = sqlite.connect(logger.LOG_DB_PATH)
|
||||
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 self.old_values.has_key('notify_on_all_muc_messages') 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 self.old_values.has_key('trayicon_notification_on_new_messages'):
|
||||
gajim.config.set('trayicon_notification_on_events',
|
||||
self.old_values['trayicon_notification_on_new_messages'])
|
||||
gajim.config.set('version', '0.10.1.7')
|
||||
|
|
117
src/common/passwords.py
Normal file
117
src/common/passwords.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
##
|
||||
## Copyright (C) 2006 Gustavo J. A. M. Carneiro <gjcarneiro@gmail.com>
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
__all__ = ['get_password', 'save_password']
|
||||
|
||||
import gobject
|
||||
|
||||
from common import gajim
|
||||
|
||||
try:
|
||||
import gnomekeyring
|
||||
except ImportError:
|
||||
USER_USES_GNOMEKEYRING = False
|
||||
else:
|
||||
if gnomekeyring.is_available():
|
||||
USER_USES_GNOMEKEYRING = True
|
||||
else:
|
||||
USER_USES_GNOMEKEYRING = False
|
||||
|
||||
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):
|
||||
return gajim.config.get_per('accounts', account_name, 'password')
|
||||
|
||||
def save_password(self, account_name, password):
|
||||
gajim.config.set_per('accounts', account_name, 'password', password)
|
||||
gajim.connections[account_name].password = password
|
||||
|
||||
|
||||
class GnomePasswordStorage(PasswordStorage):
|
||||
def __init__(self):
|
||||
# self.keyring = gnomekeyring.get_default_keyring_sync()
|
||||
|
||||
## above line commented and code below inserted as workaround
|
||||
## for the bug http://bugzilla.gnome.org/show_bug.cgi?id=363019
|
||||
self.keyring = "default"
|
||||
try:
|
||||
gnomekeyring.create_sync(self.keyring, None)
|
||||
except gnomekeyring.AlreadyExistsError:
|
||||
pass
|
||||
|
||||
def get_password(self, account_name):
|
||||
conf = gajim.config.get_per('accounts', account_name, 'password')
|
||||
if conf is None:
|
||||
return None
|
||||
try:
|
||||
unused, auth_token = conf.split('gnomekeyring:')
|
||||
auth_token = int(auth_token)
|
||||
except ValueError:
|
||||
password = conf
|
||||
## migrate the password over to keyring
|
||||
try:
|
||||
self.save_password(account_name, password, update=False)
|
||||
except gnomekeyring.NoKeyringDaemonError:
|
||||
## no keyring daemon: in the future, stop using it
|
||||
set_storage(SimplePasswordStorage())
|
||||
return password
|
||||
try:
|
||||
return gnomekeyring.item_get_info_sync(self.keyring,
|
||||
auth_token).get_secret()
|
||||
except gnomekeyring.DeniedError:
|
||||
return None
|
||||
except gnomekeyring.NoKeyringDaemonError:
|
||||
## no keyring daemon: in the future, stop using it
|
||||
set_storage(SimplePasswordStorage())
|
||||
return None
|
||||
|
||||
def save_password(self, account_name, password, update=True):
|
||||
display_name = _('Gajim account %s') % account_name
|
||||
attributes = dict(account_name=str(account_name), gajim=1)
|
||||
auth_token = gnomekeyring.item_create_sync(
|
||||
self.keyring, gnomekeyring.ITEM_GENERIC_SECRET,
|
||||
display_name, attributes, password, update)
|
||||
token = 'gnomekeyring:%i' % auth_token
|
||||
gajim.config.set_per('accounts', account_name, 'password', token)
|
||||
|
||||
|
||||
storage = None
|
||||
def get_storage():
|
||||
global storage
|
||||
if storage is None: # None is only in first time get_storage is called
|
||||
if USER_USES_GNOMEKEYRING:
|
||||
try:
|
||||
storage = GnomePasswordStorage()
|
||||
except gnomekeyring.NoKeyringDaemonError:
|
||||
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):
|
||||
return get_storage().save_password(account_name, password)
|
|
@ -186,6 +186,9 @@ class HostTester(Socks5, IdleObject):
|
|||
|
||||
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()
|
||||
|
|
126
src/common/rst_xhtml_generator.py
Normal file
126
src/common/rst_xhtml_generator.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
## rst_xhtml_generator.py
|
||||
##
|
||||
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2006 Santiago Gala
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
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:
|
||||
def create_xhtml(text):
|
||||
return None
|
||||
else:
|
||||
def jep_reference_role(role, rawtext, text, lineno, inliner,
|
||||
options={}, content=[]):
|
||||
'''Role to make handy references to Jabber Enhancement Proposals (JEP).
|
||||
|
||||
Use as :JEP:`71` (or jep, or jep-reference).
|
||||
Modeled after the sample in docutils documentation.
|
||||
'''
|
||||
|
||||
jep_base_url = 'http://www.jabber.org/jeps/'
|
||||
jep_url = 'jep-%04d.html'
|
||||
try:
|
||||
jepnum = int(text)
|
||||
if jepnum <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
msg = inliner.reporter.error(
|
||||
'JEP number must be a number greater than or equal to 1; '
|
||||
'"%s" is invalid.' % text, line=lineno)
|
||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||
return [prb], [msg]
|
||||
ref = jep_base_url + jep_url % jepnum
|
||||
set_classes(options)
|
||||
node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref,
|
||||
**options)
|
||||
return [node], []
|
||||
|
||||
roles.register_canonical_role('jep-reference', jep_reference_role)
|
||||
from docutils.parsers.rst.languages.en import roles
|
||||
roles['jep-reference'] = 'jep-reference'
|
||||
roles['jep'] = 'jep-reference'
|
||||
|
||||
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=dict(report_level=5, halt_level=5),
|
||||
config_section='general'):
|
||||
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.
|
||||
# — == u"\u2014"
|
||||
# a setting to only emit charater entities in the writer would be nice
|
||||
# FIXME: several are emitted, and they are explicitly forbidden
|
||||
# in the JEP
|
||||
# == u"\u00a0"
|
||||
self.pub.writer.translator_class.attribution_formats['dash'] = (
|
||||
u'\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)
|
||||
# entities
|
||||
return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(
|
||||
' '))
|
||||
|
||||
Generator = HTMLGenerator()
|
||||
|
||||
def create_xhtml(text):
|
||||
return Generator.create_xhtml(text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print Generator.create_xhtml('''
|
||||
test::
|
||||
|
||||
>>> print 1
|
||||
1
|
||||
|
||||
*I* like it. It is for :JEP:`71`
|
||||
|
||||
this `` should trigger`` should trigger the problem.
|
||||
|
||||
''')
|
||||
print Generator.create_xhtml('''
|
||||
*test1
|
||||
|
||||
test2_
|
||||
''')
|
|
@ -8,7 +8,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -33,13 +33,10 @@ STATE_AWAKE = 'awake'
|
|||
|
||||
SUPPORTED = True
|
||||
try:
|
||||
import common.idle as idle # when we launch gajim from sources
|
||||
import idle
|
||||
except:
|
||||
try:
|
||||
import idle # when Gajim is installed
|
||||
except:
|
||||
gajim.log.debug('Unable to load idle module')
|
||||
SUPPORTED = False
|
||||
gajim.log.debug('Unable to load idle module')
|
||||
SUPPORTED = False
|
||||
|
||||
class Sleepy:
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <nkour@jabber.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -27,11 +27,8 @@
|
|||
|
||||
|
||||
import socket
|
||||
import select
|
||||
import os
|
||||
import struct
|
||||
import sha
|
||||
import time
|
||||
from dialogs import BindPortError
|
||||
|
||||
from errno import EWOULDBLOCK
|
||||
|
@ -74,13 +71,13 @@ class SocksQueue:
|
|||
self.on_success = None
|
||||
self.on_failure = None
|
||||
|
||||
def start_listener(self, host, port, sha_str, sha_handler, sid):
|
||||
def start_listener(self, port, sha_str, sha_handler, sid):
|
||||
''' start waiting for incomming connections on (host, port)
|
||||
and do a socks5 authentication using sid for generated sha
|
||||
'''
|
||||
self.sha_handlers[sha_str] = (sha_handler, sid)
|
||||
if self.listener == None:
|
||||
self.listener = Socks5Listener(self.idlequeue, host, port)
|
||||
self.listener = Socks5Listener(self.idlequeue, port)
|
||||
self.listener.queue = self
|
||||
self.listener.bind()
|
||||
if self.listener.started is False:
|
||||
|
@ -213,7 +210,7 @@ class SocksQueue:
|
|||
|
||||
sender = self.senders[file_props['hash']]
|
||||
sender.account = account
|
||||
result = get_file_contents(0)
|
||||
result = self.get_file_contents(0)
|
||||
self.process_result(result, sender)
|
||||
|
||||
def result_sha(self, sha_str, idx):
|
||||
|
@ -350,7 +347,10 @@ class SocksQueue:
|
|||
class Socks5:
|
||||
def __init__(self, idlequeue, host, port, initiator, target, sid):
|
||||
if host is not None:
|
||||
self.host = socket.gethostbyname(host)
|
||||
try:
|
||||
self.host = socket.gethostbyname(host)
|
||||
except socket.gaierror:
|
||||
self.host = None
|
||||
self.idlequeue = idlequeue
|
||||
self.fd = -1
|
||||
self.port = port
|
||||
|
@ -787,12 +787,12 @@ class Socks5Sender(Socks5, IdleObject):
|
|||
self.queue.remove_sender(self.queue_idx, False)
|
||||
|
||||
class Socks5Listener(IdleObject):
|
||||
def __init__(self, idlequeue, host, port):
|
||||
''' handle all incomming connections on (host, port)
|
||||
def __init__(self, idlequeue, port):
|
||||
''' handle all incomming connections on (0.0.0.0, port)
|
||||
This class implements IdleObject, but we will expect
|
||||
only pollin events though
|
||||
'''
|
||||
self.host, self.port = host, port
|
||||
self.port = port
|
||||
self.queue_idx = -1
|
||||
self.idlequeue = idlequeue
|
||||
self.queue = None
|
||||
|
|
|
@ -74,10 +74,8 @@ class NBCommonClient(CommonClient):
|
|||
''' Called on disconnection. Calls disconnect handlers and cleans things up. '''
|
||||
self.connected=''
|
||||
self.DEBUG(self.DBG,'Disconnect detected','stop')
|
||||
self.disconnect_handlers.reverse()
|
||||
for i in self.disconnect_handlers:
|
||||
for i in reversed(self.disconnect_handlers):
|
||||
i()
|
||||
self.disconnect_handlers.reverse()
|
||||
if self.__dict__.has_key('NonBlockingRoster'):
|
||||
self.NonBlockingRoster.PlugOut()
|
||||
if self.__dict__.has_key('NonBlockingBind'):
|
||||
|
@ -125,6 +123,8 @@ class NBCommonClient(CommonClient):
|
|||
self.on_connect_failure(retry)
|
||||
|
||||
def _on_connected(self):
|
||||
# connect succeded, so no need of this callback anymore
|
||||
self.on_connect_failure = None
|
||||
self.connected = 'tcp'
|
||||
if self._Ssl:
|
||||
transports_nb.NonBlockingTLS().PlugIn(self, now=1)
|
||||
|
|
|
@ -74,7 +74,6 @@ class Dispatcher(PlugIn):
|
|||
self.RegisterProtocol('presence', Presence)
|
||||
self.RegisterProtocol('message', Message)
|
||||
self.RegisterDefaultHandler(self.returnStanzaHandler)
|
||||
# Register Gajim's event handler as soon as dispatcher begins
|
||||
self.RegisterEventHandler(self._owner._caller._event_dispatcher)
|
||||
self.on_responses = {}
|
||||
|
||||
|
@ -84,7 +83,10 @@ class Dispatcher(PlugIn):
|
|||
self._owner.lastErrNode = None
|
||||
self._owner.lastErr = None
|
||||
self._owner.lastErrCode = None
|
||||
self.StreamInit()
|
||||
if hasattr(self._owner, 'StreamInit'):
|
||||
self._owner.StreamInit()
|
||||
else:
|
||||
self.StreamInit()
|
||||
|
||||
def plugout(self):
|
||||
''' Prepares instance to be destructed. '''
|
||||
|
@ -129,17 +131,18 @@ class Dispatcher(PlugIn):
|
|||
try:
|
||||
self.Stream.Parse(data)
|
||||
# end stream:stream tag received
|
||||
if self.Stream and self.Stream._NodeBuilder__depth == 0:
|
||||
if self.Stream and self.Stream.has_received_endtag():
|
||||
self._owner.Connection.disconnect()
|
||||
return 0
|
||||
except ExpatError:
|
||||
sys.exc_clear()
|
||||
self.DEBUG('Invalid XML received from server. Forcing disconnect.')
|
||||
self.DEBUG('Invalid XML received from server. Forcing disconnect.', 'error')
|
||||
self._owner.Connection.pollend()
|
||||
return 0
|
||||
if len(self._pendingExceptions) > 0:
|
||||
_pendingException = self._pendingExceptions.pop()
|
||||
raise _pendingException[0], _pendingException[1], _pendingException[2]
|
||||
if len(data) == 0: return '0'
|
||||
return len(data)
|
||||
|
||||
def RegisterNamespace(self, xmlns, order='info'):
|
||||
|
@ -396,7 +399,7 @@ class Dispatcher(PlugIn):
|
|||
Additional callback arguments can be specified in args. '''
|
||||
self.SendAndWaitForResponse(stanza, 0, func, args)
|
||||
|
||||
def send(self, stanza):
|
||||
def send(self, stanza, is_message = False):
|
||||
''' Serialise stanza and put it on the wire. Assign an unique ID to it before send.
|
||||
Returns assigned ID.'''
|
||||
if type(stanza) in [type(''), type(u'')]:
|
||||
|
@ -423,7 +426,10 @@ class Dispatcher(PlugIn):
|
|||
stanza=route
|
||||
stanza.setNamespace(self._owner.Namespace)
|
||||
stanza.setParent(self._metastream)
|
||||
self._owner.Connection.send(stanza)
|
||||
if is_message:
|
||||
self._owner.Connection.send(stanza, True)
|
||||
else:
|
||||
self._owner.Connection.send(stanza)
|
||||
return _ID
|
||||
|
||||
def disconnect(self):
|
||||
|
|
|
@ -53,7 +53,6 @@ class IdleQueue:
|
|||
self.selector = select.poll()
|
||||
|
||||
def remove_timeout(self, fd):
|
||||
''' self explanatory, remove the timeout from 'read_timeouts' dict '''
|
||||
if self.read_timeouts.has_key(fd):
|
||||
del(self.read_timeouts[fd])
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ Protocol module contains tools that is needed for processing of
|
|||
xmpp-related data structures.
|
||||
"""
|
||||
|
||||
from simplexml import Node,ustr
|
||||
from simplexml import Node,NodeBuilder,ustr
|
||||
import time
|
||||
NS_ACTIVITY ='http://jabber.org/protocol/activity' # JEP-0108
|
||||
NS_ADDRESS ='http://jabber.org/protocol/address' # JEP-0033
|
||||
|
@ -94,6 +94,7 @@ NS_VCARD_UPDATE =NS_VCARD+':x:update'
|
|||
NS_VERSION ='jabber:iq:version'
|
||||
NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # JEP-0130
|
||||
NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # JEP-0071
|
||||
NS_XHTML = 'http://www.w3.org/1999/xhtml' # "
|
||||
NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # JEP-0141
|
||||
NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # JEP-0122
|
||||
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
|
||||
|
@ -348,11 +349,18 @@ class Protocol(Node):
|
|||
for tag in errtag.getChildren():
|
||||
if tag.getName()<>'text': return tag.getName()
|
||||
return errtag.getData()
|
||||
def getErrorMsg(self):
|
||||
""" Return the textual description of the error (if present) or the error condition """
|
||||
errtag=self.getTag('error')
|
||||
if errtag:
|
||||
for tag in errtag.getChildren():
|
||||
if tag.getName()=='text': return tag.getData()
|
||||
return self.getError()
|
||||
def getErrorCode(self):
|
||||
""" Return the error code. Obsolette. """
|
||||
""" Return the error code. Obsolete. """
|
||||
return self.getTagAttr('error','code')
|
||||
def setError(self,error,code=None):
|
||||
""" Set the error code. Obsolette. Use error-conditions instead. """
|
||||
""" Set the error code. Obsolete. Use error-conditions instead. """
|
||||
if code:
|
||||
if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error)
|
||||
else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error)
|
||||
|
@ -378,16 +386,29 @@ class Protocol(Node):
|
|||
|
||||
class Message(Protocol):
|
||||
""" XMPP Message stanza - "push" mechanism."""
|
||||
def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
|
||||
def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
|
||||
""" Create message object. You can specify recipient, text of message, type of message
|
||||
any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
|
||||
Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """
|
||||
Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
|
||||
if body: self.setBody(body)
|
||||
if xhtml: self.setXHTML(xhtml)
|
||||
if subject: self.setSubject(subject)
|
||||
def getBody(self):
|
||||
""" Returns text of the message. """
|
||||
return self.getTagData('body')
|
||||
def getXHTML(self, xmllang=None):
|
||||
""" Returns serialized xhtml-im element text of the message.
|
||||
|
||||
TODO: Returning a DOM could make rendering faster."""
|
||||
xhtml = self.getTag('html')
|
||||
if xhtml:
|
||||
if xmllang:
|
||||
body = xhtml.getTag('body', attrs={'xml:lang':xmllang})
|
||||
else:
|
||||
body = xhtml.getTag('body')
|
||||
return str(body)
|
||||
return None
|
||||
def getSubject(self):
|
||||
""" Returns subject of the message. """
|
||||
return self.getTagData('subject')
|
||||
|
@ -397,6 +418,22 @@ class Message(Protocol):
|
|||
def setBody(self,val):
|
||||
""" Sets the text of the message. """
|
||||
self.setTagData('body',val)
|
||||
|
||||
def setXHTML(self,val,xmllang=None):
|
||||
""" Sets the xhtml text of the message (JEP-0071).
|
||||
The parameter is the "inner html" to the body."""
|
||||
try:
|
||||
if xmllang:
|
||||
dom = NodeBuilder('<body xmlns="'+NS_XHTML+'" xml:lang="'+xmllang+'" >' + val + '</body>').getDom()
|
||||
else:
|
||||
dom = NodeBuilder('<body xmlns="'+NS_XHTML+'">'+val+'</body>',0).getDom()
|
||||
if self.getTag('html'):
|
||||
self.getTag('html').addChild(node=dom)
|
||||
else:
|
||||
self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom)
|
||||
except Exception, e:
|
||||
print "Error", e
|
||||
pass #FIXME: log. we could not set xhtml (parse error, whatever)
|
||||
def setSubject(self,val):
|
||||
""" Sets the subject of the message. """
|
||||
self.setTagData('subject',val)
|
||||
|
|
|
@ -183,7 +183,7 @@ class Session:
|
|||
if self.sendbuffer:
|
||||
try:
|
||||
# LOCK_QUEUE
|
||||
sent=self._send(self.sendbuffer) # âÌÏËÉÒÕÀÝÁÑ ÛÔÕÞËÁ!
|
||||
sent=self._send(self.sendbuffer) # blocking socket
|
||||
except:
|
||||
# UNLOCK_QUEUE
|
||||
self.set_socket_state(SOCKET_DEAD)
|
||||
|
|
|
@ -302,6 +302,8 @@ class NodeBuilder:
|
|||
self.Parse = self._parser.Parse
|
||||
|
||||
self.__depth = 0
|
||||
self.__last_depth = 0
|
||||
self.__max_depth = 0
|
||||
self._dispatch_depth = 1
|
||||
self._document_attrs = None
|
||||
self._mini_dom=initial_node
|
||||
|
@ -338,7 +340,7 @@ class NodeBuilder:
|
|||
ns=attr[:sp] #
|
||||
attrs[self.namespaces[ns]+attr[sp+1:]]=attrs[attr]
|
||||
del attrs[attr] #
|
||||
self.__depth += 1
|
||||
self._inc_depth()
|
||||
self.DEBUG(DBG_NODEBUILDER, "DEPTH -> %i , tag -> %s, attrs -> %s" % (self.__depth, tag, `attrs`), 'down')
|
||||
if self.__depth == self._dispatch_depth:
|
||||
if not self._mini_dom :
|
||||
|
@ -366,7 +368,7 @@ class NodeBuilder:
|
|||
self._ptr = self._ptr.parent
|
||||
else:
|
||||
self.DEBUG(DBG_NODEBUILDER, "Got higher than dispatch level. Stream terminated?", 'stop')
|
||||
self.__depth -= 1
|
||||
self._dec_depth()
|
||||
self.last_is_data = 0
|
||||
if self.__depth == 0: self.stream_footer_received()
|
||||
|
||||
|
@ -374,7 +376,7 @@ class NodeBuilder:
|
|||
if self.last_is_data:
|
||||
if self.data_buffer:
|
||||
self.data_buffer.append(data)
|
||||
else:
|
||||
elif self._ptr:
|
||||
self.data_buffer = [data]
|
||||
self.last_is_data = 1
|
||||
|
||||
|
@ -399,6 +401,19 @@ class NodeBuilder:
|
|||
""" Method called when stream just closed. """
|
||||
self.check_data_buffer()
|
||||
|
||||
def has_received_endtag(self, level=0):
|
||||
""" Return True if at least one end tag was seen (at level) """
|
||||
return self.__depth <= level and self.__max_depth > level
|
||||
|
||||
def _inc_depth(self):
|
||||
self.__last_depth = self.__depth
|
||||
self.__depth += 1
|
||||
self.__max_depth = max(self.__depth, self.__max_depth)
|
||||
|
||||
def _dec_depth(self):
|
||||
self.__last_depth = self.__depth
|
||||
self.__depth -= 1
|
||||
|
||||
def XML2Node(xml):
|
||||
""" Converts supplied textual string into XML node. Handy f.e. for reading configuration file.
|
||||
Raises xml.parser.expat.parsererror if provided string is not well-formed XML. """
|
||||
|
|
|
@ -143,11 +143,11 @@ class NonBlockingTcp(PlugIn, IdleObject):
|
|||
def pollin(self):
|
||||
self._do_receive()
|
||||
|
||||
def pollend(self):
|
||||
def pollend(self, retry = False):
|
||||
conn_failure_cb = self.on_connect_failure
|
||||
self.disconnect()
|
||||
if conn_failure_cb:
|
||||
conn_failure_cb()
|
||||
conn_failure_cb(retry)
|
||||
|
||||
def disconnect(self):
|
||||
if self.state == -2: # already disconnected
|
||||
|
@ -216,15 +216,19 @@ class NonBlockingTcp(PlugIn, IdleObject):
|
|||
# "received" will be empty anyhow
|
||||
if errnum == socket.SSL_ERROR_WANT_READ:
|
||||
pass
|
||||
elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]:
|
||||
elif errnum == errno.ECONNRESET:
|
||||
self.pollend(True)
|
||||
# don't proccess result, caus it will raise error
|
||||
return
|
||||
elif errnum in [errno.ENOTCONN, errno.ESHUTDOWN]:
|
||||
self.pollend()
|
||||
# don't proccess result, cas it will raise error
|
||||
# don't proccess result, caus it will raise error
|
||||
return
|
||||
elif not received :
|
||||
if errnum != socket.SSL_ERROR_EOF:
|
||||
# 8 EOF occurred in violation of protocol
|
||||
self.DEBUG('Socket error while receiving data', 'error')
|
||||
self.pollend()
|
||||
self.pollend(True)
|
||||
if self.state >= 0:
|
||||
self.disconnect()
|
||||
return
|
||||
|
|
0
src/common/zeroconf/__init__.py
Normal file
0
src/common/zeroconf/__init__.py
Normal file
647
src/common/zeroconf/client_zeroconf.py
Normal file
647
src/common/zeroconf/client_zeroconf.py
Normal file
|
@ -0,0 +1,647 @@
|
|||
## common/zeroconf/client_zeroconf.py
|
||||
##
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
## 2006 Dimitur Kirov <dkirov@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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
from common import gajim
|
||||
import common.xmpp
|
||||
from common.xmpp.idlequeue import IdleObject
|
||||
from common.xmpp import dispatcher_nb, simplexml
|
||||
from common.xmpp.client import *
|
||||
from common.xmpp.simplexml import ustr
|
||||
from common.zeroconf import zeroconf
|
||||
|
||||
from common.xmpp.protocol import *
|
||||
import socket
|
||||
import errno
|
||||
import sys
|
||||
|
||||
from common.zeroconf import roster_zeroconf
|
||||
|
||||
MAX_BUFF_LEN = 65536
|
||||
DATA_RECEIVED='DATA RECEIVED'
|
||||
DATA_SENT='DATA SENT'
|
||||
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):
|
||||
self._serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
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)
|
||||
# will fail when port is busy, or we don't have rights to bind
|
||||
try:
|
||||
self._serv.bind(('0.0.0.0', self.port))
|
||||
except Exception, e:
|
||||
# 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()
|
||||
P2PClient(sock[0], sock[1][0], sock[1][1], self.conn_holder)
|
||||
|
||||
def disconnect(self):
|
||||
''' free all resources, we are not listening anymore '''
|
||||
gajim.idlequeue.remove_timeout(self.fd)
|
||||
gajim.idlequeue.unplug_idle(self.fd)
|
||||
self.fd = -1
|
||||
self.started = False
|
||||
try:
|
||||
self._serv.close()
|
||||
except:
|
||||
pass
|
||||
self.conn_holder.kill_all_connections()
|
||||
|
||||
def accept_conn(self):
|
||||
''' accepts a new incoming connection '''
|
||||
_sock = self._serv.accept()
|
||||
_sock[0].setblocking(False)
|
||||
return _sock
|
||||
|
||||
class P2PClient(IdleObject):
|
||||
def __init__(self, _sock, host, port, conn_holder, stanzaqueue = [], to = None):
|
||||
self._owner = self
|
||||
self.Namespace = 'jabber:client'
|
||||
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 = host
|
||||
self.DBG = 'client'
|
||||
self.Connection = None
|
||||
if gajim.verbose:
|
||||
debug = ['always', 'nodebuilder']
|
||||
else:
|
||||
debug = []
|
||||
self._DEBUG = Debug.Debug(debug)
|
||||
self.DEBUG = self._DEBUG.Show
|
||||
self.debug_flags = self._DEBUG.debug_flags
|
||||
self.debug_flags.append(self.DBG)
|
||||
self.sock_hash = None
|
||||
if _sock:
|
||||
self.sock_type = TYPE_SERVER
|
||||
else:
|
||||
self.sock_type = TYPE_CLIENT
|
||||
conn = P2PConnection('', _sock, host, port, self._caller, self.on_connect, self)
|
||||
self.sock_hash = conn._sock.__hash__
|
||||
self.fd = conn.fd
|
||||
self.conn_holder.add_connection(self, self.Server, port, self.to)
|
||||
# count messages in queue
|
||||
for val in self.stanzaqueue:
|
||||
stanza, is_message = val
|
||||
if is_message:
|
||||
if self.conn_holder.number_of_awaiting_messages.has_key(self.fd):
|
||||
self.conn_holder.number_of_awaiting_messages[self.fd]+=1
|
||||
else:
|
||||
self.conn_holder.number_of_awaiting_messages[self.fd]=1
|
||||
|
||||
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:
|
||||
if self.conn_holder.number_of_awaiting_messages.has_key(self.fd):
|
||||
self.conn_holder.number_of_awaiting_messages[self.fd]+=1
|
||||
else:
|
||||
self.conn_holder.number_of_awaiting_messages[self.fd] = 1
|
||||
|
||||
return True
|
||||
|
||||
def on_message_sent(self, connection_id):
|
||||
self.conn_holder.number_of_awaiting_messages[connection_id]-=1
|
||||
|
||||
def on_connect(self, conn):
|
||||
self.Connection = conn
|
||||
self.Connection.PlugIn(self)
|
||||
dispatcher_nb.Dispatcher().PlugIn(self)
|
||||
self._register_handlers()
|
||||
if self.sock_type == TYPE_CLIENT:
|
||||
while self.stanzaqueue:
|
||||
stanza, is_message = self.stanzaqueue.pop(0)
|
||||
self.send(stanza, is_message)
|
||||
|
||||
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.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
self.Dispatcher.Stream.DEBUG = self.DEBUG
|
||||
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)
|
||||
# XXX TLS support
|
||||
#~ self._metastream.setAttr('version', '1.0')
|
||||
self.Dispatcher._metastream.setAttr('xmlns:stream', NS_STREAMS)
|
||||
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':
|
||||
self._caller.dispatch('MSGERROR',[unicode(self.to), -1, \
|
||||
_('Connection to host could not be established: Incorrect answer from server.'), None, None])
|
||||
self.Connection.DEBUG('Incorrect stream start: (%s,%s).Terminating! ' % (tag, ns), 'error')
|
||||
self.Connection.disconnect()
|
||||
return
|
||||
if self.sock_type == TYPE_SERVER:
|
||||
self.send_stream_header()
|
||||
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.conn_holder.number_of_awaiting_messages.has_key(self.fd):
|
||||
if self.conn_holder.number_of_awaiting_messages[self.fd] > 0:
|
||||
self._caller.dispatch('MSGERROR',[unicode(self.to), -1, \
|
||||
_('Connection to host could not be established'), None, None])
|
||||
del self.conn_holder.number_of_awaiting_messages[self.fd]
|
||||
self.conn_holder.remove_connection(self.sock_hash)
|
||||
if self.__dict__.has_key('Dispatcher'):
|
||||
self.Dispatcher.PlugOut()
|
||||
if self.__dict__.has_key('P2PConnection'):
|
||||
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 self.Dispatcher.Stream._document_attrs.has_key('version') 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 _register_handlers(self):
|
||||
self.RegisterHandler('message', lambda conn, data:self._caller._messageCB(self.Server, conn, data))
|
||||
self.RegisterHandler('iq', self._caller._siSetCB, 'set',
|
||||
common.xmpp.NS_SI)
|
||||
self.RegisterHandler('iq', self._caller._siErrorCB, 'error',
|
||||
common.xmpp.NS_SI)
|
||||
self.RegisterHandler('iq', self._caller._siResultCB, 'result',
|
||||
common.xmpp.NS_SI)
|
||||
self.RegisterHandler('iq', self._caller._bytestreamSetCB, 'set',
|
||||
common.xmpp.NS_BYTESTREAM)
|
||||
self.RegisterHandler('iq', self._caller._bytestreamResultCB, 'result',
|
||||
common.xmpp.NS_BYTESTREAM)
|
||||
self.RegisterHandler('iq', self._caller._bytestreamErrorCB, 'error',
|
||||
common.xmpp.NS_BYTESTREAM)
|
||||
|
||||
class P2PConnection(IdleObject, PlugIn):
|
||||
''' class for sending file to socket over socks5 '''
|
||||
def __init__(self, sock_hash, _sock, host = None, port = None, caller = None, on_connect = None, client = None):
|
||||
IdleObject.__init__(self)
|
||||
self._owner = client
|
||||
PlugIn.__init__(self)
|
||||
self.DBG_LINE='socket'
|
||||
self.sendqueue = []
|
||||
self.sendbuff = None
|
||||
self.buff_is_message = False
|
||||
self._sock = _sock
|
||||
self.sock_hash = None
|
||||
self.host, self.port = host, port
|
||||
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._sock = _sock
|
||||
self.state = 1
|
||||
self._sock.setblocking(False)
|
||||
self.fd = self._sock.fileno()
|
||||
self.on_connect(self)
|
||||
else:
|
||||
self.state = 0
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._sock.setblocking(False)
|
||||
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):
|
||||
'''Append stanza to the queue of messages to be send.
|
||||
If supplied data is unicode string, encode it to utf-8.
|
||||
'''
|
||||
if self.state <= 0:
|
||||
return
|
||||
|
||||
r = packet
|
||||
|
||||
if isinstance(r, unicode):
|
||||
r = r.encode('utf-8')
|
||||
elif not isinstance(r, str):
|
||||
r = ustr(r).encode('utf-8')
|
||||
|
||||
self.sendqueue.append((r, is_message))
|
||||
self._plug_idle()
|
||||
|
||||
def read_timeout(self):
|
||||
if self.client.conn_holder.number_of_awaiting_messages.has_key(self.fd) \
|
||||
and self.client.conn_holder.number_of_awaiting_messages[self.fd] > 0:
|
||||
self.client._caller.dispatch('MSGERROR',[unicode(self.client.to), -1, \
|
||||
_('Connection to host could not be established: Timeout while sending data.'), None, None])
|
||||
self.client.conn_holder.number_of_awaiting_messages[self.fd] = 0
|
||||
self.pollend()
|
||||
|
||||
def do_connect(self):
|
||||
errnum = 0
|
||||
try:
|
||||
self._sock.connect((self.host, self.port))
|
||||
self._sock.setblocking(False)
|
||||
except Exception, ee:
|
||||
(errnum, errstr) = ee
|
||||
if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
|
||||
return
|
||||
# win32 needs this
|
||||
elif errnum not in (0, 10056, errno.EISCONN) or self.state != 0:
|
||||
self.disconnect()
|
||||
return None
|
||||
else: # socket is already connected
|
||||
self._sock.setblocking(False)
|
||||
self.state = 1 # connected
|
||||
self.on_connect(self)
|
||||
return 1 # we are connected
|
||||
|
||||
|
||||
def pollout(self):
|
||||
if self.state == 0:
|
||||
self.do_connect()
|
||||
return
|
||||
gajim.idlequeue.remove_timeout(self.fd)
|
||||
self._do_send()
|
||||
|
||||
def pollend(self):
|
||||
self.state = -1
|
||||
self.disconnect()
|
||||
|
||||
def pollin(self):
|
||||
''' Reads all pending incoming data. Calls 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, e:
|
||||
if len(e.args) > 0 and isinstance(e.args[0], int):
|
||||
errnum = e[0]
|
||||
sys.exc_clear()
|
||||
# "received" will be empty anyhow
|
||||
if errnum == socket.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 != socket.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():
|
||||
self.DEBUG(received, 'got')
|
||||
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
|
||||
self.DEBUG('Unhandled data received: %s' % received,'error')
|
||||
self.disconnect()
|
||||
return True
|
||||
|
||||
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 disconnect(self):
|
||||
''' Closes 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 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, e:
|
||||
sys.exc_clear()
|
||||
if e[0] == socket.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():
|
||||
self.DEBUG(self.sent_data,'sent')
|
||||
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):
|
||||
self.DEBUG("Socket error while sending data",'error')
|
||||
self._owner.disconnected()
|
||||
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.number_of_awaiting_messages = {}
|
||||
|
||||
def test_avahi(self):
|
||||
try:
|
||||
import avahi
|
||||
except ImportError:
|
||||
return False
|
||||
return True
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
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 self.hash_to_port.has_key(sock_hash):
|
||||
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):
|
||||
stanza.setFrom(self.roster.zeroconf.name)
|
||||
to = stanza.getTo()
|
||||
|
||||
try:
|
||||
item = self.roster[to]
|
||||
except KeyError:
|
||||
self.caller.dispatch('MSGERROR', [unicode(to), '-1', _('Contact is offline. Your message could not be sent.'), None, None])
|
||||
return False
|
||||
|
||||
# look for hashed connections
|
||||
if to in self.recipient_to_hash:
|
||||
conn = self.connections[self.recipient_to_hash[to]]
|
||||
if conn.add_stanza(stanza, is_message):
|
||||
return
|
||||
|
||||
if item['address'] in self.ip_to_hash:
|
||||
hash = self.ip_to_hash[item['address']]
|
||||
if self.hash_to_port[hash] == item['port']:
|
||||
conn = self.connections[hash]
|
||||
if conn.add_stanza(stanza, is_message):
|
||||
return
|
||||
|
||||
# otherwise open new connection
|
||||
P2PClient(None, item['address'], item['port'], self, [(stanza, is_message)], to)
|
906
src/common/zeroconf/connection_handlers_zeroconf.py
Normal file
906
src/common/zeroconf/connection_handlers_zeroconf.py
Normal file
|
@ -0,0 +1,906 @@
|
|||
##
|
||||
## Copyright (C) 2006 Gajim Team
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <nkour@jabber.org>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
## - Travis Shirk <travis@pobox.com>
|
||||
## - Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
import sha
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from calendar import timegm
|
||||
|
||||
#import socks5
|
||||
import common.xmpp
|
||||
|
||||
from common import GnuPG
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
from common.zeroconf import 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:
|
||||
gajim.log.debug(_('Unable to load idle module'))
|
||||
HAS_IDLE = False
|
||||
|
||||
|
||||
class ConnectionBytestream:
|
||||
def __init__(self):
|
||||
self.files_props = {}
|
||||
|
||||
def is_transfer_stoped(self, file_props):
|
||||
if file_props.has_key('error') and file_props['error'] != 0:
|
||||
return True
|
||||
if file_props.has_key('completed') and file_props['completed']:
|
||||
return True
|
||||
if file_props.has_key('connected') and file_props['connected'] == False:
|
||||
return True
|
||||
if not file_props.has_key('stopped') or not file_props['stopped']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def send_success_connect_reply(self, streamhost):
|
||||
''' send reply to the initiator of FT that we
|
||||
made a connection
|
||||
'''
|
||||
if streamhost is None:
|
||||
return None
|
||||
iq = common.xmpp.Iq(to = streamhost['initiator'], typ = 'result',
|
||||
frm = streamhost['target'])
|
||||
iq.setAttr('id', streamhost['id'])
|
||||
query = iq.setTag('query')
|
||||
query.setNamespace(common.xmpp.NS_BYTESTREAM)
|
||||
stream_tag = query.setTag('streamhost-used')
|
||||
stream_tag.setAttr('jid', streamhost['jid'])
|
||||
self.connection.send(iq)
|
||||
|
||||
def remove_transfers_for_contact(self, contact):
|
||||
''' stop all active transfer for contact '''
|
||||
for file_props in self.files_props.values():
|
||||
if self.is_transfer_stoped(file_props):
|
||||
continue
|
||||
receiver_jid = unicode(file_props['receiver']).split('/')[0]
|
||||
if contact.jid == receiver_jid:
|
||||
file_props['error'] = -5
|
||||
self.remove_transfer(file_props)
|
||||
self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props, ''))
|
||||
sender_jid = unicode(file_props['sender'])
|
||||
if contact.jid == sender_jid:
|
||||
file_props['error'] = -3
|
||||
self.remove_transfer(file_props)
|
||||
|
||||
def remove_all_transfers(self):
|
||||
''' stops and removes all active connections from the socks5 pool '''
|
||||
for file_props in self.files_props.values():
|
||||
self.remove_transfer(file_props, remove_from_list = False)
|
||||
del(self.files_props)
|
||||
self.files_props = {}
|
||||
|
||||
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']
|
||||
gajim.socks5queue.remove_file_props(self.name, sid)
|
||||
|
||||
if remove_from_list:
|
||||
if self.files_props.has_key('sid'):
|
||||
del(self.files_props['sid'])
|
||||
|
||||
def disconnect_transfer(self, file_props):
|
||||
if file_props is None:
|
||||
return
|
||||
if file_props.has_key('hash'):
|
||||
gajim.socks5queue.remove_sender(file_props['hash'])
|
||||
|
||||
if file_props.has_key('streamhosts'):
|
||||
for host in file_props['streamhosts']:
|
||||
if host.has_key('idx') and host['idx'] > 0:
|
||||
gajim.socks5queue.remove_receiver(host['idx'])
|
||||
gajim.socks5queue.remove_sender(host['idx'])
|
||||
|
||||
def send_socks5_info(self, file_props, fast = True, receiver = None,
|
||||
sender = None):
|
||||
''' send iq for the present streamhosts and proxies '''
|
||||
if type(self.peerhost) != tuple:
|
||||
return
|
||||
port = gajim.config.get('file_transfers_port')
|
||||
ft_override_host_to_send = gajim.config.get('ft_override_host_to_send')
|
||||
if receiver is None:
|
||||
receiver = file_props['receiver']
|
||||
if sender is None:
|
||||
sender = file_props['sender']
|
||||
proxyhosts = []
|
||||
sha_str = helpers.get_auth_sha(file_props['sid'], sender,
|
||||
receiver)
|
||||
file_props['sha_str'] = sha_str
|
||||
if not ft_override_host_to_send:
|
||||
ft_override_host_to_send = self.peerhost[0]
|
||||
try:
|
||||
ft_override_host_to_send = socket.gethostbyname(
|
||||
ft_override_host_to_send)
|
||||
except socket.gaierror:
|
||||
self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.')))
|
||||
ft_override_host_to_send = self.peerhost[0]
|
||||
listener = gajim.socks5queue.start_listener(port,
|
||||
sha_str, self._result_socks5_sid, file_props['sid'])
|
||||
if listener == None:
|
||||
file_props['error'] = -5
|
||||
self.dispatch('FILE_REQUEST_ERROR', (unicode(receiver), file_props,
|
||||
''))
|
||||
self._connect_error(unicode(receiver), file_props['sid'],
|
||||
file_props['sid'], code = 406)
|
||||
return
|
||||
|
||||
iq = common.xmpp.Protocol(name = 'iq', to = unicode(receiver),
|
||||
typ = 'set')
|
||||
file_props['request-id'] = 'id_' + file_props['sid']
|
||||
iq.setID(file_props['request-id'])
|
||||
query = iq.setTag('query')
|
||||
query.setNamespace(common.xmpp.NS_BYTESTREAM)
|
||||
query.setAttr('mode', 'tcp')
|
||||
query.setAttr('sid', file_props['sid'])
|
||||
streamhost = query.setTag('streamhost')
|
||||
streamhost.setAttr('port', unicode(port))
|
||||
streamhost.setAttr('host', ft_override_host_to_send)
|
||||
streamhost.setAttr('jid', sender)
|
||||
self.connection.send(iq)
|
||||
|
||||
def send_file_rejection(self, file_props):
|
||||
''' informs sender that we refuse to download the file '''
|
||||
# user response to ConfirmationDialog may come after we've disconneted
|
||||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
iq = common.xmpp.Protocol(name = 'iq',
|
||||
to = unicode(file_props['sender']), typ = 'error')
|
||||
iq.setAttr('id', file_props['request-id'])
|
||||
err = common.xmpp.ErrorNode(code = '403', typ = 'cancel', name =
|
||||
'forbidden', text = 'Offer Declined')
|
||||
iq.addChild(node=err)
|
||||
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
|
||||
iq = common.xmpp.Protocol(name = 'iq',
|
||||
to = unicode(file_props['sender']), typ = 'result')
|
||||
iq.setAttr('id', file_props['request-id'])
|
||||
si = iq.setTag('si')
|
||||
si.setNamespace(common.xmpp.NS_SI)
|
||||
if file_props.has_key('offset') and file_props['offset']:
|
||||
file_tag = si.setTag('file')
|
||||
file_tag.setNamespace(common.xmpp.NS_FILE)
|
||||
range_tag = file_tag.setTag('range')
|
||||
range_tag.setAttr('offset', file_props['offset'])
|
||||
feature = si.setTag('feature')
|
||||
feature.setNamespace(common.xmpp.NS_FEATURE)
|
||||
_feature = common.xmpp.DataForm(typ='submit')
|
||||
feature.addChild(node=_feature)
|
||||
field = _feature.setField('stream-method')
|
||||
field.delAttr('type')
|
||||
field.setValue(common.xmpp.NS_BYTESTREAM)
|
||||
self.connection.send(iq)
|
||||
|
||||
def send_file_request(self, file_props):
|
||||
''' send iq for new FT request '''
|
||||
if not self.connection or self.connected < 2:
|
||||
return
|
||||
our_jid = gajim.get_jid_from_account(self.name)
|
||||
frm = our_jid
|
||||
file_props['sender'] = frm
|
||||
fjid = file_props['receiver'].jid
|
||||
iq = common.xmpp.Protocol(name = 'iq', to = fjid,
|
||||
typ = 'set')
|
||||
iq.setID(file_props['sid'])
|
||||
self.files_props[file_props['sid']] = file_props
|
||||
si = iq.setTag('si')
|
||||
si.setNamespace(common.xmpp.NS_SI)
|
||||
si.setAttr('profile', common.xmpp.NS_FILE)
|
||||
si.setAttr('id', file_props['sid'])
|
||||
file_tag = si.setTag('file')
|
||||
file_tag.setNamespace(common.xmpp.NS_FILE)
|
||||
file_tag.setAttr('name', file_props['name'])
|
||||
file_tag.setAttr('size', file_props['size'])
|
||||
desc = file_tag.setTag('desc')
|
||||
if file_props.has_key('desc'):
|
||||
desc.setData(file_props['desc'])
|
||||
file_tag.setTag('range')
|
||||
feature = si.setTag('feature')
|
||||
feature.setNamespace(common.xmpp.NS_FEATURE)
|
||||
_feature = common.xmpp.DataForm(typ='form')
|
||||
feature.addChild(node=_feature)
|
||||
field = _feature.setField('stream-method')
|
||||
field.setAttr('type', 'list-single')
|
||||
field.addOption(common.xmpp.NS_BYTESTREAM)
|
||||
self.connection.send(iq)
|
||||
|
||||
def _result_socks5_sid(self, sid, hash_id):
|
||||
''' store the result of sha message from auth. '''
|
||||
if not self.files_props.has_key(sid):
|
||||
return
|
||||
file_props = self.files_props[sid]
|
||||
file_props['hash'] = hash_id
|
||||
return
|
||||
|
||||
def _connect_error(self, to, _id, sid, code = 404):
|
||||
''' cb, when there is an error establishing BS connection, or
|
||||
when connection is rejected'''
|
||||
msg_dict = {
|
||||
404: 'Could not connect to given hosts',
|
||||
405: 'Cancel',
|
||||
406: 'Not acceptable',
|
||||
}
|
||||
msg = msg_dict[code]
|
||||
iq = None
|
||||
iq = common.xmpp.Protocol(name = 'iq', to = to,
|
||||
typ = 'error')
|
||||
iq.setAttr('id', _id)
|
||||
err = iq.setTag('error')
|
||||
err.setAttr('code', unicode(code))
|
||||
err.setData(msg)
|
||||
self.connection.send(iq)
|
||||
if code == 404:
|
||||
file_props = gajim.socks5queue.get_file_props(self.name, sid)
|
||||
if file_props is not None:
|
||||
self.disconnect_transfer(file_props)
|
||||
file_props['error'] = -3
|
||||
self.dispatch('FILE_REQUEST_ERROR', (to, file_props, msg))
|
||||
|
||||
def _proxy_auth_ok(self, proxy):
|
||||
'''cb, called after authentication to proxy server '''
|
||||
file_props = self.files_props[proxy['sid']]
|
||||
iq = common.xmpp.Protocol(name = 'iq', to = proxy['initiator'],
|
||||
typ = 'set')
|
||||
auth_id = "au_" + proxy['sid']
|
||||
iq.setID(auth_id)
|
||||
query = iq.setTag('query')
|
||||
query.setNamespace(common.xmpp.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):
|
||||
gajim.log.debug('_bytestreamErrorCB')
|
||||
id = unicode(iq_obj.getAttr('id'))
|
||||
frm = unicode(iq_obj.getFrom())
|
||||
query = iq_obj.getTag('query')
|
||||
gajim.proxy65_manager.error_cb(frm, query)
|
||||
jid = unicode(iq_obj.getFrom())
|
||||
id = id[3:]
|
||||
if not self.files_props.has_key(id):
|
||||
return
|
||||
file_props = self.files_props[id]
|
||||
file_props['error'] = -4
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _bytestreamSetCB(self, con, iq_obj):
|
||||
gajim.log.debug('_bytestreamSetCB')
|
||||
target = unicode(iq_obj.getAttr('to'))
|
||||
id = unicode(iq_obj.getAttr('id'))
|
||||
query = iq_obj.getTag('query')
|
||||
sid = unicode(query.getAttr('sid'))
|
||||
file_props = gajim.socks5queue.get_file_props(
|
||||
self.name, sid)
|
||||
streamhosts=[]
|
||||
for item in query.getChildren():
|
||||
if item.getName() == 'streamhost':
|
||||
host_dict={
|
||||
'state': 0,
|
||||
'target': target,
|
||||
'id': id,
|
||||
'sid': sid,
|
||||
'initiator': unicode(iq_obj.getFrom())
|
||||
}
|
||||
for attr in item.getAttrs():
|
||||
host_dict[attr] = item.getAttr(attr)
|
||||
streamhosts.append(host_dict)
|
||||
if file_props is None:
|
||||
if self.files_props.has_key(sid):
|
||||
file_props = self.files_props[sid]
|
||||
file_props['fast'] = streamhosts
|
||||
if file_props['type'] == 's':
|
||||
if file_props.has_key('streamhosts'):
|
||||
file_props['streamhosts'].extend(streamhosts)
|
||||
else:
|
||||
file_props['streamhosts'] = streamhosts
|
||||
if not gajim.socks5queue.get_file_props(self.name, sid):
|
||||
gajim.socks5queue.add_file_props(self.name, file_props)
|
||||
gajim.socks5queue.connect_to_hosts(self.name, sid,
|
||||
self.send_success_connect_reply, None)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
file_props['streamhosts'] = streamhosts
|
||||
if file_props['type'] == 'r':
|
||||
gajim.socks5queue.connect_to_hosts(self.name, sid,
|
||||
self.send_success_connect_reply, self._connect_error)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _ResultCB(self, con, iq_obj):
|
||||
gajim.log.debug('_ResultCB')
|
||||
# if we want to respect jep-0065 we have to check for proxy
|
||||
# activation result in any result iq
|
||||
real_id = unicode(iq_obj.getAttr('id'))
|
||||
if real_id[:3] != 'au_':
|
||||
return
|
||||
frm = unicode(iq_obj.getFrom())
|
||||
id = real_id[3:]
|
||||
if self.files_props.has_key(id):
|
||||
file_props = self.files_props[id]
|
||||
if file_props['streamhost-used']:
|
||||
for host in file_props['proxyhosts']:
|
||||
if host['initiator'] == frm and host.has_key('idx'):
|
||||
gajim.socks5queue.activate_proxy(host['idx'])
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _bytestreamResultCB(self, con, iq_obj):
|
||||
gajim.log.debug('_bytestreamResultCB')
|
||||
frm = unicode(iq_obj.getFrom())
|
||||
real_id = unicode(iq_obj.getAttr('id'))
|
||||
query = iq_obj.getTag('query')
|
||||
gajim.proxy65_manager.resolve_result(frm, query)
|
||||
|
||||
try:
|
||||
streamhost = query.getTag('streamhost-used')
|
||||
except: # this bytestream result is not what we need
|
||||
pass
|
||||
id = real_id[3:]
|
||||
if self.files_props.has_key(id):
|
||||
file_props = self.files_props[id]
|
||||
else:
|
||||
raise common.xmpp.NodeProcessed
|
||||
if streamhost is None:
|
||||
# proxy approves the activate query
|
||||
if real_id[:3] == 'au_':
|
||||
id = real_id[3:]
|
||||
if not file_props.has_key('streamhost-used') or \
|
||||
file_props['streamhost-used'] is False:
|
||||
raise common.xmpp.NodeProcessed
|
||||
if not file_props.has_key('proxyhosts'):
|
||||
raise common.xmpp.NodeProcessed
|
||||
for host in file_props['proxyhosts']:
|
||||
if host['initiator'] == frm and \
|
||||
unicode(query.getAttr('sid')) == file_props['sid']:
|
||||
gajim.socks5queue.activate_proxy(host['idx'])
|
||||
break
|
||||
raise common.xmpp.NodeProcessed
|
||||
jid = streamhost.getAttr('jid')
|
||||
if file_props.has_key('streamhost-used') and \
|
||||
file_props['streamhost-used'] is True:
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
if real_id[:3] == 'au_':
|
||||
gajim.socks5queue.send_file(file_props, self.name)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
proxy = None
|
||||
if file_props.has_key('proxyhosts'):
|
||||
for proxyhost in file_props['proxyhosts']:
|
||||
if proxyhost['jid'] == jid:
|
||||
proxy = proxyhost
|
||||
|
||||
if proxy != None:
|
||||
file_props['streamhost-used'] = True
|
||||
if not file_props.has_key('streamhosts'):
|
||||
file_props['streamhosts'] = []
|
||||
file_props['streamhosts'].append(proxy)
|
||||
file_props['is_a_proxy'] = True
|
||||
receiver = socks5.Socks5Receiver(gajim.idlequeue, proxy, file_props['sid'], file_props)
|
||||
gajim.socks5queue.add_receiver(self.name, receiver)
|
||||
proxy['idx'] = receiver.queue_idx
|
||||
gajim.socks5queue.on_success = self._proxy_auth_ok
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
else:
|
||||
gajim.socks5queue.send_file(file_props, self.name)
|
||||
if file_props.has_key('fast'):
|
||||
fasts = file_props['fast']
|
||||
if len(fasts) > 0:
|
||||
self._connect_error(frm, fasts[0]['id'], file_props['sid'],
|
||||
code = 406)
|
||||
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _siResultCB(self, con, iq_obj):
|
||||
gajim.log.debug('_siResultCB')
|
||||
self.peerhost = con._owner.Connection._sock.getsockname()
|
||||
id = iq_obj.getAttr('id')
|
||||
if not self.files_props.has_key(id):
|
||||
# no such jid
|
||||
return
|
||||
file_props = self.files_props[id]
|
||||
if file_props is None:
|
||||
# file properties for jid is none
|
||||
return
|
||||
if file_props.has_key('request-id'):
|
||||
# we have already sent streamhosts info
|
||||
return
|
||||
file_props['receiver'] = unicode(iq_obj.getFrom())
|
||||
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() != common.xmpp.NS_FEATURE:
|
||||
return
|
||||
form_tag = feature.getTag('x')
|
||||
form = common.xmpp.DataForm(node=form_tag)
|
||||
field = form.getField('stream-method')
|
||||
if field.getValue() != common.xmpp.NS_BYTESTREAM:
|
||||
return
|
||||
self.send_socks5_info(file_props, fast = True)
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _siSetCB(self, con, iq_obj):
|
||||
gajim.log.debug('_siSetCB')
|
||||
jid = unicode(iq_obj.getFrom())
|
||||
si = iq_obj.getTag('si')
|
||||
profile = si.getAttr('profile')
|
||||
mime_type = si.getAttr('mime-type')
|
||||
if profile != common.xmpp.NS_FILE:
|
||||
return
|
||||
file_tag = si.getTag('file')
|
||||
file_props = {'type': 'r'}
|
||||
for attribute in file_tag.getAttrs():
|
||||
if attribute in ('name', 'size', 'hash', 'date'):
|
||||
val = file_tag.getAttr(attribute)
|
||||
if val is None:
|
||||
continue
|
||||
file_props[attribute] = val
|
||||
file_desc_tag = file_tag.getTag('desc')
|
||||
if file_desc_tag is not None:
|
||||
file_props['desc'] = file_desc_tag.getData()
|
||||
|
||||
if mime_type is not None:
|
||||
file_props['mime-type'] = mime_type
|
||||
our_jid = gajim.get_jid_from_account(self.name)
|
||||
file_props['receiver'] = our_jid
|
||||
file_props['sender'] = unicode(iq_obj.getFrom())
|
||||
file_props['request-id'] = unicode(iq_obj.getAttr('id'))
|
||||
file_props['sid'] = unicode(si.getAttr('id'))
|
||||
gajim.socks5queue.add_file_props(self.name, file_props)
|
||||
self.dispatch('FILE_REQUEST', (jid, file_props))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
def _siErrorCB(self, con, iq_obj):
|
||||
gajim.log.debug('_siErrorCB')
|
||||
si = iq_obj.getTag('si')
|
||||
profile = si.getAttr('profile')
|
||||
if profile != common.xmpp.NS_FILE:
|
||||
return
|
||||
id = iq_obj.getAttr('id')
|
||||
if not self.files_props.has_key(id):
|
||||
# no such jid
|
||||
return
|
||||
file_props = self.files_props[id]
|
||||
if file_props is None:
|
||||
# file properties for jid is none
|
||||
return
|
||||
jid = unicode(iq_obj.getFrom())
|
||||
file_props['error'] = -3
|
||||
self.dispatch('FILE_REQUEST_ERROR', (jid, file_props, ''))
|
||||
raise common.xmpp.NodeProcessed
|
||||
|
||||
|
||||
|
||||
class ConnectionVcard:
|
||||
def __init__(self):
|
||||
self.vcard_sha = None
|
||||
self.vcard_shas = {} # sha of contacts
|
||||
self.room_jids = [] # list of gc jids so that vcard are saved in a folder
|
||||
|
||||
def add_sha(self, p, send_caps = True):
|
||||
'''
|
||||
c = p.setTag('x', namespace = common.xmpp.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
|
||||
'''
|
||||
pass
|
||||
|
||||
def add_caps(self, p):
|
||||
'''
|
||||
# advertise our capabilities in presence stanza (jep-0115)
|
||||
c = p.setTag('c', namespace = common.xmpp.NS_CAPS)
|
||||
c.setAttr('node', 'http://gajim.org/caps')
|
||||
c.setAttr('ext', 'ftrans')
|
||||
c.setAttr('ver', gajim.version)
|
||||
return p
|
||||
'''
|
||||
pass
|
||||
|
||||
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
|
||||
if not dict.has_key(name):
|
||||
dict[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):
|
||||
# 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, 0700)
|
||||
puny_nick = helpers.sanitize_filename(nick)
|
||||
path_to_file = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
|
||||
else:
|
||||
path_to_file = path
|
||||
fil = open(path_to_file, 'w')
|
||||
fil.write(str(card))
|
||||
fil.close()
|
||||
|
||||
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)
|
||||
c = f.read()
|
||||
f.close()
|
||||
card = common.xmpp.Node(node = c)
|
||||
vcard = self.node_to_dict(card)
|
||||
if vcard.has_key('PHOTO'):
|
||||
if not isinstance(vcard['PHOTO'], dict):
|
||||
del vcard['PHOTO']
|
||||
elif vcard['PHOTO'].has_key('SHA'):
|
||||
cached_sha = vcard['PHOTO']['SHA']
|
||||
if self.vcard_shas.has_key(jid) 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, is_fake_jid = False):
|
||||
'''request the VCARD. If is_fake_jid is True, it means we request a vcard
|
||||
to a fake jid, like in private messages in groupchat'''
|
||||
if not self.connection:
|
||||
return
|
||||
'''
|
||||
iq = common.xmpp.Iq(typ = 'get')
|
||||
if jid:
|
||||
iq.setTo(jid)
|
||||
iq.setTag(common.xmpp.NS_VCARD + ' vCard')
|
||||
|
||||
id = self.connection.getAnID()
|
||||
iq.setID(id)
|
||||
self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
|
||||
if is_fake_jid:
|
||||
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
|
||||
if not room_jid in self.room_jids:
|
||||
self.room_jids.append(room_jid)
|
||||
self.connection.send(iq)
|
||||
#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
|
||||
'''
|
||||
pass
|
||||
|
||||
def send_vcard(self, vcard):
|
||||
if not self.connection:
|
||||
return
|
||||
'''
|
||||
iq = common.xmpp.Iq(typ = 'set')
|
||||
iq2 = iq.setTag(common.xmpp.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 type(vcard[i]) == type([]):
|
||||
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)
|
||||
|
||||
# Add the sha of the avatar
|
||||
if vcard.has_key('PHOTO') and isinstance(vcard['PHOTO'], dict) and \
|
||||
vcard['PHOTO'].has_key('BINVAL'):
|
||||
photo = vcard['PHOTO']['BINVAL']
|
||||
photo_decoded = base64.decodestring(photo)
|
||||
our_jid = gajim.get_jid_from_account(self.name)
|
||||
gajim.interface.save_avatar_files(our_jid, photo_decoded)
|
||||
avatar_sha = sha.sha(photo_decoded).hexdigest()
|
||||
iq2.getTag('PHOTO').setTagData('SHA', avatar_sha)
|
||||
|
||||
self.awaiting_answers[id] = (VCARD_PUBLISHED, iq2)
|
||||
'''
|
||||
pass
|
||||
|
||||
class ConnectionHandlersZeroconf(ConnectionVcard, ConnectionBytestream):
|
||||
def __init__(self):
|
||||
ConnectionVcard.__init__(self)
|
||||
ConnectionBytestream.__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 = []
|
||||
try:
|
||||
idle.init()
|
||||
except:
|
||||
HAS_IDLE = False
|
||||
|
||||
def _messageCB(self, ip, con, msg):
|
||||
'''Called when we receive a message'''
|
||||
msgtxt = msg.getBody()
|
||||
msghtml = msg.getXHTML()
|
||||
mtype = msg.getType()
|
||||
subject = msg.getSubject() # if not there, it's None
|
||||
tim = msg.getTimestamp()
|
||||
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
||||
tim = time.localtime(timegm(tim))
|
||||
frm = msg.getFrom()
|
||||
if frm == None:
|
||||
for key in self.connection.zeroconf.contacts:
|
||||
if ip == self.connection.zeroconf.contacts[key][zeroconf.C_ADDRESS]:
|
||||
frm = key
|
||||
frm = unicode(frm)
|
||||
jid = frm
|
||||
no_log_for = gajim.config.get_per('accounts', self.name,
|
||||
'no_log_for').split()
|
||||
encrypted = False
|
||||
chatstate = None
|
||||
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
||||
decmsg = ''
|
||||
# invitations
|
||||
invite = None
|
||||
if not encTag:
|
||||
invite = msg.getTag('x', namespace = common.xmpp.NS_MUC_USER)
|
||||
if invite and not invite.getTag('invite'):
|
||||
invite = None
|
||||
delayed = msg.getTag('x', namespace = common.xmpp.NS_DELAY) != None
|
||||
msg_id = None
|
||||
composing_jep = None
|
||||
xtags = msg.getTags('x')
|
||||
# chatstates - look for chatstate tags in a message if not delayed
|
||||
if not delayed:
|
||||
composing_jep = False
|
||||
children = msg.getChildren()
|
||||
for child in children:
|
||||
if child.getNamespace() == 'http://jabber.org/protocol/chatstates':
|
||||
chatstate = child.getName()
|
||||
composing_jep = 'JEP-0085'
|
||||
break
|
||||
# No JEP-0085 support, fallback to JEP-0022
|
||||
if not chatstate:
|
||||
chatstate_child = msg.getTag('x', namespace = common.xmpp.NS_EVENT)
|
||||
if chatstate_child:
|
||||
chatstate = 'active'
|
||||
composing_jep = 'JEP-0022'
|
||||
if not msgtxt and chatstate_child.getTag('composing'):
|
||||
chatstate = 'composing'
|
||||
# JEP-0172 User Nickname
|
||||
user_nick = msg.getTagData('nick')
|
||||
if not user_nick:
|
||||
user_nick = ''
|
||||
|
||||
if encTag and GnuPG.USE_GPG:
|
||||
#decrypt
|
||||
encmsg = encTag.getData()
|
||||
|
||||
keyID = gajim.config.get_per('accounts', self.name, 'keyid')
|
||||
if keyID:
|
||||
decmsg = self.gpg.decrypt(encmsg, keyID)
|
||||
if decmsg:
|
||||
msgtxt = decmsg
|
||||
encrypted = True
|
||||
if mtype == 'error':
|
||||
error_msg = msg.getError()
|
||||
if not error_msg:
|
||||
error_msg = msgtxt
|
||||
msgtxt = None
|
||||
if self.name not in no_log_for:
|
||||
gajim.logger.write('error', frm, error_msg, tim = tim,
|
||||
subject = subject)
|
||||
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
|
||||
tim))
|
||||
elif mtype == 'chat': # it's type 'chat'
|
||||
if not msg.getTag('body') and chatstate is None: #no <body>
|
||||
return
|
||||
if msg.getTag('body') and self.name not in no_log_for and jid not in\
|
||||
no_log_for and msgtxt:
|
||||
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
||||
subject = subject)
|
||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, mtype, subject,
|
||||
chatstate, msg_id, composing_jep, user_nick, msghtml))
|
||||
elif mtype == 'normal': # it's single message
|
||||
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
||||
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
||||
subject = subject)
|
||||
if invite:
|
||||
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
|
||||
subject, chatstate, msg_id, composing_jep, user_nick))
|
||||
# END messageCB
|
||||
'''
|
||||
def build_http_auth_answer(self, iq_obj, answer):
|
||||
if answer == 'yes':
|
||||
iq = iq_obj.buildReply('result')
|
||||
elif answer == 'no':
|
||||
iq = iq_obj.buildReply('error')
|
||||
iq.setError('not-authorized', 401)
|
||||
self.connection.send(iq)
|
||||
'''
|
||||
|
||||
def parse_data_form(self, node):
|
||||
dic = {}
|
||||
tag = node.getTag('title')
|
||||
if tag:
|
||||
dic['title'] = tag.getData()
|
||||
tag = node.getTag('instructions')
|
||||
if tag:
|
||||
dic['instructions'] = tag.getData()
|
||||
i = 0
|
||||
for child in node.getChildren():
|
||||
if child.getName() != 'field':
|
||||
continue
|
||||
var = child.getAttr('var')
|
||||
ctype = child.getAttr('type')
|
||||
label = child.getAttr('label')
|
||||
if not var and ctype != 'fixed': # We must have var if type != fixed
|
||||
continue
|
||||
dic[i] = {}
|
||||
if var:
|
||||
dic[i]['var'] = var
|
||||
if ctype:
|
||||
dic[i]['type'] = ctype
|
||||
if label:
|
||||
dic[i]['label'] = label
|
||||
tags = child.getTags('value')
|
||||
if len(tags):
|
||||
dic[i]['values'] = []
|
||||
for tag in tags:
|
||||
data = tag.getData()
|
||||
if ctype == 'boolean':
|
||||
if data in ('yes', 'true', 'assent', '1'):
|
||||
data = True
|
||||
else:
|
||||
data = False
|
||||
dic[i]['values'].append(data)
|
||||
tag = child.getTag('desc')
|
||||
if tag:
|
||||
dic[i]['desc'] = tag.getData()
|
||||
option_tags = child.getTags('option')
|
||||
if len(option_tags):
|
||||
dic[i]['options'] = {}
|
||||
j = 0
|
||||
for option_tag in option_tags:
|
||||
dic[i]['options'][j] = {}
|
||||
label = option_tag.getAttr('label')
|
||||
tags = option_tag.getTags('value')
|
||||
dic[i]['options'][j]['values'] = []
|
||||
for tag in tags:
|
||||
dic[i]['options'][j]['values'].append(tag.getData())
|
||||
if not label:
|
||||
label = dic[i]['options'][j]['values'][0]
|
||||
dic[i]['options'][j]['label'] = label
|
||||
j += 1
|
||||
if not dic[i].has_key('values'):
|
||||
dic[i]['values'] = [dic[i]['options'][0]['values'][0]]
|
||||
i += 1
|
||||
return dic
|
||||
|
||||
def store_metacontacts(self, tags):
|
||||
''' fake empty method '''
|
||||
# serverside metacontacts are not supported with zeroconf
|
||||
# (there is no server)
|
||||
pass
|
||||
def remove_transfers_for_contact(self, contact):
|
||||
''' stop all active transfer for contact '''
|
||||
'''for file_props in self.files_props.values():
|
||||
if self.is_transfer_stoped(file_props):
|
||||
continue
|
||||
receiver_jid = unicode(file_props['receiver']).split('/')[0]
|
||||
if contact.jid == receiver_jid:
|
||||
file_props['error'] = -5
|
||||
self.remove_transfer(file_props)
|
||||
self.dispatch('FILE_REQUEST_ERROR', (contact.jid, file_props))
|
||||
sender_jid = unicode(file_props['sender']).split('/')[0]
|
||||
if contact.jid == sender_jid:
|
||||
file_props['error'] = -3
|
||||
self.remove_transfer(file_props)
|
||||
'''
|
||||
pass
|
||||
|
||||
def remove_all_transfers(self):
|
||||
''' stops and removes all active connections from the socks5 pool '''
|
||||
'''
|
||||
for file_props in self.files_props.values():
|
||||
self.remove_transfer(file_props, remove_from_list = False)
|
||||
del(self.files_props)
|
||||
self.files_props = {}
|
||||
'''
|
||||
pass
|
||||
|
||||
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']
|
||||
gajim.socks5queue.remove_file_props(self.name, sid)
|
||||
|
||||
if remove_from_list:
|
||||
if self.files_props.has_key('sid'):
|
||||
del(self.files_props['sid'])
|
||||
'''
|
||||
pass
|
||||
|
500
src/common/zeroconf/connection_zeroconf.py
Normal file
500
src/common/zeroconf/connection_zeroconf.py
Normal file
|
@ -0,0 +1,500 @@
|
|||
## common/zeroconf/connection_zeroconf.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <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-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## 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 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
|
||||
import os
|
||||
import random
|
||||
random.seed()
|
||||
|
||||
import signal
|
||||
if os.name != 'nt':
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
import getpass
|
||||
import gobject
|
||||
import notify
|
||||
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
from common import GnuPG
|
||||
from common.zeroconf import connection_handlers_zeroconf
|
||||
from common.zeroconf import client_zeroconf
|
||||
from connection_handlers_zeroconf import *
|
||||
|
||||
USE_GPG = GnuPG.USE_GPG
|
||||
|
||||
class ConnectionZeroconf(ConnectionHandlersZeroconf):
|
||||
'''Connection class'''
|
||||
def __init__(self, name):
|
||||
ConnectionHandlersZeroconf.__init__(self)
|
||||
# system username
|
||||
self.username = None
|
||||
self.name = name
|
||||
self.connected = 0 # offline
|
||||
self.connection = None
|
||||
self.gpg = None
|
||||
self.is_zeroconf = True
|
||||
self.privacy_rules_supported = False
|
||||
self.status = ''
|
||||
self.old_show = ''
|
||||
self.priority = 0
|
||||
|
||||
self.call_resolve_timeout = False
|
||||
|
||||
#self.time_to_reconnect = None
|
||||
#self.new_account_info = None
|
||||
self.bookmarks = []
|
||||
|
||||
#we don't need a password, but must be non-empty
|
||||
self.password = 'zeroconf'
|
||||
|
||||
self.autoconnect = False
|
||||
self.sync_with_global_status = True
|
||||
self.no_log_for = False
|
||||
|
||||
# Do we continue connection when we get roster (send presence,get vcard...)
|
||||
self.continue_connect_info = None
|
||||
if USE_GPG:
|
||||
self.gpg = GnuPG.GnuPG()
|
||||
gajim.config.set('usegpg', True)
|
||||
else:
|
||||
gajim.config.set('usegpg', False)
|
||||
|
||||
self.get_config_values_or_default()
|
||||
|
||||
self.muc_jid = {} # jid of muc server for each transport type
|
||||
self.vcard_supported = False
|
||||
|
||||
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)
|
||||
|
||||
#XXX make sure host is US-ASCII
|
||||
self.host = unicode(socket.gethostname())
|
||||
gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', self.host)
|
||||
gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', 5298)
|
||||
gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'is_zeroconf', True)
|
||||
self.host = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname')
|
||||
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.no_log_for = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for')
|
||||
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 = unicode(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 dispatch(self, event, data):
|
||||
if gajim.handlers.has_key(event):
|
||||
gajim.handlers[event](self.name, data)
|
||||
|
||||
def _reconnect(self):
|
||||
gajim.log.debug('reconnect')
|
||||
|
||||
signed = self.get_signed_msg(self.status)
|
||||
self.reconnect()
|
||||
|
||||
def quit(self, kill_core):
|
||||
if kill_core and self.connected > 1:
|
||||
self.disconnect()
|
||||
|
||||
def disable_account(self):
|
||||
self.disconnect()
|
||||
|
||||
def test_gpg_passphrase(self, password):
|
||||
self.gpg.passphrase = password
|
||||
keyID = gajim.config.get_per('accounts', self.name, 'keyid')
|
||||
signed = self.gpg.sign('test', keyID)
|
||||
self.gpg.password = None
|
||||
return signed != 'BAD_PASSPHRASE'
|
||||
|
||||
def get_signed_msg(self, msg):
|
||||
signed = ''
|
||||
keyID = gajim.config.get_per('accounts', self.name, 'keyid')
|
||||
if keyID and USE_GPG:
|
||||
use_gpg_agent = gajim.config.get('use_gpg_agent')
|
||||
if self.connected < 2 and self.gpg.passphrase is None and \
|
||||
not use_gpg_agent:
|
||||
# We didn't set a passphrase
|
||||
self.dispatch('ERROR', (_('OpenPGP passphrase was not given'),
|
||||
#%s is the account name here
|
||||
_('You will be connected to %s without OpenPGP.') % self.name))
|
||||
elif self.gpg.passphrase is not None or use_gpg_agent:
|
||||
signed = self.gpg.sign(msg, keyID)
|
||||
if signed == 'BAD_PASSPHRASE':
|
||||
signed = ''
|
||||
if self.connected < 2:
|
||||
self.dispatch('BAD_PASSPHRASE', ())
|
||||
return signed
|
||||
|
||||
def _on_resolve_timeout(self):
|
||||
if self.connected:
|
||||
self.connection.resolve_all()
|
||||
diffs = self.roster.getDiffs()
|
||||
for key in diffs:
|
||||
self.roster.setItem(key)
|
||||
self.dispatch('ROSTER_INFO', (key, self.roster.getName(key),
|
||||
'both', 'no', self.roster.getGroups(key)))
|
||||
self.dispatch('NOTIFY', (key, self.roster.getStatus(key),
|
||||
self.roster.getMessage(key), 'local', 0, None, 0))
|
||||
#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)
|
||||
self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
|
||||
self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
|
||||
|
||||
def _on_remove_service(self, jid):
|
||||
self.roster.delItem(jid)
|
||||
# 'NOTIFY' (account, (jid, status, status message, resource, priority,
|
||||
# keyID, timestamp))
|
||||
self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0))
|
||||
|
||||
def _on_disconnected(self):
|
||||
self.disconnect()
|
||||
self.dispatch('STATUS', 'offline')
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Connection with account "%s" has been lost') % self.name,
|
||||
_('To continue sending and receiving messages, you will need to reconnect.')))
|
||||
self.status = 'offline'
|
||||
self.disconnect()
|
||||
|
||||
def _on_name_conflictCB(self, alt_name):
|
||||
self.disconnect()
|
||||
self.dispatch('STATUS', 'offline')
|
||||
self.dispatch('ZC_NAME_CONFLICT', alt_name)
|
||||
|
||||
def _on_error(self, message):
|
||||
self.dispatch('ERROR', (_('Avahi error'), _("%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 self.connection.test_avahi():
|
||||
self.dispatch('STATUS', 'offline')
|
||||
self.status = 'offline'
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Could not connect to "%s"') % self.name,
|
||||
_('Please check if Avahi is installed.')))
|
||||
self.disconnect()
|
||||
return
|
||||
result = self.connection.connect(show, msg)
|
||||
if not result:
|
||||
self.dispatch('STATUS', 'offline')
|
||||
self.status = 'offline'
|
||||
if result is False:
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Could not start local service'),
|
||||
_('Unable to bind to port %d.' % self.port)))
|
||||
else: # result is None
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Could not start local service'),
|
||||
_('Please check if avahi-daemon is running.')))
|
||||
self.disconnect()
|
||||
return
|
||||
else:
|
||||
self.connection.announce()
|
||||
self.roster = self.connection.getRoster()
|
||||
self.dispatch('ROSTER', self.roster)
|
||||
|
||||
#display contacts already detected and resolved
|
||||
for jid in self.roster.keys():
|
||||
self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid)))
|
||||
self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0))
|
||||
|
||||
self.connected = STATUS_LIST.index(show)
|
||||
|
||||
# refresh all contacts data every five seconds
|
||||
self.call_resolve_timeout = True
|
||||
gobject.timeout_add(5000, 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 change_status(self, show, msg, sync = False, auto = False):
|
||||
if not show in STATUS_LIST:
|
||||
return -1
|
||||
self.status = show
|
||||
|
||||
check = True #to check for errors from zeroconf
|
||||
# 'connect'
|
||||
if show != 'offline' and not self.connected:
|
||||
if not self.connect(show, msg):
|
||||
return
|
||||
if show != 'invisible':
|
||||
check = self.connection.announce()
|
||||
else:
|
||||
self.connected = STATUS_LIST.index(show)
|
||||
|
||||
# 'disconnect'
|
||||
elif show == 'offline' and self.connected:
|
||||
self.disconnect()
|
||||
|
||||
# update status
|
||||
elif show != 'offline' and self.connected:
|
||||
was_invisible = self.connected == STATUS_LIST.index('invisible')
|
||||
self.connected = STATUS_LIST.index(show)
|
||||
if show == 'invisible':
|
||||
check = check and self.connection.remove_announce()
|
||||
elif was_invisible:
|
||||
if not self.connected:
|
||||
check = check and self.connect(show, msg)
|
||||
check = check and self.connection.announce()
|
||||
if self.connection and not show == 'invisible':
|
||||
check = check and self.connection.set_show_msg(show, msg)
|
||||
|
||||
#stay offline when zeroconf does something wrong
|
||||
if check:
|
||||
self.dispatch('STATUS', show)
|
||||
else:
|
||||
# show notification that avahi or system bus is down
|
||||
self.dispatch('STATUS', 'offline')
|
||||
self.status = 'offline'
|
||||
self.dispatch('CONNECTION_LOST',
|
||||
(_('Could not change status of account "%s"') % self.name,
|
||||
_('Please check if avahi-daemon is running.')))
|
||||
|
||||
def get_status(self):
|
||||
return STATUS_LIST[self.connected]
|
||||
|
||||
def send_message(self, jid, msg, keyID, type = 'chat', subject='',
|
||||
chatstate = None, msg_id = None, composing_jep = None, resource = None,
|
||||
user_nick = None):
|
||||
fjid = jid
|
||||
|
||||
if not self.connection:
|
||||
return
|
||||
if not msg and chatstate is None:
|
||||
return
|
||||
|
||||
if self.status in ('invisible', 'offline'):
|
||||
self.dispatch('MSGERROR', [unicode(jid), '-1', _('You are not connected or not visible to others. Your message could not be sent.'), None, None])
|
||||
return
|
||||
|
||||
msgtxt = msg
|
||||
msgenc = ''
|
||||
if keyID and USE_GPG:
|
||||
#encrypt
|
||||
msgenc = self.gpg.encrypt(msg, [keyID])
|
||||
if msgenc:
|
||||
msgtxt = '[This message is encrypted]'
|
||||
lang = os.getenv('LANG')
|
||||
if lang is not None or lang != 'en': # we're not english
|
||||
msgtxt = _('[This message is encrypted]') +\
|
||||
' ([This message is encrypted])' # one in locale and one en
|
||||
|
||||
|
||||
if type == 'chat':
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt, typ = type)
|
||||
|
||||
else:
|
||||
if subject:
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
|
||||
typ = 'normal', subject = subject)
|
||||
else:
|
||||
msg_iq = common.xmpp.Message(to = fjid, body = msgtxt,
|
||||
typ = 'normal')
|
||||
|
||||
if msgenc:
|
||||
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
|
||||
|
||||
# chatstates - if peer supports jep85 or jep22, send chatstates
|
||||
# please note that the only valid tag inside a message containing a <body>
|
||||
# tag is the active event
|
||||
if chatstate is not None:
|
||||
if composing_jep == 'JEP-0085' or not composing_jep:
|
||||
# JEP-0085
|
||||
msg_iq.setTag(chatstate, namespace = common.xmpp.NS_CHATSTATES)
|
||||
if composing_jep == 'JEP-0022' or not composing_jep:
|
||||
# JEP-0022
|
||||
chatstate_node = msg_iq.setTag('x', namespace = common.xmpp.NS_EVENT)
|
||||
if not msgtxt: # when no <body>, add <id>
|
||||
if not msg_id: # avoid putting 'None' in <id> tag
|
||||
msg_id = ''
|
||||
chatstate_node.setTagData('id', msg_id)
|
||||
# when msgtxt, requests JEP-0022 composing notification
|
||||
if chatstate is 'composing' or msgtxt:
|
||||
chatstate_node.addChild(name = 'composing')
|
||||
|
||||
if not self.connection.send(msg_iq, msg != None):
|
||||
return
|
||||
|
||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')
|
||||
ji = gajim.get_jid_without_resource(jid)
|
||||
if self.name not in no_log_for and ji not in no_log_for:
|
||||
log_msg = msg
|
||||
if subject:
|
||||
log_msg = _('Subject: %s\n%s') % (subject, msg)
|
||||
if log_msg:
|
||||
if type == 'chat':
|
||||
kind = 'chat_msg_sent'
|
||||
else:
|
||||
kind = 'single_msg_sent'
|
||||
gajim.logger.write(kind, jid, log_msg)
|
||||
|
||||
self.dispatch('MSGSENT', (jid, msg, keyID))
|
||||
|
||||
def send_stanza(self, stanza):
|
||||
# send a stanza untouched
|
||||
if not self.connection:
|
||||
return
|
||||
self.connection.send(stanza)
|
||||
|
||||
def ack_subscribed(self, jid):
|
||||
gajim.log.debug('This should not happen (ack_subscribed)')
|
||||
|
||||
def ack_unsubscribed(self, jid):
|
||||
gajim.log.debug('This should not happen (ack_unsubscribed)')
|
||||
|
||||
def request_subscription(self, jid, msg = '', name = '', groups = [],
|
||||
auto_auth = False):
|
||||
gajim.log.debug('This should not happen (request_subscription)')
|
||||
|
||||
def send_authorization(self, jid):
|
||||
gajim.log.debug('This should not happen (send_authorization)')
|
||||
|
||||
def refuse_authorization(self, jid):
|
||||
gajim.log.debug('This should not happen (refuse_authorization)')
|
||||
|
||||
def unsubscribe(self, jid, remove_auth = True):
|
||||
gajim.log.debug('This should not happen (unsubscribe)')
|
||||
|
||||
def unsubscribe_agent(self, agent):
|
||||
gajim.log.debug('This should not happen (unsubscribe_agent)')
|
||||
|
||||
def update_contact(self, jid, name, groups):
|
||||
if self.connection:
|
||||
self.connection.getRoster().setItem(jid = jid, name = name,
|
||||
groups = groups)
|
||||
|
||||
def new_account(self, name, config, sync = False):
|
||||
gajim.log.debug('This should not happen (new_account)')
|
||||
|
||||
def _on_new_account(self, con = None, con_type = None):
|
||||
gajim.log.debug('This should not happen (_on_new_account)')
|
||||
|
||||
def account_changed(self, new_name):
|
||||
self.name = new_name
|
||||
|
||||
def request_last_status_time(self, jid, resource):
|
||||
gajim.log.debug('This should not happen (request_last_status_time)')
|
||||
|
||||
def request_os_info(self, jid, resource):
|
||||
gajim.log.debug('This should not happen (request_os_info)')
|
||||
|
||||
def get_settings(self):
|
||||
gajim.log.debug('This should not happen (get_settings)')
|
||||
|
||||
def get_bookmarks(self):
|
||||
gajim.log.debug('This should not happen (get_bookmarks)')
|
||||
|
||||
def store_bookmarks(self):
|
||||
gajim.log.debug('This should not happen (store_bookmarks)')
|
||||
|
||||
def get_metacontacts(self):
|
||||
gajim.log.debug('This should not happen (get_metacontacts)')
|
||||
|
||||
def send_agent_status(self, agent, ptype):
|
||||
gajim.log.debug('This should not happen (send_agent_status)')
|
||||
|
||||
def gpg_passphrase(self, passphrase):
|
||||
if USE_GPG:
|
||||
use_gpg_agent = gajim.config.get('use_gpg_agent')
|
||||
if use_gpg_agent:
|
||||
self.gpg.passphrase = None
|
||||
else:
|
||||
self.gpg.passphrase = passphrase
|
||||
|
||||
def ask_gpg_keys(self):
|
||||
if USE_GPG:
|
||||
keys = self.gpg.get_keys()
|
||||
return keys
|
||||
return None
|
||||
|
||||
def ask_gpg_secrete_keys(self):
|
||||
if USE_GPG:
|
||||
keys = self.gpg.get_secret_keys()
|
||||
return keys
|
||||
return None
|
||||
|
||||
def _event_dispatcher(self, realm, event, data):
|
||||
if realm == '':
|
||||
if event == common.xmpp.transports.DATA_RECEIVED:
|
||||
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
|
||||
elif event == common.xmpp.transports.DATA_SENT:
|
||||
self.dispatch('STANZA_SENT', unicode(data))
|
||||
|
||||
# END ConnectionZeroconf
|
156
src/common/zeroconf/roster_zeroconf.py
Normal file
156
src/common/zeroconf/roster_zeroconf.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
## common/zeroconf/roster_zeroconf.py
|
||||
##
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
|
||||
from common.zeroconf import zeroconf
|
||||
|
||||
class Roster:
|
||||
def __init__(self, zeroconf):
|
||||
self._data = None
|
||||
self.zeroconf = zeroconf # our zeroconf instance
|
||||
|
||||
def update_roster(self):
|
||||
for val in self.zeroconf.contacts.values():
|
||||
self.setItem(val[zeroconf.C_NAME])
|
||||
|
||||
def getRoster(self):
|
||||
#print 'roster_zeroconf.py: getRoster'
|
||||
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 self._data.has_key(key):
|
||||
if old_data[key] != self._data[key]:
|
||||
diffs[key] = self._data[key]['status']
|
||||
#print 'roster_zeroconf.py: diffs:' + str(diffs)
|
||||
return diffs
|
||||
|
||||
def setItem(self, jid, name = '', groups = ''):
|
||||
#print 'roster_zeroconf.py: setItem %s' % jid
|
||||
contact = self.zeroconf.get_contact(jid)
|
||||
if not contact:
|
||||
return
|
||||
|
||||
(service_jid, domain, interface, protocol, host, address, port, bare_jid, txt) \
|
||||
= contact
|
||||
|
||||
self._data[jid]={}
|
||||
self._data[jid]['ask'] = 'no' #?
|
||||
self._data[jid]['subscription'] = 'both'
|
||||
self._data[jid]['groups'] = []
|
||||
self._data[jid]['resources'] = {}
|
||||
self._data[jid]['address'] = address
|
||||
self._data[jid]['host'] = host
|
||||
self._data[jid]['port'] = port
|
||||
txt_dict = self.zeroconf.txt_array_to_dict(txt)
|
||||
if txt_dict.has_key('status'):
|
||||
status = txt_dict['status']
|
||||
else:
|
||||
status = ''
|
||||
nm = ''
|
||||
if txt_dict.has_key('1st'):
|
||||
nm = txt_dict['1st']
|
||||
if txt_dict.has_key('last'):
|
||||
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 not self._data[jid]['txt_dict'].has_key('msg'):
|
||||
self._data[jid]['txt_dict']['msg'] = ''
|
||||
self._data[jid]['status'] = status
|
||||
self._data[jid]['show'] = status
|
||||
|
||||
def delItem(self, jid):
|
||||
#print 'roster_zeroconf.py: delItem %s' % jid
|
||||
if self._data.has_key(jid):
|
||||
del self._data[jid]
|
||||
|
||||
def getItem(self, jid):
|
||||
#print 'roster_zeroconf.py: getItem: %s' % jid
|
||||
if self._data.has_key(jid):
|
||||
return self._data[jid]
|
||||
|
||||
def __getitem__(self,jid):
|
||||
#print 'roster_zeroconf.py: __getitem__'
|
||||
return self._data[jid]
|
||||
|
||||
def getItems(self):
|
||||
#print 'roster_zeroconf.py: getItems'
|
||||
# Return list of all [bare] JIDs that the roster currently tracks.
|
||||
return self._data.keys()
|
||||
|
||||
def keys(self):
|
||||
#print 'roster_zeroconf.py: keys'
|
||||
return self._data.keys()
|
||||
|
||||
def getRaw(self):
|
||||
#print 'roster_zeroconf.py: getRaw'
|
||||
return self._data
|
||||
|
||||
def getResources(self, jid):
|
||||
#print 'roster_zeroconf.py: getResources(%s)' % jid
|
||||
return {}
|
||||
|
||||
def getGroups(self, jid):
|
||||
return self._data[jid]['groups']
|
||||
|
||||
def getName(self, jid):
|
||||
if self._data.has_key(jid):
|
||||
return self._data[jid]['name']
|
||||
|
||||
def getStatus(self, jid):
|
||||
if self._data.has_key(jid):
|
||||
return self._data[jid]['status']
|
||||
|
||||
def getMessage(self, jid):
|
||||
if self._data.has_key(jid):
|
||||
return self._data[jid]['txt_dict']['msg']
|
||||
|
||||
def getShow(self, jid):
|
||||
#print 'roster_zeroconf.py: getShow'
|
||||
return getStatus(jid)
|
||||
|
||||
def getPriority(jid):
|
||||
return 5
|
||||
|
||||
def getSubscription(self,jid):
|
||||
#print 'roster_zeroconf.py: getSubscription'
|
||||
return 'both'
|
||||
|
||||
def Subscribe(self,jid):
|
||||
pass
|
||||
|
||||
def Unsubscribe(self,jid):
|
||||
pass
|
||||
|
||||
def Authorize(self,jid):
|
||||
pass
|
||||
|
||||
def Unauthorize(self,jid):
|
||||
pass
|
421
src/common/zeroconf/zeroconf.py
Executable file
421
src/common/zeroconf/zeroconf.py
Executable file
|
@ -0,0 +1,421 @@
|
|||
## common/zeroconf/zeroconf.py
|
||||
##
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
from common import gajim
|
||||
from common import xmpp
|
||||
|
||||
try:
|
||||
import dbus.glib
|
||||
except ImportError, e:
|
||||
pass
|
||||
|
||||
|
||||
C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \
|
||||
C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9)
|
||||
|
||||
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):
|
||||
gajim.log.debug('Error while resolving: ' + str(err))
|
||||
|
||||
def error_callback(self, err):
|
||||
gajim.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):
|
||||
gajim.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):
|
||||
gajim.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():
|
||||
if self.contacts[key][C_BARE_NAME] == name:
|
||||
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:
|
||||
#FIXME: remove when outdated, this is for avahi < 0.6.14
|
||||
if c < 0 or c > 255:
|
||||
c = '.'
|
||||
else:
|
||||
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.decode('utf-8')
|
||||
return txt_dict
|
||||
|
||||
def service_resolved_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
|
||||
gajim.log.debug('Service data for service %s in domain %s on %i.%i:'
|
||||
% (name, domain, interface, protocol))
|
||||
gajim.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:
|
||||
self.contacts[name] = (name, domain, interface, protocol, host, address, port,
|
||||
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, 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
|
||||
self.contacts[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt)
|
||||
|
||||
def service_added_callback(self):
|
||||
gajim.log.debug('Service successfully added')
|
||||
|
||||
def service_committed_callback(self):
|
||||
gajim.log.debug('Service successfully committed')
|
||||
|
||||
def service_updated_callback(self):
|
||||
gajim.log.debug('Service successfully updated')
|
||||
|
||||
def service_add_fail_callback(self, err):
|
||||
gajim.log.debug('Error while adding service. %s' % str(err))
|
||||
if str(err) == 'Local name collision':
|
||||
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):
|
||||
if state == self.avahi.SERVER_RUNNING:
|
||||
self.create_service()
|
||||
elif state == self.avahi.SERVER_COLLISION:
|
||||
self.entrygroup.Reset()
|
||||
elif state == self.avahi.CLIENT_FAILURE:
|
||||
# does it ever go here?
|
||||
gajim.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:
|
||||
self.service_add_fail_callback('Local name collision')
|
||||
elif state == self.avahi.ENTRY_GROUP_FAILURE:
|
||||
gajim.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):
|
||||
utf8_dict = {}
|
||||
for key in self.txt:
|
||||
val = self.txt[key]
|
||||
if isinstance(val, unicode):
|
||||
utf8_dict[key] = val.encode('utf-8')
|
||||
else:
|
||||
utf8_dict[key] = val
|
||||
return self.avahi.dict_to_txt_array(utf8_dict)
|
||||
|
||||
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.iteritems():
|
||||
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 self.txt.has_key('status'):
|
||||
txt['status'] = self.replace_show(self.txt['status'])
|
||||
else:
|
||||
txt['status'] = 'avail'
|
||||
|
||||
self.txt = txt
|
||||
gajim.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, '', '', 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.dbus_bindings.DBusException, e:
|
||||
gajim.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:
|
||||
self.create_service()
|
||||
self.announced = True
|
||||
return True
|
||||
|
||||
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
|
||||
obj = self.entrygroup._obj
|
||||
obj._bus = None
|
||||
self.entrygroup._obj = None
|
||||
self.entrygroup = None
|
||||
self.announced = False
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except dbus.dbus_bindings.DBusException, e:
|
||||
gajim.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 != "":
|
||||
gajim.log.debug('Lost connection to avahi-daemon')
|
||||
self.disconnect()
|
||||
if self.disconnected_CB:
|
||||
self.disconnected_CB()
|
||||
else:
|
||||
gajim.log.debug('We are connected to avahi-daemon')
|
||||
|
||||
# connect to dbus
|
||||
def connect_dbus(self):
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
gajim.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, e:
|
||||
# System bus is not present
|
||||
self.bus = None
|
||||
gajim.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:
|
||||
gajim.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, e:
|
||||
# Avahi service is not present
|
||||
self.server = None
|
||||
gajim.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, domain)
|
||||
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
if self.connected:
|
||||
self.connected = False
|
||||
if self.service_browser:
|
||||
self.service_browser.Free()
|
||||
self.service_browser._obj._bus = None
|
||||
self.service_browser._obj = None
|
||||
if self.domain_browser:
|
||||
self.domain_browser.Free()
|
||||
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():
|
||||
self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), val[C_BARE_NAME], \
|
||||
self.stype, val[C_DOMAIN], self.avahi.PROTO_UNSPEC, dbus.UInt32(0),\
|
||||
reply_handler=self.service_resolved_all_callback, error_handler=self.error_callback)
|
||||
|
||||
def get_contacts(self):
|
||||
if not jid in self.contacts:
|
||||
return None
|
||||
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
|
645
src/config.py
645
src/config.py
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,8 @@
|
|||
## conversation_textview.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.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
|
||||
|
@ -27,7 +18,6 @@ import gtk
|
|||
import pango
|
||||
import gobject
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import tooltips
|
||||
import dialogs
|
||||
|
@ -39,12 +29,20 @@ from common import helpers
|
|||
from calendar import timegm
|
||||
from common.fuzzyclock import FuzzyClock
|
||||
|
||||
from htmltextview import HtmlTextView
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
||||
class ConversationTextview:
|
||||
'''Class for the conversation textview (where user reads already said messages)
|
||||
for chat/groupchat windows'''
|
||||
def __init__(self, account):
|
||||
# no need to inherit TextView, use it as property is safer
|
||||
self.tv = gtk.TextView()
|
||||
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'''
|
||||
self.used_in_history_window = used_in_history_window
|
||||
|
||||
# no need to inherit TextView, use it as atrribute is safer
|
||||
self.tv = HtmlTextView()
|
||||
self.tv.html_hyperlink_handler = self.html_hyperlink_handler
|
||||
|
||||
# set properties
|
||||
self.tv.set_border_width(1)
|
||||
|
@ -57,11 +55,13 @@ class ConversationTextview:
|
|||
self.handlers = {}
|
||||
|
||||
# connect signals
|
||||
id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event)
|
||||
id = self.tv.connect('motion_notify_event',
|
||||
self.on_textview_motion_notify_event)
|
||||
self.handlers[id] = self.tv
|
||||
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)
|
||||
id = self.tv.connect('button_press_event',
|
||||
self.on_textview_button_press_event)
|
||||
self.handlers[id] = self.tv
|
||||
|
||||
self.account = account
|
||||
|
@ -98,7 +98,7 @@ class ConversationTextview:
|
|||
tag.set_property('weight', pango.WEIGHT_BOLD)
|
||||
|
||||
tag = buffer.create_tag('time_sometimes')
|
||||
tag.set_property('foreground', 'grey')
|
||||
tag.set_property('foreground', 'darkgrey')
|
||||
tag.set_property('scale', pango.SCALE_SMALL)
|
||||
tag.set_property('justification', gtk.JUSTIFY_CENTER)
|
||||
|
||||
|
@ -138,6 +138,11 @@ class ConversationTextview:
|
|||
self.focus_out_end_iter_offset = None
|
||||
|
||||
self.line_tooltip = tooltips.BaseTooltip()
|
||||
|
||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
|
||||
self.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
|
||||
# use it for hr too
|
||||
self.tv.focus_out_line_pixbuf = self.focus_out_line_pixbuf
|
||||
|
||||
def del_handlers(self):
|
||||
for i in self.handlers.keys():
|
||||
|
@ -145,7 +150,7 @@ class ConversationTextview:
|
|||
self.handlers[i].disconnect(i)
|
||||
del self.handlers
|
||||
self.tv.destroy()
|
||||
#TODO
|
||||
#FIXME:
|
||||
# self.line_tooltip.destroy()
|
||||
|
||||
def update_tags(self):
|
||||
|
@ -230,19 +235,14 @@ class ConversationTextview:
|
|||
end_iter_for_previous_line)
|
||||
|
||||
# add the new focus out line
|
||||
# FIXME: Why is this loaded from disk everytime
|
||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')
|
||||
focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
|
||||
end_iter = buffer.get_end_iter()
|
||||
buffer.insert(end_iter, '\n')
|
||||
buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf)
|
||||
buffer.insert_pixbuf(end_iter, self.focus_out_line_pixbuf)
|
||||
|
||||
end_iter = buffer.get_end_iter()
|
||||
before_img_iter = end_iter.copy()
|
||||
before_img_iter.backward_char() # one char back (an image also takes one char)
|
||||
buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter)
|
||||
#FIXME: remove this workaround when bug is fixed
|
||||
# c http://bugzilla.gnome.org/show_bug.cgi?id=318569
|
||||
|
||||
self.allow_focus_out_line = False
|
||||
|
||||
|
@ -316,19 +316,29 @@ class ConversationTextview:
|
|||
|
||||
def on_textview_populate_popup(self, textview, menu):
|
||||
'''we 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)'''
|
||||
item = gtk.SeparatorMenuItem()
|
||||
menu.prepend(item)
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
|
||||
menu.prepend(item)
|
||||
id = item.connect('activate', self.clear)
|
||||
self.handlers[id] = item
|
||||
|
||||
separator_menuitem_was_added = False
|
||||
if not self.used_in_history_window:
|
||||
item = gtk.SeparatorMenuItem()
|
||||
menu.prepend(item)
|
||||
separator_menuitem_was_added = True
|
||||
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
|
||||
menu.prepend(item)
|
||||
id = item.connect('activate', self.clear)
|
||||
self.handlers[id] = item
|
||||
|
||||
if self.selected_phrase:
|
||||
s = self.selected_phrase
|
||||
if len(s) > 25:
|
||||
s = s[:21] + '...'
|
||||
item = gtk.MenuItem(_('Actions for "%s"') % s)
|
||||
if not separator_menuitem_was_added:
|
||||
item = gtk.SeparatorMenuItem()
|
||||
menu.prepend(item)
|
||||
|
||||
self.selected_phrase = helpers.reduce_chars_newlines(
|
||||
self.selected_phrase, 25, 2)
|
||||
item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase)
|
||||
menu.prepend(item)
|
||||
submenu = gtk.Menu()
|
||||
item.set_submenu(submenu)
|
||||
|
@ -360,19 +370,20 @@ class ConversationTextview:
|
|||
self.handlers[id] = item
|
||||
else:
|
||||
if dict_link.find('%s') == -1:
|
||||
#we must have %s in the url if not WIKTIONARY
|
||||
# we must have %s in the url if not WIKTIONARY
|
||||
item = gtk.MenuItem(_('Dictionary URL is missing an "%s" and it is not WIKTIONARY'))
|
||||
item.set_property('sensitive', False)
|
||||
else:
|
||||
link = dict_link % self.selected_phrase
|
||||
id = item.connect('activate', self.visit_url_from_menuitem, link)
|
||||
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
|
||||
# we must have %s in the url
|
||||
item = gtk.MenuItem(_('Web Search URL is missing an "%s"'))
|
||||
item.set_property('sensitive', False)
|
||||
else:
|
||||
|
@ -381,14 +392,18 @@ class ConversationTextview:
|
|||
id = item.connect('activate', self.visit_url_from_menuitem, link)
|
||||
self.handlers[id] = item
|
||||
submenu.append(item)
|
||||
|
||||
item = gtk.MenuItem(_('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_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 = ''
|
||||
self.selected_phrase = '' # do not move belove event button check!
|
||||
|
||||
if event.button != 3: # if not right click
|
||||
return False
|
||||
|
@ -426,18 +441,16 @@ class ConversationTextview:
|
|||
def on_start_chat_activate(self, widget, jid):
|
||||
gajim.interface.roster.new_chat_from_jid(self.account, jid)
|
||||
|
||||
def on_join_group_chat_menuitem_activate(self, widget, jid):
|
||||
room, server = jid.split('@')
|
||||
if gajim.interface.instances[self.account].has_key('join_gc'):
|
||||
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_widget('server_entry').set_text(server)
|
||||
instance.xml.get_widget('room_entry').set_text(room)
|
||||
instance.xml.get_widget('room_jid_entry').set_text(room_jid)
|
||||
gajim.interface.instances[self.account]['join_gc'].window.present()
|
||||
else:
|
||||
try:
|
||||
gajim.interface.instances[self.account]['join_gc'] = \
|
||||
dialogs.JoinGroupchatWindow(self.account, server, room)
|
||||
except RuntimeError:
|
||||
dialogs.JoinGroupchatWindow(self.account, room_jid)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
|
||||
def on_add_to_roster_activate(self, widget, jid):
|
||||
|
@ -459,6 +472,7 @@ class ConversationTextview:
|
|||
childs[6].hide() # join group chat
|
||||
childs[7].hide() # add to roster
|
||||
else: # It's a mail or a JID
|
||||
text = text.lower()
|
||||
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)
|
||||
|
@ -506,6 +520,15 @@ class ConversationTextview:
|
|||
# we launch the correct application
|
||||
helpers.launch_browser_mailer(kind, word)
|
||||
|
||||
def html_hyperlink_handler(self, texttag, widget, event, iter, kind, href):
|
||||
if event.type == gtk.gdk.BUTTON_PRESS:
|
||||
if event.button == 3: # right click
|
||||
self.make_link_menu(event, kind, href)
|
||||
else:
|
||||
# we launch the correct application
|
||||
helpers.launch_browser_mailer(kind, href)
|
||||
|
||||
|
||||
def detect_and_print_special_text(self, otext, other_tags):
|
||||
'''detects special text (emots & links & formatting)
|
||||
prints normal text before any special text it founts,
|
||||
|
@ -562,6 +585,7 @@ class ConversationTextview:
|
|||
img.show()
|
||||
#add with possible animation
|
||||
self.tv.add_child_at_anchor(img, anchor)
|
||||
#FIXME: one day, somehow sync with regexp in gajim.py
|
||||
elif special_text.startswith('http://') or \
|
||||
special_text.startswith('www.') or \
|
||||
special_text.startswith('ftp://') or \
|
||||
|
@ -638,11 +662,11 @@ class ConversationTextview:
|
|||
def print_empty_line(self):
|
||||
buffer = self.tv.get_buffer()
|
||||
end_iter = buffer.get_end_iter()
|
||||
buffer.insert(end_iter, '\n')
|
||||
buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
|
||||
|
||||
def print_conversation_line(self, text, jid, kind, name, tim,
|
||||
other_tags_for_name = [], other_tags_for_time = [],
|
||||
other_tags_for_text = [], subject = None, old_kind = None):
|
||||
other_tags_for_name = [], other_tags_for_time = [], other_tags_for_text = [],
|
||||
subject = None, old_kind = None, xhtml = None):
|
||||
'''prints 'chat' type messages'''
|
||||
buffer = self.tv.get_buffer()
|
||||
buffer.begin_user_action()
|
||||
|
@ -652,7 +676,7 @@ class ConversationTextview:
|
|||
at_the_end = True
|
||||
|
||||
if buffer.get_char_count() > 0:
|
||||
buffer.insert(end_iter, '\n')
|
||||
buffer.insert_with_tags_by_name(end_iter, '\n', 'eol')
|
||||
if kind == 'incoming_queue':
|
||||
kind = 'incoming'
|
||||
if old_kind == 'incoming_queue':
|
||||
|
@ -664,7 +688,9 @@ class ConversationTextview:
|
|||
current_print_time = gajim.config.get('print_time')
|
||||
if current_print_time == 'always' and kind != 'info':
|
||||
before_str = gajim.config.get('before_time')
|
||||
before_str = helpers.from_one_line(before_str)
|
||||
after_str = gajim.config.get('after_time')
|
||||
after_str = helpers.from_one_line(after_str)
|
||||
# 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)
|
||||
|
@ -682,10 +708,10 @@ class ConversationTextview:
|
|||
format += day_str + ' '
|
||||
format += '%X' + after_str
|
||||
tim_format = time.strftime(format, tim)
|
||||
# if tim_format comes as unicode because of day_str.
|
||||
# we convert it to the encoding that we want (and that is utf-8)
|
||||
tim_format = helpers.ensure_utf8_string(tim_format)
|
||||
tim_format = tim_format.encode('utf-8')
|
||||
if locale.getpreferredencoding() == 'UTF-8':
|
||||
# if tim_format comes as unicode because of day_str.
|
||||
# we convert it to the encoding that we want (and that is utf-8)
|
||||
tim_format = helpers.ensure_utf8_string(tim_format)
|
||||
buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
|
||||
*other_tags_for_time)
|
||||
elif current_print_time == 'sometimes' and kind != 'info':
|
||||
|
@ -725,7 +751,7 @@ class ConversationTextview:
|
|||
else:
|
||||
self.print_name(name, kind, other_tags_for_name)
|
||||
self.print_subject(subject)
|
||||
self.print_real_text(text, text_tags, name)
|
||||
self.print_real_text(text, text_tags, name, xhtml)
|
||||
|
||||
# scroll to the end of the textview
|
||||
if at_the_end or kind == 'outgoing':
|
||||
|
@ -748,7 +774,9 @@ class ConversationTextview:
|
|||
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 + after_str + ' '
|
||||
buffer.insert_with_tags_by_name(end_iter, format, *name_tags)
|
||||
|
||||
|
@ -760,8 +788,18 @@ class ConversationTextview:
|
|||
buffer.insert(end_iter, subject)
|
||||
self.print_empty_line()
|
||||
|
||||
def print_real_text(self, text, text_tags = [], name = None):
|
||||
def print_real_text(self, text, text_tags = [], name = None, xhtml = None):
|
||||
'''this adds normal and special text. call this to add text'''
|
||||
if xhtml:
|
||||
try:
|
||||
if name and (text.startswith('/me ') or text.startswith('/me\n')):
|
||||
xhtml = xhtml.replace('/me', '<dfn>%s</dfn>'% (name,), 1)
|
||||
self.tv.display_html(xhtml.encode('utf-8'))
|
||||
return
|
||||
except Exception, e:
|
||||
gajim.log.debug(str("Error processing xhtml")+str(e))
|
||||
gajim.log.debug(str("with |"+xhtml+"|"))
|
||||
|
||||
buffer = self.tv.get_buffer()
|
||||
# /me is replaced by name if name is given
|
||||
if name and (text.startswith('/me ') or text.startswith('/me\n')):
|
||||
|
|
535
src/dialogs.py
535
src/dialogs.py
|
@ -3,7 +3,7 @@
|
|||
##
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com>
|
||||
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -25,6 +25,7 @@ import os
|
|||
import gtkgui_helpers
|
||||
import vcard
|
||||
import conversation_textview
|
||||
import message_control
|
||||
|
||||
try:
|
||||
import gtkspell
|
||||
|
@ -40,6 +41,7 @@ from advanced import AdvancedConfigurationWindow
|
|||
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
||||
class EditGroupsDialog:
|
||||
'''Class for the edit group dialog window'''
|
||||
|
@ -139,6 +141,9 @@ class EditGroupsDialog:
|
|||
group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
|
||||
if not group:
|
||||
return
|
||||
# Do not allow special groups
|
||||
if group in helpers.special_groups:
|
||||
return
|
||||
# check if it already exists
|
||||
model = self.list.get_model()
|
||||
iter = model.get_iter_root()
|
||||
|
@ -180,14 +185,16 @@ class EditGroupsDialog:
|
|||
if account not in accounts:
|
||||
accounts.append(account)
|
||||
for g in gajim.groups[account].keys():
|
||||
if g in helpers.special_groups:
|
||||
continue
|
||||
if g in groups:
|
||||
continue
|
||||
groups[g] = 0
|
||||
for g in contact.groups:
|
||||
groups[g] += 1
|
||||
group_list = groups.keys()
|
||||
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()
|
||||
|
@ -264,6 +271,7 @@ class ChooseGPGKeyDialog:
|
|||
renderer = gtk.CellRendererText()
|
||||
self.keys_treeview.insert_column_with_attributes(-1, _('Contact name'),
|
||||
renderer, text = 1)
|
||||
self.keys_treeview.set_search_column(1)
|
||||
self.fill_tree(secret_keys, selected)
|
||||
self.window.show_all()
|
||||
|
||||
|
@ -405,12 +413,12 @@ class ChangeStatusMessageDialog:
|
|||
|
||||
class AddNewContactWindow:
|
||||
'''Class for AddNewContactWindow'''
|
||||
uid_labels = {'jabber': _('Jabber ID'),
|
||||
'aim': _('AIM Address'),
|
||||
'gadu-gadu': _('GG Number'),
|
||||
'icq': _('ICQ Number'),
|
||||
'msn': _('MSN Address'),
|
||||
'yahoo': _('Yahoo! Address')}
|
||||
uid_labels = {'jabber': _('Jabber ID:'),
|
||||
'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
|
||||
|
@ -441,7 +449,8 @@ class AddNewContactWindow:
|
|||
'uid_label', 'uid_entry', 'protocol_combobox', 'protocol_jid_combobox',
|
||||
'protocol_hbox', 'nickname_entry', 'message_scrolledwindow',
|
||||
'register_hbox', 'subscription_table', 'add_button',
|
||||
'message_textview', 'connected_label', 'group_comboboxentry'):
|
||||
'message_textview', 'connected_label', 'group_comboboxentry',
|
||||
'auto_authorize_checkbutton'):
|
||||
self.__dict__[w] = self.xml.get_widget(w)
|
||||
if account and len(gajim.connections) >= 2:
|
||||
prompt_text =\
|
||||
|
@ -486,8 +495,10 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
liststore.append([type_, type_])
|
||||
self.protocol_combobox.set_model(liststore)
|
||||
self.protocol_combobox.set_active(0)
|
||||
self.protocol_jid_combobox.set_sensitive(False)
|
||||
self.protocol_jid_combobox.set_no_show_all(True)
|
||||
self.protocol_jid_combobox.hide()
|
||||
self.subscription_table.set_no_show_all(True)
|
||||
self.auto_authorize_checkbutton.show()
|
||||
self.message_scrolledwindow.set_no_show_all(True)
|
||||
self.register_hbox.set_no_show_all(True)
|
||||
self.register_hbox.hide()
|
||||
|
@ -497,11 +508,13 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
self.protocol_jid_combobox.set_model(liststore)
|
||||
self.xml.signal_autoconnect(self)
|
||||
if jid:
|
||||
type_ = gajim.get_transport_name_from_jid(jid) or 'jabber'
|
||||
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_room_name_and_server_from_room_jid(jid)
|
||||
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()
|
||||
|
@ -515,13 +528,13 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
i += 1
|
||||
|
||||
# set protocol_jid_combobox
|
||||
self.protocol_combobox.set_active(0)
|
||||
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_combobox.set_active(i)
|
||||
self.protocol_jid_combobox.set_active(i)
|
||||
break
|
||||
iter = model.iter_next(iter)
|
||||
i += 1
|
||||
|
@ -626,7 +639,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
else:
|
||||
message= ''
|
||||
group = self.group_comboboxentry.child.get_text().decode('utf-8')
|
||||
auto_auth = self.xml.get_widget('auto_authorize_checkbutton').get_active()
|
||||
auto_auth = self.auto_authorize_checkbutton.get_active()
|
||||
gajim.interface.roster.req_sub(self, jid, message, self.account,
|
||||
group = group, pseudo = nickname, auto_auth = auto_auth)
|
||||
self.window.destroy()
|
||||
|
@ -641,13 +654,15 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
for jid_ in self.agents[type_]:
|
||||
model.append([jid_])
|
||||
self.protocol_jid_combobox.set_active(0)
|
||||
self.protocol_jid_combobox.set_sensitive(True)
|
||||
if len(self.agents[type_]) > 1:
|
||||
self.protocol_jid_combobox.set_no_show_all(False)
|
||||
self.protocol_jid_combobox.show_all()
|
||||
else:
|
||||
self.protocol_jid_combobox.set_sensitive(False)
|
||||
self.protocol_jid_combobox.hide()
|
||||
if type_ in self.uid_labels:
|
||||
self.uid_label.set_text(self.uid_labels[type_])
|
||||
else:
|
||||
self.uid_label.set_text(_('User ID'))
|
||||
self.uid_label.set_text(_('User ID:'))
|
||||
if type_ == 'jabber':
|
||||
self.message_scrolledwindow.show()
|
||||
else:
|
||||
|
@ -655,6 +670,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
if type_ in self.available_types:
|
||||
self.register_hbox.set_no_show_all(False)
|
||||
self.register_hbox.show_all()
|
||||
self.auto_authorize_checkbutton.hide()
|
||||
self.connected_label.hide()
|
||||
self.subscription_table.hide()
|
||||
self.add_button.set_sensitive(False)
|
||||
|
@ -668,9 +684,11 @@ _('Please fill in the data of the contact you want to add in account %s') %accou
|
|||
self.subscription_table.hide()
|
||||
self.connected_label.show()
|
||||
self.add_button.set_sensitive(False)
|
||||
self.auto_authorize_checkbutton.hide()
|
||||
return
|
||||
self.subscription_table.set_no_show_all(False)
|
||||
self.subscription_table.show_all()
|
||||
self.auto_authorize_checkbutton.show()
|
||||
self.connected_label.hide()
|
||||
self.add_button.set_sensitive(True)
|
||||
|
||||
|
@ -697,8 +715,14 @@ class AboutDialog:
|
|||
dlg.set_version(gajim.version)
|
||||
s = u'Copyright © 2003-2006 Gajim Team'
|
||||
dlg.set_copyright(s)
|
||||
text = open('../COPYING').read()
|
||||
dlg.set_license(text)
|
||||
copying_file_path = None
|
||||
if os.path.isfile(os.path.join(gajim.defs.docdir, 'COPYING')):
|
||||
copying_file_path = os.path.join(gajim.defs.docdir, 'COPYING')
|
||||
elif os.path.isfile('../COPYING'):
|
||||
copying_file_path = '../COPYING'
|
||||
if copying_file_path:
|
||||
text = open(copying_file_path).read()
|
||||
dlg.set_license(text)
|
||||
|
||||
dlg.set_comments('%s\n%s %s\n%s %s'
|
||||
% (_('A GTK+ jabber client'), \
|
||||
|
@ -706,28 +730,40 @@ class AboutDialog:
|
|||
_('PyGTK Version:'), self.tuple2str(gtk.pygtk_version)))
|
||||
dlg.set_website('http://www.gajim.org/')
|
||||
|
||||
authors = []
|
||||
authors_file = open('../AUTHORS').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)
|
||||
authors_file_path = None
|
||||
if os.path.isfile(os.path.join(gajim.defs.docdir, 'AUTHORS')):
|
||||
authors_file_path = os.path.join(gajim.defs.docdir, 'AUTHORS')
|
||||
elif os.path.isfile('../AUTHORS'):
|
||||
authors_file_path = '../AUTHORS'
|
||||
if authors_file_path:
|
||||
authors = []
|
||||
authors_file = open(authors_file_path).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 = None
|
||||
if os.path.isfile(os.path.join(gajim.defs.docdir, 'THANKS')):
|
||||
thanks_file_path = os.path.join(gajim.defs.docdir, 'THANKS')
|
||||
elif os.path.isfile('../THANKS'):
|
||||
thanks_file_path = '../THANKS'
|
||||
if thanks_file_path:
|
||||
authors.append('\n' + _('THANKS:'))
|
||||
|
||||
authors.append('\n' + _('THANKS:'))
|
||||
|
||||
text = open('../THANKS').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)
|
||||
text = open(thanks_file_path).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)
|
||||
|
||||
dlg.set_authors(authors)
|
||||
dlg.set_authors(authors)
|
||||
|
||||
if gtk.pygtk_version >= (2, 8, 0) and gtk.gtk_version >= (2, 8, 0):
|
||||
dlg.props.wrap_license = True
|
||||
|
@ -837,27 +873,31 @@ class FileChooserDialog(gtk.FileChooserDialog):
|
|||
self.set_current_folder(current_folder)
|
||||
else:
|
||||
self.set_current_folder(helpers.get_documents_path())
|
||||
|
||||
buttons = self.action_area.get_children()
|
||||
possible_responses = {gtk.STOCK_OPEN: on_response_ok,
|
||||
gtk.STOCK_SAVE: on_response_ok,
|
||||
gtk.STOCK_CANCEL: on_response_cancel}
|
||||
for b in buttons:
|
||||
for response in possible_responses:
|
||||
if b.get_label() == response:
|
||||
if not possible_responses[response]:
|
||||
b.connect('clicked', self.just_destroy)
|
||||
elif isinstance(possible_responses[response], tuple):
|
||||
if len(possible_responses[response]) == 1:
|
||||
b.connect('clicked', possible_responses[response][0])
|
||||
else:
|
||||
b.connect('clicked', *possible_responses[response])
|
||||
else:
|
||||
b.connect('clicked', possible_responses[response])
|
||||
break
|
||||
|
||||
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.RESPONSE_CANCEL, gtk.RESPONSE_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.RESPONSE_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()
|
||||
|
||||
|
@ -1015,6 +1055,12 @@ class SubscriptionRequestWindow:
|
|||
xml.signal_autoconnect(self)
|
||||
self.window.show_all()
|
||||
|
||||
def prepare_popup_menu(self):
|
||||
xml = gtkgui_helpers.get_glade('subscription_request_popup_menu.glade')
|
||||
menu = xml.get_widget('subscription_request_popup_menu')
|
||||
xml.signal_autoconnect(self)
|
||||
return menu
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
|
@ -1025,7 +1071,7 @@ class SubscriptionRequestWindow:
|
|||
if self.jid not in gajim.contacts.get_jid_list(self.account):
|
||||
AddNewContactWindow(self.account, self.jid, self.user_nick)
|
||||
|
||||
def on_contact_info_button_clicked(self, widget):
|
||||
def on_contact_info_activate(self, widget):
|
||||
'''ask vcard'''
|
||||
if gajim.interface.instances[self.account]['infos'].has_key(self.jid):
|
||||
gajim.interface.instances[self.account]['infos'][self.jid].window.present()
|
||||
|
@ -1039,38 +1085,50 @@ class SubscriptionRequestWindow:
|
|||
gajim.interface.instances[self.account]['infos'][self.jid].xml.\
|
||||
get_widget('information_notebook').remove_page(0)
|
||||
|
||||
def on_start_chat_activate(self, widget):
|
||||
'''open chat'''
|
||||
gajim.interface.roster.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)
|
||||
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.window)
|
||||
|
||||
|
||||
class JoinGroupchatWindow:
|
||||
def __init__(self, account, server = '', room = '', nick = '',
|
||||
automatic = False):
|
||||
def __init__(self, account, room_jid = '', nick = '', 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'''
|
||||
if server and room:
|
||||
jid = room + '@' + server
|
||||
if jid in gajim.gc_connected[account] and gajim.gc_connected[account][jid]:
|
||||
ErrorDialog(_('You are already in room %s') % jid)
|
||||
raise RuntimeError, 'You are already in this room'
|
||||
if room_jid != '':
|
||||
if 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'
|
||||
self.account = account
|
||||
self.automatic = automatic
|
||||
if nick == '':
|
||||
nick = gajim.nicks[self.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 RuntimeError, 'You must be connected to join a groupchat'
|
||||
_('You can not join a group chat unless you are connected.'))
|
||||
raise GajimGeneralException, 'You must be connected to join a groupchat'
|
||||
|
||||
self._empty_required_widgets = []
|
||||
|
||||
self.xml = gtkgui_helpers.get_glade('join_groupchat_window.glade')
|
||||
self.window = self.xml.get_widget('join_groupchat_window')
|
||||
self.xml.get_widget('server_entry').set_text(server)
|
||||
self.xml.get_widget('room_entry').set_text(room)
|
||||
self.xml.get_widget('nickname_entry').set_text(nick)
|
||||
self._room_jid_entry = self.xml.get_widget('room_jid_entry')
|
||||
self._nickname_entry = self.xml.get_widget('nickname_entry')
|
||||
|
||||
self._room_jid_entry.set_text(room_jid)
|
||||
self._nickname_entry.set_text(nick)
|
||||
self.xml.signal_autoconnect(self)
|
||||
gajim.interface.instances[account]['join_gc'] = self #now add us to open windows
|
||||
if len(gajim.connections) > 1:
|
||||
|
@ -1090,19 +1148,14 @@ _('You can not join a group chat unless you are connected.'))
|
|||
self.recently_combobox.append_text(g)
|
||||
if len(self.recently_groupchat) == 0:
|
||||
self.recently_combobox.set_sensitive(False)
|
||||
elif server == '' and room == '':
|
||||
elif room_jid == '':
|
||||
self.recently_combobox.set_active(0)
|
||||
self.xml.get_widget('room_entry').select_region(0, -1)
|
||||
elif room and server:
|
||||
self._room_jid_entry.select_region(0, -1)
|
||||
elif room_jid != '':
|
||||
self.xml.get_widget('join_button').grab_focus()
|
||||
|
||||
self._server_entry = self.xml.get_widget('server_entry')
|
||||
self._room_entry = self.xml.get_widget('room_entry')
|
||||
self._nickname_entry = self.xml.get_widget('nickname_entry')
|
||||
if not self._server_entry.get_text():
|
||||
self._empty_required_widgets.append(self._server_entry)
|
||||
if not self._room_entry.get_text():
|
||||
self._empty_required_widgets.append(self._room_entry)
|
||||
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):
|
||||
|
@ -1129,27 +1182,11 @@ _('You can not join a group chat unless you are connected.'))
|
|||
if len(self._empty_required_widgets) == 0:
|
||||
self.xml.get_widget('join_button').set_sensitive(True)
|
||||
|
||||
def on_room_entry_key_press_event(self, widget, event):
|
||||
# Check for pressed @ and jump to server_entry if found
|
||||
if event.keyval == gtk.keysyms.at:
|
||||
self.xml.get_widget('server_entry').grab_focus()
|
||||
return True
|
||||
|
||||
def on_server_entry_key_press_event(self, widget, event):
|
||||
# If backspace is pressed in empty server_entry, return to the room entry
|
||||
backspace = event.keyval == gtk.keysyms.BackSpace
|
||||
server_entry = self.xml.get_widget('server_entry')
|
||||
empty = len(server_entry.get_text()) == 0
|
||||
if backspace and empty:
|
||||
self.xml.get_widget('room_entry').grab_focus()
|
||||
return True
|
||||
|
||||
def on_recently_combobox_changed(self, widget):
|
||||
model = widget.get_model()
|
||||
iter = widget.get_active_iter()
|
||||
gid = model[iter][0].decode('utf-8')
|
||||
self.xml.get_widget('room_entry').set_text(gid.split('@')[0])
|
||||
self.xml.get_widget('server_entry').set_text(gid.split('@')[1])
|
||||
iter_ = widget.get_active_iter()
|
||||
room_jid = model[iter_][0].decode('utf-8')
|
||||
self._room_jid_entry.set_text(room_jid)
|
||||
|
||||
def on_cancel_button_clicked(self, widget):
|
||||
'''When Cancel button is clicked'''
|
||||
|
@ -1157,30 +1194,35 @@ _('You can not join a group chat unless you are connected.'))
|
|||
|
||||
def on_join_button_clicked(self, widget):
|
||||
'''When Join button is clicked'''
|
||||
nickname = self.xml.get_widget('nickname_entry').get_text().decode(
|
||||
'utf-8')
|
||||
room = self.xml.get_widget('room_entry').get_text().decode('utf-8')
|
||||
server = self.xml.get_widget('server_entry').get_text().decode('utf-8')
|
||||
nickname = self._nickname_entry.get_text().decode('utf-8')
|
||||
room_jid = self._room_jid_entry.get_text().decode('utf-8')
|
||||
password = self.xml.get_widget('password_entry').get_text().decode(
|
||||
'utf-8')
|
||||
jid = '%s@%s' % (room, server)
|
||||
try:
|
||||
jid = helpers.parse_jid(jid)
|
||||
room_jid = helpers.parse_jid(room_jid)
|
||||
except:
|
||||
ErrorDialog(_('Invalid room or server name'),
|
||||
_('The room name or server name has not allowed characters.'))
|
||||
ErrorDialog(_('Invalid group chat Jabber ID'),
|
||||
_('The group chat Jabber ID has not allowed characters.'))
|
||||
return
|
||||
|
||||
if jid in self.recently_groupchat:
|
||||
self.recently_groupchat.remove(jid)
|
||||
self.recently_groupchat.insert(0, jid)
|
||||
if gajim.interface.msg_win_mgr.has_window(room_jid, self.account):
|
||||
ctrl = gajim.interface.msg_win_mgr.get_control(room_jid, self.account)
|
||||
if ctrl.type_id != message_control.TYPE_GC:
|
||||
ErrorDialog(_('This is not a group chat'),
|
||||
_('%s is not the name of a group chat.') % room_jid)
|
||||
return
|
||||
if room_jid in self.recently_groupchat:
|
||||
self.recently_groupchat.remove(room_jid)
|
||||
self.recently_groupchat.insert(0, room_jid)
|
||||
if len(self.recently_groupchat) > 10:
|
||||
self.recently_groupchat = self.recently_groupchat[0:10]
|
||||
gajim.config.set('recently_groupchat', ' '.join(self.recently_groupchat))
|
||||
gajim.config.set('recently_groupchat',
|
||||
' '.join(self.recently_groupchat))
|
||||
|
||||
if self.automatic:
|
||||
gajim.automatic_rooms[self.account][jid] = self.automatic
|
||||
gajim.interface.roster.join_gc_room(self.account, jid, nickname, password)
|
||||
gajim.automatic_rooms[self.account][room_jid] = self.automatic
|
||||
gajim.interface.roster.join_gc_room(self.account, room_jid, nickname,
|
||||
password)
|
||||
|
||||
self.window.destroy()
|
||||
|
||||
|
@ -1192,7 +1234,7 @@ class NewChatDialog(InputDialog):
|
|||
title = _('Start Chat with account %s') % account
|
||||
else:
|
||||
title = _('Start Chat')
|
||||
prompt_text = _('Fill in the jid, or nick of the contact you would like\nto send a chat message to:')
|
||||
prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:')
|
||||
InputDialog.__init__(self, title, prompt_text, is_modal = False)
|
||||
|
||||
self.completion_dict = {}
|
||||
|
@ -1240,7 +1282,7 @@ class ChangePasswordDialog:
|
|||
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 RuntimeError, 'You are not connected to the server'
|
||||
raise GajimGeneralException, 'You are not connected to the server'
|
||||
self.account = account
|
||||
self.xml = gtkgui_helpers.get_glade('change_password_dialog.glade')
|
||||
self.dialog = self.xml.get_widget('change_password_dialog')
|
||||
|
@ -1302,7 +1344,8 @@ class PopupNotificationWindow:
|
|||
# default image
|
||||
if not path_to_image:
|
||||
path_to_image = os.path.abspath(
|
||||
os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'chat_msg_recv.png')) # img to display
|
||||
os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
'chat_msg_recv.png')) # img to display
|
||||
|
||||
if event_type == _('Contact Signed In'):
|
||||
bg_color = 'limegreen'
|
||||
|
@ -1322,7 +1365,7 @@ class PopupNotificationWindow:
|
|||
bg_color = 'tan1'
|
||||
elif event_type == _('Contact Changed Status'):
|
||||
bg_color = 'thistle2'
|
||||
else: # Unknown event ! Shouldn't happen but deal with it
|
||||
else: # Unknown event! Shouldn't happen but deal with it
|
||||
bg_color = 'white'
|
||||
popup_bg_color = gtk.gdk.color_parse(bg_color)
|
||||
close_button.modify_bg(gtk.STATE_NORMAL, popup_bg_color)
|
||||
|
@ -1421,8 +1464,12 @@ class SingleMessageWindow:
|
|||
self.cancel_button = self.xml.get_widget('cancel_button')
|
||||
self.close_button = self.xml.get_widget('close_button')
|
||||
self.message_tv_buffer.connect('changed', self.update_char_counter)
|
||||
|
||||
self.to_entry.set_text(to)
|
||||
if type(to) == type([]):
|
||||
jid = ', '.join( [i[0].jid + '/' + i[0].resource 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:
|
||||
|
@ -1574,22 +1621,27 @@ class SingleMessageWindow:
|
|||
ErrorDialog(_('Connection not available'),
|
||||
_('Please make sure you are connected with "%s".') % self.account)
|
||||
return
|
||||
to_whom_jid = self.to_entry.get_text().decode('utf-8')
|
||||
if self.completion_dict.has_key(to_whom_jid):
|
||||
to_whom_jid = self.completion_dict[to_whom_jid].jid
|
||||
subject = self.subject_entry.get_text().decode('utf-8')
|
||||
begin, end = self.message_tv_buffer.get_bounds()
|
||||
message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
|
||||
if type(self.to) == type([]):
|
||||
sender_list = [i[0].jid + '/' + i[0].resource for i in self.to]
|
||||
else:
|
||||
sender_list = [self.to_entry.get_text().decode('utf-8')]
|
||||
|
||||
for to_whom_jid in sender_list:
|
||||
if self.completion_dict.has_key(to_whom_jid):
|
||||
to_whom_jid = self.completion_dict[to_whom_jid].jid
|
||||
subject = self.subject_entry.get_text().decode('utf-8')
|
||||
begin, end = self.message_tv_buffer.get_bounds()
|
||||
message = self.message_tv_buffer.get_text(begin, end).decode('utf-8')
|
||||
|
||||
if to_whom_jid.find('/announce/') != -1:
|
||||
gajim.connections[self.account].send_motd(to_whom_jid, subject,
|
||||
message)
|
||||
return
|
||||
if to_whom_jid.find('/announce/') != -1:
|
||||
gajim.connections[self.account].send_motd(to_whom_jid, subject,
|
||||
message)
|
||||
return
|
||||
|
||||
# FIXME: allow GPG message some day
|
||||
gajim.connections[self.account].send_message(to_whom_jid, message,
|
||||
keyID = None, type = 'normal', subject=subject)
|
||||
|
||||
# FIXME: allow GPG message some day
|
||||
gajim.connections[self.account].send_message(to_whom_jid, message,
|
||||
keyID = None, type = 'normal', subject=subject)
|
||||
|
||||
self.subject_entry.set_text('') # we sent ok, clear the subject
|
||||
self.message_tv_buffer.set_text('') # we sent ok, clear the textview
|
||||
|
||||
|
@ -1725,10 +1777,13 @@ class XMLConsoleWindow:
|
|||
self.input_textview.grab_focus()
|
||||
|
||||
class PrivacyListWindow:
|
||||
def __init__(self, account, privacy_list, list_type):
|
||||
'''list_type can be 0 if list is created or 1 if it id edited'''
|
||||
'''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 = privacy_list
|
||||
self.privacy_list_name = privacy_list_name
|
||||
|
||||
# Dicts and Default Values
|
||||
self.active_rule = ''
|
||||
|
@ -1740,7 +1795,7 @@ class PrivacyListWindow:
|
|||
self.allow_deny = 'allow'
|
||||
|
||||
# Connect to glade
|
||||
self.xml = gtkgui_helpers.get_glade('privacy_list_edit_window.glade')
|
||||
self.xml = gtkgui_helpers.get_glade('privacy_list_window.glade')
|
||||
self.window = self.xml.get_widget('privacy_list_edit_window')
|
||||
|
||||
# Add Widgets
|
||||
|
@ -1762,10 +1817,9 @@ class PrivacyListWindow:
|
|||
'privacy_list_default_checkbutton']:
|
||||
self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)
|
||||
|
||||
# Send translations
|
||||
self.privacy_lists_title_label.set_label(
|
||||
_('Privacy List <b><i>%s</i></b>') % \
|
||||
gtkgui_helpers.escape_for_pango_markup(self.privacy_list))
|
||||
gtkgui_helpers.escape_for_pango_markup(self.privacy_list_name))
|
||||
|
||||
if len(gajim.connections) > 1:
|
||||
title = _('Privacy List for %s') % self.account
|
||||
|
@ -1776,9 +1830,9 @@ class PrivacyListWindow:
|
|||
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)
|
||||
|
||||
# Check if list is created (0) or edited (1)
|
||||
if list_type == 1:
|
||||
if action == 'EDIT':
|
||||
self.refresh_rules()
|
||||
|
||||
count = 0
|
||||
|
@ -1793,22 +1847,20 @@ class PrivacyListWindow:
|
|||
self.add_edit_vbox.set_no_show_all(True)
|
||||
self.window.show_all()
|
||||
self.add_edit_vbox.hide()
|
||||
|
||||
|
||||
self.xml.signal_autoconnect(self)
|
||||
|
||||
def on_privacy_list_edit_window_destroy(self, widget):
|
||||
'''close window'''
|
||||
if gajim.interface.instances[self.account].has_key('privacy_list_%s' % \
|
||||
self.privacy_list):
|
||||
del gajim.interface.instances[self.account]['privacy_list_%s' % \
|
||||
self.privacy_list]
|
||||
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]
|
||||
|
||||
def check_active_default(self, a_d_dict):
|
||||
if a_d_dict['active'] == self.privacy_list:
|
||||
if a_d_dict['active'] == self.privacy_list_name:
|
||||
self.privacy_list_active_checkbutton.set_active(True)
|
||||
else:
|
||||
self.privacy_list_active_checkbutton.set_active(False)
|
||||
if a_d_dict['default'] == self.privacy_list:
|
||||
if a_d_dict['default'] == self.privacy_list_name:
|
||||
self.privacy_list_default_checkbutton.set_active(True)
|
||||
else:
|
||||
self.privacy_list_default_checkbutton.set_active(False)
|
||||
|
@ -1818,11 +1870,10 @@ class PrivacyListWindow:
|
|||
self.global_rules = {}
|
||||
for rule in rules:
|
||||
if rule.has_key('type'):
|
||||
text_item = 'Order: %s, action: %s, type: %s, value: %s' % \
|
||||
(rule['order'], rule['action'], rule['type'],
|
||||
rule['value'])
|
||||
text_item = _('Order: %s, action: %s, type: %s, value: %s') % \
|
||||
(rule['order'], rule['action'], rule['type'], rule['value'])
|
||||
else:
|
||||
text_item = 'Order: %s, action: %s' % (rule['order'],
|
||||
text_item = _('Order: %s, action: %s') % (rule['order'],
|
||||
rule['action'])
|
||||
self.global_rules[text_item] = rule
|
||||
self.list_of_rules_combobox.append_text(text_item)
|
||||
|
@ -1845,22 +1896,26 @@ class PrivacyListWindow:
|
|||
gajim.connections[self.account].get_active_default_lists()
|
||||
|
||||
def refresh_rules(self):
|
||||
gajim.connections[self.account].get_privacy_list(self.privacy_list)
|
||||
gajim.connections[self.account].get_privacy_list(self.privacy_list_name)
|
||||
|
||||
def on_delete_rule_button_clicked(self, widget):
|
||||
tags = []
|
||||
for rule in self.global_rules:
|
||||
if rule != \
|
||||
self.list_of_rules_combobox.get_active_text().decode('utf-8'):
|
||||
if rule != self.list_of_rules_combobox.get_active_text():
|
||||
tags.append(self.global_rules[rule])
|
||||
gajim.connections[self.account].set_privacy_list(
|
||||
self.privacy_list, tags)
|
||||
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>'))
|
||||
_('<b>Edit a rule</b>'))
|
||||
active_num = self.list_of_rules_combobox.get_active()
|
||||
if active_num == -1:
|
||||
self.active_rule = ''
|
||||
|
@ -1909,29 +1964,31 @@ class PrivacyListWindow:
|
|||
self.edit_queries_send_checkbutton.set_active(True)
|
||||
elif child == 'message':
|
||||
self.edit_send_messages_checkbutton.set_active(True)
|
||||
|
||||
|
||||
if rule_info['action'] == 'allow':
|
||||
self.edit_allow_radiobutton.set_active(True)
|
||||
self.edit_allow_radiobutton.set_active(True)
|
||||
else:
|
||||
self.edit_deny_radiobutton.set_active(True)
|
||||
self.edit_deny_radiobutton.set_active(True)
|
||||
self.add_edit_vbox.show()
|
||||
|
||||
|
||||
def on_privacy_list_active_checkbutton_toggled(self, widget):
|
||||
if widget.get_active():
|
||||
gajim.connections[self.account].set_active_list(self.privacy_list)
|
||||
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)
|
||||
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)
|
||||
|
@ -1950,12 +2007,10 @@ class PrivacyListWindow:
|
|||
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().decode('utf-8')
|
||||
edit_value = self.edit_type_jabberid_entry.get_text()
|
||||
elif self.edit_type_group_radiobutton.get_active():
|
||||
edit_type = 'group'
|
||||
edit_value = \
|
||||
self.edit_type_group_combobox.get_active_text().decode('utf-8')
|
||||
edit_value = self.edit_type_group_combobox.get_active_text()
|
||||
elif self.edit_type_subscription_radiobutton.get_active():
|
||||
edit_type = 'subscription'
|
||||
subs = ['none', 'both', 'from', 'to']
|
||||
|
@ -1994,9 +2049,14 @@ class PrivacyListWindow:
|
|||
else:
|
||||
tags.append(current_tags)
|
||||
|
||||
gajim.connections[self.account].set_privacy_list(self.privacy_list, tags)
|
||||
gajim.connections[self.account].set_privacy_list(
|
||||
self.privacy_list_name, tags)
|
||||
self.privacy_list_received(tags)
|
||||
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()
|
||||
|
@ -2011,32 +2071,28 @@ class PrivacyListWindow:
|
|||
if active_bool:
|
||||
self.allow_deny = radiobutton
|
||||
|
||||
def on_privacy_list_close_button_clicked(self, widget):
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
||||
def on_privacy_list_refresh_button_clicked(self, widget):
|
||||
self.refresh_rules()
|
||||
self.add_edit_vbox.hide()
|
||||
|
||||
class PrivacyListsWindow:
|
||||
# To do: UTF-8 ???????
|
||||
'''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 = []
|
||||
|
||||
self.privacy_lists_save = []
|
||||
|
||||
self.xml = gtkgui_helpers.get_glade('privacy_lists_first_window.glade')
|
||||
self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade')
|
||||
|
||||
self.window = self.xml.get_widget('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', 'buttons_hbox',
|
||||
'privacy_lists_refresh_button', 'close_privacy_lists_window_button']:
|
||||
self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add)
|
||||
'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_widget(
|
||||
widget_to_add)
|
||||
|
||||
self.draw_privacy_lists_in_combobox()
|
||||
self.draw_privacy_lists_in_combobox([])
|
||||
self.privacy_lists_refresh()
|
||||
|
||||
self.enabled = True
|
||||
|
@ -2053,31 +2109,40 @@ class PrivacyListsWindow:
|
|||
self.xml.signal_autoconnect(self)
|
||||
|
||||
def on_privacy_lists_first_window_destroy(self, widget):
|
||||
'''close window'''
|
||||
if gajim.interface.instances[self.account].has_key('privacy_lists'):
|
||||
if 'privacy_lists' in gajim.interface.instances[self.account]:
|
||||
del gajim.interface.instances[self.account]['privacy_lists']
|
||||
|
||||
def draw_privacy_lists_in_combobox(self):
|
||||
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
|
||||
self.list_of_privacy_lists_combobox.append_text(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 = self.privacy_lists
|
||||
for add_item in self.privacy_lists:
|
||||
self.list_of_privacy_lists_combobox.append_text(add_item)
|
||||
if len(self.privacy_lists) == 0:
|
||||
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.buttons_hbox.set_sensitive(False)
|
||||
elif len(self.privacy_lists) == 1:
|
||||
self.list_of_privacy_lists_combobox.set_active(0)
|
||||
self.list_of_privacy_lists_combobox.set_sensitive(False)
|
||||
self.buttons_hbox.set_sensitive(True)
|
||||
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.buttons_hbox.set_sensitive(True)
|
||||
self.list_of_privacy_lists_combobox.set_active(0)
|
||||
self.privacy_lists = []
|
||||
|
||||
def on_privacy_lists_refresh_button_clicked(self, widget):
|
||||
self.privacy_lists_refresh()
|
||||
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()
|
||||
|
@ -2087,27 +2152,31 @@ class PrivacyListsWindow:
|
|||
self.list_of_privacy_lists_combobox.get_active()]
|
||||
gajim.connections[self.account].del_privacy_list(active_list)
|
||||
self.privacy_lists_save.remove(active_list)
|
||||
self.privacy_lists_received({'lists':self.privacy_lists_save})
|
||||
self.privacy_lists_received({'lists': self.privacy_lists_save})
|
||||
|
||||
def privacy_lists_received(self, lists):
|
||||
if not lists:
|
||||
return
|
||||
privacy_lists = []
|
||||
for privacy_list in lists['lists']:
|
||||
self.privacy_lists += [privacy_list]
|
||||
self.draw_privacy_lists_in_combobox()
|
||||
privacy_lists.append(privacy_list)
|
||||
self.draw_privacy_lists_in_combobox(privacy_lists)
|
||||
|
||||
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().decode('utf-8')
|
||||
if gajim.interface.instances[self.account].has_key(
|
||||
'privacy_list_%s' % name):
|
||||
gajim.interface.instances[self.account]['privacy_list_%s' % name].\
|
||||
window.present()
|
||||
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.'))
|
||||
return
|
||||
key_name = 'privacy_list_%s' % name
|
||||
if gajim.interface.instances[self.account].has_key(key_name):
|
||||
gajim.interface.instances[self.account][key_name].window.present()
|
||||
else:
|
||||
gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
|
||||
PrivacyListWindow(self.account, name, 0)
|
||||
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):
|
||||
|
@ -2116,16 +2185,17 @@ class PrivacyListsWindow:
|
|||
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 gajim.interface.instances[self.account].has_key(
|
||||
'privacy_list_%s' % name):
|
||||
gajim.interface.instances[self.account]['privacy_list_%s' % name].\
|
||||
window.present()
|
||||
key_name):
|
||||
gajim.interface.instances[self.account][key_name].window.present()
|
||||
else:
|
||||
gajim.interface.instances[self.account]['privacy_list_%s' % name] = \
|
||||
PrivacyListWindow(self.account, name, 1)
|
||||
gajim.interface.instances[self.account][key_name] = \
|
||||
PrivacyListWindow(self.account, name, 'EDIT')
|
||||
|
||||
class InvitationReceivedDialog:
|
||||
def __init__(self, account, room_jid, contact_jid, password = None, comment = None):
|
||||
def __init__(self, account, room_jid, contact_jid, password = None,
|
||||
comment = None):
|
||||
|
||||
self.room_jid = room_jid
|
||||
self.account = account
|
||||
|
@ -2133,8 +2203,8 @@ class InvitationReceivedDialog:
|
|||
self.dialog = xml.get_widget('invitation_received_dialog')
|
||||
|
||||
#FIXME: use nickname instead of contact_jid
|
||||
pritext = _('%(contact_jid)s has invited you to %(room_jid)s room') % {
|
||||
'room_jid': room_jid, 'contact_jid': contact_jid }
|
||||
pritext = _('%(contact_jid)s has invited you to group chat %(room_jid)s')\
|
||||
% {'room_jid': room_jid, 'contact_jid': contact_jid }
|
||||
|
||||
label_text = '<big><b>%s</b></big>' % pritext
|
||||
|
||||
|
@ -2155,10 +2225,9 @@ class InvitationReceivedDialog:
|
|||
|
||||
def on_accept_button_clicked(self, widget):
|
||||
self.dialog.destroy()
|
||||
room, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid)
|
||||
try:
|
||||
JoinGroupchatWindow(self.account, server = server, room = room)
|
||||
except RuntimeError:
|
||||
JoinGroupchatWindow(self.account, self.room_jid)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
|
||||
class ProgressDialog:
|
||||
|
@ -2291,6 +2360,18 @@ class ImageChooserDialog(FileChooserDialog):
|
|||
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)
|
||||
if on_response_clear:
|
||||
button.connect('clicked', on_response_clear)
|
||||
button.show_all()
|
||||
self.action_area.pack_start(button)
|
||||
self.action_area.reorder_child(button, 0)
|
||||
|
||||
class AddSpecialNotificationDialog:
|
||||
def __init__(self, jid):
|
||||
'''jid is the jid for which we want to add special notification
|
||||
|
|
287
src/disco.py
287
src/disco.py
|
@ -1,19 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
## config.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Stéphan Kochen <stephan@kochen.nl>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Stéphan Kochen <stephan@kochen.nl>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -36,10 +26,10 @@
|
|||
# - def update_actions(self)
|
||||
# - def default_action(self)
|
||||
# - def _find_item(self, jid, node)
|
||||
# - def _add_item(self, model, jid, node, item, force)
|
||||
# - def _update_item(self, model, iter, jid, node, item)
|
||||
# - def _update_info(self, model, iter, jid, node, identities, features, data)
|
||||
# - def _update_error(self, model, iter, jid, node)
|
||||
# - def _add_item(self, jid, 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
|
||||
|
@ -60,6 +50,7 @@ import groups
|
|||
|
||||
from common import gajim
|
||||
from common import xmpp
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
||||
# Dictionary mapping category, type pairs to browser class, image pairs.
|
||||
# This is a function, so we can call it after the classes are declared.
|
||||
|
@ -133,6 +124,13 @@ class CacheDictionary:
|
|||
def __call__(self):
|
||||
return self.value
|
||||
|
||||
def cleanup(self):
|
||||
for key in self.cache.keys():
|
||||
item = self.cache[key]
|
||||
if item.source:
|
||||
gobject.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:
|
||||
|
@ -144,8 +142,9 @@ class CacheDictionary:
|
|||
item = self.cache[key]
|
||||
if item.source:
|
||||
gobject.source_remove(item.source)
|
||||
source = gobject.timeout_add(self.lifetime, self._expire_timeout, key)
|
||||
item.source = source
|
||||
if self.lifetime:
|
||||
source = gobject.timeout_add(self.lifetime, self._expire_timeout, key)
|
||||
item.source = source
|
||||
|
||||
def __getitem__(self, key):
|
||||
item = self.cache[key]
|
||||
|
@ -217,11 +216,15 @@ class ServicesCache:
|
|||
ServiceCache instance.'''
|
||||
def __init__(self, account):
|
||||
self.account = account
|
||||
self._items = CacheDictionary(15, getrefresh = False)
|
||||
self._info = CacheDictionary(15, getrefresh = False)
|
||||
self._items = CacheDictionary(0, getrefresh = False)
|
||||
self._info = CacheDictionary(0, getrefresh = False)
|
||||
self._subscriptions = CacheDictionary(5, getrefresh=False)
|
||||
self._cbs = {}
|
||||
|
||||
def cleanup(self):
|
||||
self._items.cleanup()
|
||||
self._info.cleanup()
|
||||
|
||||
def _clean_closure(self, cb, type, addr):
|
||||
# A closure died, clean up
|
||||
cbkey = (type, addr)
|
||||
|
@ -393,12 +396,12 @@ class ServicesCache:
|
|||
if self._cbs.has_key(cbkey):
|
||||
del self._cbs[cbkey]
|
||||
|
||||
# object is needed so that property() works
|
||||
# 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):
|
||||
self._account = account
|
||||
self.account = account
|
||||
self.parent = parent
|
||||
if not jid:
|
||||
jid = gajim.config.get_per('accounts', account, 'hostname')
|
||||
|
@ -425,6 +428,7 @@ _('Without a connection, you can not browse available services'))
|
|||
self.xml = gtkgui_helpers.get_glade('service_discovery_window.glade')
|
||||
self.window = self.xml.get_widget('service_discovery_window')
|
||||
self.services_treeview = self.xml.get_widget('services_treeview')
|
||||
self.model = None
|
||||
# This is more reliable than the cursor-changed signal.
|
||||
selection = self.services_treeview.get_selection()
|
||||
selection.connect_after('changed',
|
||||
|
@ -455,7 +459,6 @@ _('Without a connection, you can not browse available services'))
|
|||
|
||||
liststore = gtk.ListStore(str)
|
||||
self.address_comboboxentry.set_model(liststore)
|
||||
self.address_comboboxentry.set_text_column(0)
|
||||
self.latest_addresses = gajim.config.get(
|
||||
'latest_disco_addresses').split()
|
||||
if jid in self.latest_addresses:
|
||||
|
@ -476,30 +479,35 @@ _('Without a connection, you can not browse available services'))
|
|||
self.travel(jid, node)
|
||||
self.window.show_all()
|
||||
|
||||
@property
|
||||
def _get_account(self):
|
||||
return self._account
|
||||
return self.account
|
||||
|
||||
@property
|
||||
def _set_account(self, value):
|
||||
self._account = value
|
||||
self.account = value
|
||||
self.cache.account = value
|
||||
if self.browser:
|
||||
self.browser.account = value
|
||||
account = property(_get_account, _set_account)
|
||||
|
||||
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()
|
||||
self.window.set_title(_('Service Discovery using account %s') % self.account)
|
||||
title_text = _('Service Discovery using account %s') % self.account
|
||||
self.window.set_title(title_text)
|
||||
self._set_window_banner_text(_('Service Discovery'))
|
||||
# FIXME: use self.banner_icon.clear() when we switch to GTK 2.8
|
||||
self.banner_icon.set_from_file(None)
|
||||
self.banner_icon.hide() # Just clearing it doesn't work
|
||||
if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0):
|
||||
self.banner_icon.clear()
|
||||
else:
|
||||
self.banner_icon.set_from_file(None)
|
||||
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')
|
||||
bannerfontattrs = gajim.config.get_per('themes', theme,
|
||||
'bannerfontattrs')
|
||||
|
||||
if bannerfont:
|
||||
font = pango.FontDescription(bannerfont)
|
||||
|
@ -592,6 +600,7 @@ _('Without a connection, you can not browse available services'))
|
|||
self.browser = None
|
||||
self.window.destroy()
|
||||
|
||||
self.cache.cleanup()
|
||||
for child in self.children[:]:
|
||||
child.parent = None
|
||||
if chain:
|
||||
|
@ -724,9 +733,9 @@ class AgentBrowser:
|
|||
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
|
||||
model = gtk.ListStore(str, str, str, str)
|
||||
model.set_sort_column_id(3, gtk.SORT_ASCENDING)
|
||||
self.window.services_treeview.set_model(model)
|
||||
self.model = gtk.ListStore(str, str, str, str)
|
||||
self.model.set_sort_column_id(3, gtk.SORT_ASCENDING)
|
||||
self.window.services_treeview.set_model(self.model)
|
||||
# Name column
|
||||
col = gtk.TreeViewColumn(_('Name'))
|
||||
renderer = gtk.CellRendererText()
|
||||
|
@ -744,7 +753,7 @@ class AgentBrowser:
|
|||
self.window.services_treeview.set_headers_visible(True)
|
||||
|
||||
def _clean_treemodel(self):
|
||||
self.window.services_treeview.get_model().clear()
|
||||
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)
|
||||
|
@ -876,8 +885,7 @@ class AgentBrowser:
|
|||
|
||||
def browse(self, force = False):
|
||||
'''Fill the treeview with agents, fetching the info if necessary.'''
|
||||
model = self.window.services_treeview.get_model()
|
||||
model.clear()
|
||||
self.model.clear()
|
||||
self._total_items = self._progress = 0
|
||||
self.window.progressbar.show()
|
||||
self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb)
|
||||
|
@ -894,21 +902,21 @@ class AgentBrowser:
|
|||
def _find_item(self, jid, node):
|
||||
'''Check if an item is already in the treeview. Return an iter to it
|
||||
if so, None otherwise.'''
|
||||
model = self.window.services_treeview.get_model()
|
||||
iter = model.get_iter_root()
|
||||
iter = self.model.get_iter_root()
|
||||
while iter:
|
||||
cjid = model.get_value(iter, 0).decode('utf-8')
|
||||
cnode = model.get_value(iter, 1).decode('utf-8')
|
||||
cjid = self.model.get_value(iter, 0).decode('utf-8')
|
||||
cnode = self.model.get_value(iter, 1).decode('utf-8')
|
||||
if jid == cjid and node == cnode:
|
||||
break
|
||||
iter = model.iter_next(iter)
|
||||
iter = self.model.iter_next(iter)
|
||||
if iter:
|
||||
return iter
|
||||
return None
|
||||
|
||||
def _agent_items(self, jid, node, items, force):
|
||||
'''Callback for when we receive a list of agent items.'''
|
||||
model = self.window.services_treeview.get_model()
|
||||
self.model.clear()
|
||||
self._total_items = 0
|
||||
gobject.source_remove(self._pulse_timeout)
|
||||
self.window.progressbar.hide()
|
||||
# The server returned an error
|
||||
|
@ -920,53 +928,48 @@ class AgentBrowser:
|
|||
_('This service does not contain any items to browse.'))
|
||||
return
|
||||
# We got a list of items
|
||||
self.window.services_treeview.set_model(None)
|
||||
for item in items:
|
||||
jid = item['jid']
|
||||
node = item.get('node', '')
|
||||
iter = self._find_item(jid, node)
|
||||
if iter:
|
||||
# Already in the treeview
|
||||
self._update_item(model, iter, jid, node, item)
|
||||
else:
|
||||
# Not in the treeview
|
||||
self._total_items += 1
|
||||
self._add_item(model, jid, node, item, force)
|
||||
self._total_items += 1
|
||||
self._add_item(jid, node, item, force)
|
||||
self.window.services_treeview.set_model(self.model)
|
||||
|
||||
def _agent_info(self, jid, node, identities, features, data):
|
||||
'''Callback for when we receive info about an agent's item.'''
|
||||
addr = get_agent_address(jid, node)
|
||||
model = self.window.services_treeview.get_model()
|
||||
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(model, iter, jid, node)
|
||||
self._update_error(iter, jid, node)
|
||||
else:
|
||||
# We got our info
|
||||
self._update_info(model, iter, jid, node,
|
||||
self._update_info(iter, jid, node,
|
||||
identities, features, data)
|
||||
self.update_actions()
|
||||
|
||||
def _add_item(self, model, jid, node, item, force):
|
||||
def _add_item(self, jid, node, item, force):
|
||||
'''Called when an item should be added to the model. The result of a
|
||||
disco#items query.'''
|
||||
model.append((jid, node, item.get('name', ''),
|
||||
self.model.append((jid, node, item.get('name', ''),
|
||||
get_agent_address(jid, node)))
|
||||
|
||||
def _update_item(self, model, iter, jid, node, item):
|
||||
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. (seldom)'''
|
||||
if item.has_key('name'):
|
||||
model[iter][2] = item['name']
|
||||
self.model[iter][2] = item['name']
|
||||
|
||||
def _update_info(self, model, iter, jid, node, identities, features, data):
|
||||
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.'''
|
||||
model[iter][2] = identities[0].get('name', '')
|
||||
self.model[iter][2] = identities[0].get('name', '')
|
||||
|
||||
def _update_error(self, model, iter, jid, node):
|
||||
def _update_error(self, iter, jid, node):
|
||||
'''Called when a disco#info query failed for an item.'''
|
||||
pass
|
||||
|
||||
|
@ -1050,14 +1053,12 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
|
||||
# These are all callbacks to make tooltips work
|
||||
def on_treeview_leave_notify_event(self, widget, event):
|
||||
model = widget.get_model()
|
||||
props = widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if self.tooltip.timeout > 0:
|
||||
if not props or self.tooltip.id == props[0]:
|
||||
self.tooltip.hide_tooltip()
|
||||
|
||||
def on_treeview_motion_notify_event(self, widget, event):
|
||||
model = widget.get_model()
|
||||
props = widget.get_path_at_pos(int(event.x), int(event.y))
|
||||
if self.tooltip.timeout > 0:
|
||||
if not props or self.tooltip.id != props[0]:
|
||||
|
@ -1066,12 +1067,12 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
[row, col, x, y] = props
|
||||
iter = None
|
||||
try:
|
||||
iter = model.get_iter(row)
|
||||
iter = self.model.get_iter(row)
|
||||
except:
|
||||
self.tooltip.hide_tooltip()
|
||||
return
|
||||
jid = model[iter][0]
|
||||
state = model[iter][4]
|
||||
jid = self.model[iter][0]
|
||||
state = self.model[iter][4]
|
||||
# Not a category, and we have something to say about state
|
||||
if jid and state > 0 and \
|
||||
(self.tooltip.timeout == 0 or self.tooltip.id != props[0]):
|
||||
|
@ -1088,10 +1089,10 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
# JID, node, icon, description, state
|
||||
# State means 2 when error, 1 when fetching, 0 when succes.
|
||||
view = self.window.services_treeview
|
||||
model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
|
||||
model.set_sort_func(4, self._treemodel_sort_func)
|
||||
model.set_sort_column_id(4, gtk.SORT_ASCENDING)
|
||||
view.set_model(model)
|
||||
self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int)
|
||||
self.model.set_sort_func(4, self._treemodel_sort_func)
|
||||
self.model.set_sort_column_id(4, gtk.SORT_ASCENDING)
|
||||
view.set_model(self.model)
|
||||
|
||||
col = gtk.TreeViewColumn()
|
||||
# Icon Renderer
|
||||
|
@ -1195,16 +1196,10 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
if not iter:
|
||||
return
|
||||
service = model[iter][0].decode('utf-8')
|
||||
if service.find('@') != -1:
|
||||
services = service.split('@', 1)
|
||||
room = services[0]
|
||||
service = services[1]
|
||||
else:
|
||||
room = ''
|
||||
if not gajim.interface.instances[self.account].has_key('join_gc'):
|
||||
try:
|
||||
dialogs.JoinGroupchatWindow(self.account, service, room)
|
||||
except RuntimeError:
|
||||
dialogs.JoinGroupchatWindow(self.account, service)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
else:
|
||||
gajim.interface.instances[self.account]['join_gc'].window.present()
|
||||
|
@ -1251,10 +1246,11 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
# We can register this agent
|
||||
registered_transports = []
|
||||
jid_list = gajim.contacts.get_jid_list(self.account)
|
||||
for j in jid_list:
|
||||
contact = gajim.contacts.get_first_contact_from_jid(self.account, j)
|
||||
for jid in jid_list:
|
||||
contact = gajim.contacts.get_first_contact_from_jid(
|
||||
self.account, jid)
|
||||
if _('Transports') in contact.groups:
|
||||
registered_transports.append(j)
|
||||
registered_transports.append(jid)
|
||||
if jid in registered_transports:
|
||||
self.register_button.set_label(_('_Edit'))
|
||||
else:
|
||||
|
@ -1333,41 +1329,38 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
|
||||
def _create_category(self, cat, type=None):
|
||||
'''Creates a category row.'''
|
||||
model = self.window.services_treeview.get_model()
|
||||
cat, prio = self._friendly_category(cat, type)
|
||||
return model.append(None, ('', '', None, cat, prio))
|
||||
return self.model.append(None, ('', '', None, cat, prio))
|
||||
|
||||
def _find_category(self, cat, type=None):
|
||||
'''Looks up a category row and returns the iterator to it, or None.'''
|
||||
model = self.window.services_treeview.get_model()
|
||||
cat, prio = self._friendly_category(cat, type)
|
||||
iter = model.get_iter_root()
|
||||
iter = self.model.get_iter_root()
|
||||
while iter:
|
||||
if model.get_value(iter, 3).decode('utf-8') == cat:
|
||||
if self.model.get_value(iter, 3).decode('utf-8') == cat:
|
||||
break
|
||||
iter = model.iter_next(iter)
|
||||
iter = self.model.iter_next(iter)
|
||||
if iter:
|
||||
return iter
|
||||
return None
|
||||
|
||||
def _find_item(self, jid, node):
|
||||
model = self.window.services_treeview.get_model()
|
||||
iter = None
|
||||
cat_iter = model.get_iter_root()
|
||||
cat_iter = self.model.get_iter_root()
|
||||
while cat_iter and not iter:
|
||||
iter = model.iter_children(cat_iter)
|
||||
iter = self.model.iter_children(cat_iter)
|
||||
while iter:
|
||||
cjid = model.get_value(iter, 0).decode('utf-8')
|
||||
cnode = model.get_value(iter, 1).decode('utf-8')
|
||||
cjid = self.model.get_value(iter, 0).decode('utf-8')
|
||||
cnode = self.model.get_value(iter, 1).decode('utf-8')
|
||||
if jid == cjid and node == cnode:
|
||||
break
|
||||
iter = model.iter_next(iter)
|
||||
cat_iter = model.iter_next(cat_iter)
|
||||
iter = self.model.iter_next(iter)
|
||||
cat_iter = self.model.iter_next(cat_iter)
|
||||
if iter:
|
||||
return iter
|
||||
return None
|
||||
|
||||
def _add_item(self, model, jid, node, item, force):
|
||||
def _add_item(self, jid, node, item, force):
|
||||
# Row text
|
||||
addr = get_agent_address(jid, node)
|
||||
if item.has_key('name'):
|
||||
|
@ -1391,21 +1384,21 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
cat = self._find_category(*cat_args)
|
||||
if not cat:
|
||||
cat = self._create_category(*cat_args)
|
||||
model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1))
|
||||
self.model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1))
|
||||
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, model, iter, jid, node, item):
|
||||
def _update_item(self, iter, jid, node, item):
|
||||
addr = get_agent_address(jid, node)
|
||||
if item.has_key('name'):
|
||||
descr = "<b>%s</b>\n%s" % (item['name'], addr)
|
||||
else:
|
||||
descr = "<b>%s</b>" % addr
|
||||
model[iter][3] = descr
|
||||
self.model[iter][3] = descr
|
||||
|
||||
def _update_info(self, model, iter, jid, node, identities, features, data):
|
||||
def _update_info(self, iter, jid, node, identities, features, data):
|
||||
addr = get_agent_address(jid, node)
|
||||
name = identities[0].get('name', '')
|
||||
if name:
|
||||
|
@ -1427,32 +1420,32 @@ class ToplevelAgentBrowser(AgentBrowser):
|
|||
break
|
||||
|
||||
# Check if we have to move categories
|
||||
old_cat_iter = model.iter_parent(iter)
|
||||
old_cat = model.get_value(old_cat_iter, 3).decode('utf-8')
|
||||
if model.get_value(old_cat_iter, 3) == cat:
|
||||
old_cat_iter = self.model.iter_parent(iter)
|
||||
old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8')
|
||||
if self.model.get_value(old_cat_iter, 3) == cat:
|
||||
# Already in the right category, just update
|
||||
model[iter][2] = pix
|
||||
model[iter][3] = descr
|
||||
model[iter][4] = 0
|
||||
self.model[iter][2] = pix
|
||||
self.model[iter][3] = descr
|
||||
self.model[iter][4] = 0
|
||||
return
|
||||
# Not in the right category, move it.
|
||||
model.remove(iter)
|
||||
self.model.remove(iter)
|
||||
|
||||
# Check if the old category is empty
|
||||
if not model.iter_is_valid(old_cat_iter):
|
||||
if not self.model.iter_is_valid(old_cat_iter):
|
||||
old_cat_iter = self._find_category(old_cat)
|
||||
if not model.iter_children(old_cat_iter):
|
||||
model.remove(old_cat_iter)
|
||||
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)
|
||||
model.append(cat_iter, (jid, node, pix, descr, 0))
|
||||
self.model.append(cat_iter, (jid, node, pix, descr, 0))
|
||||
self._expand_all()
|
||||
|
||||
def _update_error(self, model, iter, jid, node):
|
||||
def _update_error(self, iter, jid, node):
|
||||
addr = get_agent_address(jid, node)
|
||||
model[iter][4] = 2
|
||||
self.model[iter][4] = 2
|
||||
self._progress += 1
|
||||
self._update_progressbar()
|
||||
|
||||
|
@ -1466,11 +1459,13 @@ class MucBrowser(AgentBrowser):
|
|||
# JID, node, name, users, 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.
|
||||
model = gtk.ListStore(str, str, str, str, str, bool)
|
||||
model.set_sort_column_id(2, gtk.SORT_ASCENDING)
|
||||
self.window.services_treeview.set_model(model)
|
||||
self.model = gtk.ListStore(str, str, str, str, str, bool)
|
||||
self.model.set_sort_column_id(2, gtk.SORT_ASCENDING)
|
||||
self.window.services_treeview.set_model(self.model)
|
||||
# Name column
|
||||
col = gtk.TreeViewColumn(_('Name'))
|
||||
col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
|
||||
col.set_fixed_width(100)
|
||||
renderer = gtk.CellRendererText()
|
||||
col.pack_start(renderer)
|
||||
col.set_attributes(renderer, text = 2)
|
||||
|
@ -1490,6 +1485,13 @@ class MucBrowser(AgentBrowser):
|
|||
col.set_attributes(renderer, text = 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)
|
||||
col.set_attributes(renderer, text = 0)
|
||||
self.window.services_treeview.insert_column(col, -1)
|
||||
col.set_resizable(True)
|
||||
self.window.services_treeview.set_headers_visible(True)
|
||||
# Source id for idle callback used to start disco#info queries.
|
||||
self._fetch_source = None
|
||||
|
@ -1499,7 +1501,8 @@ class MucBrowser(AgentBrowser):
|
|||
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)
|
||||
self.size_cbid = self.window.services_scrollwin.connect(
|
||||
'size-allocate', self.on_scroll)
|
||||
|
||||
def _clean_treemodel(self):
|
||||
if self.size_cbid:
|
||||
|
@ -1528,16 +1531,12 @@ class MucBrowser(AgentBrowser):
|
|||
if not iter:
|
||||
return
|
||||
service = model[iter][0].decode('utf-8')
|
||||
if service.find('@') != -1:
|
||||
services = service.split('@', 1)
|
||||
room = services[0]
|
||||
service = services[1]
|
||||
else:
|
||||
room = model[iter][1].decode('utf-8')
|
||||
if not gajim.interface.instances[self.account].has_key('join_gc'):
|
||||
room = model[iter][1].decode('utf-8')
|
||||
if 'join_gc' not in gajim.interface.instances[self.account]:
|
||||
try:
|
||||
dialogs.JoinGroupchatWindow(self.account, service, room)
|
||||
except RuntimeError:
|
||||
room_jid = '%s@%s' % (service, room)
|
||||
dialogs.JoinGroupchatWindow(self.account, service)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
else:
|
||||
gajim.interface.instances[self.account]['join_gc'].window.present()
|
||||
|
@ -1572,7 +1571,6 @@ class MucBrowser(AgentBrowser):
|
|||
# Prevent a silly warning, try again in a bit.
|
||||
self._fetch_source = gobject.timeout_add(100, self._start_info_query)
|
||||
return
|
||||
model = view.get_model()
|
||||
# We have to do this in a pygtk <2.8 compatible way :/
|
||||
#start, end = self.window.services_treeview.get_visible_range()
|
||||
rect = view.get_visible_rect()
|
||||
|
@ -1581,7 +1579,7 @@ class MucBrowser(AgentBrowser):
|
|||
try:
|
||||
sx, sy = view.tree_to_widget_coords(rect.x, rect.y)
|
||||
spath = view.get_path_at_pos(sx, sy)[0]
|
||||
iter = model.get_iter(spath)
|
||||
iter = self.model.get_iter(spath)
|
||||
except TypeError:
|
||||
self._fetch_source = None
|
||||
return
|
||||
|
@ -1595,14 +1593,14 @@ class MucBrowser(AgentBrowser):
|
|||
except TypeError:
|
||||
# We're at the end of the model, we can leave end=None though.
|
||||
pass
|
||||
while iter and model.get_path(iter) != end:
|
||||
if not model.get_value(iter, 5):
|
||||
jid = model.get_value(iter, 0).decode('utf-8')
|
||||
node = model.get_value(iter, 1).decode('utf-8')
|
||||
while iter and self.model.get_path(iter) != end:
|
||||
if not self.model.get_value(iter, 5):
|
||||
jid = self.model.get_value(iter, 0).decode('utf-8')
|
||||
node = self.model.get_value(iter, 1).decode('utf-8')
|
||||
self.cache.get_info(jid, node, self._agent_info)
|
||||
self._fetch_source = True
|
||||
return
|
||||
iter = model.iter_next(iter)
|
||||
iter = self.model.iter_next(iter)
|
||||
self._fetch_source = None
|
||||
|
||||
def _channel_altinfo(self, jid, node, items, name = None):
|
||||
|
@ -1623,22 +1621,21 @@ class MucBrowser(AgentBrowser):
|
|||
self._fetch_source = None
|
||||
return
|
||||
else:
|
||||
model = self.window.services_treeview.get_model()
|
||||
iter = self._find_item(jid, node)
|
||||
if iter:
|
||||
if name:
|
||||
model[iter][2] = name
|
||||
model[iter][3] = len(items) # The number of users
|
||||
model[iter][5] = True
|
||||
self.model[iter][2] = name
|
||||
self.model[iter][3] = len(items) # The number of users
|
||||
self.model[iter][5] = True
|
||||
self._fetch_source = None
|
||||
self._query_visible()
|
||||
|
||||
def _add_item(self, model, jid, node, item, force):
|
||||
model.append((jid, node, item.get('name', ''), '', '', False))
|
||||
def _add_item(self, jid, node, item, force):
|
||||
self.model.append((jid, node, item.get('name', ''), '', '', False))
|
||||
if not self._fetch_source:
|
||||
self._fetch_source = gobject.idle_add(self._start_info_query)
|
||||
|
||||
def _update_info(self, model, iter, jid, node, identities, features, data):
|
||||
def _update_info(self, iter, jid, node, identities, features, data):
|
||||
name = identities[0].get('name', '')
|
||||
for form in data:
|
||||
typefield = form.getField('FORM_TYPE')
|
||||
|
@ -1648,14 +1645,14 @@ class MucBrowser(AgentBrowser):
|
|||
users = form.getField('muc#roominfo_occupants')
|
||||
descr = form.getField('muc#roominfo_description')
|
||||
if users:
|
||||
model[iter][3] = users.getValue()
|
||||
self.model[iter][3] = users.getValue()
|
||||
if descr:
|
||||
model[iter][4] = descr.getValue()
|
||||
self.model[iter][4] = 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.
|
||||
model[iter][2] = name
|
||||
model[iter][5] = True
|
||||
self.model[iter][2] = name
|
||||
self.model[iter][5] = True
|
||||
break
|
||||
else:
|
||||
# We didn't find a form, switch to alternate query mode
|
||||
|
@ -1665,7 +1662,7 @@ class MucBrowser(AgentBrowser):
|
|||
self._fetch_source = None
|
||||
self._query_visible()
|
||||
|
||||
def _update_error(self, model, iter, jid, node):
|
||||
def _update_error(self, iter, jid, node):
|
||||
# switch to alternate query mode
|
||||
self.cache.get_items(jid, node, self._channel_altinfo)
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ class FileTransfersWindow:
|
|||
_('Connection with peer cannot be established.'))
|
||||
self.tree.get_selection().unselect_all()
|
||||
|
||||
def show_stopped(self, jid, file_props):
|
||||
def show_stopped(self, jid, file_props, error_msg = ''):
|
||||
self.window.present()
|
||||
self.window.window.focus()
|
||||
if file_props['type'] == 'r':
|
||||
|
@ -222,6 +222,8 @@ _('Connection with peer cannot be established.'))
|
|||
file_name = file_props['name']
|
||||
sectext = '\t' + _('Filename: %s') % file_name
|
||||
sectext += '\n\t' + _('Recipient: %s') % jid
|
||||
if error_msg:
|
||||
sectext += '\n\t' + _('Error message: %s') % error_msg
|
||||
dialogs.ErrorDialog(_('File transfer stopped by the contact of the other side'), \
|
||||
sectext)
|
||||
self.tree.get_selection().unselect_all()
|
||||
|
@ -244,11 +246,16 @@ _('Connection with peer cannot be established.'))
|
|||
gtk.RESPONSE_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()
|
||||
)
|
||||
|
||||
btn = dialog.add_button(_('_Send'), gtk.RESPONSE_OK)
|
||||
btn.set_use_stock(True) # FIXME: add send icon to this button (JUMP_TO)
|
||||
btn.connect('clicked', on_ok)
|
||||
btn = gtk.Button(_('_Send'))
|
||||
btn.set_property('can-default', True)
|
||||
# FIXME: add send icon to this button (JUMP_TO)
|
||||
dialog.add_action_widget(btn, gtk.RESPONSE_OK)
|
||||
dialog.set_default_response(gtk.RESPONSE_OK)
|
||||
btn.show()
|
||||
|
||||
def send_file(self, account, contact, file_path):
|
||||
''' start the real transfer(upload) of the file '''
|
||||
|
@ -448,8 +455,10 @@ _('Connection with peer cannot be established.'))
|
|||
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.parameters[1]['sid'] == file_props['sid']:
|
||||
if event.parameters['sid'] == file_props['sid']:
|
||||
gajim.events.remove_events(account, jid, event)
|
||||
gajim.interface.roster.draw_contact(jid, account)
|
||||
gajim.interface.roster.show_title()
|
||||
del(self.files_props[sid[0]][sid[1:]])
|
||||
del(file_props)
|
||||
|
||||
|
@ -558,7 +567,7 @@ _('Connection with peer cannot be established.'))
|
|||
(file_path, file_name) = os.path.split(file_props['file-name'])
|
||||
else:
|
||||
file_name = file_props['name']
|
||||
text_props = file_name + '\n'
|
||||
text_props = gtkgui_helpers.escape_for_pango_markup(file_name) + '\n'
|
||||
text_props += contact.get_shown_name()
|
||||
self.model.set(iter, 1, text_labels, 2, text_props, C_SID,
|
||||
file_props['type'] + file_props['sid'])
|
||||
|
|
|
@ -1,22 +1,8 @@
|
|||
#!/bin/sh
|
||||
''':'
|
||||
exec python -OOt "$0" ${1+"$@"}
|
||||
' '''
|
||||
## scripts/gajim-remote.py
|
||||
#!/usr/bin/env python
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005 Dimitur Kirov <dkirov@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
|
||||
|
@ -28,7 +14,7 @@ exec python -OOt "$0" ${1+"$@"}
|
|||
## GNU General Public License for more details.
|
||||
##
|
||||
|
||||
# gajim-remote help will show you the DBUS API of Gajim
|
||||
# gajim-remote help will show you the D-BUS API of Gajim
|
||||
|
||||
import sys
|
||||
import locale
|
||||
|
@ -51,13 +37,11 @@ def send_error(error_message):
|
|||
|
||||
try:
|
||||
import dbus
|
||||
except:
|
||||
raise exceptions.DbusNotSupported
|
||||
|
||||
_version = getattr(dbus, 'version', (0, 20, 0))
|
||||
if _version[1] >= 41:
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
except:
|
||||
print str(exceptions.DbusNotSupported())
|
||||
sys.exit(1)
|
||||
|
||||
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
|
||||
INTERFACE = 'org.gajim.dbus.RemoteInterface'
|
||||
|
@ -90,8 +74,8 @@ class GajimRemote:
|
|||
_('Shows or hides the roster window'),
|
||||
[]
|
||||
],
|
||||
'show_next_unread': [
|
||||
_('Popups a window with the next unread message'),
|
||||
'show_next_pending_event': [
|
||||
_('Popups a window with the next pending event'),
|
||||
[]
|
||||
],
|
||||
'list_contacts': [
|
||||
|
@ -320,14 +304,8 @@ class GajimRemote:
|
|||
except:
|
||||
raise exceptions.SessionBusNotPresent
|
||||
|
||||
if _version[1] >= 30:
|
||||
obj = self.sbus.get_object(SERVICE, OBJ_PATH)
|
||||
interface = dbus.Interface(obj, INTERFACE)
|
||||
elif _version[1] < 30:
|
||||
self.service = self.sbus.get_service(SERVICE)
|
||||
interface = self.service.get_object(OBJ_PATH, INTERFACE)
|
||||
else:
|
||||
send_error(_('Unknown D-Bus version: %s') % _version[1])
|
||||
obj = self.sbus.get_object(SERVICE, OBJ_PATH)
|
||||
interface = dbus.Interface(obj, INTERFACE)
|
||||
|
||||
# get the function asked
|
||||
self.method = interface.__getattr__(self.command)
|
||||
|
@ -447,10 +425,7 @@ class GajimRemote:
|
|||
''' calls self.method with arguments from sys.argv[2:] '''
|
||||
args = sys.argv[2:]
|
||||
args = [i.decode(PREFERRED_ENCODING) for i in sys.argv[2:]]
|
||||
if _version[1] >= 41:
|
||||
args = [dbus.String(i) for i in args]
|
||||
else:
|
||||
args = [i.encode('UTF-8') for i in sys.argv[2:]]
|
||||
args = [dbus.String(i) for i in args]
|
||||
try:
|
||||
res = self.method(*args)
|
||||
return res
|
||||
|
|
387
src/gajim.py
387
src/gajim.py
|
@ -1,23 +1,11 @@
|
|||
#!/bin/sh
|
||||
''':'
|
||||
exec python -OOt "$0" ${1+"$@"}
|
||||
' '''
|
||||
#!/usr/bin/env python
|
||||
## gajim.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
## - Travis Shirk <travis@pobox.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005 Travis Shirk <travis@pobox.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
|
||||
|
@ -41,6 +29,8 @@ from chat_control import ChatControlBase
|
|||
from atom_window import AtomWindow
|
||||
|
||||
from common import exceptions
|
||||
from common.zeroconf import connection_zeroconf
|
||||
from common import dbus_support
|
||||
|
||||
if os.name == 'posix': # dl module is Unix Only
|
||||
try: # rename the process name to gajim
|
||||
|
@ -82,16 +72,14 @@ except exceptions.PysqliteNotAvailable, e:
|
|||
if os.name == 'nt':
|
||||
try:
|
||||
import winsound # windows-only built-in module for playing wav
|
||||
import win32api
|
||||
import win32con
|
||||
except:
|
||||
pritext = _('Gajim needs pywin32 to run')
|
||||
sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018'
|
||||
|
||||
if pritext:
|
||||
dlg = gtk.MessageDialog(None,
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
|
||||
gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
|
||||
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext)
|
||||
|
||||
dlg.format_secondary_text(sectext)
|
||||
dlg.run()
|
||||
|
@ -144,23 +132,16 @@ for o, a in opts:
|
|||
elif o in ('-p', '--profile'): # gajim --profile name
|
||||
profile = a
|
||||
|
||||
pid_filename = os.path.expanduser('~/.gajim/gajim')
|
||||
config_filename = os.path.expanduser('~/.gajim/config')
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
# Documents and Settings\[User Name]\Application Data\Gajim\logs
|
||||
config_filename = os.environ['appdata'] + '/Gajim/config'
|
||||
pid_filename = os.environ['appdata'] + '/Gajim/gajim'
|
||||
except KeyError:
|
||||
# win9x so ./config
|
||||
config_filename = 'config'
|
||||
pid_filename = 'gajim'
|
||||
import locale
|
||||
profile = unicode(profile, locale.getpreferredencoding())
|
||||
|
||||
if profile:
|
||||
config_filename += '.%s' % profile
|
||||
pid_filename += '.%s' % profile
|
||||
import common.configpaths
|
||||
common.configpaths.init_profile(profile)
|
||||
gajimpaths = common.configpaths.gajimpaths
|
||||
|
||||
pid_filename = gajimpaths['PID_FILE']
|
||||
config_filename = gajimpaths['CONFIG_FILE']
|
||||
|
||||
pid_filename += '.pid'
|
||||
import dialogs
|
||||
if os.path.exists(pid_filename):
|
||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
|
||||
|
@ -194,7 +175,6 @@ atexit.register(on_exit)
|
|||
parser = optparser.OptionsParser(config_filename)
|
||||
|
||||
import roster_window
|
||||
import systray
|
||||
import profile_window
|
||||
import config
|
||||
|
||||
|
@ -217,7 +197,7 @@ class GlibIdleQueue(idlequeue.IdleQueue):
|
|||
Start listening for events from fd
|
||||
'''
|
||||
res = gobject.io_add_watch(fd, flags, self.process_events,
|
||||
priority=gobject.PRIORITY_LOW)
|
||||
priority=gobject.PRIORITY_LOW)
|
||||
# store the id of the watch, so that we can remove it on unplug
|
||||
self.events[fd] = res
|
||||
|
||||
|
@ -274,7 +254,7 @@ class Interface:
|
|||
on_response_no = (response, account, data[3], 'no'))
|
||||
|
||||
def handle_event_error_answer(self, account, array):
|
||||
#('ERROR_ANSWER', account, (id, jid_from. errmsg, errcode))
|
||||
#('ERROR_ANSWER', account, (id, jid_from, errmsg, errcode))
|
||||
id, jid_from, errmsg, errcode = array
|
||||
if unicode(errcode) in ('403', '406') and id:
|
||||
# show the error dialog
|
||||
|
@ -286,7 +266,7 @@ class Interface:
|
|||
file_props = ft.files_props['s'][sid]
|
||||
file_props['error'] = -4
|
||||
self.handle_event_file_request_error(account,
|
||||
(jid_from, file_props))
|
||||
(jid_from, file_props, errmsg))
|
||||
conn = gajim.connections[account]
|
||||
conn.disconnect_transfer(file_props)
|
||||
return
|
||||
|
@ -341,7 +321,8 @@ class Interface:
|
|||
# Inform all controls for this account of the connection state change
|
||||
for ctrl in self.msg_win_mgr.get_controls():
|
||||
if ctrl.account == account:
|
||||
if status == 'offline':
|
||||
if status == 'offline' or (status == 'invisible' and \
|
||||
gajim.connections[account].is_zeroconf):
|
||||
ctrl.got_disconnected()
|
||||
else:
|
||||
# Other code rejoins all GCs, so we don't do it here
|
||||
|
@ -422,12 +403,13 @@ class Interface:
|
|||
elif contact1.show in statuss:
|
||||
old_show = statuss.index(contact1.show)
|
||||
if (resources != [''] and (len(lcontact) != 1 or
|
||||
lcontact[0].show != 'offline')) and jid.find('@') > 0:
|
||||
lcontact[0].show != 'offline')) and jid.find('@') > 0:
|
||||
old_show = 0
|
||||
contact1 = gajim.contacts.copy_contact(contact1)
|
||||
lcontact.append(contact1)
|
||||
contact1.resource = resource
|
||||
if contact1.jid.find('@') > 0 and len(lcontact) == 1: # It's not an agent
|
||||
if contact1.jid.find('@') > 0 and len(lcontact) == 1:
|
||||
# It's not an agent
|
||||
if old_show == 0 and new_show > 1:
|
||||
if not contact1.jid in gajim.newly_added[account]:
|
||||
gajim.newly_added[account].append(contact1.jid)
|
||||
|
@ -459,6 +441,7 @@ class Interface:
|
|||
if ji in jid_list:
|
||||
# Update existing iter
|
||||
self.roster.draw_contact(ji, account)
|
||||
self.roster.draw_group(_('Transports'), account)
|
||||
# transport just signed in/out, don't show popup notifications
|
||||
# for 30s
|
||||
account_ji = account + '/' + ji
|
||||
|
@ -509,18 +492,23 @@ class Interface:
|
|||
|
||||
def handle_event_msg(self, account, array):
|
||||
# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
|
||||
# chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
|
||||
# chatstate, msg_id, composing_jep, user_nick, xhtml))
|
||||
# user_nick is JEP-0172
|
||||
|
||||
full_jid_with_resource = array[0]
|
||||
jid = gajim.get_jid_without_resource(full_jid_with_resource)
|
||||
resource = gajim.get_resource_from_jid(full_jid_with_resource)
|
||||
|
||||
message = array[1]
|
||||
encrypted = array[3]
|
||||
msg_type = array[4]
|
||||
subject = array[5]
|
||||
chatstate = array[6]
|
||||
msg_id = array[7]
|
||||
composing_jep = array[8]
|
||||
xhtml = array[10]
|
||||
if gajim.config.get('ignore_incoming_xhtml'):
|
||||
xhtml = None
|
||||
if gajim.jid_is_transport(jid):
|
||||
jid = jid.replace('@', '')
|
||||
|
||||
|
@ -530,6 +518,7 @@ class Interface:
|
|||
message_control.TYPE_GC:
|
||||
# It's a Private message
|
||||
pm = True
|
||||
msg_type = 'pm'
|
||||
|
||||
chat_control = None
|
||||
jid_of_control = full_jid_with_resource
|
||||
|
@ -544,7 +533,8 @@ class Interface:
|
|||
# unknow contact or offline message
|
||||
jid_of_control = jid
|
||||
chat_control = self.msg_win_mgr.get_control(jid, account)
|
||||
elif highest_contact and resource != highest_contact.resource:
|
||||
elif highest_contact and resource != highest_contact.resource and \
|
||||
highest_contact.show != 'offline':
|
||||
jid_of_control = full_jid_with_resource
|
||||
chat_control = None
|
||||
elif not pm:
|
||||
|
@ -575,39 +565,51 @@ class Interface:
|
|||
contact.msg_id = msg_id
|
||||
|
||||
# THIS MUST BE AFTER chatstates handling
|
||||
# AND BEFORE playsound (else we here sounding on chatstates!)
|
||||
# AND BEFORE playsound (else we ear sounding on chatstates!)
|
||||
if not message: # empty message text
|
||||
return
|
||||
|
||||
if gajim.config.get('ignore_unknown_contacts') and \
|
||||
not gajim.contacts.get_contact(account, jid) and not pm:
|
||||
return
|
||||
|
||||
if not contact:
|
||||
# contact is not in the roster, create a fake one to display
|
||||
# notification
|
||||
contact = common.contacts.Contact(jid = jid, resource = resource)
|
||||
advanced_notif_num = notify.get_advanced_notification('message_received',
|
||||
account, contact)
|
||||
|
||||
# Is it a first or next message received ?
|
||||
first = False
|
||||
if not chat_control and not gajim.events.get_events(account,
|
||||
jid_of_control, ['chat']):
|
||||
# It's a first message and not a Private Message
|
||||
if msg_type == 'normal':
|
||||
if not gajim.events.get_events(account, jid, ['normal']):
|
||||
first = True
|
||||
elif not chat_control and not gajim.events.get_events(account,
|
||||
jid_of_control, [msg_type]): # msg_type can be chat or pm
|
||||
first = True
|
||||
|
||||
if pm:
|
||||
nickname = resource
|
||||
msg_type = 'pm'
|
||||
groupchat_control.on_private_message(nickname, message, array[2])
|
||||
groupchat_control.on_private_message(nickname, message, array[2],
|
||||
xhtml)
|
||||
else:
|
||||
# array: (jid, msg, time, encrypted, msg_type, subject)
|
||||
self.roster.on_message(jid, message, array[2], account, array[3],
|
||||
msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
|
||||
if encrypted:
|
||||
self.roster.on_message(jid, message, array[2], account, array[3],
|
||||
msg_type, subject, resource, msg_id, array[9],
|
||||
advanced_notif_num)
|
||||
else:
|
||||
# xhtml in last element
|
||||
self.roster.on_message(jid, message, array[2], account, array[3],
|
||||
msg_type, subject, resource, msg_id, array[9],
|
||||
advanced_notif_num, xhtml = xhtml)
|
||||
nickname = gajim.get_name_from_jid(account, jid)
|
||||
# Check and do wanted notifications
|
||||
msg = message
|
||||
if subject:
|
||||
msg = _('Subject: %s') % subject + '\n' + msg
|
||||
notify.notify('new_message', jid, account, [msg_type, first, nickname,
|
||||
msg], advanced_notif_num)
|
||||
notify.notify('new_message', jid_of_control, account, [msg_type,
|
||||
first, nickname, msg], advanced_notif_num)
|
||||
|
||||
if self.remote_ctrl:
|
||||
self.remote_ctrl.raise_signal('NewMessage', (account, array))
|
||||
|
@ -617,33 +619,35 @@ class Interface:
|
|||
full_jid_with_resource = array[0]
|
||||
jids = full_jid_with_resource.split('/', 1)
|
||||
jid = jids[0]
|
||||
gcs = self.msg_win_mgr.get_controls(message_control.TYPE_GC)
|
||||
for gc_control in gcs:
|
||||
if jid == gc_control.contact.jid:
|
||||
if len(jids) > 1: # it's a pm
|
||||
nick = jids[1]
|
||||
if not self.msg_win_mgr.get_control(full_jid_with_resource, account):
|
||||
tv = gc_control.list_treeview
|
||||
model = tv.get_model()
|
||||
i = gc_control.get_contact_iter(nick)
|
||||
if i:
|
||||
show = model[i][3]
|
||||
else:
|
||||
show = 'offline'
|
||||
gc_c = gajim.contacts.create_gc_contact(room_jid = jid,
|
||||
name = nick, show = show)
|
||||
c = gajim.contacts.contact_from_gc_contact(gc_c)
|
||||
self.roster.new_chat(c, account, private_chat = True)
|
||||
ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
|
||||
ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
|
||||
'status')
|
||||
return
|
||||
|
||||
gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
|
||||
if gc_control.parent_win.get_active_jid() == jid:
|
||||
gc_control.set_subject(gc_control.subject)
|
||||
gc_control = self.msg_win_mgr.get_control(jid, account)
|
||||
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 not self.msg_win_mgr.get_control(full_jid_with_resource,
|
||||
account):
|
||||
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,
|
||||
name = nick, show = show)
|
||||
c = gajim.contacts.contact_from_gc_contact(gc_c)
|
||||
self.roster.new_chat(c, account, private_chat = True)
|
||||
ctrl = self.msg_win_mgr.get_control(full_jid_with_resource, account)
|
||||
ctrl.print_conversation('Error %s: %s' % (array[1], array[2]),
|
||||
'status')
|
||||
return
|
||||
|
||||
gc_control.print_conversation('Error %s: %s' % (array[1], array[2]))
|
||||
if 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 = array[2]
|
||||
|
@ -697,8 +701,8 @@ class Interface:
|
|||
self.remote_ctrl.raise_signal('Subscribed', (account, array))
|
||||
|
||||
def handle_event_unsubscribed(self, account, jid):
|
||||
dialogs.InformationDialog(_('Contact "%s" removed subscription from you') % jid,
|
||||
_('You will always see him or her as offline.'))
|
||||
dialogs.InformationDialog(_('Contact "%s" removed subscription from you')\
|
||||
% jid, _('You will always see him or her as offline.'))
|
||||
# FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does not show deny
|
||||
gajim.connections[account].ack_unsubscribed(jid)
|
||||
if self.remote_ctrl:
|
||||
|
@ -741,8 +745,8 @@ class Interface:
|
|||
config.ServiceRegistrationWindow(array[0], array[1], account,
|
||||
array[2])
|
||||
else:
|
||||
dialogs.ErrorDialog(_('Contact with "%s" cannot be established')\
|
||||
% array[0], _('Check your connection or try again later.'))
|
||||
dialogs.ErrorDialog(_('Contact with "%s" cannot be established') \
|
||||
% array[0], _('Check your connection or try again later.'))
|
||||
|
||||
def handle_event_agent_info_items(self, account, array):
|
||||
#('AGENT_INFO_ITEMS', account, (agent, node, items))
|
||||
|
@ -851,6 +855,7 @@ class Interface:
|
|||
self.remote_ctrl.raise_signal('LastStatusTime', (account, array))
|
||||
|
||||
def handle_event_os_info(self, account, array):
|
||||
#'OS_INFO' (account, (jid, resource, client_info, os_info))
|
||||
win = None
|
||||
if self.instances[account]['infos'].has_key(array[0]):
|
||||
win = self.instances[account]['infos'][array[0]]
|
||||
|
@ -875,8 +880,8 @@ class Interface:
|
|||
# Get the window and control for the updated status, this may be a PrivateChatControl
|
||||
control = self.msg_win_mgr.get_control(room_jid, account)
|
||||
if control:
|
||||
control.chg_contact_status(nick, show, status, array[4], array[5], array[6],
|
||||
array[7], array[8], array[9], array[10])
|
||||
control.chg_contact_status(nick, show, status, array[4], array[5],
|
||||
array[6], array[7], array[8], array[9], array[10])
|
||||
|
||||
# print status in chat window and update status/GPG image
|
||||
if self.msg_win_mgr.has_window(fjid, account):
|
||||
|
@ -892,38 +897,48 @@ class Interface:
|
|||
if self.remote_ctrl:
|
||||
self.remote_ctrl.raise_signal('GCPresence', (account, array))
|
||||
|
||||
|
||||
def handle_event_gc_msg(self, account, array):
|
||||
# ('GC_MSG', account, (jid, msg, time))
|
||||
# ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg))
|
||||
jids = array[0].split('/', 1)
|
||||
room_jid = jids[0]
|
||||
gc_control = self.msg_win_mgr.get_control(room_jid, account)
|
||||
if not gc_control:
|
||||
return
|
||||
xhtml = array[4]
|
||||
if gajim.config.get('ignore_incoming_xhtml'):
|
||||
xhtml = None
|
||||
if len(jids) == 1:
|
||||
# message from server
|
||||
nick = ''
|
||||
else:
|
||||
# message from someone
|
||||
nick = jids[1]
|
||||
gc_control.on_message(nick, array[1], array[2])
|
||||
gc_control.on_message(nick, array[1], array[2], array[3], xhtml)
|
||||
if self.remote_ctrl:
|
||||
self.remote_ctrl.raise_signal('GCMessage', (account, array))
|
||||
|
||||
def handle_event_gc_subject(self, account, array):
|
||||
#('GC_SUBJECT', account, (jid, subject, body))
|
||||
#('GC_SUBJECT', account, (jid, subject, body, has_timestamp))
|
||||
jids = array[0].split('/', 1)
|
||||
jid = jids[0]
|
||||
gc_control = self.msg_win_mgr.get_control(jid, account)
|
||||
if not gc_control:
|
||||
return
|
||||
gc_control.set_subject(array[1])
|
||||
# We can receive a subject with a body that contains "X has set the subject to Y" ...
|
||||
if array[2]:
|
||||
gc_control.print_conversation(array[2])
|
||||
# ... Or the message comes from the occupant who set the subject
|
||||
elif len(jids) > 1:
|
||||
gc_control.print_conversation('%s has set the subject to %s' % (jids[1], array[1]))
|
||||
# Standard way, the message comes from the occupant who set the subject
|
||||
text = None
|
||||
if len(jids) > 1:
|
||||
text = '%s has set the subject to %s' % (jids[1], array[1])
|
||||
# Workaround for psi bug http://flyspray.psi-im.org/task/595 , to be
|
||||
# deleted one day. We can receive a subject with a body that contains
|
||||
# "X has set the subject to Y" ...
|
||||
elif array[2]:
|
||||
text = array[2]
|
||||
if text is not None:
|
||||
if array[3]:
|
||||
gc_control.print_old_conversation(text)
|
||||
else:
|
||||
gc_control.print_conversation(text)
|
||||
|
||||
def handle_event_gc_config(self, account, array):
|
||||
#('GC_CONFIG', account, (jid, config)) config is a dict
|
||||
|
@ -959,7 +974,7 @@ class Interface:
|
|||
self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
|
||||
array[3]))
|
||||
|
||||
if helpers.allow_showing_notification(account, 'notify_on_new_message'):
|
||||
if helpers.allow_showing_notification(account):
|
||||
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
'gc_invitation.png')
|
||||
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
|
||||
|
@ -1010,7 +1025,7 @@ class Interface:
|
|||
return
|
||||
# Add it to roster
|
||||
contact = gajim.contacts.create_contact(jid = jid, name = name,
|
||||
groups = groups, show = 'offline', sub = sub, ask = ask)
|
||||
groups = groups, show = 'offline', sub = sub, ask = ask)
|
||||
gajim.contacts.add_contact(account, contact)
|
||||
self.roster.add_contact_to_roster(jid, account)
|
||||
else:
|
||||
|
@ -1075,13 +1090,17 @@ class Interface:
|
|||
gmail_messages_list = array[2]
|
||||
if gajim.config.get('notify_on_new_gmail_email'):
|
||||
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
'single_msg_recv.png') #FIXME: find a better image
|
||||
title = _('New E-mail on %(gmail_mail_address)s') % \
|
||||
'new_email_recv.png')
|
||||
title = _('New mail on %(gmail_mail_address)s') % \
|
||||
{'gmail_mail_address': jid}
|
||||
text = i18n.ngettext('You have %d new E-mail message', 'You have %d new E-mail messages', gmail_new_messages, gmail_new_messages, gmail_new_messages)
|
||||
text = i18n.ngettext('You have %d new mail conversation',
|
||||
'You have %d new mail conversations', gmail_new_messages,
|
||||
gmail_new_messages, gmail_new_messages)
|
||||
|
||||
if gajim.config.get('notify_on_new_gmail_email_extra'):
|
||||
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
|
||||
text += _('\nFrom: %(from_address)s') % \
|
||||
{'from_address': gmessage['From']}
|
||||
|
@ -1149,8 +1168,15 @@ class Interface:
|
|||
|
||||
self.roster.show_title()
|
||||
if no_queue: # We didn't have a queue: we change icons
|
||||
if not gajim.contacts.get_contact_with_highest_priority(account, jid):
|
||||
# add contact to roster ("Not In The Roster") if he is not
|
||||
self.roster.add_to_not_in_the_roster(account, jid)
|
||||
self.roster.draw_contact(jid, account)
|
||||
|
||||
# Show contact in roster (if he is invisible for example) and select line
|
||||
path = self.roster.get_path(jid, account)
|
||||
self.roster.show_and_select_path(path, jid, account)
|
||||
|
||||
def remove_first_event(self, account, jid, type_ = None):
|
||||
event = gajim.events.get_first_event(account, jid, type_)
|
||||
self.remove_event(account, jid, event)
|
||||
|
@ -1161,24 +1187,26 @@ class Interface:
|
|||
return
|
||||
# no other event?
|
||||
if not len(gajim.events.get_events(account, jid)):
|
||||
if not gajim.config.get('showoffline'):
|
||||
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||
jid)
|
||||
if contact:
|
||||
self.roster.really_remove_contact(contact, account)
|
||||
contact = gajim.contacts.get_contact_with_highest_priority(account,
|
||||
jid)
|
||||
show_transport = gajim.config.get('show_transports_group')
|
||||
if contact and (contact.show in ('error', 'offline') and \
|
||||
not gajim.config.get('showoffline') or (
|
||||
gajim.jid_is_transport(jid) and not show_transport)):
|
||||
self.roster.really_remove_contact(contact, account)
|
||||
self.roster.show_title()
|
||||
self.roster.draw_contact(jid, account)
|
||||
|
||||
def handle_event_file_request_error(self, account, array):
|
||||
jid = array[0]
|
||||
file_props = array[1]
|
||||
# ('FILE_REQUEST_ERROR', account, (jid, file_props, error_msg))
|
||||
jid, file_props, errmsg = array
|
||||
ft = self.instances['file_transfers']
|
||||
ft.set_status(file_props['type'], file_props['sid'], 'stop')
|
||||
errno = file_props['error']
|
||||
|
||||
if helpers.allow_popup_window(account):
|
||||
if errno in (-4, -5):
|
||||
ft.show_stopped(jid, file_props)
|
||||
ft.show_stopped(jid, file_props, errmsg)
|
||||
else:
|
||||
ft.show_request_error(file_props)
|
||||
return
|
||||
|
@ -1224,8 +1252,11 @@ class Interface:
|
|||
path_to_image = path, title = event_type, text = txt)
|
||||
|
||||
def handle_event_file_progress(self, account, file_props):
|
||||
self.instances['file_transfers'].set_progress(file_props['type'],
|
||||
file_props['sid'], file_props['received-len'])
|
||||
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 handle_event_file_rcv_completed(self, account, file_props):
|
||||
ft = self.instances['file_transfers']
|
||||
|
@ -1328,7 +1359,9 @@ class Interface:
|
|||
self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing')
|
||||
|
||||
def handle_event_vcard_published(self, account, array):
|
||||
dialogs.InformationDialog(_('vCard publication succeeded'), _('Your personal information has been published successfully.'))
|
||||
if self.instances[account].has_key('profile'):
|
||||
win = self.instances[account]['profile']
|
||||
win.vcard_published()
|
||||
for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC):
|
||||
if gc_control.account == account:
|
||||
show = gajim.SHOW_LIST[gajim.connections[account].connected]
|
||||
|
@ -1337,7 +1370,9 @@ class Interface:
|
|||
gc_control.room_jid, show, status)
|
||||
|
||||
def handle_event_vcard_not_published(self, account, array):
|
||||
dialogs.InformationDialog(_('vCard publication failed'), _('There was an error while publishing your personal information, try again later.'))
|
||||
if self.instances[account].has_key('profile'):
|
||||
win = self.instances[account]['profile']
|
||||
win.vcard_not_published()
|
||||
|
||||
def handle_event_signed_in(self, account, empty):
|
||||
'''SIGNED_IN event is emitted when we sign in, so handle it'''
|
||||
|
@ -1362,12 +1397,11 @@ class Interface:
|
|||
if gajim.gc_connected[account].has_key(room_jid) and\
|
||||
gajim.gc_connected[account][room_jid]:
|
||||
continue
|
||||
room, server = gajim.get_room_name_and_server_from_room_jid(room_jid)
|
||||
nick = gc_control.nick
|
||||
password = ''
|
||||
if gajim.gc_passwords.has_key(room_jid):
|
||||
password = gajim.gc_passwords[room_jid]
|
||||
gajim.connections[account].join_gc(nick, room, server, password)
|
||||
gajim.connections[account].join_gc(nick, room_jid, password)
|
||||
|
||||
def handle_event_metacontacts(self, account, tags_list):
|
||||
gajim.contacts.define_metacontacts(account, tags_list)
|
||||
|
@ -1401,6 +1435,21 @@ class Interface:
|
|||
if win.startswith('privacy_list_'):
|
||||
self.instances[account][win].check_active_default(data)
|
||||
|
||||
def handle_event_zc_name_conflict(self, account, data):
|
||||
dlg = dialogs.InputDialog(_('Username Conflict'),
|
||||
_('Please type a new username for your local account'),
|
||||
is_modal = True)
|
||||
dlg.input_entry.set_text(data)
|
||||
response = dlg.get_response()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
new_name = dlg.input_entry.get_text()
|
||||
gajim.config.set_per('accounts', account, 'name', new_name)
|
||||
status = gajim.connections[account].status
|
||||
gajim.connections[account].username = new_name
|
||||
gajim.connections[account].change_status(status, '')
|
||||
else:
|
||||
gajim.connections[account].change_status('offline','')
|
||||
|
||||
def read_sleepy(self):
|
||||
'''Check idle status and change that status if needed'''
|
||||
if not self.sleeper.poll():
|
||||
|
@ -1614,25 +1663,28 @@ class Interface:
|
|||
menu.show_all()
|
||||
return menu
|
||||
|
||||
def init_emoticons(self):
|
||||
if not gajim.config.get('emoticons_theme'):
|
||||
def init_emoticons(self, need_reload = False):
|
||||
emot_theme = gajim.config.get('emoticons_theme')
|
||||
if not emot_theme:
|
||||
return
|
||||
|
||||
#initialize emoticons dictionary and unique images list
|
||||
self.emoticons_images = list()
|
||||
self.emoticons = dict()
|
||||
|
||||
emot_theme = gajim.config.get('emoticons_theme')
|
||||
if not emot_theme:
|
||||
return
|
||||
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
|
||||
if not os.path.exists(path): # theme doesn't exist, disable emoticons
|
||||
gajim.config.set('emoticons_theme', '')
|
||||
return
|
||||
sys.path.append(path)
|
||||
from emoticons import emoticons as emots
|
||||
import emoticons
|
||||
if need_reload:
|
||||
# we need to reload else that doesn't work when changing emoticon set
|
||||
reload(emoticons)
|
||||
emots = emoticons.emoticons
|
||||
for emot in emots:
|
||||
emot_file = os.path.join(path, emots[emot])
|
||||
if not self.image_is_ok(emot_file):
|
||||
|
@ -1646,7 +1698,7 @@ class Interface:
|
|||
self.emoticons_images.append((emot, pix))
|
||||
self.emoticons[emot.upper()] = emot_file
|
||||
sys.path.remove(path)
|
||||
del emots
|
||||
del emoticons
|
||||
if self.emoticons_menu:
|
||||
self.emoticons_menu.destroy()
|
||||
self.emoticons_menu = self.prepare_emoticons_menu()
|
||||
|
@ -1707,6 +1759,7 @@ class Interface:
|
|||
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
|
||||
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
|
||||
self.handle_event_privacy_lists_active_default,
|
||||
'ZC_NAME_CONFLICT': self.handle_event_zc_name_conflict,
|
||||
}
|
||||
gajim.handlers = self.handlers
|
||||
|
||||
|
@ -1726,20 +1779,28 @@ class Interface:
|
|||
err_str)
|
||||
sys.exit()
|
||||
|
||||
def handle_event(self, account, jid, type_):
|
||||
def handle_event(self, account, fjid, type_):
|
||||
w = None
|
||||
fjid = jid
|
||||
resource = gajim.get_resource_from_jid(jid)
|
||||
jid = gajim.get_jid_without_resource(jid)
|
||||
resource = gajim.get_resource_from_jid(fjid)
|
||||
jid = gajim.get_jid_without_resource(fjid)
|
||||
if type_ in ('printed_gc_msg', 'gc_msg'):
|
||||
w = self.msg_win_mgr.get_window(jid, account)
|
||||
elif type_ in ('printed_chat', 'chat'):
|
||||
elif type_ in ('printed_chat', 'chat', ''):
|
||||
# '' is for log in/out notifications
|
||||
if self.msg_win_mgr.has_window(fjid, account):
|
||||
w = self.msg_win_mgr.get_window(fjid, account)
|
||||
else:
|
||||
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):
|
||||
resource = None
|
||||
fjid = jid
|
||||
contact = gajim.contacts.get_contact(account, jid, resource)
|
||||
if isinstance(contact, list):
|
||||
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
|
||||
if not contact or isinstance(contact, list):
|
||||
contact = highest_contact
|
||||
self.roster.new_chat(contact, account, resource = resource)
|
||||
w = self.msg_win_mgr.get_window(fjid, account)
|
||||
gajim.last_message_time[account][jid] = 0 # long time ago
|
||||
|
@ -1763,16 +1824,18 @@ class Interface:
|
|||
elif type_ in ('normal', 'file-request', 'file-request-error',
|
||||
'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
|
||||
# Get the first single message event
|
||||
event = gajim.events.get_first_event(account, jid, type_)
|
||||
# Open the window
|
||||
self.roster.open_event(account, jid, event)
|
||||
elif type_ == 'gmail':
|
||||
if gajim.config.get_per('accounts', account, 'savepass'):
|
||||
url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\
|
||||
(urllib.quote(gajim.config.get_per('accounts', account, 'name')),
|
||||
urllib.quote(gajim.config.get_per('accounts', account, 'password')))
|
||||
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_)
|
||||
# Open the window
|
||||
self.roster.open_event(account, jid, event)
|
||||
else:
|
||||
url = ('http://mail.google.com/')
|
||||
# Open the window
|
||||
self.roster.open_event(account, fjid, event)
|
||||
elif type_ == 'gmail':
|
||||
url = 'http://mail.google.com/mail?account_id=%s' % urllib.quote(
|
||||
gajim.config.get_per('accounts', account, 'name'))
|
||||
helpers.launch_browser_mailer('url', url)
|
||||
elif type_ == 'gc-invitation':
|
||||
event = gajim.events.get_first_event(account, jid, type_)
|
||||
|
@ -1780,6 +1843,7 @@ class Interface:
|
|||
dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
|
||||
data[1])
|
||||
gajim.events.remove_events(account, jid, event)
|
||||
self.roster.draw_contact(jid, account)
|
||||
if w:
|
||||
w.set_active_tab(fjid, account)
|
||||
w.window.present()
|
||||
|
@ -1857,11 +1921,17 @@ class Interface:
|
|||
self.handle_event_file_progress)
|
||||
gajim.proxy65_manager = proxy65_manager.Proxy65Manager(gajim.idlequeue)
|
||||
self.register_handlers()
|
||||
if gajim.config.get('enable_zeroconf'):
|
||||
gajim.connections[gajim.ZEROCONF_ACC_NAME] = common.zeroconf.connection_zeroconf.ConnectionZeroconf(gajim.ZEROCONF_ACC_NAME)
|
||||
for account in gajim.config.get_per('accounts'):
|
||||
gajim.connections[account] = common.connection.Connection(account)
|
||||
|
||||
if not gajim.config.get_per('accounts', account, 'is_zeroconf'):
|
||||
gajim.connections[account] = common.connection.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')
|
||||
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
|
||||
gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url')
|
||||
|
||||
self.instances = {'logs': {}}
|
||||
|
||||
|
@ -1891,6 +1961,12 @@ class Interface:
|
|||
else:
|
||||
self.remote_ctrl = None
|
||||
|
||||
if gajim.config.get('networkmanager_support') and dbus_support.supported:
|
||||
try:
|
||||
import network_manager_listener
|
||||
except:
|
||||
print >> sys.stderr, _('Network Manager support not available')
|
||||
|
||||
self.show_vcard_when_connect = []
|
||||
|
||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
|
||||
|
@ -1904,18 +1980,18 @@ class Interface:
|
|||
self.systray_enabled = False
|
||||
self.systray_capabilities = False
|
||||
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
import systraywin32
|
||||
except: # user doesn't have trayicon capabilities
|
||||
pass
|
||||
else:
|
||||
self.systray_capabilities = True
|
||||
self.systray = systraywin32.SystrayWin32()
|
||||
else:
|
||||
if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\
|
||||
gtk.gtk_version >= (2, 10, 0):
|
||||
import statusicon
|
||||
self.systray = statusicon.StatusIcon()
|
||||
self.systray_capabilities = True
|
||||
else: # use ours, not GTK+ one
|
||||
# [FIXME: remove this when we migrate to 2.10 and we can do
|
||||
# cool tooltips somehow and (not dying to keep) animation]
|
||||
import systray
|
||||
self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES
|
||||
if self.systray_capabilities:
|
||||
self.systray = systray.Systray()
|
||||
self.systray = systray.Systray()
|
||||
|
||||
if self.systray_capabilities and gajim.config.get('trayicon'):
|
||||
self.show_systray()
|
||||
|
@ -1945,6 +2021,9 @@ class Interface:
|
|||
'choose another language by setting the speller_language option.'
|
||||
) % lang)
|
||||
gajim.config.set('use_speller', False)
|
||||
|
||||
self.last_ftwindow_update = 0
|
||||
|
||||
gobject.timeout_add(100, self.autoconnect)
|
||||
gobject.timeout_add(200, self.process_connections)
|
||||
gobject.timeout_add(500, self.read_sleepy)
|
||||
|
@ -1968,7 +2047,8 @@ if __name__ == '__main__':
|
|||
cli = gnome.ui.master_client()
|
||||
cli.connect('die', die_cb)
|
||||
|
||||
path_to_gajim_script = gtkgui_helpers.get_abspath_for_script('gajim')
|
||||
path_to_gajim_script = gtkgui_helpers.get_abspath_for_script(
|
||||
'gajim')
|
||||
|
||||
if path_to_gajim_script:
|
||||
argv = [path_to_gajim_script]
|
||||
|
@ -1983,5 +2063,6 @@ if __name__ == '__main__':
|
|||
gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
|
||||
|
||||
check_paths.check_and_possibly_create_paths()
|
||||
|
||||
Interface()
|
||||
gtk.main()
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -51,7 +51,7 @@ class GajimThemesWindow:
|
|||
self.themes_tree = self.xml.get_widget('themes_treeview')
|
||||
self.theme_options_vbox = self.xml.get_widget('theme_options_vbox')
|
||||
self.colorbuttons = {}
|
||||
for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone',
|
||||
for chatstate in ('inactive', 'composing', 'paused', 'gone',
|
||||
'muc_msg', 'muc_directed_msg'):
|
||||
self.colorbuttons[chatstate] = self.xml.get_widget(chatstate + \
|
||||
'_colorbutton')
|
||||
|
@ -198,7 +198,7 @@ class GajimThemesWindow:
|
|||
self.no_update = False
|
||||
gajim.interface.roster.change_roster_style(None)
|
||||
|
||||
for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone',
|
||||
for chatstate in ('inactive', 'composing', 'paused', 'gone',
|
||||
'muc_msg', 'muc_directed_msg'):
|
||||
color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \
|
||||
'_color')
|
||||
|
@ -333,11 +333,6 @@ class GajimThemesWindow:
|
|||
font_props[1] = True
|
||||
return font_props
|
||||
|
||||
def on_active_colorbutton_color_set(self, widget):
|
||||
self.no_update = True
|
||||
self._set_color(True, widget, 'state_active_color')
|
||||
self.no_update = False
|
||||
|
||||
def on_inactive_colorbutton_color_set(self, widget):
|
||||
self.no_update = True
|
||||
self._set_color(True, widget, 'state_inactive_color')
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -39,12 +39,13 @@ from common import helpers
|
|||
from chat_control import ChatControl
|
||||
from chat_control import ChatControlBase
|
||||
from conversation_textview import ConversationTextview
|
||||
from common.exceptions import GajimGeneralException
|
||||
|
||||
#(status_image, type, nick, shown_nick)
|
||||
(
|
||||
C_IMG, # image to show state (online, new message etc)
|
||||
C_TYPE, # type of the row ('contact' or 'group')
|
||||
C_NICK, # contact nickame or group name
|
||||
C_NICK, # contact nickame or ROLE name
|
||||
C_TYPE, # type of the row ('contact' or 'role')
|
||||
C_TEXT, # text shown in the cellrenderer
|
||||
C_AVATAR, # avatar of the contact
|
||||
) = range(5)
|
||||
|
@ -94,6 +95,9 @@ class PrivateChatControl(ChatControl):
|
|||
TYPE_ID = message_control.TYPE_PM
|
||||
|
||||
def __init__(self, parent_win, contact, acct):
|
||||
room_jid = contact.jid.split('/')[0]
|
||||
room_ctrl = gajim.interface.msg_win_mgr.get_control(room_jid, acct)
|
||||
self.room_name = room_ctrl.name
|
||||
ChatControl.__init__(self, parent_win, contact, acct)
|
||||
self.TYPE_ID = 'pm'
|
||||
self.display_names = (_('Private Chat'), _('Private Chats'))
|
||||
|
@ -103,9 +107,10 @@ class PrivateChatControl(ChatControl):
|
|||
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)
|
||||
# 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 contact is None:
|
||||
# contact was from pm in MUC
|
||||
room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
|
||||
|
@ -114,7 +119,7 @@ class PrivateChatControl(ChatControl):
|
|||
dialogs.ErrorDialog(
|
||||
_('Sending private message failed'),
|
||||
#in second %s code replaces with nickname
|
||||
_('You are no longer in room "%s" or "%s" has left.') % \
|
||||
_('You are no longer in group chat "%s" or "%s" has left.') % \
|
||||
(room, nick))
|
||||
return
|
||||
|
||||
|
@ -172,17 +177,20 @@ class GroupchatControl(ChatControlBase):
|
|||
self.nick = contact.name
|
||||
self.name = self.room_jid.split('@')[0]
|
||||
|
||||
self.hide_chat_buttons_always = gajim.config.get('always_hide_groupchat_buttons')
|
||||
self.hide_chat_buttons_always = gajim.config.get(
|
||||
'always_hide_groupchat_buttons')
|
||||
self.chat_buttons_set_visible(self.hide_chat_buttons_always)
|
||||
self.widget_set_visible(self.xml.get_widget('banner_eventbox'), gajim.config.get('hide_groupchat_banner'))
|
||||
self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'), gajim.config.get('hide_groupchat_occupants_list'))
|
||||
self.widget_set_visible(self.xml.get_widget('banner_eventbox'),
|
||||
gajim.config.get('hide_groupchat_banner'))
|
||||
self.widget_set_visible(self.xml.get_widget('list_scrolledwindow'),
|
||||
gajim.config.get('hide_groupchat_occupants_list'))
|
||||
self.gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char')
|
||||
|
||||
self._last_selected_contact = None # None or holds jid, account tuple
|
||||
# alphanum sorted
|
||||
self.muc_cmds = ['ban', 'chat', 'query', 'clear', 'close', 'compact',
|
||||
'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick', 'part',
|
||||
'names', 'say', 'topic']
|
||||
'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick',
|
||||
'part', 'names', 'say', 'topic']
|
||||
# muc attention flag (when we are mentioned in a muc)
|
||||
# if True, the room has mentioned us
|
||||
self.attention_flag = False
|
||||
|
@ -200,7 +208,8 @@ class GroupchatControl(ChatControlBase):
|
|||
xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
|
||||
|
||||
widget = xm.get_widget('bookmark_room_menuitem')
|
||||
id = widget.connect('activate', self._on_bookmark_room_menuitem_activate)
|
||||
id = widget.connect('activate',
|
||||
self._on_bookmark_room_menuitem_activate)
|
||||
self.handlers[id] = widget
|
||||
|
||||
widget = xm.get_widget('change_nick_menuitem')
|
||||
|
@ -208,11 +217,13 @@ class GroupchatControl(ChatControlBase):
|
|||
self.handlers[id] = widget
|
||||
|
||||
widget = xm.get_widget('configure_room_menuitem')
|
||||
id = widget.connect('activate', self._on_configure_room_menuitem_activate)
|
||||
id = widget.connect('activate',
|
||||
self._on_configure_room_menuitem_activate)
|
||||
self.handlers[id] = widget
|
||||
|
||||
widget = xm.get_widget('change_subject_menuitem')
|
||||
id = widget.connect('activate', self._on_change_subject_menuitem_activate)
|
||||
id = widget.connect('activate',
|
||||
self._on_change_subject_menuitem_activate)
|
||||
self.handlers[id] = widget
|
||||
|
||||
widget = xm.get_widget('compact_view_menuitem')
|
||||
|
@ -226,29 +237,27 @@ class GroupchatControl(ChatControlBase):
|
|||
self.gc_popup_menu = xm.get_widget('gc_control_popup_menu')
|
||||
|
||||
self.name_label = self.xml.get_widget('banner_name_label')
|
||||
id = self.parent_win.window.connect('focus-in-event',
|
||||
self._on_window_focus_in_event)
|
||||
self.handlers[id] = self.parent_win.window
|
||||
|
||||
# set the position of the current hpaned
|
||||
self.hpaned_position = gajim.config.get('gc-hpaned-position')
|
||||
self.hpaned = self.xml.get_widget('hpaned')
|
||||
self.hpaned.set_position(self.hpaned_position)
|
||||
|
||||
list_treeview = self.list_treeview = self.xml.get_widget('list_treeview')
|
||||
selection = list_treeview.get_selection()
|
||||
self.list_treeview = self.xml.get_widget('list_treeview')
|
||||
selection = self.list_treeview.get_selection()
|
||||
id = selection.connect('changed',
|
||||
self.on_list_treeview_selection_changed)
|
||||
self.handlers[id] = selection
|
||||
id = list_treeview.connect('style-set', self.on_list_treeview_style_set)
|
||||
self.handlers[id] = list_treeview
|
||||
id = self.list_treeview.connect('style-set',
|
||||
self.on_list_treeview_style_set)
|
||||
self.handlers[id] = self.list_treeview
|
||||
# we want to know when the the widget resizes, because that is
|
||||
# an indication that the hpaned has moved...
|
||||
# FIXME: Find a better indicator that the hpaned has moved.
|
||||
id = self.list_treeview.connect('size-allocate',
|
||||
self.on_treeview_size_allocate)
|
||||
self.handlers[id] = self.list_treeview
|
||||
#status_image, type, nickname, shown_nick
|
||||
#status_image, shown_nick, type, nickname, avatar
|
||||
store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
|
||||
store.set_sort_column_id(C_TEXT, gtk.SORT_ASCENDING)
|
||||
self.list_treeview.set_model(store)
|
||||
|
@ -275,7 +284,8 @@ class GroupchatControl(ChatControlBase):
|
|||
renderer_text = gtk.CellRendererText() # nickname
|
||||
column.pack_start(renderer_text, expand = True)
|
||||
column.add_attribute(renderer_text, 'markup', C_TEXT)
|
||||
column.set_cell_data_func(renderer_text, tree_cell_data_func, self.list_treeview)
|
||||
column.set_cell_data_func(renderer_text, tree_cell_data_func,
|
||||
self.list_treeview)
|
||||
|
||||
self.list_treeview.append_column(column)
|
||||
|
||||
|
@ -316,15 +326,6 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
menu.show_all()
|
||||
|
||||
def notify_on_new_messages(self):
|
||||
return gajim.config.get('notify_on_all_muc_messages') or \
|
||||
self.attention_flag
|
||||
|
||||
def _on_window_focus_in_event(self, widget, event):
|
||||
'''When window gets focus'''
|
||||
if self.parent_win.get_active_jid() == self.room_jid:
|
||||
self.conv_textview.allow_focus_out_line = True
|
||||
|
||||
def on_treeview_size_allocate(self, widget, allocation):
|
||||
'''The MUC treeview has resized. Move the hpaned in all tabs to match'''
|
||||
self.hpaned_position = self.hpaned.get_position()
|
||||
|
@ -371,39 +372,44 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
has_focus = self.parent_win.window.get_property('has-toplevel-focus')
|
||||
current_tab = self.parent_win.get_active_control() == self
|
||||
color_name = None
|
||||
color = None
|
||||
theme = gajim.config.get('roster_theme')
|
||||
if chatstate == 'attention' and (not has_focus or not current_tab):
|
||||
self.attention_flag = True
|
||||
color = gajim.config.get_per('themes', theme,
|
||||
color_name = gajim.config.get_per('themes', theme,
|
||||
'state_muc_directed_msg_color')
|
||||
elif chatstate:
|
||||
if chatstate == 'active' or (current_tab and has_focus):
|
||||
self.attention_flag = False
|
||||
color = gajim.config.get_per('themes', theme,
|
||||
'state_active_color')
|
||||
# get active color from gtk
|
||||
color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
|
||||
elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\
|
||||
not self.attention_flag:
|
||||
color = gajim.config.get_per('themes', theme, 'state_muc_msg_color')
|
||||
if color:
|
||||
color = gtk.gdk.colormap_get_system().alloc_color(color)
|
||||
|
||||
color_name = gajim.config.get_per('themes', theme,
|
||||
'state_muc_msg_color')
|
||||
if color_name:
|
||||
color = gtk.gdk.colormap_get_system().alloc_color(color_name)
|
||||
|
||||
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 = '[' + unicode(num_unread) + ']'
|
||||
label_str = unread + label_str
|
||||
return (label_str, color)
|
||||
|
||||
def get_tab_image(self):
|
||||
# Set tab image (always 16x16); unread messages show the 'message' image
|
||||
img_16 = gajim.interface.roster.get_appropriate_state_images(
|
||||
self.room_jid, icon_name = 'message')
|
||||
|
||||
# Set tab image (always 16x16)
|
||||
tab_image = None
|
||||
if self.attention_flag and gajim.config.get('show_unread_tab_icon'):
|
||||
tab_image = img_16['message']
|
||||
if gajim.gc_connected[self.account][self.room_jid]:
|
||||
tab_image = gajim.interface.roster.load_icon('muc_active')
|
||||
else:
|
||||
if gajim.gc_connected[self.account][self.room_jid]:
|
||||
tab_image = img_16['muc_active']
|
||||
else:
|
||||
tab_image = img_16['muc_inactive']
|
||||
tab_image = gajim.interface.roster.load_icon('muc_inactive')
|
||||
return tab_image
|
||||
|
||||
def update_ui(self):
|
||||
|
@ -430,15 +436,18 @@ class GroupchatControl(ChatControlBase):
|
|||
childs[3].set_sensitive(False)
|
||||
return menu
|
||||
|
||||
def on_message(self, nick, msg, tim):
|
||||
def on_message(self, nick, msg, tim, has_timestamp = False, xhtml = None):
|
||||
if not nick:
|
||||
# message from server
|
||||
self.print_conversation(msg, tim = tim)
|
||||
self.print_conversation(msg, tim = tim, xhtml = xhtml)
|
||||
else:
|
||||
# message from someone
|
||||
self.print_conversation(msg, nick, tim)
|
||||
if has_timestamp:
|
||||
self.print_old_conversation(msg, nick, tim, xhtml)
|
||||
else:
|
||||
self.print_conversation(msg, nick, tim, xhtml)
|
||||
|
||||
def on_private_message(self, nick, msg, tim):
|
||||
def on_private_message(self, nick, msg, tim, xhtml):
|
||||
# Do we have a queue?
|
||||
fjid = self.room_jid + '/' + nick
|
||||
no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
|
||||
|
@ -446,11 +455,11 @@ class GroupchatControl(ChatControlBase):
|
|||
# We print if window is opened
|
||||
pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
|
||||
if pm_control:
|
||||
pm_control.print_conversation(msg, tim = tim)
|
||||
pm_control.print_conversation(msg, tim = tim, xhtml = xhtml)
|
||||
return
|
||||
|
||||
event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
|
||||
False, '', None))
|
||||
False, '', None, xhtml))
|
||||
gajim.events.add_event(self.account, fjid, event)
|
||||
|
||||
autopopup = gajim.config.get('autopopup')
|
||||
|
@ -458,7 +467,7 @@ class GroupchatControl(ChatControlBase):
|
|||
iter = self.get_contact_iter(nick)
|
||||
path = self.list_treeview.get_model().get_path(iter)
|
||||
if not autopopup or (not autopopupaway and \
|
||||
gajim.connections[self.account].connected > 2):
|
||||
gajim.connections[self.account].connected > 2):
|
||||
if no_queue: # We didn't have a queue: we change icons
|
||||
model = self.list_treeview.get_model()
|
||||
state_images =\
|
||||
|
@ -467,6 +476,7 @@ class GroupchatControl(ChatControlBase):
|
|||
image = state_images['message']
|
||||
model[iter][C_IMG] = image
|
||||
self.parent_win.show_title()
|
||||
self.parent_win.redraw_tab(self)
|
||||
else:
|
||||
self._start_private_message(nick)
|
||||
# Scroll to line
|
||||
|
@ -499,7 +509,26 @@ class GroupchatControl(ChatControlBase):
|
|||
gc_count_nicknames_colors = 0
|
||||
gc_custom_colors = {}
|
||||
|
||||
def print_conversation(self, text, contact = '', tim = None):
|
||||
def print_old_conversation(self, text, contact = '', tim = None,
|
||||
xhtml = None):
|
||||
if isinstance(text, str):
|
||||
text = unicode(text, 'utf-8')
|
||||
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'], xhtml = xhtml)
|
||||
|
||||
def print_conversation(self, text, contact = '', tim = None, xhtml = 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)
|
||||
|
@ -553,11 +582,16 @@ class GroupchatControl(ChatControlBase):
|
|||
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)
|
||||
other_tags_for_name, [], other_tags_for_text, xhtml = xhtml)
|
||||
|
||||
def get_nb_unread(self):
|
||||
nb = len(gajim.events.get_events(self.account, self.room_jid,
|
||||
['printed_gc_msg']))
|
||||
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']))
|
||||
|
@ -570,8 +604,7 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
# Do we play a sound on every muc message?
|
||||
if gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'):
|
||||
if gajim.config.get('notify_on_all_muc_messages'):
|
||||
sound = 'received'
|
||||
sound = 'received'
|
||||
|
||||
# Are any of the defined highlighting words in the text?
|
||||
if self.needs_visual_notification(text):
|
||||
|
@ -622,13 +655,16 @@ class GroupchatControl(ChatControlBase):
|
|||
word[char_position:char_position+1]
|
||||
if (refer_to_nick_char != ''):
|
||||
refer_to_nick_char_code = ord(refer_to_nick_char)
|
||||
if ((refer_to_nick_char_code < 65 or refer_to_nick_char_code > 123)\
|
||||
or (refer_to_nick_char_code < 97 and refer_to_nick_char_code > 90)):
|
||||
if ((refer_to_nick_char_code < 65 or \
|
||||
refer_to_nick_char_code > 123) or \
|
||||
(refer_to_nick_char_code < 97 and \
|
||||
refer_to_nick_char_code > 90)):
|
||||
return True
|
||||
else:
|
||||
# This is A->Z or a->z, we can be sure our nick is the beginning
|
||||
# of a real word, do not highlight. Note that we can probably
|
||||
# do a better detection of non-punctuation characters
|
||||
# This is A->Z or a->z, we can be sure our nick is the
|
||||
# beginning of a real word, do not highlight. Note that we
|
||||
# can probably do a better detection of non-punctuation
|
||||
# characters
|
||||
return False
|
||||
else: # Special word == word, no char after in word
|
||||
return True
|
||||
|
@ -638,7 +674,7 @@ class GroupchatControl(ChatControlBase):
|
|||
self.subject = subject
|
||||
|
||||
self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
|
||||
subject = gtkgui_helpers.reduce_chars_newlines(subject, max_lines = 2)
|
||||
subject = helpers.reduce_chars_newlines(subject, max_lines = 2)
|
||||
subject = gtkgui_helpers.escape_for_pango_markup(subject)
|
||||
font_attrs, font_attrs_small = self.get_font_attrs()
|
||||
text = '<span %s>%s</span>' % (font_attrs, self.room_jid)
|
||||
|
@ -646,11 +682,10 @@ class GroupchatControl(ChatControlBase):
|
|||
text += '\n<span %s>%s</span>' % (font_attrs_small, subject)
|
||||
self.name_label.set_markup(text)
|
||||
event_box = self.name_label.get_parent()
|
||||
if subject == '':
|
||||
self.subject = _('This room has no subject')
|
||||
|
||||
# tooltip must always hold ALL the subject
|
||||
self.subject_tooltip.set_tip(event_box, self.subject)
|
||||
if self.subject:
|
||||
# tooltip must always hold ALL the subject
|
||||
self.subject_tooltip.set_tip(event_box, self.subject)
|
||||
|
||||
def got_connected(self):
|
||||
gajim.gc_connected[self.account][self.room_jid] = True
|
||||
|
@ -677,7 +712,8 @@ class GroupchatControl(ChatControlBase):
|
|||
gc_contact.affiliation, gc_contact.status,
|
||||
gc_contact.jid)
|
||||
|
||||
def on_send_pm(self, widget=None, model=None, iter=None, nick=None, msg=None):
|
||||
def on_send_pm(self, widget = None, model = None, iter = None, nick = None,
|
||||
msg = None):
|
||||
'''opens a chat window and msg is not None sends private message to a
|
||||
contact in a room'''
|
||||
if nick is None:
|
||||
|
@ -686,14 +722,16 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
self._start_private_message(nick)
|
||||
if msg:
|
||||
gajim.interface.msg_win_mgr.get_control(fjid, self.account).send_message(msg)
|
||||
gajim.interface.msg_win_mgr.get_control(fjid, self.account).\
|
||||
send_message(msg)
|
||||
|
||||
def draw_contact(self, nick, selected=False, focus=False):
|
||||
iter = self.get_contact_iter(nick)
|
||||
if not iter:
|
||||
return
|
||||
model = self.list_treeview.get_model()
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
|
||||
nick)
|
||||
state_images = gajim.interface.roster.jabber_state_images['16']
|
||||
if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
|
||||
image = state_images['message']
|
||||
|
@ -706,7 +744,7 @@ class GroupchatControl(ChatControlBase):
|
|||
if status and gajim.config.get('show_status_msgs_in_roster'):
|
||||
status = status.strip()
|
||||
if status != '':
|
||||
status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1)
|
||||
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)
|
||||
|
@ -720,6 +758,8 @@ class GroupchatControl(ChatControlBase):
|
|||
def draw_avatar(self, nick):
|
||||
model = self.list_treeview.get_model()
|
||||
iter = self.get_contact_iter(nick)
|
||||
if not iter:
|
||||
return
|
||||
if gajim.config.get('show_avatars_in_roster'):
|
||||
pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.room_jid + \
|
||||
'/' + nick, True)
|
||||
|
@ -731,8 +771,8 @@ class GroupchatControl(ChatControlBase):
|
|||
scaled_pixbuf = None
|
||||
model[iter][C_AVATAR] = scaled_pixbuf
|
||||
|
||||
def chg_contact_status(self, nick, show, status, role, affiliation, jid, reason, actor,
|
||||
statusCode, new_nick):
|
||||
def chg_contact_status(self, nick, show, status, role, affiliation, jid,
|
||||
reason, actor, statusCode, new_nick):
|
||||
'''When an occupant changes his or her status'''
|
||||
if show == 'invisible':
|
||||
return
|
||||
|
@ -824,7 +864,8 @@ class GroupchatControl(ChatControlBase):
|
|||
self.add_contact_to_roster(nick, show, role,
|
||||
affiliation, status, jid)
|
||||
else:
|
||||
c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
|
||||
c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
|
||||
nick)
|
||||
if c.show == show and c.status == status and \
|
||||
c.affiliation == affiliation: #no change
|
||||
return
|
||||
|
@ -841,20 +882,22 @@ class GroupchatControl(ChatControlBase):
|
|||
print_status = None
|
||||
for bookmark in gajim.connections[self.account].bookmarks:
|
||||
if bookmark['jid'] == self.room_jid:
|
||||
print_status = bookmark['print_status']
|
||||
print_status = bookmark.get('print_status', None)
|
||||
break
|
||||
if print_status is None:
|
||||
if not print_status:
|
||||
print_status = gajim.config.get('print_status_in_muc')
|
||||
nick_jid = nick
|
||||
if jid:
|
||||
nick_jid += ' (%s)' % jid
|
||||
# delete ressource
|
||||
simple_jid = gajim.get_jid_without_resource(jid)
|
||||
nick_jid += ' (%s)' % simple_jid
|
||||
if show == 'offline' and print_status in ('all', 'in_and_out'):
|
||||
st = _('%s has left') % nick_jid
|
||||
if reason:
|
||||
st += ' [%s]' % reason
|
||||
else:
|
||||
if newly_created and print_status in ('all', 'in_and_out'):
|
||||
st = _('%s has joined the room') % nick_jid
|
||||
st = _('%s has joined the group chat') % nick_jid
|
||||
elif print_status == 'all':
|
||||
st = _('%s is now %s') % (nick_jid, helpers.get_uf_show(show))
|
||||
if st:
|
||||
|
@ -881,9 +924,9 @@ class GroupchatControl(ChatControlBase):
|
|||
role_iter = self.get_role_iter(role)
|
||||
if not role_iter:
|
||||
role_iter = model.append(None,
|
||||
(gajim.interface.roster.jabber_state_images['16']['closed'], 'role',
|
||||
role, '<b>%s</b>' % role_name, None))
|
||||
iter = model.append(role_iter, (None, 'contact', nick, name, None))
|
||||
(gajim.interface.roster.jabber_state_images['16']['closed'], role,
|
||||
'role', '<b>%s</b>' % role_name, None))
|
||||
iter = model.append(role_iter, (None, nick, 'contact', name, None))
|
||||
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,
|
||||
name = nick, show = show, status = status, role = role,
|
||||
|
@ -892,7 +935,7 @@ class GroupchatControl(ChatControlBase):
|
|||
self.draw_contact(nick)
|
||||
self.draw_avatar(nick)
|
||||
# Do not ask avatar to irc rooms as irc transports reply with messages
|
||||
r, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid)
|
||||
server = gajim.get_server_from_jid(self.room_jid)
|
||||
if gajim.config.get('ask_avatars_on_startup') and \
|
||||
not server.startswith('irc'):
|
||||
fjid = self.room_jid + '/' + nick
|
||||
|
@ -925,7 +968,8 @@ class GroupchatControl(ChatControlBase):
|
|||
iter = self.get_contact_iter(nick)
|
||||
if not iter:
|
||||
return
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
|
||||
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 = model.iter_parent(iter)
|
||||
|
@ -1005,8 +1049,10 @@ class GroupchatControl(ChatControlBase):
|
|||
new_topic = message_array.pop(0)
|
||||
gajim.connections[self.account].send_gc_subject(self.room_jid,
|
||||
new_topic)
|
||||
else:
|
||||
elif self.subject is not '':
|
||||
self.print_conversation(self.subject, 'info')
|
||||
else:
|
||||
self.print_conversation(_('This group chat has no subject'), 'info')
|
||||
self.clear(self.msg_textview)
|
||||
return True
|
||||
elif command == 'invite':
|
||||
|
@ -1034,15 +1080,13 @@ class GroupchatControl(ChatControlBase):
|
|||
elif command == 'join':
|
||||
# example: /join room@conference.example.com/nick
|
||||
if len(message_array):
|
||||
message_array = message_array[0]
|
||||
if message_array.find('@') >= 0:
|
||||
room, servernick = message_array.split('@')
|
||||
if servernick.find('/') >= 0:
|
||||
server, nick = servernick.split('/', 1)
|
||||
room_jid = message_array[0]
|
||||
if room_jid.find('@') >= 0:
|
||||
if room_jid.find('/') >= 0:
|
||||
room_jid, nick = room_jid.split('/', 1)
|
||||
else:
|
||||
server = servernick
|
||||
nick = ''
|
||||
#join_gc window is needed in order to provide for password entry.
|
||||
# join_gc window is needed in order to provide for password entry.
|
||||
if gajim.interface.instances[self.account].has_key('join_gc'):
|
||||
gajim.interface.instances[self.account]['join_gc'].\
|
||||
window.present()
|
||||
|
@ -1050,13 +1094,13 @@ class GroupchatControl(ChatControlBase):
|
|||
try:
|
||||
gajim.interface.instances[self.account]['join_gc'] =\
|
||||
dialogs.JoinGroupchatWindow(self.account,
|
||||
server = server, room = room, nick = nick)
|
||||
except RuntimeError:
|
||||
room_jid = room_jid, nick = nick)
|
||||
except GajimGeneralException:
|
||||
pass
|
||||
self.clear(self.msg_textview)
|
||||
else:
|
||||
#%s is something the user wrote but it is not a jid so we inform
|
||||
s = _('%s does not appear to be a valid JID') % message_array
|
||||
s = _('%s does not appear to be a valid JID') % message_array[0]
|
||||
self.print_conversation(s, 'info')
|
||||
else:
|
||||
self.get_command_help(command)
|
||||
|
@ -1066,21 +1110,21 @@ class GroupchatControl(ChatControlBase):
|
|||
reason = 'offline'
|
||||
if len(message_array):
|
||||
reason = message_array.pop(0)
|
||||
self.parent_win.remove_tab(self,reason)
|
||||
self.parent_win.remove_tab(self, self.parent_win.CLOSE_COMMAND, reason)
|
||||
self.clear(self.msg_textview)
|
||||
return True
|
||||
elif command == 'ban':
|
||||
if len(message_array):
|
||||
message_array = message_array[0].split()
|
||||
nick = message_array.pop(0)
|
||||
room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
|
||||
room_nicks = gajim.contacts.get_nick_list(self.account,
|
||||
self.room_jid)
|
||||
reason = ' '.join(message_array)
|
||||
if nick in room_nicks:
|
||||
ban_jid = gajim.construct_fjid(self.room_jid, nick)
|
||||
gajim.connections[self.account].gc_set_affiliation(self.room_jid,
|
||||
ban_jid, 'outcast', reason)
|
||||
self.clear(self.msg_textview)
|
||||
elif nick.find('@') >= 0:
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account,
|
||||
self.room_jid, nick)
|
||||
nick = gc_contact.jid
|
||||
if nick.find('@') >= 0:
|
||||
gajim.connections[self.account].gc_set_affiliation(self.room_jid,
|
||||
nick, 'outcast', reason)
|
||||
self.clear(self.msg_textview)
|
||||
|
@ -1094,7 +1138,8 @@ class GroupchatControl(ChatControlBase):
|
|||
if len(message_array):
|
||||
message_array = message_array[0].split()
|
||||
nick = message_array.pop(0)
|
||||
room_nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
|
||||
room_nicks = gajim.contacts.get_nick_list(self.account,
|
||||
self.room_jid)
|
||||
reason = ' '.join(message_array)
|
||||
if nick in room_nicks:
|
||||
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
|
||||
|
@ -1154,7 +1199,8 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
if not self._process_command(message):
|
||||
# Send the message
|
||||
gajim.connections[self.account].send_gc_message(self.room_jid, message)
|
||||
gajim.connections[self.account].send_gc_message(self.room_jid,
|
||||
message)
|
||||
self.msg_textview.get_buffer().set_text('')
|
||||
self.msg_textview.grab_focus()
|
||||
|
||||
|
@ -1162,10 +1208,11 @@ class GroupchatControl(ChatControlBase):
|
|||
if command == 'help':
|
||||
self.print_conversation(_('Commands: %s') % self.muc_cmds, 'info')
|
||||
elif command == 'ban':
|
||||
s = _('Usage: /%s <nickname|JID> [reason], bans the JID from the room.'
|
||||
s = _('Usage: /%s <nickname|JID> [reason], bans the JID from the group chat.'
|
||||
' The nickname of an occupant may be substituted, but not if it '
|
||||
'contains "@". If the JID is currently in the room, he/she/it will '
|
||||
'also be kicked. Does NOT support spaces in nickname.') % command
|
||||
'contains "@". If the JID is currently in the group chat, '
|
||||
'he/she/it will also be kicked. Does NOT support spaces in '
|
||||
'nickname.') % command
|
||||
self.print_conversation(s, 'info')
|
||||
elif command == 'chat' or command == 'query':
|
||||
self.print_conversation(_('Usage: /%s <nickname>, opens a private chat'
|
||||
|
@ -1177,10 +1224,11 @@ class GroupchatControl(ChatControlBase):
|
|||
self.print_conversation(_('Usage: /%s [reason], closes the current '
|
||||
'window or tab, displaying reason if specified.') % command, 'info')
|
||||
elif command == 'compact':
|
||||
self.print_conversation(_('Usage: /%s, hide the chat buttons.') % command, 'info')
|
||||
self.print_conversation(_('Usage: /%s, hide the chat buttons.') % \
|
||||
command, 'info')
|
||||
elif command == 'invite':
|
||||
self.print_conversation(_('Usage: /%s <JID> [reason], invites JID to '
|
||||
'the current room, optionally providing a reason.') % command,
|
||||
'the current group chat, optionally providing a reason.') % command,
|
||||
'info')
|
||||
elif command == 'join':
|
||||
self.print_conversation(_('Usage: /%s <room>@<server>[/nickname], '
|
||||
|
@ -1188,12 +1236,12 @@ class GroupchatControl(ChatControlBase):
|
|||
% command, 'info')
|
||||
elif command == 'kick':
|
||||
self.print_conversation(_('Usage: /%s <nickname> [reason], removes '
|
||||
'the occupant specified by nickname from the room and optionally '
|
||||
'displays a reason. Does NOT support spaces in nickname.') % \
|
||||
command, 'info')
|
||||
'the occupant specified by nickname from the group chat and '
|
||||
'optionally displays a reason. Does NOT support spaces in '
|
||||
'nickname.') % command, 'info')
|
||||
elif command == 'me':
|
||||
self.print_conversation(_('Usage: /%s <action>, sends action to the '
|
||||
'current room. Use third person. (e.g. /%s explodes.)') % \
|
||||
'current group chat. Use third person. (e.g. /%s explodes.)') % \
|
||||
(command, command), 'info')
|
||||
elif command == 'msg':
|
||||
s = _('Usage: /%s <nickname> [message], opens a private message window'
|
||||
|
@ -1201,16 +1249,16 @@ class GroupchatControl(ChatControlBase):
|
|||
command
|
||||
self.print_conversation(s, 'info')
|
||||
elif command == 'nick':
|
||||
s = _('Usage: /%s <nickname>, changes your nickname in current room.')\
|
||||
% command
|
||||
s = _('Usage: /%s <nickname>, changes your nickname in current group '
|
||||
'chat.') % command
|
||||
self.print_conversation(s, 'info')
|
||||
elif command == 'names':
|
||||
s = _('Usage: /%s , display the names of room occupants.')\
|
||||
s = _('Usage: /%s , display the names of group chat occupants.')\
|
||||
% command
|
||||
self.print_conversation(s, 'info')
|
||||
elif command == 'topic':
|
||||
self.print_conversation(_('Usage: /%s [topic], displays or updates the'
|
||||
' current room topic.') % command, 'info')
|
||||
' current group chat topic.') % command, 'info')
|
||||
elif command == 'say':
|
||||
self.print_conversation(_('Usage: /%s <message>, sends a message '
|
||||
'without looking for other commands.') % command, 'info')
|
||||
|
@ -1218,7 +1266,8 @@ class GroupchatControl(ChatControlBase):
|
|||
self.print_conversation(_('No help info for /%s') % command, 'info')
|
||||
|
||||
def get_role(self, nick):
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
|
||||
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid,
|
||||
nick)
|
||||
if gc_contact:
|
||||
return gc_contact.role
|
||||
else:
|
||||
|
@ -1249,7 +1298,13 @@ class GroupchatControl(ChatControlBase):
|
|||
self.handlers[i].disconnect(i)
|
||||
del self.handlers[i]
|
||||
|
||||
def allow_shutdown(self):
|
||||
def allow_shutdown(self, method):
|
||||
'''If check_selection is True, '''
|
||||
if method == self.parent_win.CLOSE_ESC:
|
||||
model, iter = self.list_treeview.get_selection().get_selected()
|
||||
if iter:
|
||||
self.list_treeview.get_selection().unselect_all()
|
||||
return False
|
||||
retval = True
|
||||
includes = gajim.config.get('confirm_close_muc_rooms').split(' ')
|
||||
excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ')
|
||||
|
@ -1257,9 +1312,10 @@ class GroupchatControl(ChatControlBase):
|
|||
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:
|
||||
pritext = _('Are you sure you want to leave room "%s"?') % self.name
|
||||
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 room.')
|
||||
'from this group chat.')
|
||||
|
||||
dialog = dialogs.ConfirmationDialogCheck(pritext, sectext,
|
||||
_('Do _not ask me again'))
|
||||
|
@ -1275,6 +1331,7 @@ class GroupchatControl(ChatControlBase):
|
|||
return retval
|
||||
|
||||
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:
|
||||
|
@ -1299,7 +1356,8 @@ class GroupchatControl(ChatControlBase):
|
|||
_('Please specify the new subject:'), self.subject)
|
||||
response = instance.get_response()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
# Note, we don't update self.subject since we don't know whether it will work yet
|
||||
# Note, we don't update self.subject since we don't know whether it
|
||||
# will work yet
|
||||
subject = instance.input_entry.get_text().decode('utf-8')
|
||||
gajim.connections[self.account].send_gc_subject(self.room_jid, subject)
|
||||
|
||||
|
@ -1324,15 +1382,14 @@ class GroupchatControl(ChatControlBase):
|
|||
'jid': self.room_jid,
|
||||
'autojoin': '0',
|
||||
'password': '',
|
||||
'nick': self.nick,
|
||||
'print_status' : gajim.config.get('print_status_in_muc')
|
||||
'nick': self.nick
|
||||
}
|
||||
|
||||
for bookmark in gajim.connections[self.account].bookmarks:
|
||||
if bookmark['jid'] == bm['jid']:
|
||||
dialogs.ErrorDialog(
|
||||
_('Bookmark already set'),
|
||||
_('Room "%s" is already in your bookmarks.') % bm['jid'])
|
||||
_('Group Chat "%s" is already in your bookmarks.') % bm['jid'])
|
||||
return
|
||||
|
||||
gajim.connections[self.account].bookmarks.append(bm)
|
||||
|
@ -1344,7 +1401,8 @@ class GroupchatControl(ChatControlBase):
|
|||
_('Bookmark has been added successfully'),
|
||||
_('You can manage your bookmarks via Actions menu in your roster.'))
|
||||
|
||||
def handle_message_textview_mykey_press(self, widget, event_keyval, event_keymod):
|
||||
def handle_message_textview_mykey_press(self, widget, event_keyval,
|
||||
event_keymod):
|
||||
# NOTE: handles mykeypress which is custom signal connected to this
|
||||
# CB in new_room(). for this singal see message_textview.py
|
||||
|
||||
|
@ -1356,17 +1414,30 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
message_buffer = widget.get_buffer()
|
||||
start_iter, end_iter = message_buffer.get_bounds()
|
||||
message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
|
||||
message = message_buffer.get_text(start_iter, end_iter, False).decode(
|
||||
'utf-8')
|
||||
|
||||
if event.keyval == gtk.keysyms.Tab: # TAB
|
||||
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).decode('utf-8')
|
||||
if text.endswith(' '):
|
||||
if not self.last_key_tabs:
|
||||
return False
|
||||
text = message_buffer.get_text(start_iter, end_iter, False).decode(
|
||||
'utf-8')
|
||||
|
||||
splitted_text = text.split()
|
||||
# topic completion
|
||||
splitted_text2 = text.split(None, 1)
|
||||
if text.startswith('/topic '):
|
||||
if len(splitted_text2) == 2 and \
|
||||
self.subject.startswith(splitted_text2[1]) and\
|
||||
len(self.subject) > len(splitted_text2[1]):
|
||||
message_buffer.insert_at_cursor(
|
||||
self.subject[len(splitted_text2[1]):])
|
||||
return True
|
||||
elif len(splitted_text2) == 1 and text.startswith('/topic '):
|
||||
message_buffer.delete(start_iter, end_iter)
|
||||
message_buffer.insert_at_cursor('/topic '+self.subject)
|
||||
return True
|
||||
|
||||
# command completion
|
||||
if text.startswith('/') and len(splitted_text) == 1:
|
||||
text = splitted_text[0]
|
||||
|
@ -1435,7 +1506,11 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
def on_list_treeview_key_press_event(self, widget, event):
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
widget.get_selection().unselect_all()
|
||||
selection = widget.get_selection()
|
||||
model, iter = selection.get_selected()
|
||||
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'''
|
||||
|
@ -1473,8 +1548,8 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
# 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_affiliation = gajim.contacts.get_gc_contact(self.account,
|
||||
self.room_jid, user_nick).affiliation
|
||||
user_role = self.get_role(user_nick)
|
||||
|
||||
# making menu from glade
|
||||
|
@ -1483,9 +1558,10 @@ class GroupchatControl(ChatControlBase):
|
|||
# these conditions were taken from JEP 0045
|
||||
item = xml.get_widget('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'):
|
||||
(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
|
||||
|
@ -1493,18 +1569,18 @@ class GroupchatControl(ChatControlBase):
|
|||
item = xml.get_widget('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'):
|
||||
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)
|
||||
nick)
|
||||
self.handlers[id] = item
|
||||
|
||||
item = xml.get_widget('moderator_checkmenuitem')
|
||||
item.set_active(target_role == 'moderator')
|
||||
if not user_affiliation in ('admin', 'owner') or \
|
||||
target_affiliation in ('admin', 'owner'):
|
||||
target_affiliation in ('admin', 'owner'):
|
||||
item.set_sensitive(False)
|
||||
id = item.connect('activate', self.on_moderator_checkmenuitem_activate,
|
||||
nick)
|
||||
|
@ -1512,8 +1588,8 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
item = xml.get_widget('ban_menuitem')
|
||||
if not user_affiliation in ('admin', 'owner') or \
|
||||
(target_affiliation in ('admin', 'owner') and\
|
||||
user_affiliation != 'owner'):
|
||||
(target_affiliation in ('admin', 'owner') and\
|
||||
user_affiliation != 'owner'):
|
||||
item.set_sensitive(False)
|
||||
id = item.connect('activate', self.ban, jid)
|
||||
self.handlers[id] = item
|
||||
|
@ -1521,7 +1597,7 @@ class GroupchatControl(ChatControlBase):
|
|||
item = xml.get_widget('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')):
|
||||
(user_affiliation != 'owner' and target_affiliation in ('admin','owner')):
|
||||
item.set_sensitive(False)
|
||||
id = item.connect('activate', self.on_member_checkmenuitem_activate,
|
||||
jid)
|
||||
|
@ -1711,19 +1787,23 @@ class GroupchatControl(ChatControlBase):
|
|||
|
||||
def grant_voice(self, widget, nick):
|
||||
'''grant voice privilege to a user'''
|
||||
gajim.connections[self.account].gc_set_role(self.room_jid, nick, 'participant')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
gajim.connections[self.account].gc_set_role(self.room_jid, nick,
|
||||
'participant')
|
||||
|
||||
def ban(self, widget, jid):
|
||||
'''ban a user'''
|
||||
|
@ -1738,17 +1818,17 @@ class GroupchatControl(ChatControlBase):
|
|||
else:
|
||||
return # stop banning procedure
|
||||
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
|
||||
'outcast', reason)
|
||||
'outcast', reason)
|
||||
|
||||
def grant_membership(self, widget, jid):
|
||||
'''grant membership privilege to a user'''
|
||||
gajim.connections[self.account].gc_set_affiliation(self.room_jid, jid,
|
||||
'member')
|
||||
'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')
|
||||
'none')
|
||||
|
||||
def grant_admin(self, widget, jid):
|
||||
'''grant administrative privilege to a user'''
|
||||
|
@ -1772,10 +1852,11 @@ class GroupchatControl(ChatControlBase):
|
|||
c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
|
||||
c2 = gajim.contacts.contact_from_gc_contact(c)
|
||||
if gajim.interface.instances[self.account]['infos'].has_key(c2.jid):
|
||||
gajim.interface.instances[self.account]['infos'][c2.jid].window.present()
|
||||
gajim.interface.instances[self.account]['infos'][c2.jid].window.\
|
||||
present()
|
||||
else:
|
||||
gajim.interface.instances[self.account]['infos'][c2.jid] = \
|
||||
vcard.VcardWindow(c2, self.account, is_fake = True)
|
||||
vcard.VcardWindow(c2, self.account, c)
|
||||
|
||||
def on_history(self, widget, nick):
|
||||
jid = gajim.construct_fjid(self.room_jid, nick)
|
||||
|
|
|
@ -1,18 +1,7 @@
|
|||
## gtkexcepthook.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
##
|
||||
## Initially written and submitted by Gustavo J. A. M. Carneiro
|
||||
##
|
||||
|
@ -33,6 +22,7 @@ import threading
|
|||
|
||||
import gtk
|
||||
import pango
|
||||
from common import i18n
|
||||
import dialogs
|
||||
|
||||
from cStringIO import StringIO
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
##
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2004-2005 Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005 Travis Shirk <travis@pobox.com>
|
||||
## Copyright (C) 2005 Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -169,30 +169,6 @@ def get_default_font():
|
|||
|
||||
return None
|
||||
|
||||
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(str):
|
||||
if len(str) > max_chars:
|
||||
str = str[:max_chars - 3] + '...'
|
||||
return str
|
||||
|
||||
if max_lines == 0:
|
||||
lines = text.split('\n')
|
||||
else:
|
||||
lines = text.split('\n', max_lines)[:max_lines]
|
||||
if max_chars > 0:
|
||||
if lines:
|
||||
lines = map(lambda e: _cut_if_long(e), lines)
|
||||
if lines:
|
||||
reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines)
|
||||
else:
|
||||
reduced_text = ''
|
||||
return reduced_text
|
||||
|
||||
def escape_for_pango_markup(string):
|
||||
# escapes < > & ' "
|
||||
# for pango markup not to break
|
||||
|
@ -207,7 +183,30 @@ def escape_for_pango_markup(string):
|
|||
return escaped_str
|
||||
|
||||
def autodetect_browser_mailer():
|
||||
# recognize the environment for appropriate browser/mailer
|
||||
# recognize the environment and set appropriate browser/mailer
|
||||
if user_runs_gnome():
|
||||
gajim.config.set('openwith', 'gnome-open')
|
||||
elif user_runs_kde():
|
||||
gajim.config.set('openwith', 'kfmclient exec')
|
||||
elif user_runs_xfce():
|
||||
gajim.config.set('openwith', 'exo-open')
|
||||
else:
|
||||
gajim.config.set('openwith', 'custom')
|
||||
|
||||
def user_runs_gnome():
|
||||
return 'gnome-session' in get_running_processes()
|
||||
|
||||
def user_runs_kde():
|
||||
return 'startkde' in get_running_processes()
|
||||
|
||||
def user_runs_xfce():
|
||||
procs = get_running_processes()
|
||||
if 'startxfce4' in procs or 'xfce4-session' in procs:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_running_processes():
|
||||
'''returns running processes or None (if not /proc exists)'''
|
||||
if os.path.isdir('/proc'):
|
||||
# under Linux: checking if 'gnome-session' or
|
||||
# 'startkde' programs were run before gajim, by
|
||||
|
@ -237,12 +236,9 @@ def autodetect_browser_mailer():
|
|||
|
||||
# list of processes
|
||||
processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files]
|
||||
if 'gnome-session' in processes:
|
||||
gajim.config.set('openwith', 'gnome-open')
|
||||
elif 'startkde' in processes:
|
||||
gajim.config.set('openwith', 'kfmclient exec')
|
||||
else:
|
||||
gajim.config.set('openwith', 'custom')
|
||||
|
||||
return processes
|
||||
return []
|
||||
|
||||
def move_window(window, x, y):
|
||||
'''moves the window but also checks if out of screen'''
|
||||
|
@ -390,7 +386,7 @@ def possibly_move_window_in_current_desktop(window):
|
|||
current virtual desktop
|
||||
window is GTK window'''
|
||||
if os.name == 'nt':
|
||||
return
|
||||
return False
|
||||
|
||||
root_window = gtk.gdk.screen_get_default().get_root_window()
|
||||
# current user's vd
|
||||
|
@ -406,6 +402,8 @@ def possibly_move_window_in_current_desktop(window):
|
|||
# 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):
|
||||
'''returns True if file is locked (WINDOWS ONLY)'''
|
||||
|
@ -680,6 +678,14 @@ default_name = ''):
|
|||
file_path = dialog.get_filename()
|
||||
file_path = decode_filechooser_file_paths((file_path,))[0]
|
||||
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?'),
|
||||
False)
|
||||
|
@ -688,6 +694,13 @@ default_name = ''):
|
|||
response = dialog2.get_response()
|
||||
if response < 0:
|
||||
return
|
||||
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
|
||||
|
||||
# Get pixbuf
|
||||
pixbuf = None
|
||||
|
@ -710,8 +723,8 @@ default_name = ''):
|
|||
try:
|
||||
pixbuf.save(file_path, type_)
|
||||
except:
|
||||
#XXX Check for permissions
|
||||
os.remove(file_path)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg'
|
||||
dialog2 = dialogs.ConfirmationDialog(_('Extension not supported'),
|
||||
_('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path},
|
||||
|
@ -735,3 +748,6 @@ default_name = ''):
|
|||
dialog.set_current_name(default_name)
|
||||
dialog.connect('delete-event', lambda widget, event:
|
||||
on_cancel(widget))
|
||||
|
||||
def on_bm_header_changed_state(widget, event):
|
||||
widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
#!/bin/sh
|
||||
''':'
|
||||
exec python -OOt "$0" ${1+"$@"}
|
||||
' '''
|
||||
#!/usr/bin/env python
|
||||
## history_manager.py
|
||||
##
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
|
@ -33,6 +30,9 @@ import dialogs
|
|||
import gtkgui_helpers
|
||||
from common.logger import LOG_DB_PATH, constants
|
||||
|
||||
#FIXME: constants should implement 2 way mappings
|
||||
status = dict((constants.__dict__[i], i[5:].lower()) for i in \
|
||||
constants.__dict__.keys() if i.startswith('SHOW_'))
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
|
||||
|
@ -44,14 +44,17 @@ C_SUBJECT,
|
|||
C_NICKNAME
|
||||
) = range(2, 6)
|
||||
|
||||
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
import sqlite3 as sqlite # python 2.5
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError:
|
||||
raise exceptions.PysqliteNotAvailable
|
||||
|
||||
|
||||
class HistoryManager:
|
||||
|
||||
def __init__(self):
|
||||
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
|
||||
pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
|
||||
|
@ -193,7 +196,9 @@ class HistoryManager:
|
|||
gtk.main_quit()
|
||||
|
||||
def _fill_jids_listview(self):
|
||||
self.cur.execute('SELECT jid, jid_id FROM jids ORDER BY jid')
|
||||
# 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')
|
||||
rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
|
||||
for row in rows:
|
||||
self.jids_already_in.append(row[0]) # jid
|
||||
|
@ -310,6 +315,7 @@ class HistoryManager:
|
|||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
color = None
|
||||
if kind in (constants.KIND_SINGLE_MSG_RECV,
|
||||
constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
|
||||
# it is the other side
|
||||
|
@ -325,11 +331,14 @@ class HistoryManager:
|
|||
message = ''
|
||||
else:
|
||||
message = ' : ' + message
|
||||
message = helpers.get_uf_show(show) + message
|
||||
|
||||
message = '<span foreground="%s">%s</span>' % (color,
|
||||
gtkgui_helpers.escape_for_pango_markup(message))
|
||||
self.logs_liststore.append((log_line_id, jid_id, time_, message,
|
||||
message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message
|
||||
|
||||
message_ = '<span'
|
||||
if color:
|
||||
message_ += ' foreground="%s"' % color
|
||||
message_ += '>%s</span>' % \
|
||||
gtkgui_helpers.escape_for_pango_markup(message)
|
||||
self.logs_liststore.append((log_line_id, jid_id, time_, message_,
|
||||
subject, nickname))
|
||||
|
||||
def _fill_search_results_listview(self, text):
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -59,7 +59,8 @@ class HistoryWindow:
|
|||
|
||||
self.calendar = xml.get_widget('calendar')
|
||||
scrolledwindow = xml.get_widget('scrolledwindow')
|
||||
self.history_textview = conversation_textview.ConversationTextview(account)
|
||||
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')
|
||||
|
@ -209,7 +210,9 @@ class HistoryWindow:
|
|||
|
||||
if gajim.config.get('print_time') == 'always':
|
||||
before_str = gajim.config.get('before_time')
|
||||
before_str = helpers.from_one_line(before_str)
|
||||
after_str = gajim.config.get('after_time')
|
||||
after_str = helpers.from_one_line(after_str)
|
||||
format = before_str + '%X' + after_str + ' '
|
||||
tim = time.strftime(format, time.localtime(float(tim)))
|
||||
buf.insert(end_iter, tim) # add time
|
||||
|
@ -277,7 +280,9 @@ class HistoryWindow:
|
|||
if contact_name and kind != constants.KIND_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 + ' '
|
||||
buf.insert_with_tags_by_name(end_iter, format, tag_name)
|
||||
|
||||
|
|
963
src/htmltextview.py
Normal file
963
src/htmltextview.py
Normal file
|
@ -0,0 +1,963 @@
|
|||
### Copyright (C) 2005 Gustavo J. A. M. Carneiro
|
||||
### Copyright (C) 2006 Santiago Gala
|
||||
###
|
||||
### This library is free software; you can redistribute it and/or
|
||||
### modify it under the terms of the GNU Lesser General Public
|
||||
### License as published by the Free Software Foundation; either
|
||||
### version 2 of the License, or (at your option) any later version.
|
||||
###
|
||||
### This library 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
|
||||
### Lesser General Public License for more details.
|
||||
###
|
||||
### You should have received a copy of the GNU Lesser General Public
|
||||
### License along with this library; if not, write to the
|
||||
### Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
### Boston, MA 02111-1307, USA.
|
||||
|
||||
|
||||
"""
|
||||
A gtk.TextView-based renderer for XHTML-IM, as described in:
|
||||
http://www.jabber.org/jeps/jep-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.
|
||||
|
||||
"""
|
||||
|
||||
import gobject
|
||||
import pango
|
||||
import gtk
|
||||
import xml.sax, xml.sax.handler
|
||||
import re
|
||||
import warnings
|
||||
from cStringIO import StringIO
|
||||
import urllib2
|
||||
import operator
|
||||
|
||||
from common import gajim
|
||||
#from common import i18n
|
||||
|
||||
|
||||
import tooltips
|
||||
|
||||
|
||||
__all__ = ['HtmlTextView']
|
||||
|
||||
whitespace_rx = re.compile("\\s+")
|
||||
allwhitespace_rx = re.compile("^\\s*$")
|
||||
|
||||
## pixels = points * display_resolution
|
||||
display_resolution = 0.3514598*(gtk.gdk.screen_height() /
|
||||
float(gtk.gdk.screen_height_mm()))
|
||||
|
||||
#embryo of CSS classes
|
||||
classes = {
|
||||
#'system-message':';display: none',
|
||||
'problematic':';color: red',
|
||||
}
|
||||
|
||||
#styles for elemens
|
||||
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']
|
||||
|
||||
class_styles = {
|
||||
}
|
||||
|
||||
"""
|
||||
==========
|
||||
JEP-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 build_patterns(view, config, interface):
|
||||
#extra, rst does not mark _underline_ or /it/ up
|
||||
#actually <b>, <i> or <u> are not in the JEP-0071, but are seen in the wild
|
||||
basic_pattern = r'(?<!\w|\<|/|:)' r'/[^\s/]' r'([^/]*[^\s/])?' r'/(?!\w|/|:)|'\
|
||||
r'(?<!\w)' r'_[^\s_]' r'([^_]*[^\s_])?' r'_(?!\w)'
|
||||
view.basic_pattern_re = re.compile(basic_pattern)
|
||||
#TODO: emoticons
|
||||
emoticons_pattern = ''
|
||||
if 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 = interface.emoticons.keys()
|
||||
keys.sort(interface.on_emoticon_sort)
|
||||
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
|
||||
emoticons_pattern += emoticon_escaped + '|'# | means or in regexp
|
||||
if (emoticon_length != len(emoticon)):
|
||||
# Build up expressions to match emoticons next to other emoticons
|
||||
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 = '|' + \
|
||||
'(?:(?<![\w.]' + emoticons_pattern_prematch[:-1] + '))' + \
|
||||
'(?:' + emoticons_pattern[:-1] + ')' + \
|
||||
'(?:(?![\w.]' + emoticons_pattern_postmatch[:-1] + '))'
|
||||
|
||||
# because emoticons match later (in the string) they need to be after
|
||||
# basic matches that may occur earlier
|
||||
emot_and_basic_pattern = basic_pattern + emoticons_pattern
|
||||
view.emot_and_basic_re = re.compile(emot_and_basic_pattern, re.IGNORECASE)
|
||||
|
||||
|
||||
def _parse_css_color(color):
|
||||
'''_parse_css_color(css_color) -> gtk.gdk.Color'''
|
||||
if color.startswith("rgb(") and color.endswith(')'):
|
||||
r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
|
||||
return gtk.gdk.Color(r, g, b)
|
||||
else:
|
||||
return gtk.gdk.color_parse(color)
|
||||
|
||||
|
||||
class HtmlHandler(xml.sax.handler.ContentHandler):
|
||||
|
||||
def __init__(self, textview, startiter):
|
||||
xml.sax.handler.ContentHandler.__init__(self)
|
||||
self.textbuf = textview.get_buffer()
|
||||
self.textview = textview
|
||||
self.iter = startiter
|
||||
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)
|
||||
if gtk.gtk_version >= (2, 8):
|
||||
tag.set_property("paragraph-background-gdk", color)
|
||||
|
||||
|
||||
if gtk.gtk_version >= (2, 8, 5) or gobject.pygtk_version >= (2, 8, 1):
|
||||
|
||||
def _get_current_attributes(self):
|
||||
attrs = self.textview.get_default_attributes()
|
||||
self.iter.backward_char()
|
||||
self.iter.get_attributes(attrs)
|
||||
self.iter.forward_char()
|
||||
return attrs
|
||||
|
||||
else:
|
||||
|
||||
## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455
|
||||
def _get_current_style_attr(self, propname, comb_oper=None):
|
||||
tags = [tag for tag in self.styles if tag is not None]
|
||||
tags.reverse()
|
||||
is_set_name = propname + "-set"
|
||||
value = None
|
||||
for tag in tags:
|
||||
if tag.get_property(is_set_name):
|
||||
if value is None:
|
||||
value = tag.get_property(propname)
|
||||
if comb_oper is None:
|
||||
return value
|
||||
else:
|
||||
value = comb_oper(value, tag.get_property(propname))
|
||||
return value
|
||||
|
||||
class _FakeAttrs(object):
|
||||
__slots__ = ("font", "font_scale")
|
||||
|
||||
def _get_current_attributes(self):
|
||||
attrs = self._FakeAttrs()
|
||||
attrs.font_scale = self._get_current_style_attr("scale",
|
||||
operator.mul)
|
||||
if attrs.font_scale is None:
|
||||
attrs.font_scale = 1.0
|
||||
attrs.font = self._get_current_style_attr("font-desc")
|
||||
if attrs.font is None:
|
||||
attrs.font = self.textview.style.font_desc
|
||||
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, callback, *args):
|
||||
'''Parse/calc length, converting to pixels, calls callback(length, *args)
|
||||
when the length is first computed or changes'''
|
||||
if value.endswith('%'):
|
||||
frac = float(value[:-1])/100
|
||||
if font_relative:
|
||||
attrs = self._get_current_attributes()
|
||||
font_size = attrs.font.get_size() / pango.SCALE
|
||||
callback(frac*display_resolution*font_size, *args)
|
||||
else:
|
||||
## 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)
|
||||
|
||||
elif value.endswith('pt'): # points
|
||||
callback(float(value[:-2])*display_resolution, *args)
|
||||
|
||||
elif value.endswith('em'): # ems, the height of the element's font
|
||||
attrs = self._get_current_attributes()
|
||||
font_size = attrs.font.get_size() / pango.SCALE
|
||||
callback(float(value[:-2])*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()
|
||||
font_size = attrs.font.get_size() / pango.SCALE
|
||||
callback(float(value[:-2])*display_resolution*font_size, *args)
|
||||
|
||||
elif value.endswith('px'): # pixels
|
||||
callback(int(value[:-2]), *args)
|
||||
|
||||
else:
|
||||
warnings.warn("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:
|
||||
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,
|
||||
} [value]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
attrs = self._get_current_attributes()
|
||||
tag.set_property("scale", scale / attrs.font_scale)
|
||||
return
|
||||
if value == 'smaller':
|
||||
tag.set_property("scale", pango.SCALE_SMALL)
|
||||
return
|
||||
if value == 'larger':
|
||||
tag.set_property("scale", pango.SCALE_LARGE)
|
||||
return
|
||||
self._parse_length(value, True, 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:
|
||||
warnings.warn("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):
|
||||
self._parse_length(value, False, self.__frac_length_tag_cb,
|
||||
tag, "left-margin")
|
||||
|
||||
def _parse_style_margin_right(self, tag, value):
|
||||
self._parse_length(value, False, 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:
|
||||
warnings.warn("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.JUSTIFY_LEFT,
|
||||
'right': gtk.JUSTIFY_RIGHT,
|
||||
'center': gtk.JUSTIFY_CENTER,
|
||||
'justify': gtk.JUSTIFY_FILL,
|
||||
} [value]
|
||||
except KeyError:
|
||||
warnings.warn("Invalid text-align:%s requested" % value)
|
||||
else:
|
||||
tag.set_property("justification", align)
|
||||
|
||||
def _parse_style_text_decoration(self, tag, value):
|
||||
if value == "none":
|
||||
tag.set_property("underline", pango.UNDERLINE_NONE)
|
||||
tag.set_property("strikethrough", False)
|
||||
elif value == "underline":
|
||||
tag.set_property("underline", pango.UNDERLINE_SINGLE)
|
||||
tag.set_property("strikethrough", False)
|
||||
elif value == "overline":
|
||||
warnings.warn("text-decoration:overline not implemented")
|
||||
tag.set_property("underline", pango.UNDERLINE_NONE)
|
||||
tag.set_property("strikethrough", False)
|
||||
elif value == "line-through":
|
||||
tag.set_property("underline", pango.UNDERLINE_NONE)
|
||||
tag.set_property("strikethrough", True)
|
||||
elif value == "blink":
|
||||
warnings.warn("text-decoration:blink not implemented")
|
||||
else:
|
||||
warnings.warn("text-decoration:%s not implemented" % value)
|
||||
|
||||
def _parse_style_white_space(self, tag, value):
|
||||
if value == 'pre':
|
||||
tag.set_property("wrap_mode", gtk.WRAP_NONE)
|
||||
elif value == 'normal':
|
||||
tag.set_property("wrap_mode", gtk.WRAP_WORD)
|
||||
elif value == 'nowrap':
|
||||
tag.set_property("wrap_mode", gtk.WRAP_NONE)
|
||||
|
||||
|
||||
## 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' ]:
|
||||
try:
|
||||
method = locals()["_parse_style_%s" % style.replace('-', '_')]
|
||||
except KeyError:
|
||||
warnings.warn("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_):
|
||||
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.html_hyperlink_handler, 'url', href)
|
||||
tag.set_property('foreground', '#0000ff')
|
||||
tag.set_property('underline', pango.UNDERLINE_SINGLE)
|
||||
tag.is_anchor = True
|
||||
if title:
|
||||
tag.title = title
|
||||
return tag
|
||||
|
||||
|
||||
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()
|
||||
for attr, val in [item.split(':', 1) for item in style.split(';') if len(item.strip())]:
|
||||
attr = attr.strip().lower()
|
||||
val = val.strip()
|
||||
try:
|
||||
method = self.__style_methods[attr]
|
||||
except KeyError:
|
||||
warnings.warn("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):
|
||||
if self.starting and text != '\n':
|
||||
self.starting = (text[-1] == '\n')
|
||||
tags = self._get_style_tags()
|
||||
if tags:
|
||||
self.textbuf.insert_with_tags(self.iter, text, *tags)
|
||||
else:
|
||||
self.textbuf.insert(self.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 == gtk.gdk.BUTTON_PRESS:
|
||||
self.textview.emit("url-clicked", href, type_)
|
||||
return True
|
||||
return False
|
||||
|
||||
def handle_specials(self, text):
|
||||
index = 0
|
||||
se = self.textview.config.get('show_ascii_formatting_chars')
|
||||
if self.textview.config.get('emoticons_theme'):
|
||||
iterator = self.textview.emot_and_basic_re.finditer(text)
|
||||
else:
|
||||
iterator = self.textview.basic_pattern_re.finditer(text)
|
||||
for match in iterator:
|
||||
start, end = match.span()
|
||||
special_text = text[start:end]
|
||||
if start != 0:
|
||||
self._insert_text(text[index:start])
|
||||
index = end # update index
|
||||
#emoticons
|
||||
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
|
||||
if self.textview.config.get('emoticons_theme') and \
|
||||
possible_emot_ascii_caps in self.textview.interface.emoticons.keys():
|
||||
#it's an emoticon
|
||||
emot_ascii = possible_emot_ascii_caps
|
||||
anchor = self.textbuf.create_child_anchor(self.iter)
|
||||
img = gtk.Image()
|
||||
img.set_from_file(self.textview.interface.emoticons[emot_ascii])
|
||||
img.show()
|
||||
# TODO: add alt/tooltip with the special_text (a11y)
|
||||
self.textview.add_child_at_anchor(img, anchor)
|
||||
else:
|
||||
# now print it
|
||||
if special_text.startswith('/'): # it's explicit italics
|
||||
self.startElement('i', {})
|
||||
elif special_text.startswith('_'): # it's explicit underline
|
||||
self.startElement("u", {})
|
||||
if se: self._insert_text(special_text[0])
|
||||
self.handle_specials(special_text[1:-1])
|
||||
if se: self._insert_text(special_text[0])
|
||||
if special_text.startswith('_'): # it's explicit underline
|
||||
self.endElement('u')
|
||||
if special_text.startswith('/'): # it's explicit italics
|
||||
self.endElement('i')
|
||||
if index < len(text):
|
||||
self._insert_text(text[index:])
|
||||
|
||||
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 = attrs.get('style','')
|
||||
#Add styles defined for classes
|
||||
#TODO: priority between class and style elements?
|
||||
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)
|
||||
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 = title
|
||||
tag.is_anchor = True
|
||||
elif name in LIST_ELEMS:
|
||||
style += ';margin-left: 2em'
|
||||
if name in element_styles:
|
||||
style += element_styles[name]
|
||||
|
||||
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 = unichr(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 == 'img':
|
||||
try:
|
||||
## Max image size = 10 MB (to try to prevent DoS)
|
||||
mem = urllib2.urlopen(attrs['src']).read(10*1024*1024)
|
||||
## Caveat: GdkPixbuf is known not to be safe to load
|
||||
## images from network... this program is now potentially
|
||||
## hackable ;)
|
||||
loader = gtk.gdk.PixbufLoader()
|
||||
loader.write(mem); loader.close()
|
||||
pixbuf = loader.get_pixbuf()
|
||||
except Exception, ex:
|
||||
gajim.log.debug(str('Error loading image'+ex))
|
||||
pixbuf = None
|
||||
alt = attrs.get('alt', "Broken image")
|
||||
try:
|
||||
loader.close()
|
||||
except: pass
|
||||
if pixbuf is not None:
|
||||
tags = self._get_style_tags()
|
||||
if tags:
|
||||
tmpmark = self.textbuf.create_mark(None, self.iter, True)
|
||||
|
||||
self.textbuf.insert_pixbuf(self.iter, pixbuf)
|
||||
|
||||
if tags:
|
||||
start = self.textbuf.get_iter_at_mark(tmpmark)
|
||||
for tag in tags:
|
||||
self.textbuf.apply_tag(tag, start, self.iter)
|
||||
self.textbuf.delete_mark(tmpmark)
|
||||
else:
|
||||
self._insert_text("[IMG: %s]" % alt)
|
||||
elif name == 'body' or name == 'html':
|
||||
pass
|
||||
elif name == 'a':
|
||||
pass
|
||||
elif name in INLINE:
|
||||
pass
|
||||
else:
|
||||
warnings.warn("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()
|
||||
try:
|
||||
self.textbuf.insert_pixbuf(self.iter, self.textview.focus_out_line_pixbuf)
|
||||
#self._insert_text(u"\u2550"*40)
|
||||
self._jump_line()
|
||||
except Exception, e:
|
||||
gajim.log.debug(str("Error in hr"+e))
|
||||
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
|
||||
else:
|
||||
warnings.warn("Unhandled element '%s'" % name)
|
||||
self._flush_text()
|
||||
if endPreserving:
|
||||
self.preserve = False
|
||||
if newLine:
|
||||
self._jump_line()
|
||||
self._end_span()
|
||||
#if not self._starts_line():
|
||||
# self.text = ' '
|
||||
|
||||
class HtmlTextView(gtk.TextView):
|
||||
__gtype_name__ = 'HtmlTextView'
|
||||
__gsignals__ = {
|
||||
'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
gobject.GObject.__init__(self)
|
||||
self.set_wrap_mode(gtk.WRAP_CHAR)
|
||||
self.set_editable(False)
|
||||
self._changed_cursor = False
|
||||
self.connect("motion-notify-event", self.__motion_notify_event)
|
||||
self.connect("leave-notify-event", self.__leave_event)
|
||||
self.connect("enter-notify-event", self.__motion_notify_event)
|
||||
self.get_buffer().create_tag('eol', scale = pango.SCALE_XX_SMALL)
|
||||
self.tooltip = tooltips.BaseTooltip()
|
||||
self.config = gajim.config
|
||||
self.interface = gajim.interface
|
||||
# end big hack
|
||||
build_patterns(self,gajim.config,gajim.interface)
|
||||
|
||||
def __leave_event(self, widget, event):
|
||||
if self._changed_cursor:
|
||||
window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||
window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
|
||||
self._changed_cursor = False
|
||||
|
||||
def show_tooltip(self, tag):
|
||||
if not self.tooltip.win:
|
||||
# check if the current pointer is still over the line
|
||||
text = getattr(tag, 'title', False)
|
||||
if text:
|
||||
pointer = self.get_pointer()
|
||||
position = self.window.get_origin()
|
||||
win = self.get_toplevel()
|
||||
self.tooltip.show_tooltip(text, 8, position[1] + pointer[1])
|
||||
|
||||
def __motion_notify_event(self, widget, event):
|
||||
x, y, _ = widget.window.get_pointer()
|
||||
x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
|
||||
tags = widget.get_iter_at_location(x, y).get_tags()
|
||||
is_over_anchor = False
|
||||
for tag in tags:
|
||||
if getattr(tag, 'is_anchor', False):
|
||||
is_over_anchor = True
|
||||
break
|
||||
if self.tooltip.timeout != 0:
|
||||
# Check if we should hide the line tooltip
|
||||
if not is_over_anchor:
|
||||
self.tooltip.hide_tooltip()
|
||||
if not self._changed_cursor and is_over_anchor:
|
||||
window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||
window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
|
||||
self._changed_cursor = True
|
||||
gobject.timeout_add(500,
|
||||
self.show_tooltip, tag)
|
||||
elif self._changed_cursor and not is_over_anchor:
|
||||
window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
|
||||
window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
|
||||
self._changed_cursor = False
|
||||
return False
|
||||
|
||||
def display_html(self, html):
|
||||
buffer = self.get_buffer()
|
||||
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(self, eob))
|
||||
parser.parse(StringIO(html))
|
||||
|
||||
#if not eob.starts_line():
|
||||
# buffer.insert(eob, "\n")
|
||||
|
||||
if gobject.pygtk_version < (2, 8):
|
||||
gobject.type_register(HtmlTextView)
|
||||
|
||||
change_cursor = None
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
htmlview = HtmlTextView()
|
||||
|
||||
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
|
||||
pointer_x, pointer_y, spam = htmlview.window.get_pointer()
|
||||
x, y = htmlview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
|
||||
pointer_y)
|
||||
tags = htmlview.get_iter_at_location(x, y).get_tags()
|
||||
if change_cursor:
|
||||
htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
|
||||
gtk.gdk.Cursor(gtk.gdk.XTERM))
|
||||
change_cursor = None
|
||||
tag_table = htmlview.get_buffer().get_tag_table()
|
||||
over_line = False
|
||||
for tag in tags:
|
||||
try:
|
||||
if tag.is_anchor:
|
||||
htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
|
||||
gtk.gdk.Cursor(gtk.gdk.HAND2))
|
||||
change_cursor = tag
|
||||
elif tag == tag_table.lookup('focus-out-line'):
|
||||
over_line = True
|
||||
except: 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 = gobject.timeout_add(500,
|
||||
# show_line_tooltip)
|
||||
# htmlview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
|
||||
# gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
|
||||
# change_cursor = tag
|
||||
|
||||
htmlview.connect('motion_notify_event', on_textview_motion_notify_event)
|
||||
|
||||
def handler(texttag, widget, event, iter, kind, href):
|
||||
if event.type == gtk.gdk.BUTTON_PRESS:
|
||||
print href
|
||||
|
||||
htmlview.html_hyperlink_handler = handler
|
||||
|
||||
htmlview.display_html('<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.display_html("<hr />")
|
||||
htmlview.display_html("""
|
||||
<p style='font-size:large'>
|
||||
<span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>,
|
||||
I'm <span style='color:green'>green</span>
|
||||
with <span style='font-weight: bold'>envy</span>!
|
||||
</p>
|
||||
""")
|
||||
htmlview.display_html("<hr />")
|
||||
htmlview.display_html("""
|
||||
<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%'>
|
||||
"A foolish consistency is the hobgoblin of little minds."
|
||||
</p>
|
||||
</body>
|
||||
""")
|
||||
htmlview.display_html("<hr />")
|
||||
htmlview.display_html("""
|
||||
<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.jabber.org/images/psa-license.jpg'
|
||||
alt='A License to Jabber'
|
||||
height='261'
|
||||
width='537'/></p>
|
||||
</body>
|
||||
""")
|
||||
htmlview.display_html("<hr />")
|
||||
htmlview.display_html("""
|
||||
<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<0: raise ValueError("Must be non-negative")
|
||||
return faciter(n,1)</pre>
|
||||
</body>
|
||||
""")
|
||||
htmlview.display_html("<hr />")
|
||||
htmlview.display_html("""
|
||||
<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> Two </li>
|
||||
<li> Three </li>
|
||||
</ul></li>
|
||||
<li> Three </li></ol>
|
||||
</body>
|
||||
""")
|
||||
htmlview.show()
|
||||
sw = gtk.ScrolledWindow()
|
||||
sw.set_property("hscrollbar-policy", gtk.POLICY_AUTOMATIC)
|
||||
sw.set_property("vscrollbar-policy", gtk.POLICY_AUTOMATIC)
|
||||
sw.set_property("border-width", 0)
|
||||
sw.add(htmlview)
|
||||
sw.show()
|
||||
frame = gtk.Frame()
|
||||
frame.set_shadow_type(gtk.SHADOW_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()
|
|
@ -57,7 +57,7 @@ class MessageControl:
|
|||
or inactive (state is False)'''
|
||||
pass # Derived types MUST implement this method
|
||||
|
||||
def allow_shutdown(self):
|
||||
def allow_shutdown(self, method):
|
||||
'''Called to check is a control is allowed to shutdown.
|
||||
If a control is not in a suitable shutdown state this method
|
||||
should return False'''
|
||||
|
@ -68,10 +68,6 @@ class MessageControl:
|
|||
# NOTE: Derived classes MUST implement this
|
||||
pass
|
||||
|
||||
def notify_on_new_messages(self):
|
||||
# NOTE: Derived classes MUST implement this
|
||||
return False
|
||||
|
||||
def repaint_themed_widgets(self, theme):
|
||||
pass # NOTE: Derived classes SHOULD implement this
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Nikos Kouremenos <kourem@gmail.com>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
|
@ -40,6 +40,13 @@ class MessageWindow:
|
|||
# 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):
|
||||
# A dictionary of dictionaries where _contacts[account][jid] == A MessageControl
|
||||
|
@ -104,6 +111,16 @@ class MessageWindow:
|
|||
self.notebook.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.DND_TARGETS,
|
||||
gtk.gdk.ACTION_MOVE)
|
||||
|
||||
def change_account_name(self, old_name, new_name):
|
||||
if self._controls.has_key(old_name):
|
||||
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 get_num_controls(self):
|
||||
n = 0
|
||||
for dict in self._controls.values():
|
||||
|
@ -130,7 +147,7 @@ class MessageWindow:
|
|||
def _on_window_delete(self, win, event):
|
||||
# Make sure all controls are okay with being deleted
|
||||
for ctrl in self.controls():
|
||||
if not ctrl.allow_shutdown():
|
||||
if not ctrl.allow_shutdown(self.CLOSE_CLOSE_BUTTON):
|
||||
return True # halt the delete
|
||||
return False
|
||||
|
||||
|
@ -189,7 +206,7 @@ class MessageWindow:
|
|||
self.popup_menu(event)
|
||||
elif event.button == 2: # middle click
|
||||
ctrl = self._widget_to_control(child)
|
||||
self.remove_tab(ctrl)
|
||||
self.remove_tab(ctrl, self.CLOSE_TAB_MIDDLE_CLICK)
|
||||
|
||||
def _on_message_textview_mykeypress_event(self, widget, event_keyval,
|
||||
event_keymod):
|
||||
|
@ -214,15 +231,17 @@ class MessageWindow:
|
|||
|
||||
def _on_close_button_clicked(self, button, control):
|
||||
'''When close button is pressed: close a tab'''
|
||||
self.remove_tab(control)
|
||||
self.remove_tab(control, self.CLOSE_CLOSE_BUTTON)
|
||||
|
||||
def show_title(self, urgent = True, control = None):
|
||||
'''redraw the window's title'''
|
||||
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:
|
||||
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()
|
||||
|
||||
|
@ -269,10 +288,11 @@ class MessageWindow:
|
|||
ctrl_page = self.notebook.page_num(ctrl.widget)
|
||||
self.notebook.set_current_page(ctrl_page)
|
||||
|
||||
def remove_tab(self, ctrl, reason = None):
|
||||
'''reason is only for gc (offline status message)'''
|
||||
def remove_tab(self, ctrl, mothod, reason = None, force = False):
|
||||
'''reason is only for gc (offline status message)
|
||||
if force is True, do not ask any confirmation'''
|
||||
# Shutdown the MessageControl
|
||||
if not ctrl.allow_shutdown():
|
||||
if not force and not ctrl.allow_shutdown(mothod):
|
||||
return
|
||||
if reason is not None: # We are leaving gc with a status message
|
||||
ctrl.shutdown(reason)
|
||||
|
@ -292,28 +312,22 @@ class MessageWindow:
|
|||
if len(self._controls[ctrl.account]) == 0:
|
||||
del self._controls[ctrl.account]
|
||||
|
||||
# Notify a dupicate nick to update their banner and clear account display
|
||||
for c in self.controls():
|
||||
if c == self:
|
||||
continue
|
||||
if ctrl.contact.get_shown_name() == c.contact.get_shown_name():
|
||||
c.draw_banner()
|
||||
|
||||
if self.get_num_controls() == 1: # we are going from two tabs to one
|
||||
show_tabs_if_one_tab = gajim.config.get('tabs_always_visible')
|
||||
self.notebook.set_show_tabs(show_tabs_if_one_tab)
|
||||
if not show_tabs_if_one_tab:
|
||||
self.alignment.set_property('top-padding', 0)
|
||||
self.show_title()
|
||||
elif self.get_num_controls() == 0:
|
||||
if 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.disconnect(self.hid)
|
||||
self.notebook.drag_dest_unset()
|
||||
|
||||
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
|
||||
show_tabs_if_one_tab = gajim.config.get('tabs_always_visible')
|
||||
self.notebook.set_show_tabs(show_tabs_if_one_tab)
|
||||
if not show_tabs_if_one_tab:
|
||||
self.alignment.set_property('top-padding', 0)
|
||||
self.show_title()
|
||||
|
||||
|
||||
def redraw_tab(self, ctrl, chatstate = None):
|
||||
hbox = self.notebook.get_tab_label(ctrl.widget).get_children()[0]
|
||||
|
@ -488,9 +502,9 @@ class MessageWindow:
|
|||
elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
|
||||
self.move_to_next_unread_tab(True)
|
||||
elif event.keyval == gtk.keysyms.F4: # CTRL + F4
|
||||
self.remove_tab(ctrl)
|
||||
self.remove_tab(ctrl, self.CLOSE_CTRL_KEY)
|
||||
elif event.keyval == gtk.keysyms.w: # CTRL + W
|
||||
self.remove_tab(ctrl)
|
||||
self.remove_tab(ctrl, self.CLOSE_CTRL_KEY)
|
||||
|
||||
# MOD1 (ALT) mask
|
||||
elif event.state & gtk.gdk.MOD1_MASK:
|
||||
|
@ -513,7 +527,7 @@ class MessageWindow:
|
|||
# Close tab bindings
|
||||
elif event.keyval == gtk.keysyms.Escape and \
|
||||
gajim.config.get('escape_key_closes'): # Escape
|
||||
self.remove_tab(ctrl)
|
||||
self.remove_tab(ctrl, self.CLOSE_ESC)
|
||||
else:
|
||||
# If the active control has a message_textview pass the event to it
|
||||
active_ctrl = self.get_active_control()
|
||||
|
@ -618,7 +632,11 @@ class MessageWindowMgr:
|
|||
# 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)
|
||||
|
||||
|
||||
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):
|
||||
win = MessageWindow(acct, type)
|
||||
# we track the lifetime of this window
|
||||
|
|
159
src/music_track_listener.py
Normal file
159
src/music_track_listener.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
## musictracklistener.py
|
||||
##
|
||||
## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro@gmail.com>
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
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
|
||||
import dbus.glib
|
||||
|
||||
class MusicTrackInfo(object):
|
||||
__slots__ = ['title', 'album', 'artist', 'duration', 'track_number']
|
||||
|
||||
|
||||
class MusicTrackListener(gobject.GObject):
|
||||
__gsignals__ = {
|
||||
'music-track-changed': (gobject.SIGNAL_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()
|
||||
|
||||
## 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._rhythmbox_music_track_change_cb,
|
||||
'playingUriChanged', 'org.gnome.Rhythmbox.Player')
|
||||
bus.add_signal_receiver(self._player_name_owner_changed,
|
||||
'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
|
||||
bus.add_signal_receiver(self._player_playing_changed_cb,
|
||||
'playingChanged', 'org.gnome.Rhythmbox.Player')
|
||||
|
||||
def do_music_track_changed(self, info):
|
||||
if info is not None:
|
||||
self._last_playing_music = info
|
||||
|
||||
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 _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_properties_extract(self, props):
|
||||
info = MusicTrackInfo()
|
||||
info.title = props['title']
|
||||
info.album = props['album']
|
||||
info.artist = props['artist']
|
||||
info.duration = int(props['duration'])
|
||||
info.track_number = int(props['track-number'])
|
||||
return info
|
||||
|
||||
def _rhythmbox_music_track_change_cb(self, uri):
|
||||
bus = dbus.SessionBus()
|
||||
rbshellobj = bus.get_object('org.gnome.Rhythmbox',
|
||||
'/org/gnome/Rhythmbox/Shell')
|
||||
rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
|
||||
props = rbshell.getSongProperties(uri)
|
||||
info = self._rhythmbox_properties_extract(props)
|
||||
self.emit('music-track-changed', info)
|
||||
|
||||
def 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
|
||||
if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
||||
'org.gnome.Muine'):
|
||||
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
|
||||
if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
||||
'org.gnome.Rhythmbox'):
|
||||
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')
|
||||
uri = player.getPlayingUri()
|
||||
props = rbshell.getSongProperties(uri)
|
||||
info = self._rhythmbox_properties_extract(props)
|
||||
self._last_playing_music = info
|
||||
return info
|
||||
|
||||
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()
|
47
src/network_manager_listener.py
Normal file
47
src/network_manager_listener.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
## network_manager_listener.py
|
||||
## Copyright (C) 2006 Jeffrey C. Ollie <jeff at ocjtech.us>
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan at lanpartei.de>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
from common import gajim
|
||||
|
||||
def device_now_active(self, *args):
|
||||
for connection in gajim.connections.itervalues():
|
||||
if gajim.config.get_per('accounts', connection.name, 'listen_to_network_manager') and gajim.config.get_per('accounts', connection.name, 'sync_with_global_status'):
|
||||
connection.change_status('online', '')
|
||||
|
||||
def device_no_longer_active(self, *args):
|
||||
for connection in gajim.connections.itervalues():
|
||||
if gajim.config.get_per('accounts', connection.name, 'listen_to_network_manager') and gajim.config.get_per('accounts', connection.name, 'sync_with_global_status'):
|
||||
connection.change_status('offline', '')
|
||||
|
||||
|
||||
from common.dbus_support import system_bus
|
||||
|
||||
import dbus
|
||||
import dbus.glib
|
||||
|
||||
bus = system_bus.SystemBus()
|
||||
|
||||
bus.add_signal_receiver(device_no_longer_active,
|
||||
'DeviceNoLongerActive',
|
||||
'org.freedesktop.NetworkManager',
|
||||
'org.freedesktop.NetworkManager',
|
||||
'/org/freedesktop/NetworkManager')
|
||||
|
||||
bus.add_signal_receiver(device_now_active,
|
||||
'DeviceNowActive',
|
||||
'org.freedesktop.NetworkManager',
|
||||
'org.freedesktop.NetworkManager',
|
||||
'/org/freedesktop/NetworkManager')
|
||||
|
157
src/notify.py
157
src/notify.py
|
@ -4,7 +4,7 @@
|
|||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
|
||||
##
|
||||
## DBUS/libnotify connection code:
|
||||
## Notification daemon connection via D-Bus code:
|
||||
## Copyright (C) 2005 by Sebastian Estienne
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
|
@ -25,12 +25,19 @@ import gtkgui_helpers
|
|||
from common import gajim
|
||||
from common import helpers
|
||||
|
||||
import dbus_support
|
||||
from common import dbus_support
|
||||
if dbus_support.supported:
|
||||
import dbus
|
||||
if dbus_support.version >= (0, 41, 0):
|
||||
import dbus.glib
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
import dbus.service
|
||||
|
||||
|
||||
USER_HAS_PYNOTIFY = True # user has pynotify module
|
||||
try:
|
||||
import pynotify
|
||||
pynotify.init('Gajim Notification')
|
||||
except ImportError:
|
||||
USER_HAS_PYNOTIFY = False
|
||||
|
||||
def get_show_in_roster(event, account, contact):
|
||||
'''Return True if this event must be shown in roster, else False'''
|
||||
|
@ -42,27 +49,23 @@ def get_show_in_roster(event, account, contact):
|
|||
return False
|
||||
if event == 'message_received':
|
||||
chat_control = helpers.get_chat_control(account, contact)
|
||||
if not chat_control:
|
||||
return True
|
||||
elif event == 'ft_request':
|
||||
return True
|
||||
return False
|
||||
if chat_control:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_show_in_systray(event, account, contact):
|
||||
'''Return True if this event must be shown in roster, else False'''
|
||||
'''Return True if this event must be shown in systray, else False'''
|
||||
num = get_advanced_notification(event, account, contact)
|
||||
if num != None:
|
||||
if gajim.config.get_per('notifications', str(num), 'systray') == 'yes':
|
||||
return True
|
||||
if gajim.config.get_per('notifications', str(num), 'systray') == 'no':
|
||||
return False
|
||||
if event in ('message_received', 'ft_request', 'gc_msg_highlight',
|
||||
'ft_request'):
|
||||
return True
|
||||
return False
|
||||
return gajim.config.get('trayicon_notification_on_events')
|
||||
|
||||
def get_advanced_notification(event, account, contact):
|
||||
'''Returns the number of the first advanced notification or None'''
|
||||
'''Returns the number of the first (top most)
|
||||
advanced notification else None'''
|
||||
num = 0
|
||||
notif = gajim.config.get_per('notifications', str(num))
|
||||
while notif:
|
||||
|
@ -98,7 +101,7 @@ def get_advanced_notification(event, account, contact):
|
|||
if tab_opened == 'both':
|
||||
tab_opened_ok = True
|
||||
else:
|
||||
chat_control = helper.get_chat_control(account, contact)
|
||||
chat_control = helpers.get_chat_control(account, contact)
|
||||
if (chat_control and tab_opened == 'yes') or (not chat_control and \
|
||||
tab_opened == 'no'):
|
||||
tab_opened_ok = True
|
||||
|
@ -109,17 +112,19 @@ def get_advanced_notification(event, account, contact):
|
|||
notif = gajim.config.get_per('notifications', str(num))
|
||||
|
||||
def notify(event, jid, account, parameters, advanced_notif_num = None):
|
||||
'''Check what type of notifications we want, depending on basic configuration
|
||||
of notifications and advanced one and do these notifications'''
|
||||
'''Check what type of notifications we want, depending on basic
|
||||
and the advanced configuration of notifications and do these notifications;
|
||||
advanced_notif_num holds the number of the first (top most) advanced
|
||||
notification'''
|
||||
# First, find what notifications we want
|
||||
do_popup = False
|
||||
do_sound = False
|
||||
do_cmd = False
|
||||
if (event == 'status_change'):
|
||||
if event == 'status_change':
|
||||
new_show = parameters[0]
|
||||
status_message = parameters[1]
|
||||
# Default : No popup for status change
|
||||
elif (event == 'contact_connected'):
|
||||
# Default: No popup for status change
|
||||
elif event == 'contact_connected':
|
||||
status_message = parameters
|
||||
j = gajim.get_jid_without_resource(jid)
|
||||
server = gajim.get_server_from_jid(j)
|
||||
|
@ -135,43 +140,44 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
'enabled') and not gajim.block_signed_in_notifications[account] and \
|
||||
not block_transport:
|
||||
do_sound = True
|
||||
elif (event == 'contact_disconnected'):
|
||||
elif event == 'contact_disconnected':
|
||||
status_message = parameters
|
||||
if helpers.allow_showing_notification(account, 'notify_on_signout'):
|
||||
do_popup = True
|
||||
if gajim.config.get_per('soundevents', 'contact_disconnected',
|
||||
'enabled'):
|
||||
do_sound = True
|
||||
elif (event == 'new_message'):
|
||||
elif event == 'new_message':
|
||||
message_type = parameters[0]
|
||||
first = parameters[1]
|
||||
is_first_message = parameters[1]
|
||||
nickname = parameters[2]
|
||||
message = parameters[3]
|
||||
if helpers.allow_showing_notification(account, 'notify_on_new_message',
|
||||
advanced_notif_num, first):
|
||||
advanced_notif_num, is_first_message):
|
||||
do_popup = True
|
||||
if first and helpers.allow_sound_notification('first_message_received',
|
||||
advanced_notif_num):
|
||||
if is_first_message and helpers.allow_sound_notification(
|
||||
'first_message_received', advanced_notif_num):
|
||||
do_sound = True
|
||||
elif not first and helpers.allow_sound_notification(
|
||||
elif not is_first_message and helpers.allow_sound_notification(
|
||||
'next_message_received', advanced_notif_num):
|
||||
do_sound = True
|
||||
else:
|
||||
print '*Event not implemeted yet*'
|
||||
|
||||
if advanced_notif_num != None and gajim.config.get_per('notifications',
|
||||
if advanced_notif_num is not None and gajim.config.get_per('notifications',
|
||||
str(advanced_notif_num), 'run_command'):
|
||||
do_cmd = True
|
||||
|
||||
# Do the wanted notifications
|
||||
if (do_popup):
|
||||
if (event == 'contact_connected' or event == 'contact_disconnected' or \
|
||||
event == 'status_change'): # Common code for popup for these 3 events
|
||||
if (event == 'contact_disconnected'):
|
||||
if do_popup:
|
||||
if event in ('contact_connected', 'contact_disconnected',
|
||||
'status_change'): # Common code for popup for these three events
|
||||
if event == 'contact_disconnected':
|
||||
show_image = 'offline.png'
|
||||
suffix = '_notif_size_bw.png'
|
||||
else: #Status Change or Connected
|
||||
# TODO : for status change, we don't always 'online.png', but we
|
||||
# FIXME: for status change,
|
||||
# we don't always 'online.png', but we
|
||||
# first need 48x48 for all status
|
||||
show_image = 'online.png'
|
||||
suffix = '_notif_size_colored.png'
|
||||
|
@ -186,7 +192,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
iconset, '48x48', show_image)
|
||||
path = gtkgui_helpers.get_path_to_generic_or_avatar(img,
|
||||
jid = jid, suffix = suffix)
|
||||
if (event == 'status_change'):
|
||||
if event == 'status_change':
|
||||
title = _('%(nick)s Changed Status') % \
|
||||
{'nick': gajim.get_name_from_jid(account, jid)}
|
||||
text = _('%(nick)s is now %(status)s') % \
|
||||
|
@ -196,7 +202,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
text = text + " : " + status_message
|
||||
popup(_('Contact Changed Status'), jid, account,
|
||||
path_to_image = path, title = title, text = text)
|
||||
elif (event == 'contact_connected'):
|
||||
elif event == 'contact_connected':
|
||||
title = _('%(nickname)s Signed In') % \
|
||||
{'nickname': gajim.get_name_from_jid(account, jid)}
|
||||
text = ''
|
||||
|
@ -204,7 +210,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
text = status_message
|
||||
popup(_('Contact Signed In'), jid, account,
|
||||
path_to_image = path, title = title, text = text)
|
||||
elif (event == 'contact_disconnected'):
|
||||
elif event == 'contact_disconnected':
|
||||
title = _('%(nickname)s Signed Out') % \
|
||||
{'nickname': gajim.get_name_from_jid(account, jid)}
|
||||
text = ''
|
||||
|
@ -212,7 +218,7 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
text = status_message
|
||||
popup(_('Contact Signed Out'), jid, account,
|
||||
path_to_image = path, title = title, text = text)
|
||||
elif (event == 'new_message'):
|
||||
elif event == 'new_message':
|
||||
if message_type == 'normal': # single message
|
||||
event_type = _('New Single Message')
|
||||
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
|
@ -222,10 +228,10 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
text = message
|
||||
elif message_type == 'pm': # private message
|
||||
event_type = _('New Private Message')
|
||||
room_name, t = gajim.get_room_name_and_server_from_room_jid(jid)
|
||||
room_name = gajim.get_nick_from_jid(jid)
|
||||
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
'priv_msg_recv.png')
|
||||
title = _('New Private Message from room %s') % room_name
|
||||
title = _('New Private Message from group chat %s') % room_name
|
||||
text = _('%(nickname)s: %(message)s') % {'nickname': nickname,
|
||||
'message': message}
|
||||
else: # chat message
|
||||
|
@ -239,18 +245,18 @@ def notify(event, jid, account, parameters, advanced_notif_num = None):
|
|||
popup(event_type, jid, account, message_type,
|
||||
path_to_image = path, title = title, text = text)
|
||||
|
||||
if (do_sound):
|
||||
if do_sound:
|
||||
snd_file = None
|
||||
snd_event = None # If not snd_file, play the event
|
||||
if (event == 'new_message'):
|
||||
if advanced_notif_num != None and gajim.config.get_per('notifications',
|
||||
str(advanced_notif_num), 'sound') == 'yes':
|
||||
if event == 'new_message':
|
||||
if advanced_notif_num is not None and gajim.config.get_per(
|
||||
'notifications', str(advanced_notif_num), 'sound') == 'yes':
|
||||
snd_file = gajim.config.get_per('notifications',
|
||||
str(advanced_notif_num), 'sound_file')
|
||||
elif advanced_notif_num != None and gajim.config.get_per(
|
||||
elif advanced_notif_num is not None and gajim.config.get_per(
|
||||
'notifications', str(advanced_notif_num), 'sound') == 'no':
|
||||
pass # do not set snd_event
|
||||
elif first:
|
||||
elif is_first_message:
|
||||
snd_event = 'first_message_received'
|
||||
else:
|
||||
snd_event = 'next_message_received'
|
||||
|
@ -276,20 +282,61 @@ def popup(event_type, jid, account, msg_type = '', path_to_image = None,
|
|||
the older style PopupNotificationWindow method.'''
|
||||
text = gtkgui_helpers.escape_for_pango_markup(text)
|
||||
title = gtkgui_helpers.escape_for_pango_markup(title)
|
||||
|
||||
if gajim.config.get('use_notif_daemon') and dbus_support.supported:
|
||||
try:
|
||||
DesktopNotification(event_type, jid, account, msg_type, path_to_image,
|
||||
title, text)
|
||||
return
|
||||
DesktopNotification(event_type, jid, account, msg_type,
|
||||
path_to_image, title, text)
|
||||
return # sucessfully did D-Bus Notification procedure!
|
||||
except dbus.dbus_bindings.DBusException, e:
|
||||
# Connection to D-Bus failed, try popup
|
||||
# Connection to D-Bus failed
|
||||
gajim.log.debug(str(e))
|
||||
except TypeError, e:
|
||||
# This means that we sent the message incorrectly
|
||||
gajim.log.debug(str(e))
|
||||
instance = dialogs.PopupNotificationWindow(event_type, jid, account, msg_type, \
|
||||
path_to_image, title, text)
|
||||
gajim.interface.roster.popup_notification_windows.append(instance)
|
||||
# we failed to speak to notification daemon via D-Bus
|
||||
if USER_HAS_PYNOTIFY: # try via libnotify
|
||||
if not text:
|
||||
text = gajim.get_name_from_jid(account, jid) # default value of text
|
||||
if not title:
|
||||
title = event_type
|
||||
# default image
|
||||
if not path_to_image:
|
||||
path_to_image = os.path.abspath(
|
||||
os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
|
||||
'chat_msg_recv.png')) # img to display
|
||||
|
||||
|
||||
notification = pynotify.Notification(title, text)
|
||||
timeout = gajim.config.get('notification_timeout') * 1000 # make it ms
|
||||
notification.set_timeout(timeout)
|
||||
|
||||
notification.set_category(event_type)
|
||||
notification.set_data('event_type', event_type)
|
||||
notification.set_data('jid', jid)
|
||||
notification.set_data('account', account)
|
||||
notification.set_data('msg_type', event_type)
|
||||
notification.set_data('path_to_image', path_to_image)
|
||||
notification.add_action('default', 'Default Action',
|
||||
on_pynotify_notification_clicked)
|
||||
|
||||
notification.show()
|
||||
|
||||
else: # go old style
|
||||
instance = dialogs.PopupNotificationWindow(event_type, jid,
|
||||
account, msg_type, path_to_image, title, text)
|
||||
gajim.interface.roster.popup_notification_windows.append(
|
||||
instance)
|
||||
|
||||
def on_pynotify_notification_clicked(notification, action):
|
||||
event_type = notification.get_data('event_type')
|
||||
jid = notification.get_data('jid')
|
||||
account = notification.get_data('account')
|
||||
msg_type = notification.get_data('msg_type')
|
||||
path_to_image = notification.get_data('path_to_image')
|
||||
|
||||
notification.close()
|
||||
gajim.interface.handle_event(account, jid, msg_type)
|
||||
|
||||
class NotificationResponseManager:
|
||||
'''Collects references to pending DesktopNotifications and manages there
|
||||
|
@ -342,7 +389,7 @@ class NotificationResponseManager:
|
|||
notification_response_manager = NotificationResponseManager()
|
||||
|
||||
class DesktopNotification:
|
||||
'''A DesktopNotification that interfaces with DBus via the Desktop
|
||||
'''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):
|
||||
|
|
|
@ -13,18 +13,17 @@
|
|||
## GNU General Public License for more details.
|
||||
##
|
||||
|
||||
# THIS FILE IS FOR **OUR** PROFILE (when we edit our INFO)
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
import base64
|
||||
import mimetypes
|
||||
import os
|
||||
import time
|
||||
import locale
|
||||
|
||||
import gtkgui_helpers
|
||||
import dialogs
|
||||
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
from common.i18n import Q_
|
||||
|
||||
|
@ -60,17 +59,40 @@ class ProfileWindow:
|
|||
def __init__(self, account):
|
||||
self.xml = gtkgui_helpers.get_glade('profile_window.glade')
|
||||
self.window = self.xml.get_widget('profile_window')
|
||||
self.progressbar = self.xml.get_widget('progressbar')
|
||||
self.statusbar = self.xml.get_widget('statusbar')
|
||||
self.context_id = self.statusbar.get_context_id('profile')
|
||||
|
||||
self.account = account
|
||||
self.jid = gajim.get_jid_from_account(account)
|
||||
|
||||
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 = gobject.timeout_add(100,
|
||||
self.update_progressbar)
|
||||
self.remove_statusbar_timeout_id = None
|
||||
|
||||
# Create Image for avatar button
|
||||
image = gtk.Image()
|
||||
self.xml.get_widget('PHOTO_button').set_image(image)
|
||||
self.xml.signal_autoconnect(self)
|
||||
self.window.show_all()
|
||||
|
||||
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:
|
||||
gobject.source_remove(self.update_progressbar_timeout_id)
|
||||
if self.remove_statusbar_timeout_id is not None:
|
||||
gobject.source_remove(self.remove_statusbar_timeout_id)
|
||||
del gajim.interface.instances[self.account]['profile']
|
||||
|
||||
def on_profile_window_key_press_event(self, widget, event):
|
||||
|
@ -79,8 +101,10 @@ class ProfileWindow:
|
|||
|
||||
def on_clear_button_clicked(self, widget):
|
||||
# empty the image
|
||||
self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
|
||||
gtk.ICON_SIZE_DIALOG)
|
||||
button = self.xml.get_widget('PHOTO_button')
|
||||
image = button.get_image()
|
||||
image.set_from_pixbuf(None)
|
||||
button.set_label(_('Click to set your avatar'))
|
||||
self.avatar_encoded = None
|
||||
self.avatar_mime_type = None
|
||||
|
||||
|
@ -124,24 +148,36 @@ class ProfileWindow:
|
|||
pixbuf = gtkgui_helpers.get_pixbuf_from_data(data)
|
||||
# rescale it
|
||||
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
|
||||
image = self.xml.get_widget('PHOTO_image')
|
||||
button = self.xml.get_widget('PHOTO_button')
|
||||
image = button.get_image()
|
||||
image.set_from_pixbuf(pixbuf)
|
||||
button.set_label('')
|
||||
self.avatar_encoded = base64.encodestring(data)
|
||||
# returns None if unknown type
|
||||
self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0]
|
||||
|
||||
self.dialog = dialogs.ImageChooserDialog(on_response_ok = on_ok)
|
||||
def on_clear(widget):
|
||||
self.dialog.destroy()
|
||||
self.on_clear_button_clicked(widget)
|
||||
|
||||
self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok,
|
||||
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()
|
||||
nick = gajim.config.get_per('accounts', self.account, 'name')
|
||||
menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.jid, None, nick + '.jpeg')
|
||||
menu.append(menuitem)
|
||||
|
||||
# Try to get pixbuf
|
||||
pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(self.jid)
|
||||
|
||||
if pixbuf:
|
||||
nick = gajim.config.get_per('accounts', self.account, 'name')
|
||||
menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.jid, None, nick + '.jpeg')
|
||||
menu.append(menuitem)
|
||||
# show clear
|
||||
menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
|
||||
menuitem.connect('activate', self.on_clear_button_clicked)
|
||||
|
@ -162,18 +198,23 @@ class ProfileWindow:
|
|||
def set_values(self, vcard):
|
||||
if not 'PHOTO' in vcard:
|
||||
# set default image
|
||||
image = self.xml.get_widget('PHOTO_image')
|
||||
image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
|
||||
button = self.xml.get_widget('PHOTO_button')
|
||||
image = button.get_image()
|
||||
image.set_from_pixbuf(None)
|
||||
button.set_label(_('Click to set your avatar'))
|
||||
for i in vcard.keys():
|
||||
if i == 'PHOTO':
|
||||
pixbuf, self.avatar_encoded, self.avatar_mime_type = \
|
||||
get_avatar_pixbuf_encoded_mime(vcard[i])
|
||||
image = self.xml.get_widget('PHOTO_image')
|
||||
button = self.xml.get_widget('PHOTO_button')
|
||||
image = button.get_image()
|
||||
if not pixbuf:
|
||||
image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
|
||||
image.set_from_pixbuf(None)
|
||||
button.set_label(_('Click to set your avatar'))
|
||||
continue
|
||||
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
|
||||
image.set_from_pixbuf(pixbuf)
|
||||
button.set_label('')
|
||||
continue
|
||||
if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
|
||||
for entry in vcard[i]:
|
||||
|
@ -191,6 +232,18 @@ class ProfileWindow:
|
|||
vcard[i], 0)
|
||||
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 = gobject.timeout_add(3000,
|
||||
self.remove_statusbar, self.message_id)
|
||||
gobject.source_remove(self.update_progressbar_timeout_id)
|
||||
# redraw progressbar after avatar is set so that windows is already
|
||||
# resized. Else progressbar is not correctly redrawn
|
||||
gobject.idle_add(self.progressbar.set_fraction, 0)
|
||||
self.update_progressbar_timeout_id = None
|
||||
|
||||
def add_to_vcard(self, vcard, entry, txt):
|
||||
'''Add an information to the vCard dictionary'''
|
||||
|
@ -248,6 +301,9 @@ class ProfileWindow:
|
|||
return vcard
|
||||
|
||||
def on_publish_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 '
|
||||
|
@ -261,8 +317,42 @@ class ProfileWindow:
|
|||
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.update_progressbar_timeout_id = gobject.timeout_add(100,
|
||||
self.update_progressbar)
|
||||
|
||||
def vcard_published(self):
|
||||
if self.message_id:
|
||||
self.statusbar.remove(self.context_id, self.message_id)
|
||||
self.message_id = self.statusbar.push(self.context_id,
|
||||
_('Information published'))
|
||||
self.remove_statusbar_timeout_id = gobject.timeout_add(3000,
|
||||
self.remove_statusbar, self.message_id)
|
||||
if self.update_progressbar_timeout_id is not None:
|
||||
gobject.source_remove(self.update_progressbar_timeout_id)
|
||||
self.progressbar.set_fraction(0)
|
||||
self.update_progressbar_timeout_id = None
|
||||
|
||||
def vcard_not_published(self):
|
||||
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 = gobject.timeout_add(3000,
|
||||
self.remove_statusbar, self.message_id)
|
||||
if self.update_progressbar_timeout_id is not None:
|
||||
gobject.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.'))
|
||||
|
||||
def on_retrieve_button_clicked(self, widget):
|
||||
if self.update_progressbar_timeout_id:
|
||||
# Operation in progress
|
||||
return
|
||||
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',
|
||||
|
@ -275,9 +365,18 @@ class ProfileWindow:
|
|||
for e in entries:
|
||||
self.xml.get_widget(e + '_entry').set_text('')
|
||||
self.xml.get_widget('DESC_textview').get_buffer().set_text('')
|
||||
self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person',
|
||||
gtk.ICON_SIZE_DIALOG)
|
||||
button = self.xml.get_widget('PHOTO_button')
|
||||
image = button.get_image()
|
||||
image.set_from_pixbuf(None)
|
||||
button.set_label(_('Click to set your avatar'))
|
||||
gajim.connections[self.account].request_vcard(self.jid)
|
||||
else:
|
||||
dialogs.ErrorDialog(_('You are not connected to the server'),
|
||||
_('Without a connection, you can not get your contact information.'))
|
||||
_('Without a connection, you can not get your contact information.'))
|
||||
self.message_id = self.statusbar.push(self.context_id,
|
||||
_('Retrieving profile...'))
|
||||
self.update_progressbar_timeout_id = gobject.timeout_add(100,
|
||||
self.update_progressbar)
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
|
|
@ -1,19 +1,9 @@
|
|||
## remote_control.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
## - Andrew Sayman <lorien420@myrealbox.com>
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
|
||||
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -33,60 +23,33 @@ from common import helpers
|
|||
from time import time
|
||||
from dialogs import AddNewContactWindow, NewChatDialog
|
||||
|
||||
import dbus_support
|
||||
from common import dbus_support
|
||||
if dbus_support.supported:
|
||||
import dbus
|
||||
if dbus_support.version >= (0, 41, 0):
|
||||
if dbus_support:
|
||||
import dbus.service
|
||||
import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it
|
||||
DbusPrototype = dbus.service.Object
|
||||
elif dbus_support.version >= (0, 20, 0):
|
||||
DbusPrototype = dbus.Object
|
||||
else: #dbus is not defined
|
||||
DbusPrototype = str
|
||||
import dbus.glib
|
||||
|
||||
INTERFACE = 'org.gajim.dbus.RemoteInterface'
|
||||
OBJ_PATH = '/org/gajim/dbus/RemoteObject'
|
||||
SERVICE = 'org.gajim.dbus'
|
||||
|
||||
# type mapping, it is different in each version
|
||||
ident = lambda e: e
|
||||
if dbus_support.version[1] >= 43:
|
||||
# in most cases it is a utf-8 string
|
||||
DBUS_STRING = dbus.String
|
||||
# type mapping
|
||||
|
||||
# general type (for use in dicts,
|
||||
# where all values should have the same type)
|
||||
DBUS_VARIANT = dbus.Variant
|
||||
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
|
||||
DBUS_NONE = lambda : dbus.Variant(0)
|
||||
# in most cases it is a utf-8 string
|
||||
DBUS_STRING = dbus.String
|
||||
|
||||
else: # 33, 35, 36
|
||||
DBUS_DICT_SV = lambda : {}
|
||||
DBUS_DICT_SS = lambda : {}
|
||||
DBUS_STRING = lambda e: unicode(e).encode('utf-8')
|
||||
# this is the only way to return lists and dicts of mixed types
|
||||
DBUS_VARIANT = lambda e: (isinstance(e, (str, unicode)) and \
|
||||
DBUS_STRING(e)) or repr(e)
|
||||
DBUS_NONE = lambda : ''
|
||||
if dbus_support.version[1] >= 41: # 35, 36
|
||||
DBUS_BOOLEAN = dbus.Boolean
|
||||
DBUS_DOUBLE = dbus.Double
|
||||
DBUS_INT32 = dbus.Int32
|
||||
else: # 33
|
||||
DBUS_BOOLEAN = ident
|
||||
DBUS_INT32 = ident
|
||||
DBUS_DOUBLE = ident
|
||||
|
||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible']
|
||||
# general type (for use in dicts, where all values should have the same type)
|
||||
DBUS_VARIANT = dbus.Variant
|
||||
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
|
||||
DBUS_NONE = lambda : dbus.Variant(0)
|
||||
|
||||
def get_dbus_struct(obj):
|
||||
''' recursively go through all the items and replace
|
||||
|
@ -123,65 +86,35 @@ class Remote:
|
|||
self.signal_object = None
|
||||
session_bus = dbus_support.session_bus.SessionBus()
|
||||
|
||||
if dbus_support.version[1] >= 41:
|
||||
service = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||
self.signal_object = SignalObject(service)
|
||||
elif dbus_support.version[1] <= 40 and dbus_support.version[1] >= 20:
|
||||
service=dbus.Service(SERVICE, session_bus)
|
||||
self.signal_object = SignalObject(service)
|
||||
service = dbus.service.BusName(SERVICE, bus=session_bus)
|
||||
self.signal_object = SignalObject(service)
|
||||
|
||||
def raise_signal(self, signal, arg):
|
||||
if self.signal_object:
|
||||
self.signal_object.raise_signal(signal,
|
||||
get_dbus_struct(arg))
|
||||
get_dbus_struct(arg))
|
||||
|
||||
|
||||
class SignalObject(DbusPrototype):
|
||||
''' Local object definition for /org/gajim/dbus/RemoteObject. This doc must
|
||||
not be visible, because the clients can access only the remote object. '''
|
||||
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, service):
|
||||
self.first_show = True
|
||||
self.vcard_account = None
|
||||
|
||||
# register our dbus API
|
||||
if dbus_support.version[1] >= 41:
|
||||
DbusPrototype.__init__(self, service, OBJ_PATH)
|
||||
elif dbus_support.version[1] >= 30:
|
||||
DbusPrototype.__init__(self, OBJ_PATH, service)
|
||||
else:
|
||||
DbusPrototype.__init__(self, OBJ_PATH, service,
|
||||
[ self.toggle_roster_appearance,
|
||||
self.show_next_unread,
|
||||
self.list_contacts,
|
||||
self.list_accounts,
|
||||
self.account_info,
|
||||
self.change_status,
|
||||
self.open_chat,
|
||||
self.send_message,
|
||||
self.send_single_message,
|
||||
self.contact_info,
|
||||
self.send_file,
|
||||
self.prefs_list,
|
||||
self.prefs_store,
|
||||
self.prefs_del,
|
||||
self.prefs_put,
|
||||
self.add_contact,
|
||||
self.remove_contact,
|
||||
self.get_status,
|
||||
self.get_status_message,
|
||||
self.start_chat,
|
||||
self.send_xml,
|
||||
])
|
||||
dbus.service.Object.__init__(self, service, OBJ_PATH)
|
||||
|
||||
def raise_signal(self, signal, arg):
|
||||
''' raise a signal, with a single string message '''
|
||||
'''raise a signal, with a single string message'''
|
||||
from dbus import dbus_bindings
|
||||
message = dbus_bindings.Signal(OBJ_PATH, INTERFACE, signal)
|
||||
i = message.get_iter(True)
|
||||
i.append(arg)
|
||||
self._connection.send(message)
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def get_status(self, *args):
|
||||
'''get_status(account = None)
|
||||
returns status (show to be exact) which is the global one
|
||||
|
@ -193,8 +126,9 @@ class SignalObject(DbusPrototype):
|
|||
return helpers.get_global_show()
|
||||
# return show for the given account
|
||||
index = gajim.connections[account].connected
|
||||
return DBUS_STRING(STATUS_LIST[index])
|
||||
return DBUS_STRING(gajim.SHOW_LIST[index])
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def get_status_message(self, *args):
|
||||
'''get_status(account = None)
|
||||
returns status which is the global one
|
||||
|
@ -208,7 +142,7 @@ class SignalObject(DbusPrototype):
|
|||
status = gajim.connections[account].status
|
||||
return DBUS_STRING(status)
|
||||
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def get_account_and_contact(self, account, jid):
|
||||
''' get the account (if not given) and contact instance from jid'''
|
||||
connected_account = None
|
||||
|
@ -236,6 +170,7 @@ class SignalObject(DbusPrototype):
|
|||
|
||||
return connected_account, contact
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def send_file(self, *args):
|
||||
'''send_file(file_path, jid, account=None)
|
||||
send file, located at 'file_path' to 'jid', using account
|
||||
|
@ -254,7 +189,7 @@ class SignalObject(DbusPrototype):
|
|||
return False
|
||||
|
||||
def _send_message(self, jid, message, keyID, account, type = 'chat', subject = None):
|
||||
''' can be called from send_chat_message (default when send_message)
|
||||
'''can be called from send_chat_message (default when send_message)
|
||||
or send_single_message'''
|
||||
if not jid or not message:
|
||||
return None # or raise error
|
||||
|
@ -269,28 +204,31 @@ class SignalObject(DbusPrototype):
|
|||
return True
|
||||
return False
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def send_chat_message(self, *args):
|
||||
''' send_message(jid, message, keyID=None, account=None)
|
||||
'''send_message(jid, message, keyID=None, account=None)
|
||||
send chat 'message' to 'jid', using account (optional) 'account'.
|
||||
if keyID is specified, encrypt the message with the pgp key '''
|
||||
jid, message, keyID, account = self._get_real_arguments(args, 4)
|
||||
jid = self._get_real_jid(jid, account)
|
||||
return self._send_message(jid, message, keyID, account)
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def send_single_message(self, *args):
|
||||
''' send_single_message(jid, subject, message, keyID=None, account=None)
|
||||
'''send_single_message(jid, subject, message, keyID=None, account=None)
|
||||
send single 'message' to 'jid', using account (optional) 'account'.
|
||||
if keyID is specified, encrypt the message with the pgp key '''
|
||||
jid, subject, message, keyID, account = self._get_real_arguments(args, 5)
|
||||
jid = self._get_real_jid(jid, account)
|
||||
return self._send_message(jid, message, keyID, account, type, subject)
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def open_chat(self, *args):
|
||||
''' start_chat(jid, account=None) -> shows the tabbed window for new
|
||||
message to 'jid', using account(optional) 'account' '''
|
||||
jid, account = self._get_real_arguments(args, 2)
|
||||
if not jid:
|
||||
# FIXME: raise exception for missing argument (dbus0.35+)
|
||||
raise MissingArgument
|
||||
return None
|
||||
jid = self._get_real_jid(jid, account)
|
||||
|
||||
|
@ -332,13 +270,14 @@ class SignalObject(DbusPrototype):
|
|||
return True
|
||||
return False
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def change_status(self, *args, **keywords):
|
||||
''' change_status(status, message, account). account is optional -
|
||||
if not specified status is changed for all accounts. '''
|
||||
status, message, account = self._get_real_arguments(args, 3)
|
||||
if status not in ('offline', 'online', 'chat',
|
||||
'away', 'xa', 'dnd', 'invisible'):
|
||||
# FIXME: raise exception for bad status (dbus0.35)
|
||||
raise InvalidArgument
|
||||
return None
|
||||
if account:
|
||||
gobject.idle_add(gajim.interface.roster.send_status, account,
|
||||
|
@ -346,25 +285,29 @@ class SignalObject(DbusPrototype):
|
|||
else:
|
||||
# account not specified, so change the status of all accounts
|
||||
for acc in gajim.contacts.get_accounts():
|
||||
if not gajim.config.get_per('accounts', acc, 'sync_with_global_status'):
|
||||
continue
|
||||
gobject.idle_add(gajim.interface.roster.send_status, acc,
|
||||
status, message)
|
||||
return None
|
||||
|
||||
def show_next_unread(self, *args):
|
||||
''' Show the window(s) with next waiting messages in tabbed/group chats. '''
|
||||
@dbus.service.method(INTERFACE)
|
||||
def show_next_pending_event(self, *args):
|
||||
'''Show the window(s) with next pending event in tabbed/group chats.'''
|
||||
if gajim.events.get_nb_events():
|
||||
gajim.interface.systray.handle_first_event()
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def contact_info(self, *args):
|
||||
''' get vcard info for a contact. Return cached value of the vcard.
|
||||
'''get vcard info for a contact. Return cached value of the vcard.
|
||||
'''
|
||||
[jid] = self._get_real_arguments(args, 1)
|
||||
if not isinstance(jid, unicode):
|
||||
jid = unicode(jid)
|
||||
if not jid:
|
||||
# FIXME: raise exception for missing argument (0.3+)
|
||||
raise MissingArgument
|
||||
return None
|
||||
jid = self._get_real_jid(jid, account)
|
||||
jid = self._get_real_jid(jid)
|
||||
|
||||
cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
|
||||
if cached_vcard:
|
||||
|
@ -373,8 +316,9 @@ class SignalObject(DbusPrototype):
|
|||
# return empty dict
|
||||
return DBUS_DICT_SV()
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def list_accounts(self, *args):
|
||||
''' list register accounts '''
|
||||
'''list register accounts'''
|
||||
result = gajim.contacts.get_accounts()
|
||||
if result and len(result) > 0:
|
||||
result_array = []
|
||||
|
@ -383,8 +327,9 @@ class SignalObject(DbusPrototype):
|
|||
return result_array
|
||||
return None
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def account_info(self, *args):
|
||||
''' show info on account: resource, jid, nick, prio, message '''
|
||||
'''show info on account: resource, jid, nick, prio, message'''
|
||||
[for_account] = self._get_real_arguments(args, 1)
|
||||
if not gajim.connections.has_key(for_account):
|
||||
# account is invalid
|
||||
|
@ -392,19 +337,19 @@ class SignalObject(DbusPrototype):
|
|||
account = gajim.connections[for_account]
|
||||
result = DBUS_DICT_SS()
|
||||
index = account.connected
|
||||
result['status'] = DBUS_STRING(STATUS_LIST[index])
|
||||
result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
|
||||
result['name'] = DBUS_STRING(account.name)
|
||||
result['jid'] = DBUS_STRING(gajim.get_jid_from_account(account.name))
|
||||
result['message'] = DBUS_STRING(account.status)
|
||||
result['priority'] = DBUS_STRING(unicode(gajim.config.get_per('accounts',
|
||||
account.name, 'priority')))
|
||||
result['priority'] = DBUS_STRING(unicode(account.priority))
|
||||
result['resource'] = DBUS_STRING(unicode(gajim.config.get_per('accounts',
|
||||
account.name, 'resource')))
|
||||
account.name, 'resource')))
|
||||
return result
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def list_contacts(self, *args):
|
||||
''' list all contacts in the roster. If the first argument is specified,
|
||||
then return the contacts for the specified account '''
|
||||
'''list all contacts in the roster. If the first argument is specified,
|
||||
then return the contacts for the specified account'''
|
||||
[for_account] = self._get_real_arguments(args, 1)
|
||||
result = []
|
||||
accounts = gajim.contacts.get_accounts()
|
||||
|
@ -426,6 +371,7 @@ class SignalObject(DbusPrototype):
|
|||
return None
|
||||
return result
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def toggle_roster_appearance(self, *args):
|
||||
''' shows/hides the roster window '''
|
||||
win = gajim.interface.roster.window
|
||||
|
@ -439,6 +385,7 @@ class SignalObject(DbusPrototype):
|
|||
else:
|
||||
win.window.focus(long(time()))
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def prefs_list(self, *args):
|
||||
prefs_dict = DBUS_DICT_SS()
|
||||
def get_prefs(data, name, path, value):
|
||||
|
@ -453,6 +400,7 @@ class SignalObject(DbusPrototype):
|
|||
gajim.config.foreach(get_prefs)
|
||||
return prefs_dict
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def prefs_store(self, *args):
|
||||
try:
|
||||
gajim.interface.save_config()
|
||||
|
@ -460,6 +408,7 @@ class SignalObject(DbusPrototype):
|
|||
return False
|
||||
return True
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def prefs_del(self, *args):
|
||||
[key] = self._get_real_arguments(args, 1)
|
||||
if not key:
|
||||
|
@ -473,6 +422,7 @@ class SignalObject(DbusPrototype):
|
|||
gajim.config.del_per(key_path[0], key_path[1], key_path[2])
|
||||
return True
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def prefs_put(self, *args):
|
||||
[key] = self._get_real_arguments(args, 1)
|
||||
if not key:
|
||||
|
@ -486,6 +436,7 @@ class SignalObject(DbusPrototype):
|
|||
gajim.config.set_per(key_path[0], key_path[1], subname, value)
|
||||
return True
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def add_contact(self, *args):
|
||||
[jid, account] = self._get_real_arguments(args, 2)
|
||||
if account:
|
||||
|
@ -501,6 +452,7 @@ class SignalObject(DbusPrototype):
|
|||
AddNewContactWindow(account = None, jid = jid)
|
||||
return True
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def remove_contact(self, *args):
|
||||
[jid, account] = self._get_real_arguments(args, 2)
|
||||
jid = self._get_real_jid(jid, account)
|
||||
|
@ -595,9 +547,11 @@ class SignalObject(DbusPrototype):
|
|||
contact_dict['resources'] = DBUS_VARIANT(contact_dict['resources'])
|
||||
return contact_dict
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def get_unread_msgs_number(self, *args):
|
||||
return str(gajim.events.get_nb_events)
|
||||
return str(gajim.events.get_nb_events())
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def start_chat(self, *args):
|
||||
[account] = self._get_real_arguments(args, 1)
|
||||
if not account:
|
||||
|
@ -606,6 +560,7 @@ class SignalObject(DbusPrototype):
|
|||
NewChatDialog(account)
|
||||
return True
|
||||
|
||||
@dbus.service.method(INTERFACE)
|
||||
def send_xml(self, *args):
|
||||
xml, account = self._get_real_arguments(args, 2)
|
||||
if account:
|
||||
|
@ -613,36 +568,3 @@ class SignalObject(DbusPrototype):
|
|||
else:
|
||||
for acc in gajim.contacts.get_accounts():
|
||||
gajim.connections[acc].send_stanza(xml)
|
||||
|
||||
if dbus_support.version[1] >= 30 and dbus_support.version[1] <= 40:
|
||||
method = dbus.method
|
||||
signal = dbus.signal
|
||||
elif dbus_support.version[1] >= 41:
|
||||
method = dbus.service.method
|
||||
signal = dbus.service.signal
|
||||
|
||||
# prevent using decorators, because they are not supported
|
||||
# on python < 2.4
|
||||
# FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD
|
||||
toggle_roster_appearance = method(INTERFACE)(toggle_roster_appearance)
|
||||
list_contacts = method(INTERFACE)(list_contacts)
|
||||
list_accounts = method(INTERFACE)(list_accounts)
|
||||
show_next_unread = method(INTERFACE)(show_next_unread)
|
||||
change_status = method(INTERFACE)(change_status)
|
||||
open_chat = method(INTERFACE)(open_chat)
|
||||
contact_info = method(INTERFACE)(contact_info)
|
||||
send_message = method(INTERFACE)(send_chat_message)
|
||||
send_single_message = method(INTERFACE)(send_single_message)
|
||||
send_file = method(INTERFACE)(send_file)
|
||||
prefs_list = method(INTERFACE)(prefs_list)
|
||||
prefs_put = method(INTERFACE)(prefs_put)
|
||||
prefs_del = method(INTERFACE)(prefs_del)
|
||||
prefs_store = method(INTERFACE)(prefs_store)
|
||||
remove_contact = method(INTERFACE)(remove_contact)
|
||||
add_contact = method(INTERFACE)(add_contact)
|
||||
get_status = method(INTERFACE)(get_status)
|
||||
get_status_message = method(INTERFACE)(get_status_message)
|
||||
account_info = method(INTERFACE)(account_info)
|
||||
get_unread_msgs_number = method(INTERFACE)(get_unread_msgs_number)
|
||||
start_chat = method(INTERFACE)(start_chat)
|
||||
send_xml = method(INTERFACE)(send_xml)
|
||||
|
|
1250
src/roster_window.py
1250
src/roster_window.py
File diff suppressed because it is too large
Load diff
68
src/statusicon.py
Normal file
68
src/statusicon.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
## statusicon.py
|
||||
##
|
||||
## Copyright (C) 2006 Nikos Kouremenos <kourem@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 2
|
||||
## of the License, or (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
|
||||
import gtk
|
||||
import systray
|
||||
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
|
||||
class StatusIcon(systray.Systray):
|
||||
'''Class for the notification area icon'''
|
||||
#FIXME: when we migrate to GTK 2.10 stick only to this class
|
||||
# (move base stuff from systray.py and rm it)
|
||||
#NOTE: gtk api does NOT allow:
|
||||
# leave, enter motion notify
|
||||
# and can't do cool tooltips we use
|
||||
# and we could use blinking instead of unsupported animation
|
||||
# or we could emulate animation by every foo ms chaning the image
|
||||
def __init__(self):
|
||||
systray.Systray.__init__(self)
|
||||
self.status_icon = gtk.StatusIcon()
|
||||
|
||||
def show_icon(self):
|
||||
self.status_icon.connect('activate', self.on_status_icon_left_clicked)
|
||||
self.status_icon.connect('popup-menu', self.on_status_icon_right_clicked)
|
||||
|
||||
self.set_img()
|
||||
self.status_icon.props.visible = True
|
||||
|
||||
def on_status_icon_right_clicked(self, widget, event_button, event_time):
|
||||
self.make_menu(event_button, event_time)
|
||||
|
||||
def hide_icon(self):
|
||||
self.status_icon.props.visible = False
|
||||
|
||||
def on_status_icon_left_clicked(self, widget):
|
||||
self.on_left_click()
|
||||
|
||||
def set_img(self):
|
||||
'''apart from image, we also update tooltip text here'''
|
||||
if not gajim.interface.systray_enabled:
|
||||
return
|
||||
text = helpers.get_notification_icon_tooltip_text()
|
||||
self.status_icon.set_tooltip(text)
|
||||
if gajim.events.get_nb_systray_events():
|
||||
state = 'message'
|
||||
else:
|
||||
state = self.status
|
||||
#FIXME: do not always use 16x16 (ask actually used size and use that)
|
||||
image = gajim.interface.roster.jabber_state_images['16'][state]
|
||||
if image.get_storage_type() == gtk.IMAGE_PIXBUF:
|
||||
self.status_icon.props.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.IMAGE_ANIMATION:
|
||||
# self.img_tray.set_from_animation(image.get_animation())
|
|
@ -43,7 +43,7 @@ except:
|
|||
|
||||
class Systray:
|
||||
'''Class for icon in the notification area
|
||||
This class is both base class (for systraywin32.py) and normal class
|
||||
This class is both base class (for statusicon.py) and normal class
|
||||
for trayicon in GNU/Linux'''
|
||||
|
||||
def __init__(self):
|
||||
|
@ -94,18 +94,17 @@ class Systray:
|
|||
def on_new_chat(self, widget, account):
|
||||
dialogs.NewChatDialog(account)
|
||||
|
||||
def make_menu(self, event = None):
|
||||
'''create chat with and new message (sub) menus/menuitems
|
||||
event is None when we're in Windows
|
||||
'''
|
||||
|
||||
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_widget('chat_with_menuitem')
|
||||
single_message_menuitem = self.xml.get_widget('single_message_menuitem')
|
||||
single_message_menuitem = self.xml.get_widget(
|
||||
'single_message_menuitem')
|
||||
status_menuitem = self.xml.get_widget('status_menu')
|
||||
join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
|
||||
sounds_mute_menuitem = self.xml.get_widget('sounds_mute_menuitem')
|
||||
|
||||
if self.single_message_handler_id:
|
||||
single_message_menuitem.handler_disconnect(
|
||||
|
@ -124,11 +123,12 @@ class Systray:
|
|||
|
||||
# We need our own set of status icons, let's make 'em!
|
||||
iconset = gajim.config.get('iconset')
|
||||
if not iconset:
|
||||
iconset = 'dcraven'
|
||||
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
|
||||
state_images = gajim.interface.roster.load_iconset(path)
|
||||
|
||||
if state_images.has_key('muc_active'):
|
||||
join_gc_menuitem.set_image(state_images['muc_active'])
|
||||
|
||||
for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||
uf_show = helpers.get_uf_show(show, use_mnemonic = True)
|
||||
item = gtk.ImageMenuItem(uf_show)
|
||||
|
@ -159,7 +159,8 @@ class Systray:
|
|||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_show_menuitem_activate, 'offline')
|
||||
|
||||
iskey = connected_accounts > 0
|
||||
iskey = connected_accounts > 0 and not (connected_accounts == 1 and
|
||||
gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
|
||||
chat_with_menuitem.set_sensitive(iskey)
|
||||
single_message_menuitem.set_sensitive(iskey)
|
||||
join_gc_menuitem.set_sensitive(iskey)
|
||||
|
@ -170,12 +171,15 @@ class Systray:
|
|||
self.popup_menus.append(account_menu_for_chat_with)
|
||||
|
||||
account_menu_for_single_message = gtk.Menu()
|
||||
single_message_menuitem.set_submenu(account_menu_for_single_message)
|
||||
single_message_menuitem.set_submenu(
|
||||
account_menu_for_single_message)
|
||||
self.popup_menus.append(account_menu_for_single_message)
|
||||
|
||||
accounts_list = gajim.contacts.get_accounts()
|
||||
accounts_list.sort()
|
||||
for account in accounts_list:
|
||||
if gajim.connections[account].is_zeroconf:
|
||||
continue
|
||||
if gajim.connections[account].connected > 1:
|
||||
#for chat_with
|
||||
item = gtk.MenuItem(_('using account %s') % account)
|
||||
|
@ -194,8 +198,11 @@ class Systray:
|
|||
label.set_use_underline(False)
|
||||
gc_item = gtk.MenuItem()
|
||||
gc_item.add(label)
|
||||
gc_item.connect('state-changed',
|
||||
gtkgui_helpers.on_bm_header_changed_state)
|
||||
gc_sub_menu.append(gc_item)
|
||||
gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account)
|
||||
gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
|
||||
account)
|
||||
|
||||
elif connected_accounts == 1: # one account
|
||||
# one account connected, no need to show 'as jid'
|
||||
|
@ -205,24 +212,25 @@ class Systray:
|
|||
'activate', self.on_new_chat, account)
|
||||
# for single message
|
||||
single_message_menuitem.remove_submenu()
|
||||
self.single_message_handler_id = single_message_menuitem.connect(
|
||||
'activate', self.on_single_message_menuitem_activate, account)
|
||||
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)
|
||||
gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
|
||||
account)
|
||||
break # No other connected account
|
||||
|
||||
if event is None:
|
||||
# None means windows (we explicitly popup in systraywin32.py)
|
||||
if self.added_hide_menuitem is False:
|
||||
self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
|
||||
item = gtk.MenuItem(_('Hide this menu'))
|
||||
self.systray_context_menu.prepend(item)
|
||||
self.added_hide_menuitem = True
|
||||
sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
|
||||
if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\
|
||||
gtk.gtk_version >= (2, 10, 0):
|
||||
self.systray_context_menu.popup(None, None,
|
||||
gtk.status_icon_position_menu, event_button,
|
||||
event_time, self.status_icon)
|
||||
|
||||
else: # GNU and Unices
|
||||
self.systray_context_menu.popup(None, None, None, event.button,
|
||||
event.time)
|
||||
self.systray_context_menu.popup(None, None, None, event_button,
|
||||
event_time)
|
||||
self.systray_context_menu.show_all()
|
||||
|
||||
def on_show_all_events_menuitem_activate(self, widget):
|
||||
|
@ -232,6 +240,10 @@ class Systray:
|
|||
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())
|
||||
gajim.interface.save_config()
|
||||
|
||||
def on_show_roster_menuitem_activate(self, widget):
|
||||
win = gajim.interface.roster.window
|
||||
win.present()
|
||||
|
@ -250,11 +262,11 @@ class Systray:
|
|||
if len(gajim.events.get_systray_events()) == 0:
|
||||
# no pending events, so toggle visible/hidden for roster window
|
||||
if win.get_property('visible'): # visible in ANY virtual desktop?
|
||||
win.hide() # we hide it from VD that was visible in
|
||||
|
||||
# but we could be in another VD right now. eg vd2
|
||||
# and we want not only to hide it in vd1 but also show it in vd2
|
||||
gtkgui_helpers.possibly_move_window_in_current_desktop(win)
|
||||
# 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):
|
||||
win.hide() # else we hide it from VD that was visible in
|
||||
else:
|
||||
win.present()
|
||||
else:
|
||||
|
@ -275,12 +287,14 @@ class Systray:
|
|||
|
||||
def on_clicked(self, widget, event):
|
||||
self.on_tray_leave_notify_event(widget, None)
|
||||
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: # Left click
|
||||
if event.type != gtk.gdk.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)
|
||||
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)
|
||||
|
@ -295,6 +309,7 @@ class Systray:
|
|||
active = gajim.interface.roster.status_combobox.get_active()
|
||||
status = model[active][2].decode('utf-8')
|
||||
dlg = dialogs.ChangeStatusMessageDialog(status)
|
||||
dlg.window.present()
|
||||
message = dlg.run()
|
||||
if message is not None: # None if user press Cancel
|
||||
accounts = gajim.connections.keys()
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
## src/systraywin32.py
|
||||
##
|
||||
## Contributors for this file:
|
||||
## - Yann Le Boulanger <asterix@lagaule.org>
|
||||
## - Nikos Kouremenos <kourem@gmail.com>
|
||||
## - Dimitur Kirov <dkirov@gmail.com>
|
||||
##
|
||||
## code initially based on
|
||||
## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779
|
||||
## with some ideas/help from pysystray.sf.net
|
||||
##
|
||||
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Vincent Hanquez <tab@snarc.org>
|
||||
## Nikos Kouremenos <nkour@jabber.org>
|
||||
## Dimitur Kirov <dkirov@gmail.com>
|
||||
## Travis Shirk <travis@pobox.com>
|
||||
## Norman Rasmussen <norman@rasmussen.co.za>
|
||||
##
|
||||
## 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; version 2 only.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
|
||||
|
||||
import win32gui
|
||||
import pywintypes
|
||||
import win32con # winapi constants
|
||||
import systray
|
||||
import gtk
|
||||
import os
|
||||
|
||||
WM_TASKBARCREATED = win32gui.RegisterWindowMessage('TaskbarCreated')
|
||||
WM_TRAYMESSAGE = win32con.WM_USER + 20
|
||||
|
||||
import gtkgui_helpers
|
||||
from common import gajim
|
||||
from common import i18n
|
||||
|
||||
class SystrayWINAPI:
|
||||
def __init__(self, gtk_window):
|
||||
self._window = gtk_window
|
||||
self._hwnd = gtk_window.window.handle
|
||||
self._message_map = {}
|
||||
|
||||
self.notify_icon = None
|
||||
|
||||
# Sublass the window and inject a WNDPROC to process messages.
|
||||
self._oldwndproc = win32gui.SetWindowLong(self._hwnd,
|
||||
win32con.GWL_WNDPROC, self._wndproc)
|
||||
|
||||
|
||||
def add_notify_icon(self, menu, hicon=None, tooltip=None):
|
||||
""" Creates a notify icon for the gtk window. """
|
||||
if not self.notify_icon:
|
||||
if not hicon:
|
||||
hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
|
||||
self.notify_icon = NotifyIcon(self._hwnd, hicon, tooltip)
|
||||
|
||||
# Makes redraw if the taskbar is restarted.
|
||||
self.message_map({WM_TASKBARCREATED: self.notify_icon._redraw})
|
||||
|
||||
|
||||
def message_map(self, msg_map={}):
|
||||
""" Maps message processing to callback functions ala win32gui. """
|
||||
if msg_map:
|
||||
if self._message_map:
|
||||
duplicatekeys = [key for key in msg_map.keys()
|
||||
if self._message_map.has_key(key)]
|
||||
|
||||
for key in duplicatekeys:
|
||||
new_value = msg_map[key]
|
||||
|
||||
if isinstance(new_value, list):
|
||||
raise TypeError('Dict cannot have list values')
|
||||
|
||||
value = self._message_map[key]
|
||||
|
||||
if new_value != value:
|
||||
new_value = [new_value]
|
||||
|
||||
if isinstance(value, list):
|
||||
value += new_value
|
||||
else:
|
||||
value = [value] + new_value
|
||||
|
||||
msg_map[key] = value
|
||||
self._message_map.update(msg_map)
|
||||
|
||||
def remove_notify_icon(self):
|
||||
""" Removes the notify icon. """
|
||||
if self.notify_icon:
|
||||
self.notify_icon.remove()
|
||||
self.notify_icon = None
|
||||
|
||||
def remove(self, *args):
|
||||
""" Unloads the extensions. """
|
||||
self._message_map = {}
|
||||
self.remove_notify_icon()
|
||||
self = None
|
||||
|
||||
def show_balloon_tooltip(self, title, text, timeout=10,
|
||||
icon=win32gui.NIIF_NONE):
|
||||
""" Shows a baloon tooltip. """
|
||||
if not self.notify_icon:
|
||||
self.add_notifyicon()
|
||||
self.notify_icon.show_balloon(title, text, timeout, icon)
|
||||
|
||||
def _wndproc(self, hwnd, msg, wparam, lparam):
|
||||
""" A WINDPROC to process window messages. """
|
||||
if self._message_map.has_key(msg):
|
||||
callback = self._message_map[msg]
|
||||
if isinstance(callback, list):
|
||||
for cb in callback:
|
||||
cb(hwnd, msg, wparam, lparam)
|
||||
else:
|
||||
callback(hwnd, msg, wparam, lparam)
|
||||
|
||||
return win32gui.CallWindowProc(self._oldwndproc, hwnd, msg, wparam,
|
||||
lparam)
|
||||
|
||||
|
||||
class NotifyIcon:
|
||||
|
||||
def __init__(self, hwnd, hicon, tooltip=None):
|
||||
self._hwnd = hwnd
|
||||
self._id = 0
|
||||
self._flags = win32gui.NIF_MESSAGE | win32gui.NIF_ICON
|
||||
self._callbackmessage = WM_TRAYMESSAGE
|
||||
self._hicon = hicon
|
||||
|
||||
try:
|
||||
win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
|
||||
except pywintypes.error:
|
||||
pass
|
||||
if tooltip: self.set_tooltip(tooltip)
|
||||
|
||||
|
||||
def _get_nid(self):
|
||||
""" Function to initialise & retrieve the NOTIFYICONDATA Structure. """
|
||||
nid = [self._hwnd, self._id, self._flags, self._callbackmessage, self._hicon]
|
||||
|
||||
if not hasattr(self, '_tip'): self._tip = ''
|
||||
nid.append(self._tip)
|
||||
|
||||
if not hasattr(self, '_info'): self._info = ''
|
||||
nid.append(self._info)
|
||||
|
||||
if not hasattr(self, '_timeout'): self._timeout = 0
|
||||
nid.append(self._timeout)
|
||||
|
||||
if not hasattr(self, '_infotitle'): self._infotitle = ''
|
||||
nid.append(self._infotitle)
|
||||
|
||||
if not hasattr(self, '_infoflags'):self._infoflags = win32gui.NIIF_NONE
|
||||
nid.append(self._infoflags)
|
||||
|
||||
return tuple(nid)
|
||||
|
||||
def remove(self):
|
||||
""" Removes the tray icon. """
|
||||
try:
|
||||
win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._get_nid())
|
||||
except pywintypes.error:
|
||||
pass
|
||||
|
||||
|
||||
def set_tooltip(self, tooltip):
|
||||
""" Sets the tray icon tooltip. """
|
||||
self._flags = self._flags | win32gui.NIF_TIP
|
||||
self._tip = tooltip
|
||||
try:
|
||||
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
|
||||
except pywintypes.error:
|
||||
pass
|
||||
|
||||
|
||||
def show_balloon(self, title, text, timeout=10, icon=win32gui.NIIF_NONE):
|
||||
""" Shows a balloon tooltip from the tray icon. """
|
||||
self._flags = self._flags | win32gui.NIF_INFO
|
||||
self._infotitle = title
|
||||
self._info = text
|
||||
self._timeout = timeout * 1000
|
||||
self._infoflags = icon
|
||||
try:
|
||||
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
|
||||
except pywintypes.error:
|
||||
pass
|
||||
|
||||
def _redraw(self, *args):
|
||||
""" Redraws the tray icon. """
|
||||
self.remove()
|
||||
try:
|
||||
win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
|
||||
except pywintypes.error:
|
||||
pass
|
||||
|
||||
|
||||
class SystrayWin32(systray.Systray):
|
||||
def __init__(self):
|
||||
# Note: gtk window must be realized before installing extensions.
|
||||
systray.Systray.__init__(self)
|
||||
self.jids = []
|
||||
self.status = 'offline'
|
||||
self.xml = gtkgui_helpers.get_glade('systray_context_menu.glade')
|
||||
self.systray_context_menu = self.xml.get_widget('systray_context_menu')
|
||||
self.added_hide_menuitem = False
|
||||
|
||||
# self.tray_ico_imgs = self.load_icos()
|
||||
|
||||
w = gtk.Window() # just a window to pass
|
||||
w.realize() # realize it so gtk window exists
|
||||
self.systray_winapi = SystrayWINAPI(w)
|
||||
|
||||
self.xml.signal_autoconnect(self)
|
||||
|
||||
# Set up the callback messages
|
||||
self.systray_winapi.message_map({
|
||||
WM_TRAYMESSAGE: self.on_clicked
|
||||
})
|
||||
|
||||
def show_icon(self):
|
||||
#self.systray_winapi.add_notify_icon(self.systray_context_menu, tooltip = 'Gajim')
|
||||
#self.systray_winapi.notify_icon.menu = self.systray_context_menu
|
||||
# do not remove set_img does both above.
|
||||
# maybe I can only change img without readding
|
||||
# the notify icon? HOW??
|
||||
self.set_img()
|
||||
|
||||
def hide_icon(self):
|
||||
self.systray_winapi.remove()
|
||||
|
||||
def on_clicked(self, hwnd, message, wparam, lparam):
|
||||
if lparam == win32con.WM_RBUTTONUP: # Right click
|
||||
self.make_menu()
|
||||
self.systray_winapi.notify_icon.menu.popup(None, None, None, 0, 0)
|
||||
elif lparam == win32con.WM_MBUTTONUP: # Middle click
|
||||
self.on_middle_click()
|
||||
elif lparam == win32con.WM_LBUTTONUP: # Left click
|
||||
self.on_left_click()
|
||||
|
||||
def set_img(self):
|
||||
self.tray_ico_imgs = self.load_icos() #FIXME: do not do this here
|
||||
# see gajim.interface.roster.reload_jabber_state_images() to merge
|
||||
|
||||
if len(self.jids) > 0:
|
||||
state = 'message'
|
||||
else:
|
||||
state = self.status
|
||||
hicon = self.tray_ico_imgs[state]
|
||||
if hicon is None:
|
||||
return
|
||||
|
||||
self.systray_winapi.remove_notify_icon()
|
||||
self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
|
||||
'Gajim')
|
||||
self.systray_winapi.notify_icon.menu = self.systray_context_menu
|
||||
|
||||
nb = gajim.events.get_nb_systray_events()
|
||||
|
||||
if nb > 0:
|
||||
text = i18n.ngettext(
|
||||
'Gajim - %d unread message',
|
||||
'Gajim - %d unread messages',
|
||||
nb, nb, nb)
|
||||
else:
|
||||
text = 'Gajim'
|
||||
self.systray_winapi.notify_icon.set_tooltip(text)
|
||||
|
||||
def load_icos(self):
|
||||
'''load .ico files and return them to a dic of SHOW --> img_obj'''
|
||||
iconset = str(gajim.config.get('iconset'))
|
||||
if not iconset:
|
||||
iconset = 'dcraven'
|
||||
|
||||
imgs = {}
|
||||
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16', 'icos')
|
||||
# icon folder for missing icons
|
||||
path_dcraven_iconset = os.path.join(gajim.DATA_DIR, 'iconsets', 'dcraven',
|
||||
'16x16', 'icos')
|
||||
states_list = gajim.SHOW_LIST
|
||||
# trayicon apart from show holds message state too
|
||||
states_list.append('message')
|
||||
for state in states_list:
|
||||
path_to_ico = os.path.join(path, state + '.ico')
|
||||
if not os.path.isfile(path_to_ico): # fallback to dcraven iconset
|
||||
path_to_ico = os.path.join(path_dcraven_iconset, state + '.ico')
|
||||
if os.path.exists(path_to_ico):
|
||||
hinst = win32gui.GetModuleHandle(None)
|
||||
img_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
|
||||
try:
|
||||
image = win32gui.LoadImage(hinst, path_to_ico,
|
||||
win32con.IMAGE_ICON, 0, 0, img_flags)
|
||||
except pywintypes.error:
|
||||
imgs[state] = None
|
||||
else:
|
||||
imgs[state] = image
|
||||
|
||||
return imgs
|
310
src/tooltips.py
310
src/tooltips.py
|
@ -21,7 +21,6 @@ import time
|
|||
import locale
|
||||
|
||||
import gtkgui_helpers
|
||||
import message_control
|
||||
|
||||
from common import gajim
|
||||
from common import helpers
|
||||
|
@ -38,7 +37,7 @@ class BaseTooltip:
|
|||
tooltip.hide_tooltip()
|
||||
|
||||
* data - the text to be displayed (extenders override this argument and
|
||||
dislpay more complex contents)
|
||||
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
|
||||
|
||||
|
@ -135,7 +134,7 @@ class BaseTooltip:
|
|||
preferred_y = widget_y_position + widget_height + 4
|
||||
|
||||
self.preferred_position = [pointer_x, preferred_y]
|
||||
self.widget_height =widget_height
|
||||
self.widget_height = widget_height
|
||||
self.win.ensure_style()
|
||||
self.win.show_all()
|
||||
|
||||
|
@ -177,10 +176,12 @@ class StatusTable:
|
|||
# make sure 'status' is unicode before we send to to reduce_chars
|
||||
if isinstance(status, str):
|
||||
status = unicode(status, encoding='utf-8')
|
||||
# reduce to 200 chars, 1 line
|
||||
status = gtkgui_helpers.reduce_chars_newlines(status, 200, 1)
|
||||
str_status += ' - ' + status
|
||||
return gtkgui_helpers.escape_for_pango_markup(str_status)
|
||||
# reduce to 100 chars, 1 line
|
||||
status = helpers.reduce_chars_newlines(status, 100, 1)
|
||||
str_status = gtkgui_helpers.escape_for_pango_markup(str_status)
|
||||
status = gtkgui_helpers.escape_for_pango_markup(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):
|
||||
''' appends a new row with status icon to the table '''
|
||||
|
@ -213,13 +214,6 @@ class StatusTable:
|
|||
gtk.ICON_SIZE_MENU)
|
||||
self.table.attach(lock_image, 4, 5, self.current_row,
|
||||
self.current_row + 1, 0, 0, 0, 0)
|
||||
if status_time:
|
||||
self.current_row += 1
|
||||
# decode locale encoded string, the same way as below (10x nk)
|
||||
local_time = time.strftime("%c", status_time)
|
||||
local_time = local_time.decode(locale.getpreferredencoding())
|
||||
status_time_label = gtk.Label(local_time)
|
||||
status_time_label.set_alignment(0, 0)
|
||||
|
||||
class NotificationAreaTooltip(BaseTooltip, StatusTable):
|
||||
''' Tooltip that is shown in the notification area '''
|
||||
|
@ -227,27 +221,6 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
|
|||
BaseTooltip.__init__(self)
|
||||
StatusTable.__init__(self)
|
||||
|
||||
def get_accounts_info(self):
|
||||
accounts = []
|
||||
accounts_list = gajim.contacts.get_accounts()
|
||||
accounts_list.sort()
|
||||
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 = helpers.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 fill_table_with_accounts(self, accounts):
|
||||
iconset = gajim.config.get('iconset')
|
||||
if not iconset:
|
||||
|
@ -259,7 +232,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
|
|||
# there are possible pango TBs on 'set_markup'
|
||||
if isinstance(message, str):
|
||||
message = unicode(message, encoding = 'utf-8')
|
||||
message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
|
||||
message = helpers.reduce_chars_newlines(message, 100, 1)
|
||||
message = gtkgui_helpers.escape_for_pango_markup(message)
|
||||
if gajim.con_types.has_key(acct['name']) and \
|
||||
gajim.con_types[acct['name']] in ('tls', 'ssl'):
|
||||
|
@ -278,67 +251,16 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
|
|||
def populate(self, data):
|
||||
self.create_window()
|
||||
self.create_table()
|
||||
self.hbox = gtk.HBox()
|
||||
self.table.set_property('column-spacing', 1)
|
||||
text, single_line = '', ''
|
||||
|
||||
unread_chat = gajim.events.get_nb_events(types = ['printed_chat', 'chat'])
|
||||
unread_single_chat = gajim.events.get_nb_events(types = ['normal'])
|
||||
unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg',
|
||||
'gc_msg'])
|
||||
unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm'])
|
||||
|
||||
accounts = self.get_accounts_info()
|
||||
|
||||
if unread_chat or unread_single_chat or unread_gc or unread_pm:
|
||||
text = 'Gajim '
|
||||
awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm
|
||||
if awaiting_events == unread_chat or awaiting_events == unread_single_chat \
|
||||
or awaiting_events == unread_gc or awaiting_events == unread_pm:
|
||||
# This condition is like previous if but with xor...
|
||||
# Print in one line
|
||||
text += '-'
|
||||
else:
|
||||
# Print in multiple lines
|
||||
text += '\n '
|
||||
if unread_chat:
|
||||
text += i18n.ngettext(
|
||||
' %d unread message',
|
||||
' %d unread messages',
|
||||
unread_chat, unread_chat, unread_chat)
|
||||
text += '\n '
|
||||
if unread_single_chat:
|
||||
text += i18n.ngettext(
|
||||
' %d unread single message',
|
||||
' %d unread single messages',
|
||||
unread_single_chat, unread_single_chat, unread_single_chat)
|
||||
text += '\n '
|
||||
if unread_gc:
|
||||
text += i18n.ngettext(
|
||||
' %d unread group chat message',
|
||||
' %d unread group chat messages',
|
||||
unread_gc, unread_gc, unread_gc)
|
||||
text += '\n '
|
||||
if unread_pm:
|
||||
text += i18n.ngettext(
|
||||
' %d unread private message',
|
||||
' %d unread private messages',
|
||||
unread_pm, unread_pm, unread_pm)
|
||||
text += '\n '
|
||||
text = text[:-4] # remove latest '\n '
|
||||
elif len(accounts) > 1:
|
||||
text = _('Gajim')
|
||||
self.current_current_row = 1
|
||||
accounts = helpers.get_accounts_info()
|
||||
if len(accounts) > 1:
|
||||
self.table.resize(2, 1)
|
||||
self.fill_table_with_accounts(accounts)
|
||||
self.hbox = gtk.HBox()
|
||||
self.table.set_property('column-spacing', 1)
|
||||
|
||||
elif len(accounts) == 1:
|
||||
message = accounts[0]['status_line']
|
||||
message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1)
|
||||
message = gtkgui_helpers.escape_for_pango_markup(message)
|
||||
text = _('Gajim - %s') % message
|
||||
else:
|
||||
text = _('Gajim - %s') % helpers.get_uf_show('offline')
|
||||
text = helpers.get_notification_icon_tooltip_text()
|
||||
text = gtkgui_helpers.escape_for_pango_markup(text)
|
||||
|
||||
self.add_text_row(text)
|
||||
self.hbox.add(self.table)
|
||||
self.win.add(self.hbox)
|
||||
|
@ -364,27 +286,37 @@ class GCTooltip(BaseTooltip):
|
|||
vcard_table.set_homogeneous(False)
|
||||
vcard_current_row = 1
|
||||
properties = []
|
||||
|
||||
if contact.jid.strip() != '':
|
||||
jid_markup = '<span weight="bold">' + contact.jid + '</span>'
|
||||
else:
|
||||
jid_markup = '<span weight="bold">' + \
|
||||
|
||||
nick_markup = '<b>' + \
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \
|
||||
+ '</span>'
|
||||
properties.append((jid_markup, None))
|
||||
properties.append((_('Role: '), helpers.get_uf_role(contact.role)))
|
||||
properties.append((_('Affiliation: '), contact.affiliation.capitalize()))
|
||||
if hasattr(contact, 'resource') and contact.resource.strip() != '':
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
|
||||
show = helpers.get_uf_show(contact.show)
|
||||
if contact.status:
|
||||
+ '</b>'
|
||||
properties.append((nick_markup, None))
|
||||
|
||||
if contact.status: # status message
|
||||
status = contact.status.strip()
|
||||
if status != '':
|
||||
# escape markup entities
|
||||
status = gtkgui_helpers.reduce_chars_newlines(status, 200, 5)
|
||||
show += ' - ' + gtkgui_helpers.escape_for_pango_markup(status)
|
||||
properties.append((_('Status: '), show))
|
||||
status = helpers.reduce_chars_newlines(status, 100, 5)
|
||||
status = '<i>' +\
|
||||
gtkgui_helpers.escape_for_pango_markup(status) + '</i>'
|
||||
properties.append((status, None))
|
||||
else: # no status message, show SHOW instead
|
||||
show = helpers.get_uf_show(contact.show)
|
||||
show = '<i>' + show + '</i>'
|
||||
properties.append((show, None))
|
||||
|
||||
if contact.jid.strip() != '':
|
||||
properties.append((_('JID: '), contact.jid))
|
||||
|
||||
if hasattr(contact, 'resource') and contact.resource.strip() != '':
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) ))
|
||||
if contact.affiliation != 'none':
|
||||
uf_affiliation = helpers.get_uf_affiliation(contact.affiliation)
|
||||
affiliation_str = \
|
||||
_('%(owner_or_admin_or_member)s of this group chat') %\
|
||||
{'owner_or_admin_or_member': uf_affiliation}
|
||||
properties.append((affiliation_str, None))
|
||||
|
||||
# Add avatar
|
||||
puny_name = helpers.sanitize_filename(contact.name)
|
||||
|
@ -410,22 +342,23 @@ class GCTooltip(BaseTooltip):
|
|||
label.set_alignment(0, 0)
|
||||
if property[1]:
|
||||
label.set_markup(property[0])
|
||||
vcard_table.attach(label, 1, 2, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.FILL, vertical_fill, 0, 0)
|
||||
vcard_table.attach(label, 1, 2, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
|
||||
label = gtk.Label()
|
||||
label.set_alignment(0, 0)
|
||||
label.set_markup(property[1])
|
||||
label.set_line_wrap(True)
|
||||
vcard_table.attach(label, 2, 3, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.EXPAND | gtk.FILL, vertical_fill, 0, 0)
|
||||
vcard_table.attach(label, 2, 3, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
|
||||
vertical_fill, 0, 0)
|
||||
else:
|
||||
label.set_markup(property[0])
|
||||
vcard_table.attach(label, 1, 3, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.FILL, vertical_fill, 0)
|
||||
vcard_table.attach(label, 1, 3, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
|
||||
|
||||
self.avatar_image.set_alignment(0, 0)
|
||||
vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row +1,
|
||||
gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
|
||||
vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row + 1,
|
||||
gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
|
||||
self.win.add(vcard_table)
|
||||
|
||||
class RosterTooltip(NotificationAreaTooltip):
|
||||
|
@ -445,8 +378,7 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
self.create_table()
|
||||
if not contacts or len(contacts) == 0:
|
||||
# Tooltip for merged accounts row
|
||||
accounts = self.get_accounts_info()
|
||||
self.current_current_row = 0
|
||||
accounts = helpers.get_accounts_info()
|
||||
self.table.resize(2, 1)
|
||||
self.spacer_label = ''
|
||||
self.fill_table_with_accounts(accounts)
|
||||
|
@ -458,15 +390,6 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
prim_contact = gajim.contacts.get_highest_prio_contact_from_contacts(
|
||||
contacts)
|
||||
|
||||
transport = gajim.get_transport_name_from_jid(prim_contact.jid)
|
||||
if transport:
|
||||
file_path = os.path.join(gajim.DATA_DIR, 'iconsets', 'transports',
|
||||
transport , '16x16')
|
||||
else:
|
||||
iconset = gajim.config.get('iconset')
|
||||
if not iconset:
|
||||
iconset = 'dcraven'
|
||||
file_path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
|
||||
puny_jid = helpers.sanitize_filename(prim_contact.jid)
|
||||
table_size = 3
|
||||
|
||||
|
@ -486,23 +409,12 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
vcard_table.set_homogeneous(False)
|
||||
vcard_current_row = 1
|
||||
properties = []
|
||||
jid_markup = '<span weight="bold">' + prim_contact.jid + '</span>'
|
||||
properties.append((jid_markup, None))
|
||||
|
||||
properties.append((_('Name: '), gtkgui_helpers.escape_for_pango_markup(
|
||||
prim_contact.get_shown_name())))
|
||||
if prim_contact.sub:
|
||||
properties.append(( _('Subscription: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
|
||||
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:
|
||||
properties.append((_('OpenPGP: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(keyID)))
|
||||
name_markup = u'<span weight="bold">' + \
|
||||
gtkgui_helpers.escape_for_pango_markup(prim_contact.get_shown_name())\
|
||||
+ '</span>'
|
||||
properties.append((name_markup, None))
|
||||
|
||||
num_resources = 0
|
||||
# put contacts in dict, where key is priority
|
||||
contacts_dict = {}
|
||||
|
@ -514,12 +426,20 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
else:
|
||||
contacts_dict[contact.priority] = [contact]
|
||||
|
||||
if num_resources == 1 and contact.resource:
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) + ' (' + \
|
||||
unicode(contact.priority) + ')'))
|
||||
if num_resources > 1:
|
||||
properties.append((_('Status: '), ' '))
|
||||
transport = gajim.get_transport_name_from_jid(
|
||||
prim_contact.jid)
|
||||
if transport:
|
||||
file_path = os.path.join(gajim.DATA_DIR, 'iconsets',
|
||||
'transports', transport , '16x16')
|
||||
else:
|
||||
iconset = gajim.config.get('iconset')
|
||||
if not iconset:
|
||||
iconset = 'dcraven'
|
||||
file_path = os.path.join(gajim.DATA_DIR,
|
||||
'iconsets', iconset, '16x16')
|
||||
|
||||
contact_keys = contacts_dict.keys()
|
||||
contact_keys.sort()
|
||||
contact_keys.reverse()
|
||||
|
@ -535,29 +455,63 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
else: # only one resource
|
||||
if contact.show:
|
||||
show = helpers.get_uf_show(contact.show)
|
||||
if contact.last_status_time:
|
||||
vcard_current_row += 1
|
||||
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)
|
||||
local_time = local_time.decode(
|
||||
locale.getpreferredencoding())
|
||||
text = text % local_time
|
||||
show += text
|
||||
show = '<i>' + show + '</i>'
|
||||
# we append show below
|
||||
|
||||
if contact.status:
|
||||
status = contact.status.strip()
|
||||
if status:
|
||||
# reduce long status
|
||||
# (no more than 200 chars on line and no more than 5 lines)
|
||||
status = gtkgui_helpers.reduce_chars_newlines(status, 200, 5)
|
||||
# (no more than 100 chars on line and no more than 5 lines)
|
||||
status = helpers.reduce_chars_newlines(status, 100, 5)
|
||||
# escape markup entities.
|
||||
status = gtkgui_helpers.escape_for_pango_markup(status)
|
||||
show += ' - ' + status
|
||||
properties.append((_('Status: '), show))
|
||||
|
||||
if contact.last_status_time:
|
||||
vcard_current_row += 1
|
||||
if contact.show == 'offline':
|
||||
text = _('Last status on %s')
|
||||
else:
|
||||
text = _('Since %s')
|
||||
properties.append(('<i>%s</i>' % status, None))
|
||||
properties.append((show, None))
|
||||
|
||||
properties.append((_('Jabber ID: '), prim_contact.jid ))
|
||||
|
||||
# time.strftime returns locale encoded string
|
||||
local_time = time.strftime('%c', contact.last_status_time)
|
||||
local_time = local_time.decode(locale.getpreferredencoding())
|
||||
text = text % local_time
|
||||
properties.append(('<span style="italic">%s</span>' % text, None))
|
||||
# contact has only one ressource
|
||||
if num_resources == 1 and contact.resource:
|
||||
properties.append((_('Resource: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(contact.resource) +\
|
||||
' (' + unicode(contact.priority) + ')'))
|
||||
|
||||
if prim_contact.sub and prim_contact.sub != 'both':
|
||||
# ('both' is the normal sub so we don't show it)
|
||||
properties.append(( _('Subscription: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(helpers.get_uf_sub(prim_contact.sub))))
|
||||
|
||||
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:
|
||||
properties.append((_('OpenPGP: '),
|
||||
gtkgui_helpers.escape_for_pango_markup(keyID)))
|
||||
|
||||
while properties:
|
||||
property = properties.pop(0)
|
||||
vcard_current_row += 1
|
||||
|
@ -568,28 +522,26 @@ class RosterTooltip(NotificationAreaTooltip):
|
|||
label.set_alignment(0, 0)
|
||||
if property[1]:
|
||||
label.set_markup(property[0])
|
||||
vcard_table.attach(label, 1, 2, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.FILL, vertical_fill, 0, 0)
|
||||
vcard_table.attach(label, 1, 2, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.FILL, vertical_fill, 0, 0)
|
||||
label = gtk.Label()
|
||||
if num_resources > 1 and not properties:
|
||||
label.set_alignment(0, 1)
|
||||
else:
|
||||
label.set_alignment(0, 0)
|
||||
label.set_alignment(0, 0)
|
||||
label.set_markup(property[1])
|
||||
label.set_line_wrap(True)
|
||||
vcard_table.attach(label, 2, 3, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.EXPAND | gtk.FILL, vertical_fill, 0, 0)
|
||||
vcard_table.attach(label, 2, 3, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.EXPAND | gtk.FILL,
|
||||
vertical_fill, 0, 0)
|
||||
else:
|
||||
if isinstance(property[0], unicode):
|
||||
if isinstance(property[0], (unicode, str)): #FIXME: rm unicode?
|
||||
label.set_markup(property[0])
|
||||
else:
|
||||
label = property[0]
|
||||
vcard_table.attach(label, 1, 3, vcard_current_row, vcard_current_row + 1,
|
||||
gtk.FILL, vertical_fill, 0)
|
||||
vcard_table.attach(label, 1, 3, vcard_current_row,
|
||||
vcard_current_row + 1, gtk.FILL, vertical_fill, 0)
|
||||
self.avatar_image.set_alignment(0, 0)
|
||||
if table_size == 4:
|
||||
vcard_table.attach(self.avatar_image, 3, 4, 2, vcard_current_row +1,
|
||||
gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
|
||||
vcard_table.attach(self.avatar_image, 3, 4, 2,
|
||||
vcard_current_row + 1, gtk.FILL, gtk.FILL | gtk.EXPAND, 3, 3)
|
||||
self.win.add(vcard_table)
|
||||
|
||||
class FileTransfersTooltip(BaseTooltip):
|
||||
|
|
298
src/vcard.py
298
src/vcard.py
|
@ -2,6 +2,7 @@
|
|||
##
|
||||
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
|
||||
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
|
||||
## Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published
|
||||
|
@ -13,16 +14,15 @@
|
|||
## GNU General Public License for more details.
|
||||
##
|
||||
|
||||
# THIS FILE IS FOR **OTHERS'** PROFILE (when we VIEW their INFO)
|
||||
|
||||
import gtk
|
||||
import gobject
|
||||
import base64
|
||||
import mimetypes
|
||||
import os
|
||||
import time
|
||||
import locale
|
||||
|
||||
import gtkgui_helpers
|
||||
import dialogs
|
||||
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
|
@ -57,25 +57,49 @@ def get_avatar_pixbuf_encoded_mime(photo):
|
|||
class VcardWindow:
|
||||
'''Class for contact's information window'''
|
||||
|
||||
def __init__(self, contact, account, is_fake = False):
|
||||
def __init__(self, contact, account, gc_contact = None):
|
||||
# the contact variable is the jid if vcard is true
|
||||
self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade')
|
||||
self.window = self.xml.get_widget('vcard_information_window')
|
||||
self.progressbar = self.xml.get_widget('progressbar')
|
||||
|
||||
self.contact = contact
|
||||
self.account = account
|
||||
self.is_fake = is_fake
|
||||
self.gc_contact = gc_contact
|
||||
|
||||
self.avatar_mime_type = None
|
||||
self.avatar_encoded = None
|
||||
self.vcard_arrived = False
|
||||
self.os_info_arrived = False
|
||||
self.update_progressbar_timeout_id = gobject.timeout_add(100,
|
||||
self.update_progressbar)
|
||||
|
||||
self.fill_jabber_page()
|
||||
annotations = gajim.connections[self.account].annotations
|
||||
if self.contact.jid in annotations:
|
||||
buffer = self.xml.get_widget('textview_annotation').get_buffer()
|
||||
buffer.set_text(annotations[self.contact.jid])
|
||||
|
||||
self.xml.signal_autoconnect(self)
|
||||
self.window.show_all()
|
||||
self.xml.get_widget('close_button').grab_focus()
|
||||
|
||||
def update_progressbar(self):
|
||||
self.progressbar.pulse()
|
||||
return True # loop forever
|
||||
|
||||
def on_vcard_information_window_destroy(self, widget):
|
||||
if self.update_progressbar_timeout_id is not None:
|
||||
gobject.source_remove(self.update_progressbar_timeout_id)
|
||||
del gajim.interface.instances[self.account]['infos'][self.contact.jid]
|
||||
buffer = self.xml.get_widget('textview_annotation').get_buffer()
|
||||
annotation = buffer.get_text(buffer.get_start_iter(),
|
||||
buffer.get_end_iter())
|
||||
connection = gajim.connections[self.account]
|
||||
if annotation != connection.annotations.get(self.contact.jid, ''):
|
||||
connection.annotations[self.contact.jid] = annotation
|
||||
connection.store_annotations()
|
||||
|
||||
|
||||
def on_vcard_information_window_key_press_event(self, widget, event):
|
||||
if event.keyval == gtk.keysyms.Escape:
|
||||
|
@ -104,7 +128,8 @@ class VcardWindow:
|
|||
menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.contact.jid, self.account, self.contact.name + '.jpeg')
|
||||
self.contact.jid, self.account, self.contact.get_shown_name() +
|
||||
'.jpeg')
|
||||
menu.append(menuitem)
|
||||
menu.connect('selection-done', lambda w:w.destroy())
|
||||
# show the menu
|
||||
|
@ -113,13 +138,24 @@ class VcardWindow:
|
|||
|
||||
def set_value(self, entry_name, value):
|
||||
try:
|
||||
self.xml.get_widget(entry_name).set_text(value)
|
||||
if value and entry_name == 'URL_label':
|
||||
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
|
||||
widget = gtk.LinkButton(value, value)
|
||||
else:
|
||||
widget = gtk.Label(value)
|
||||
widget.set_selectable(True)
|
||||
widget.show()
|
||||
table = self.xml.get_widget('personal_info_table')
|
||||
table.attach(widget, 1, 4, 3, 4, yoptions = 0)
|
||||
else:
|
||||
self.xml.get_widget(entry_name).set_text(value)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def set_values(self, vcard):
|
||||
for i in vcard.keys():
|
||||
if i == 'PHOTO':
|
||||
if i == 'PHOTO' and self.xml.get_widget('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_widget('PHOTO_image')
|
||||
|
@ -130,7 +166,7 @@ class VcardWindow:
|
|||
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
|
||||
image.set_from_pixbuf(pixbuf)
|
||||
continue
|
||||
if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
|
||||
if i in ('ADR', 'TEL', 'EMAIL'):
|
||||
for entry in vcard[i]:
|
||||
add_on = '_HOME'
|
||||
if 'WORK' in entry:
|
||||
|
@ -144,14 +180,23 @@ class VcardWindow:
|
|||
if i == 'DESC':
|
||||
self.xml.get_widget('DESC_textview').get_buffer().set_text(
|
||||
vcard[i], 0)
|
||||
else:
|
||||
elif i != 'jid': # Do not override jid_label
|
||||
self.set_value(i + '_label', vcard[i])
|
||||
self.vcard_arrived = True
|
||||
self.test_remove_progressbar()
|
||||
|
||||
def test_remove_progressbar(self):
|
||||
if self.update_progressbar_timeout_id is not None and \
|
||||
self.vcard_arrived and self.os_info_arrived:
|
||||
gobject.source_remove(self.update_progressbar_timeout_id)
|
||||
self.progressbar.hide()
|
||||
self.update_progressbar_timeout_id = None
|
||||
|
||||
def set_last_status_time(self):
|
||||
self.fill_status_label()
|
||||
|
||||
def set_os_info(self, resource, client_info, os_info):
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 4:
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 5:
|
||||
return
|
||||
i = 0
|
||||
client = ''
|
||||
|
@ -174,16 +219,25 @@ class VcardWindow:
|
|||
os = Q_('?OS:Unknown')
|
||||
self.xml.get_widget('client_name_version_label').set_text(client)
|
||||
self.xml.get_widget('os_label').set_text(os)
|
||||
self.os_info_arrived = True
|
||||
self.test_remove_progressbar()
|
||||
|
||||
def fill_status_label(self):
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 4:
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 5:
|
||||
return
|
||||
contact_list = gajim.contacts.get_contact(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 = ''
|
||||
one = True # Are we adding the first line ?
|
||||
if contact_list:
|
||||
for c in contact_list:
|
||||
if connected_contact_list:
|
||||
for c in connected_contact_list:
|
||||
if not one:
|
||||
stats += '\n'
|
||||
stats += helpers.get_uf_show(c.show)
|
||||
|
@ -212,26 +266,38 @@ class VcardWindow:
|
|||
self.contact.get_shown_name() +
|
||||
'</span></b>')
|
||||
self.xml.get_widget('jid_label').set_text(self.contact.jid)
|
||||
uf_sub = helpers.get_uf_sub(self.contact.sub)
|
||||
self.xml.get_widget('subscription_label').set_text(uf_sub)
|
||||
eb = self.xml.get_widget('subscription_label_eventbox')
|
||||
if self.contact.sub == 'from':
|
||||
tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
|
||||
elif self.contact.sub == 'to':
|
||||
tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
|
||||
elif self.contact.sub == 'both':
|
||||
tt_text = _("You and the contact are interested in each other's presence information")
|
||||
else: # None
|
||||
tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
|
||||
tooltips.set_tip(eb, tt_text)
|
||||
|
||||
subscription_label = self.xml.get_widget('subscription_label')
|
||||
ask_label = self.xml.get_widget('ask_label')
|
||||
if self.gc_contact:
|
||||
self.xml.get_widget('subscription_title_label').set_text(_("Role:"))
|
||||
uf_role = helpers.get_uf_role(self.gc_contact.role)
|
||||
subscription_label.set_text(uf_role)
|
||||
|
||||
self.xml.get_widget('ask_title_label').set_text(_("Affiliation:"))
|
||||
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)
|
||||
eb = self.xml.get_widget('subscription_label_eventbox')
|
||||
if self.contact.sub == 'from':
|
||||
tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence")
|
||||
elif self.contact.sub == 'to':
|
||||
tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours")
|
||||
elif self.contact.sub == 'both':
|
||||
tt_text = _("You and the contact are interested in each other's presence information")
|
||||
else: # None
|
||||
tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours")
|
||||
tooltips.set_tip(eb, tt_text)
|
||||
|
||||
uf_ask = helpers.get_uf_ask(self.contact.ask)
|
||||
ask_label.set_text(uf_ask)
|
||||
eb = self.xml.get_widget('ask_label_eventbox')
|
||||
if self.contact.ask == 'subscribe':
|
||||
tooltips.set_tip(eb,
|
||||
_("You are waiting contact's answer about your subscription request"))
|
||||
|
||||
label = self.xml.get_widget('ask_label')
|
||||
uf_ask = helpers.get_uf_ask(self.contact.ask)
|
||||
label.set_text(uf_ask)
|
||||
eb = self.xml.get_widget('ask_label_eventbox')
|
||||
if self.contact.ask == 'subscribe':
|
||||
tooltips.set_tip(eb,
|
||||
_("You are waiting contact's answer about your subscription request"))
|
||||
log = True
|
||||
if self.contact.jid in gajim.config.get_per('accounts', self.account,
|
||||
'no_log_for').split(' '):
|
||||
|
@ -251,8 +317,12 @@ class VcardWindow:
|
|||
gajim.connections[self.account].request_last_status_time(self.contact.jid,
|
||||
self.contact.resource)
|
||||
|
||||
# Request os info in contact is connected
|
||||
if self.contact.show not in ('offline', 'error'):
|
||||
# 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
|
||||
gobject.idle_add(gajim.connections[self.account].request_os_info,
|
||||
self.contact.jid, self.contact.resource)
|
||||
self.os_info = {0: {'resource': self.contact.resource, 'client': '',
|
||||
|
@ -282,4 +352,160 @@ class VcardWindow:
|
|||
|
||||
self.fill_status_label()
|
||||
|
||||
gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
|
||||
gajim.connections[self.account].request_vcard(self.contact.jid,
|
||||
self.gc_contact is not None)
|
||||
|
||||
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_glade('zeroconf_information_window.glade')
|
||||
self.window = self.xml.get_widget('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.signal_autoconnect(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 == gtk.keysyms.Escape:
|
||||
self.window.destroy()
|
||||
|
||||
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.contact.jid in no_log_for:
|
||||
oldlog = False
|
||||
log = widget.get_active()
|
||||
if not log and not self.contact.jid in no_log_for:
|
||||
no_log_for.append(self.contact.jid)
|
||||
if log and self.contact.jid in no_log_for:
|
||||
no_log_for.remove(self.contact.jid)
|
||||
if oldlog != log:
|
||||
gajim.config.set_per('accounts', self.account, 'no_log_for',
|
||||
' '.join(no_log_for))
|
||||
|
||||
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.ImageMenuItem(gtk.STOCK_SAVE_AS)
|
||||
menuitem.connect('activate',
|
||||
gtkgui_helpers.on_avatar_save_as_menuitem_activate,
|
||||
self.contact.jid, self.account, self.contact.get_shown_name() +
|
||||
'.jpeg')
|
||||
menu.append(menuitem)
|
||||
menu.connect('selection-done', lambda w:w.destroy())
|
||||
# show the menu
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time)
|
||||
|
||||
def set_value(self, entry_name, value):
|
||||
try:
|
||||
if value and entry_name == 'URL_label':
|
||||
if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0):
|
||||
widget = gtk.LinkButton(value, value)
|
||||
else:
|
||||
widget = gtk.Label(value)
|
||||
widget.set_selectable(True)
|
||||
table = self.xml.get_widget('personal_info_table')
|
||||
table.attach(widget, 1, 4, 3, 4, yoptions = 0)
|
||||
else:
|
||||
self.xml.get_widget(entry_name).set_text(value)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def fill_status_label(self):
|
||||
if self.xml.get_widget('information_notebook').get_n_pages() < 2:
|
||||
return
|
||||
contact_list = gajim.contacts.get_contact(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_widget('status_label')
|
||||
status_label.set_max_width_chars(15)
|
||||
status_label.set_text(stats)
|
||||
|
||||
tip = gtk.Tooltips()
|
||||
status_label_eventbox = self.xml.get_widget('status_label_eventbox')
|
||||
tip.set_tip(status_label_eventbox, stats)
|
||||
|
||||
def fill_contact_page(self):
|
||||
tooltips = gtk.Tooltips()
|
||||
self.xml.get_widget('nickname_label').set_markup(
|
||||
'<b><span size="x-large">' +
|
||||
self.contact.get_shown_name() +
|
||||
'</span></b>')
|
||||
self.xml.get_widget('local_jid_label').set_text(self.contact.jid)
|
||||
|
||||
log = True
|
||||
if self.contact.jid in gajim.config.get_per('accounts', self.account,
|
||||
'no_log_for').split(' '):
|
||||
log = False
|
||||
checkbutton = self.xml.get_widget('log_history_checkbutton')
|
||||
checkbutton.set_active(log)
|
||||
checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled)
|
||||
|
||||
resources = '%s (%s)' % (self.contact.resource, unicode(
|
||||
self.contact.priority))
|
||||
uf_resources = self.contact.resource + _(' resource with priority ')\
|
||||
+ unicode(self.contact.priority)
|
||||
if not self.contact.status:
|
||||
self.contact.status = ''
|
||||
|
||||
# Request list time status
|
||||
# gajim.connections[self.account].request_last_status_time(self.contact.jid,
|
||||
# self.contact.resource)
|
||||
|
||||
self.xml.get_widget('resource_prio_label').set_text(resources)
|
||||
resource_prio_label_eventbox = self.xml.get_widget(
|
||||
'resource_prio_label_eventbox')
|
||||
tooltips.set_tip(resource_prio_label_eventbox, uf_resources)
|
||||
|
||||
self.fill_status_label()
|
||||
|
||||
# gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
|
||||
|
||||
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 not contact['txt_dict'].has_key(key):
|
||||
contact['txt_dict'][key] = ''
|
||||
self.xml.get_widget('first_name_label').set_text(contact['txt_dict']['1st'])
|
||||
self.xml.get_widget('last_name_label').set_text(contact['txt_dict']['last'])
|
||||
self.xml.get_widget('jabber_id_label').set_text(contact['txt_dict']['jid'])
|
||||
self.xml.get_widget('email_label').set_text(contact['txt_dict']['email'])
|
||||
|
||||
def on_close_button_clicked(self, widget):
|
||||
self.window.destroy()
|
||||
|
|
Loading…
Add table
Reference in a new issue