merged trunk r6780

removed i18n imports from zeroconf/connection files
This commit is contained in:
Stefan Bethge 2006-09-14 16:48:03 +00:00
parent 61dab0d59a
commit 2b3120244f
54 changed files with 4924 additions and 2296 deletions

View File

@ -1,21 +1,24 @@
# Set the C flags to include the GTK+ and Python libraries
PYTHON ?= python
PYTHONVER = `$(PYTHON) -c 'import sys; print sys.version[:3]'`
CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fPIC -I/usr/include/python$(PYTHONVER) -I.
LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -lpython$(PYTHONVER)
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)
$(CC) -shared $^ -o $@ $(LDFLAGS) $(gtk_LDFLAGS)
gtkspell.so:
$(CC) $(OPTFLAGS) $(CFLAGS) `pkg-config --cflags gtkspell-2.0` -shared gtkspellmodule.c $^ -o $@ $(LDFLAGS) `pkg-config --libs gtkspell-2.0`
$(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 \

View File

@ -25,16 +25,9 @@
##
import gtk
import gtk.glade
import gtkgui_helpers
from common import gajim
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
(
OPT_TYPE,
@ -53,6 +46,8 @@ class AdvancedConfigurationWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade')
self.window = self.xml.get_widget('advanced_configuration_window')
self.window.set_transient_for(
gajim.interface.instances['preferences'].window)
self.entry = self.xml.get_widget('advanced_entry')
self.desc_label = self.xml.get_widget('advanced_desc_label')
self.restart_label = self.xml.get_widget('restart_label')

View File

@ -18,13 +18,13 @@
import os
import time
import gtk
import gtk.glade
import pango
import gobject
import gtkgui_helpers
import message_control
import dialogs
import history_window
import notify
from common import gajim
from common import helpers
@ -41,11 +41,14 @@ try:
except:
HAS_GTK_SPELL = False
####################
# FIXME: Can't this stuff happen once?
from common import i18n
_ = i18n._
APP = i18n.APP
# the next script, executed in the "po" directory,
# generates the following list.
##!/bin/sh
#LANG=$(for i in *.po; do j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done)
#echo "{_('en'):'en'",$LANG"}"
langs = {_('English'): 'en', _('Bulgarian'): 'bg', _('Briton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basc'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norvegian b'): 'nb', _('Dutch'): 'nl', _('Norvegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'}
################################################################################
class ChatControlBase(MessageControl):
@ -56,7 +59,7 @@ class ChatControlBase(MessageControl):
theme = gajim.config.get('roster_theme')
bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
if bannerfont:
font = pango.FontDescription(bannerfont)
else:
@ -67,16 +70,24 @@ class ChatControlBase(MessageControl):
font.set_weight(pango.WEIGHT_HEAVY)
if 'I' in bannerfontattrs:
font.set_style(pango.STYLE_ITALIC)
font_attrs = 'font_desc="%s"' % font.to_string()
# in case there is no font specified we use x-large font size
if font.get_size() == 0:
font_attrs = '%s size="x-large"' % font_attrs
font.set_weight(pango.WEIGHT_NORMAL)
font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
return (font_attrs, font_attrs_small)
def get_nb_unread(self):
jid = self.contact.jid
if self.resource:
jid += '/' + self.resource
type_ = self.type_id
return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
type_]))
def draw_banner(self):
self._paint_banner()
self._update_banner_state_image()
@ -97,14 +108,15 @@ class ChatControlBase(MessageControl):
event_keymod):
pass # Derived should implement this rather than connecting to the event itself.
def __init__(self, type_id, parent_win, widget_name, display_names, contact, acct, resource = None):
def __init__(self, type_id, parent_win, widget_name, display_names, contact,
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
widget = self.xml.get_widget('emoticons_button')
id = widget.connect('clicked', self.on_emoticons_button_clicked)
self.handlers[id] = widget
id = self.widget.connect('key_press_event', self._on_keypress_event)
self.handlers[id] = self.widget
@ -112,10 +124,10 @@ class ChatControlBase(MessageControl):
id = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id] = widget
# Create textviews and connect signals
self.conv_textview = ConversationTextview(self.account)
self.conv_scrolledwindow = self.xml.get_widget(
'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview.tv)
@ -127,20 +139,23 @@ class ChatControlBase(MessageControl):
self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
self.msg_textview = MessageTextView()
id = self.msg_textview.connect('mykeypress',
self._on_message_textview_mykeypress_event)
self._on_message_textview_mykeypress_event)
self.handlers[id] = self.msg_textview
self.msg_scrolledwindow.add(self.msg_textview)
id = self.msg_textview.connect('key_press_event',
self._on_message_textview_key_press_event)
self._on_message_textview_key_press_event)
self.handlers[id] = self.msg_textview
id = self.msg_textview.connect('size-request', self.size_request)
self.handlers[id] = self.msg_textview
id = self.msg_textview.connect('populate_popup',
self.on_msg_textview_populate_popup)
self.handlers[id] = self.msg_textview
self.update_font()
# Hook up send button
widget = self.xml.get_widget('send_button')
id = widget.connect('clicked',
self._on_send_button_clicked)
id = widget.connect('clicked', self._on_send_button_clicked)
self.handlers[id] = widget
# the following vars are used to keep history of user's messages
@ -149,8 +164,6 @@ class ChatControlBase(MessageControl):
self.typing_new = False
self.orig_msg = ''
self.nb_unread = 0
# Emoticons menu
# set image no matter if user wants at this time emoticons or not
# (so toggle works ok)
@ -162,8 +175,27 @@ class ChatControlBase(MessageControl):
# Attach speller
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
try:
gtkspell.Spell(self.msg_textview)
except gobject.GError, msg:
spell = gtkspell.Spell(self.msg_textview)
# loop removing non-existant dictionaries
# iterating on a copy
for lang in dict(langs):
try:
spell.set_language(langs[lang])
except:
del langs[lang]
# now set the one the user selected
per_type = 'contacts'
if self.type_id == message_control.TYPE_GC:
per_type = 'rooms'
lang = gajim.config.get_per(per_type, self.contact.jid,
'speller_language')
if not lang:
# use the default one
lang = gajim.config.get('speller_language')
if lang:
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 '
@ -175,6 +207,48 @@ class ChatControlBase(MessageControl):
self.style_event_id = 0
self.conv_textview.tv.show()
# For JEP-0172
self.user_nick = None
def on_msg_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend an option to switch languages'''
def _on_select_dictionary(widget, lang):
per_type = 'contacts'
if self.type_id == message_control.TYPE_GC:
per_type = 'rooms'
if not gajim.config.get_per(per_type, self.contact.jid):
gajim.config.add_per(per_type, self.contact.jid)
gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
lang)
spell = gtkspell.get_from_text_view(self.msg_textview)
self.msg_textview.lang = lang
spell.set_language(lang)
widget.set_active(True)
item = gtk.SeparatorMenuItem()
menu.prepend(item)
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
item = gtk.MenuItem(_('Spelling language'))
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)
for lang in sorted(langs):
item = gtk.CheckMenuItem(lang)
if langs[lang] == self.msg_textview.lang:
item.set_active(True)
submenu.append(item)
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
def _on_banner_eventbox_button_press_event(self, widget, event):
'''If right-clicked, show popup'''
@ -251,7 +325,7 @@ class ChatControlBase(MessageControl):
if event.state & gtk.gdk.CONTROL_MASK:
# CTRL + l|L: clear conv_textview
if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
self.conv_textview.tv.get_buffer().set_text('')
self.conv_textview.clear()
return True
# CTRL + v: Paste into msg_textview
elif event.keyval == gtk.keysyms.v:
@ -425,13 +499,18 @@ class ChatControlBase(MessageControl):
if not message or message == '\n':
return
if not self._process_command(message):
MessageControl.send_message(self, message, keyID, type = type,
chatstate = chatstate, msg_id = msg_id,
composing_jep = composing_jep, resource = resource)
composing_jep = composing_jep, resource = resource,
user_nick = self.user_nick)
# Record message history
self.save_sent_message(message)
# Be sure to send user nickname only once according to JEP-0172
self.user_nick = None
# Clear msg input
message_buffer = self.msg_textview.get_buffer()
message_buffer.set_text('') # clear message buffer (and tv of course)
@ -473,12 +552,25 @@ class ChatControlBase(MessageControl):
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'):
self.nb_unread += 1
if gajim.interface.systray_enabled and self.notify_on_new_messages():
gajim.interface.systray.add_jid(full_jid, self.account, self.type_id)
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():
type_ = 'printed_' + self.type_id
if self.type_id == message_control.TYPE_GC:
type_ = 'printed_gc_msg'
show_in_roster = notify.get_show_in_roster('message_received',
self.account, self.contact)
show_in_systray = notify.get_show_in_systray('message_received',
self.account, self.contact)
event = gajim.events.create_event(type_, None,
show_in_roster = show_in_roster,
show_in_systray = show_in_systray)
gajim.events.add_event(self.account, full_jid, event)
# We need to redraw contact if we show in roster
if show_in_roster:
gajim.interface.roster.draw_contact(self.contact.jid,
self.account)
self.parent_win.redraw_tab(self)
if not self.parent_win.is_active():
ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
@ -503,6 +595,7 @@ class ChatControlBase(MessageControl):
else: # we are the beginning of buffer
buffer.insert_at_cursor('%s ' % str_)
self.msg_textview.grab_focus()
def on_emoticons_button_clicked(self, widget):
'''popup emoticons menu'''
gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
@ -510,12 +603,10 @@ class ChatControlBase(MessageControl):
def on_actions_button_clicked(self, widget):
'''popup action menu'''
#FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786
self.button_clicked = widget
menu = self.prepare_context_menu()
menu.show_all()
gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.parent_win)
gtkgui_helpers.popup_emoticons_under_button(menu, widget,
self.parent_win)
def update_font(self):
font = pango.FontDescription(gajim.config.get('conversation_font'))
@ -549,15 +640,24 @@ class ChatControlBase(MessageControl):
if state:
jid = self.contact.jid
if self.conv_textview.at_the_end():
#we are at the end
if self.nb_unread > 0:
self.nb_unread = self.get_specific_unread()
# we are at the end
type_ = 'printed_' + self.type_id
if self.type_id == message_control.TYPE_GC:
type_ = 'printed_gc_msg'
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()
if gajim.interface.systray_enabled:
gajim.interface.systray.remove_jid(self.get_full_jid(),
self.account,
self.type_id)
# 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.msg_textview.grab_focus()
# Note, we send None chatstate to preserve current
self.parent_win.redraw_tab(self)
@ -630,19 +730,28 @@ class ChatControlBase(MessageControl):
return True
def on_conversation_vadjustment_value_changed(self, widget):
if not self.nb_unread:
if self.resource:
jid = self.contact.get_full_jid()
else:
jid = self.contact.jid
type_ = self.type_id
if type_ == message_control.TYPE_GC:
type_ = 'gc_msg'
if not len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
type_])):
return
jid = self.contact.jid
if self.conv_textview.at_the_end() and \
self.parent_win.get_active_control() == self and \
self.parent_win.window.is_active():
#we are at the end
self.nb_unread = self.get_specific_unread()
self.parent_win.redraw_tab(self)
self.parent_win.show_title()
if gajim.interface.systray_enabled:
gajim.interface.systray.remove_jid(jid, self.account,
self.type_id)
# we are at the end
type_ = self.type_id
if type_ == message_control.TYPE_GC:
type_ = 'gc_msg'
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()
def sent_messages_scroll(self, direction, conv_buf):
size = len(self.sent_history)
@ -701,6 +810,7 @@ class ChatControlBase(MessageControl):
def got_disconnected(self):
self.msg_textview.set_sensitive(False)
self.msg_textview.set_editable(False)
self.conv_textview.tv.grab_focus()
self.xml.get_widget('send_button').set_sensitive(False)
################################################################################
@ -754,10 +864,12 @@ class ChatControl(ChatControlBase):
id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
self.handlers[id] = widget
widget = self.xml.get_widget('avatar_eventbox')
id = widget.connect('leave-notify-event', self.on_avatar_eventbox_leave_notify_event)
self.handlers[id] = widget
id = widget.connect('button-press-event', self.on_avatar_eventbox_button_press_event)
self.handlers[id] = widget
widget = self.xml.get_widget('gpg_togglebutton')
id = widget.connect('clicked', self.on_toggle_gpg_togglebutton)
self.handlers[id] = widget
@ -769,6 +881,8 @@ 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')
@ -803,6 +917,23 @@ class ChatControl(ChatControlBase):
if self.show_bigger_avatar_timeout_id is not None:
gobject.source_remove(self.show_bigger_avatar_timeout_id)
def on_avatar_eventbox_button_press_event(self, widget, event):
'''If right-clicked, show popup'''
if event.button == 3: # right click
menu = gtk.Menu()
menuitem = gtk.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.handlers[id] = menuitem
menu.append(menuitem)
menu.show_all()
menu.connect('selection-done', lambda w:w.destroy())
# show the menu
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
return True
def _on_window_motion_notify(self, widget, event):
'''it gets called no matter if it is the active window or not'''
if self.parent_win.get_active_jid() == self.contact.jid:
@ -874,17 +1005,27 @@ class ChatControl(ChatControlBase):
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 = 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
# 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:
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)
break
@ -901,9 +1042,9 @@ class ChatControl(ChatControlBase):
if cs and st in ('composing_only', 'all'):
if contact.show == 'offline':
chatstate = ''
elif st == 'all' and contact.composing_jep == 'JEP-0085':
elif contact.composing_jep == 'JEP-0085':
chatstate = helpers.get_uf_chatstate(cs)
elif st == 'composing_only' or contact.composing_jep == 'JEP-0022':
elif contact.composing_jep == 'JEP-0022':
if cs in ('composing', 'paused'):
# only print composing, paused
chatstate = helpers.get_uf_chatstate(cs)
@ -911,16 +1052,16 @@ class ChatControl(ChatControlBase):
chatstate = ''
elif chatstate is None:
chatstate = helpers.get_uf_chatstate(cs)
label_text = '<span %s>%s</span><span %s>%s %s</span>' % \
(font_attrs, name, font_attrs_small, acct_info, chatstate)
(font_attrs, name, font_attrs_small, acct_info, chatstate)
else:
# weight="heavy" size="x-large"
label_text = '<span %s>%s</span><span %s>%s</span>' % \
(font_attrs, name, font_attrs_small, acct_info)
(font_attrs, name, font_attrs_small, acct_info)
if status_escaped:
label_text += '\n<span %s>%s</span>' %\
(font_attrs_small, status_escaped)
(font_attrs_small, status_escaped)
banner_eventbox = self.xml.get_widget('banner_eventbox')
self.status_tooltip.set_tip(banner_eventbox, status)
self.status_tooltip.enable()
@ -928,7 +1069,7 @@ class ChatControl(ChatControlBase):
self.status_tooltip.disable()
# setup the label that holds name and jid
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())
@ -943,12 +1084,12 @@ class ChatControl(ChatControlBase):
tt = _('OpenPGP Encryption')
# restore gpg pref
gpg_pref = gajim.config.get_per('contacts',
self.contact.get_full_jid(), 'gpg_enabled')
gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
'gpg_enabled')
if gpg_pref == None:
gajim.config.add_per('contacts', self.contact.get_full_jid())
gpg_pref = gajim.config.get_per('contacts',
self.contact.get_full_jid(), 'gpg_enabled')
gajim.config.add_per('contacts', self.contact.jid)
gpg_pref = gajim.config.get_per('contacts', self.contact.jid,
'gpg_enabled')
tb.set_active(gpg_pref)
else:
@ -1108,7 +1249,12 @@ class ChatControl(ChatControlBase):
def get_tab_label(self, chatstate):
unread = ''
num_unread = self.nb_unread
if self.resource:
jid = self.contact.get_full_jid()
else:
jid = self.contact.jid
num_unread = len(gajim.events.get_events(self.account, jid,
['printed_' + self.type_id, self.type_id]))
if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'):
unread = '*'
elif num_unread > 1:
@ -1151,7 +1297,12 @@ class ChatControl(ChatControlBase):
return (label_str, color)
def get_tab_image(self):
num_unread = self.nb_unread
if self.resource:
jid = self.contact.get_full_jid()
else:
jid = self.contact.jid
num_unread = len(gajim.events.get_events(self.account, jid,
['printed_' + self.type_id, self.type_id]))
# Set tab image (always 16x16); unread messages show the 'message' image
tab_img = None
@ -1160,8 +1311,8 @@ class ChatControl(ChatControlBase):
self.contact.jid, icon_name = 'message')
tab_img = img_16['message']
else:
contact = gajim.contacts.get_contact_with_highest_priority(self.account,
self.contact.jid)
contact = gajim.contacts.get_contact_with_highest_priority(
self.account, self.contact.jid)
if not contact or self.resource:
# For transient contacts
contact = self.contact
@ -1334,10 +1485,12 @@ class ChatControl(ChatControlBase):
# Disconnect timer callbacks
gobject.source_remove(self.possible_paused_timeout_id)
gobject.source_remove(self.possible_inactive_timeout_id)
# Clean up systray
if gajim.interface.systray_enabled and self.nb_unread > 0:
gajim.interface.systray.remove_jid(self.contact.jid, self.account,
self.type_id)
# Remove bigger avatar window
if self.bigger_avatar_window:
self.bigger_avatar_window.destroy()
# Clean events
gajim.events.remove_events(self.account, self.get_full_jid(),
types = ['printed_' + self.type_id, self.type_id])
# remove all register handlers on wigets, created by self.xml
# to prevent circular references among objects
for i in self.handlers.keys():
@ -1354,7 +1507,7 @@ class ChatControl(ChatControlBase):
# 2 seconds
dialog = dialogs.ConfirmationDialog(
#%s is being replaced in the code with JID
_('You just received a new message from "%s"' % self.contact.jid),
_('You just received a new message from "%s"') % self.contact.jid,
_('If you close this tab and you have history disabled, '\
'this message will be lost.'))
if dialog.get_response() != gtk.RESPONSE_OK:
@ -1439,17 +1592,14 @@ class ChatControl(ChatControlBase):
if restore_how_many <= 0:
return
timeout = gajim.config.get('restore_timeout') # in minutes
# number of messages that are in queue and are already logged
pending_how_many = 0 # we want to avoid duplication
if gajim.awaiting_events[self.account].has_key(jid):
events = gajim.awaiting_events[self.account][jid]
for event in events:
if event[0] == 'chat':
pending_how_many += 1
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)
rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
pending_how_many, timeout)
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
@ -1465,10 +1615,14 @@ class ChatControl(ChatControlBase):
tim = time.localtime(float(row[0]))
if gajim.config.get('restored_messages_small'):
small_attr = ['small']
else:
small_attr = []
ChatControlBase.print_conversation_line(self, row[2], kind, name, tim,
['small'],
['small', 'restored_message'],
['small', 'restored_message'],
small_attr,
small_attr + ['restored_message'],
small_attr + ['restored_message'],
False, old_kind = local_old_kind)
if row[2].startswith('/me ') or row[2].startswith('/me\n'):
local_old_kind = None
@ -1483,7 +1637,7 @@ class ChatControl(ChatControlBase):
jid_with_resource = jid
if self.resource:
jid_with_resource += '/' + self.resource
l = gajim.awaiting_events[self.account][jid_with_resource]
events = gajim.events.get_events(self.account, jid_with_resource)
# Is it a pm ?
is_pm = False
@ -1491,15 +1645,12 @@ class ChatControl(ChatControlBase):
control = gajim.interface.msg_win_mgr.get_control(room_jid, self.account)
if control and control.type_id == message_control.TYPE_GC:
is_pm = True
events_to_keep = []
# list of message ids which should be marked as read
message_ids = []
for event in l:
typ = event[0]
if typ != 'chat':
events_to_keep.append(event)
for event in events:
if event.type_ != self.type_id:
continue
data = event[1]
data = event.parameters
kind = data[2]
if kind == 'error':
kind = 'info'
@ -1509,33 +1660,31 @@ class ChatControl(ChatControlBase):
encrypted = data[4], subject = data[1])
if len(data) > 6 and isinstance(data[6], int):
message_ids.append(data[6])
# remove from gc nb_unread if it's pm or from roster
if is_pm:
control.nb_unread -= 1
else:
gajim.interface.roster.nb_unread -= 1
if message_ids:
gajim.logger.set_read_messages(message_ids)
if is_pm:
control.parent_win.show_title()
else:
gajim.interface.roster.show_title()
# Keep only non-messages events
if len(events_to_keep):
gajim.awaiting_events[self.account][jid_with_resource] = events_to_keep
else:
del gajim.awaiting_events[self.account][jid_with_resource]
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'
gajim.interface.roster.draw_contact(jid, self.account)
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 gajim.interface.systray_enabled:
gajim.interface.systray.remove_jid(jid_with_resource, self.account, typ)
if (self.contact.show == 'offline' or self.contact.show == 'error'):
showOffline = gajim.config.get('showoffline')
if not showOffline and typ == 'chat' and \
@ -1548,6 +1697,9 @@ class ChatControl(ChatControlBase):
def show_bigger_avatar(self, small_avatar):
'''resizes the avatar, if needed, so it has at max half the screen size
and shows it'''
if not small_avatar.window:
# Tab has been closed since we hovered the avatar
return
is_fake = False
if self.type_id == message_control.TYPE_PM:
is_fake = True
@ -1562,7 +1714,7 @@ class ChatControl(ChatControlBase):
# It's why I set it transparent.
image = self.xml.get_widget('avatar_image')
pixbuf = image.get_pixbuf()
pixbuf.fill(0xffffff00) # RGBA
pixbuf.fill(0xffffff00L) # RGBA
image.queue_draw()
screen_w = gtk.gdk.screen_width()
@ -1612,6 +1764,7 @@ class ChatControl(ChatControlBase):
def _on_window_avatar_leave_notify_event(self, widget, event):
'''we just left the popup window that holds avatar'''
self.bigger_avatar_window.destroy()
self.bigger_avatar_window = None
# Re-show the small avatar
self.show_avatar()

View File

@ -138,7 +138,7 @@ else:
def verify(self, str, sign):
if not USE_GPG:
return str
if not str:
if str == None:
return ''
f = tmpfile()
fd = f.fileno()

View File

@ -6,19 +6,19 @@ HAVE_XSCRNSAVER = $(shell pkg-config --exists xscrnsaver && echo 'YES')
ifeq ($(HAVE_XSCRNSAVER),YES)
# We link with libXScrnsaver from modular X.Org X11
CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0 xscrnsaver` -fpic -I/usr/include/python$(PYTHONVER) -I.
LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0 xscrnsaver` -lpython$(PYTHONVER)
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
CFLAGS = `pkg-config --cflags gtk+-2.0 pygtk-2.0` -fpic -I/usr/include/python$(PYTHONVER) -I.
LDFLAGS = `pkg-config --libs gtk+-2.0 pygtk-2.0` -L/usr/X11R6$(LIBDIR) -lX11 \
-lXss -lXext -lpython$(PYTHONVER)
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) -shared idle.c $^ -o $@
$(CC) $(OPTFLAGS) $(CFLAGS) $(LDFLAGS) $(gtk_and_x_CFLAGS) $(gtk_and_x_LDFLAGS) -shared idle.c $^ -o $@
clean:
rm -f *.so

View File

@ -28,10 +28,6 @@ import stat
from common import gajim
import logger
import i18n
_ = i18n._
Q_ = i18n.Q_
from pysqlite2 import dbapi2 as sqlite # DO NOT MOVE ABOVE OF import gajim
@ -61,6 +57,11 @@ def create_log_db():
jid_id INTEGER
);
CREATE TABLE transports_cache (
transport TEXT UNIQUE,
type INTEGER
);
CREATE TABLE logs(
log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid_id INTEGER,

View File

@ -20,8 +20,6 @@
import sre
import copy
import i18n
_ = i18n._
(
@ -50,7 +48,7 @@ class Config:
'notify_on_signout': [ opt_bool, False ],
'notify_on_new_message': [ opt_bool, True ],
'autopopupaway': [ opt_bool, False ],
'use_notif_daemon': [ opt_bool, True , _('Use DBus and Notification-Daemon to show notifications') ],
'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 ],
'autoaway': [ opt_bool, True ],
@ -76,17 +74,19 @@ class Config:
'statusmsgcolor': [ opt_color, '#1eaa1e', '', True ],
'markedmsgcolor': [ opt_color, '#ff8080', '', True ],
'urlmsgcolor': [ opt_color, '#0000ff', '', True ],
'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed'), True ],
'collapsed_rows': [ opt_str, '', _('List (space separated) of rows (accounts and groups) that are collapsed.'), True ],
'roster_theme': [ opt_str, 'gtk+', '', True ],
'saveposition': [ opt_bool, True ],
'mergeaccounts': [ opt_bool, False, '', True ],
'sort_by_show': [ opt_bool, True, '', True ],
'use_speller': [ 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.') ],
'emoticons_theme': [opt_str, 'static', '', True ],
'ascii_formatting': [ opt_bool, True,
_('Treat * / _ pairs as possible formatting characters.'), True],
'show_ascii_formatting_chars': [ opt_bool, False , _('If True, do not '
'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not '
'remove */_ . So *abc* will be bold but with * * not removed.')],
'sounds_on': [ opt_bool, True ],
# 'aplay', 'play', 'esdplay', 'artsplay' detected first time only
@ -95,12 +95,12 @@ class Config:
'custombrowser': [ opt_str, 'firefox' ],
'custommailapp': [ opt_str, 'mozilla-thunderbird -compose' ],
'custom_file_manager': [ opt_str, 'xffm' ],
'gc-hpaned-position': [opt_int, 540],
'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat')],
'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat')],
'gc-hpaned-position': [opt_int, 430],
'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nick completion (tab) in group chat.')],
'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when desired nickname is used by someone else in group chat.')],
'msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
'msgwin-width': [opt_int, 480],
'msgwin-width': [opt_int, 500],
'msgwin-height': [opt_int, 440],
'chat-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'chat-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
@ -108,7 +108,7 @@ class Config:
'chat-msgwin-height': [opt_int, 440],
'gc-msgwin-x-position': [opt_int, -1], # Default is to let the window manager decide
'gc-msgwin-y-position': [opt_int, -1], # Default is to let the window manager decide
'gc-msgwin-width': [opt_int, 480],
'gc-msgwin-width': [opt_int, 600],
'gc-msgwin-height': [opt_int, 440],
'single-msg-x-position': [opt_int, 0],
'single-msg-y-position': [opt_int, 0],
@ -126,6 +126,7 @@ class Config:
'after_nickname': [ opt_str, ':' ],
'send_os_info': [ opt_bool, True ],
'notify_on_new_gmail_email': [ opt_bool, True ],
'notify_on_new_gmail_email_extra': [ opt_bool, False ],
'usegpg': [ opt_bool, False, '', True ],
'use_gpg_agent': [ opt_bool, False ],
'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
@ -134,7 +135,7 @@ 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.0.1' ], # which version created the config
'version': [ opt_str, '0.10.1.3' ], # 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],
@ -142,7 +143,7 @@ class Config:
'remote_control': [opt_bool, True, _('If checked, Gajim can be controlled remotely using gajim-remote.'), 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')],
'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.')],
@ -177,29 +178,33 @@ class Config:
'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.')],
'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window'), True],
'show_status_msgs_in_roster': [opt_bool, True, _('If True, Gajim will display the status message, if not empty, for every contact under the contact name in roster window.'), True],
'show_avatars_in_roster': [opt_bool, True, '', True],
'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 room.')],
'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],
'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 groupchat presences')],
'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.')],
'one_message_window': [opt_str, 'always',
_('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')],
'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window')],
'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window')],
'always_hide_groupchat_buttons': [opt_bool, False, _('Hides the buttons in group chat window')],
'always_hide_chat_buttons': [opt_bool, False, _('Hides the buttons in two persons chat window')],
#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.')],
'show_avatar_in_chat': [opt_bool, True, _('If False, you will no longer see the avatar in the chat window.')],
'escape_key_closes': [opt_bool, True, _('If True, pressing the escape key closes a tab/window.')],
'always_hide_groupchat_buttons': [opt_bool, False, _('Hides the buttons in group chat window.')],
'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 groupchat window')],
'chat_merge_consecutive_nickname': [opt_bool, False, _('Merge consecutive nickname in chat window')],
'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickame')],
'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.')],
'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.')],
'zeroconf_enabled': [opt_bool, True, _('Enable zeroconf network')],
}
@ -246,6 +251,10 @@ class Config:
'statusmsg': ({
'message': [ opt_str, '' ],
}, {}),
'defaultstatusmsg': ({
'enabled': [ opt_bool, False ],
'message': [ opt_str, '' ],
}, {}),
'soundevents': ({
'enabled': [ opt_bool, True ],
'path': [ opt_str, '' ],
@ -288,7 +297,27 @@ class Config:
'state_muc_directed_msg_color': [ opt_color, 'red2' ],
}, {}),
'contacts': ({
'gpg_enabled': [ opt_bool, True ],
'gpg_enabled': [ opt_bool, True, _('Is OpenPGP enabled for this contact?')],
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
}, {}),
'rooms': ({
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
}, {}),
'notifications': ({
'event': [opt_str, ''],
'recipient_type': [opt_str, 'all'],
'recipients': [opt_str, ''],
'status': [opt_str, 'all', _('all or space separated status')],
'tab_opened': [opt_str, 'both', _("'yes', 'no', or 'both'")],
'sound': [opt_str, '', _("'yes', 'no' or ''")],
'sound_file': [opt_str, ''],
'popup': [opt_str, '', _("'yes', 'no' or ''")],
'auto_open': [opt_str, '', _("'yes', 'no' or ''")],
'run_command': [opt_bool, False],
'command': [opt_str, ''],
'systray': [opt_str, '', _("'yes', 'no' or ''")],
'roster': [opt_str, '', _("'yes', 'no' or ''")],
'urgency_hint': [opt_bool, False],
}, {}),
}
@ -302,6 +331,16 @@ class Config:
_('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') ],
'invisible': [ False, _('Bye!') ],
'offline': [ False, _('Bye!') ],
}
soundevents_default = {
'first_message_received': [ True, '../data/sounds/message1.wav' ],
'next_message_received': [ True, '../data/sounds/message2.wav' ],
@ -515,3 +554,8 @@ class Config:
self.set_per('soundevents', event, 'enabled', default[0])
self.set_per('soundevents', event, 'path', default[1])
for status in self.defaultstatusmsg_default:
default = self.defaultstatusmsg_default[status]
self.add_per('defaultstatusmsg', status)
self.set_per('defaultstatusmsg', status, 'enabled', default[0])
self.set_per('defaultstatusmsg', status, 'message', default[1])

View File

@ -42,9 +42,6 @@ from common import GnuPG
from connection_handlers import *
USE_GPG = GnuPG.USE_GPG
from common import i18n
_ = i18n._
class Connection(ConnectionHandlers):
'''Connection class'''
def __init__(self, name):
@ -88,8 +85,12 @@ class Connection(ConnectionHandlers):
self.on_connect_failure = None
self.retrycount = 0
self.jids_for_auto_auth = [] # list of jid to auto-authorize
self.muc_jid = {} # jid of muc server for each transport type
self.available_transports = {} # list of available transports on this
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
self.vcard_supported = True
# END __init__
def put_event(self, ev):
if gajim.handlers.has_key(ev[0]):
gajim.handlers[ev[0]](self.name, ev[1])
@ -137,22 +138,21 @@ class Connection(ConnectionHandlers):
if not self.on_purpose:
self.disconnect()
if gajim.config.get_per('accounts', self.name, 'autoreconnect') \
and self.retrycount <= 10:
and self.retrycount <= 10:
self.connected = 1
self.dispatch('STATUS', 'connecting')
self.time_to_reconnect = 10
# this check has moved from _reconnect method
if self.retrycount > 5:
self.time_to_reconnect = 20
else:
self.time_to_reconnect = 10
gajim.idlequeue.set_alarm(self._reconnect_alarm,
self.time_to_reconnect)
gajim.idlequeue.set_alarm(self._reconnect_alarm,
self.time_to_reconnect)
elif self.on_connect_failure:
self.on_connect_failure()
self.on_connect_failure = None
else:
# show error dialog
# show error dialog
self._connection_lost()
else:
self.disconnect()
@ -162,9 +162,9 @@ class Connection(ConnectionHandlers):
def _connection_lost(self):
self.disconnect(on_purpose = False)
self.dispatch('STATUS', 'offline')
self.dispatch('ERROR',
(_('Connection with account "%s" has been lost') % self.name,
_('To continue sending and receiving messages, you will need to reconnect.')))
self.dispatch('CONNECTION_LOST',
(_('Connection with account "%s" has been lost') % self.name,
_('To continue sending and receiving messages, you will need to reconnect.')))
def _event_dispatcher(self, realm, event, data):
if realm == common.xmpp.NS_REGISTER:
@ -211,6 +211,34 @@ class Connection(ConnectionHandlers):
else:
conf = data[1].asDict()
self.dispatch('REGISTER_AGENT_INFO', (data[0], conf, is_form))
elif realm == common.xmpp.NS_PRIVACY:
if event == common.xmpp.features_nb.PRIVACY_LISTS_RECEIVED:
# data is (list)
self.dispatch('PRIVACY_LISTS_RECEIVED', (data))
elif event == common.xmpp.features_nb.PRIVACY_LIST_RECEIVED:
# data is (resp)
if not data:
return
rules = []
name = data.getTag('query').getTag('list').getAttr('name')
for child in data.getTag('query').getTag('list').getChildren():
dict_item = child.getAttrs()
childs = []
if dict_item.has_key('type'):
for scnd_child in child.getChildren():
childs += [scnd_child.getName()]
rules.append({'action':dict_item['action'],
'type':dict_item['type'], 'order':dict_item['order'],
'value':dict_item['value'], 'child':childs})
else:
for scnd_child in child.getChildren():
childs.append(scnd_child.getName())
rules.append({'action':dict_item['action'],
'order':dict_item['order'], 'child':childs})
self.dispatch('PRIVACY_LIST_RECEIVED', (name, rules))
elif event == common.xmpp.features_nb.PRIVACY_LISTS_ACTIVE_DEFAULT:
# data is (dict)
self.dispatch('PRIVACY_LISTS_ACTIVE_DEFAULT', (data))
elif realm == '':
if event == common.xmpp.transports.DATA_RECEIVED:
self.dispatch('STANZA_ARRIVED', unicode(data, errors = 'ignore'))
@ -360,7 +388,8 @@ class Connection(ConnectionHandlers):
if not self.retrycount and self.connected != 0:
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
self.dispatch('CONNECTION_LOST',
(_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later.')))
def _connect_success(self, con, con_type):
@ -396,7 +425,8 @@ class Connection(ConnectionHandlers):
if not con:
self.disconnect(on_purpose = True)
self.dispatch('STATUS', 'offline')
self.dispatch('ERROR', (_('Could not connect to "%s"') % self._hostname,
self.dispatch('CONNECTION_LOST',
(_('Could not connect to "%s"') % self._hostname,
_('Check your connection or try again later')))
if self.on_connect_auth:
self.on_connect_auth(None)
@ -433,6 +463,41 @@ class Connection(ConnectionHandlers):
if kill_core and self.connected > 1:
self.disconnect(on_purpose = True)
def get_privacy_lists(self):
if not self.connection:
return
common.xmpp.features_nb.getPrivacyLists(self.connection)
def get_active_default_lists(self):
if not self.connection:
return
common.xmpp.features_nb.getActiveAndDefaultPrivacyLists(self.connection)
def del_privacy_list(self, privacy_list):
if not self.connection:
return
common.xmpp.features_nb.delPrivacyList(self.connection, privacy_list)
def get_privacy_list(self, title):
if not self.connection:
return
common.xmpp.features_nb.getPrivacyList(self.connection, title)
def set_privacy_list(self, listname, tags):
if not self.connection:
return
common.xmpp.features_nb.setPrivacyList(self.connection, listname, tags)
def set_active_list(self, listname):
if not self.connection:
return
common.xmpp.features_nb.setActivePrivacyList(self.connection, listname, 'active')
def set_default_list(self, listname):
if not self.connection:
return
common.xmpp.features_nb.setDefaultPrivacyList(self.connection, listname)
def build_privacy_rule(self, name, action):
'''Build a Privacy rule stanza for invisibility'''
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
@ -442,6 +507,8 @@ class Connection(ConnectionHandlers):
return iq
def activate_privacy_rule(self, name):
if not self.connection:
return
'''activate a privacy rule'''
iq = common.xmpp.Iq('set', common.xmpp.NS_PRIVACY, xmlns = '')
iq.getTag('query').setTag('active', {'name': name})
@ -528,7 +595,7 @@ class Connection(ConnectionHandlers):
# Ask metacontacts before roster
self.get_metacontacts()
def change_status(self, show, msg, sync = False, auto = False):
def change_status(self, show, msg, auto = False):
if not show in STATUS_LIST:
return -1
sshow = helpers.get_xmpp_show(show)
@ -607,7 +674,8 @@ class Connection(ConnectionHandlers):
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):
chatstate = None, msg_id = None, composing_jep = None, resource = None,
user_nick = None):
if not self.connection:
return
if not msg and chatstate is None:
@ -638,6 +706,11 @@ class Connection(ConnectionHandlers):
if msgenc:
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
# JEP-0172: user_nickname
if user_nick:
msg_iq.setTag('nick', namespace = common.xmpp.NS_NICK).setData(
user_nick)
# 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
@ -692,7 +765,7 @@ class Connection(ConnectionHandlers):
self.connection.send(p)
def request_subscription(self, jid, msg = '', name = '', groups = [],
auto_auth = False):
auto_auth = False, user_nick = ''):
if not self.connection:
return
gajim.log.debug('subscription request for %s' % jid)
@ -710,10 +783,11 @@ class Connection(ConnectionHandlers):
self.connection.send(iq)
p = common.xmpp.Presence(jid, 'subscribe')
if user_nick:
p.setTag('nick', namespace = common.xmpp.NS_NICK).setData(user_nick)
p = self.add_sha(p)
if not msg:
msg = _('I would like to add you to my roster.')
p.setStatus(msg)
if msg:
p.setStatus(msg)
self.connection.send(p)
def send_authorization(self, jid):
@ -796,6 +870,10 @@ class Connection(ConnectionHandlers):
def request_os_info(self, jid, resource):
if not self.connection:
return
# If we are invisible, do not request
if self.connected == gajim.SHOW_LIST.index('invisible'):
self.dispatch('OS_INFO', (jid, resource, _('Not fetched because of invisible status'), _('Not fetched because of invisible status')))
return
to_whom_jid = jid
if resource:
to_whom_jid += '/' + resource
@ -852,6 +930,9 @@ class Connection(ConnectionHandlers):
iq = common.xmpp.Iq(typ='get')
iq2 = iq.addChild(name='query', namespace='jabber:iq:private')
iq2.addChild(name='storage', namespace='storage:metacontacts')
id = self.connection.getAnID()
iq.setID(id)
self.awaiting_answers[id] = (METACONTACTS_ARRIVED, )
self.connection.send(iq)
def store_metacontacts(self, tags_list):
@ -873,7 +954,8 @@ class Connection(ConnectionHandlers):
def send_agent_status(self, agent, ptype):
if not self.connection:
return
p = common.xmpp.Presence(to = agent, typ = ptype)
show = helpers.get_xmpp_show(STATUS_LIST[self.connected])
p = common.xmpp.Presence(to = agent, typ = ptype, show = show)
p = self.add_sha(p, ptype != 'unavailable')
self.connection.send(p)

View File

@ -24,6 +24,7 @@ import sha
import socket
import sys
from time import localtime, strftime, gmtime
from calendar import timegm
import socks5
@ -32,8 +33,6 @@ import common.xmpp
from common import GnuPG
from common import helpers
from common import gajim
from common import i18n
_ = i18n._
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
@ -41,6 +40,7 @@ STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
VCARD_PUBLISHED = 'vcard_published'
VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed'
METACONTACTS_ARRIVED = 'metacontacts_arrived'
HAS_IDLE = True
try:
import common.idle as idle # when we launch gajim from sources
@ -90,7 +90,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
@ -169,12 +169,18 @@ class ConnectionBytestream:
file_props['sha_str'] = sha_str
if not ft_override_host_to_send:
ft_override_host_to_send = self.peerhost[0]
ft_override_host_to_send = socket.gethostbyname(ft_override_host_to_send)
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(self.peerhost[0], 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
@ -216,8 +222,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)
@ -309,8 +315,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']]
@ -339,7 +345,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):
@ -556,7 +562,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:
@ -580,8 +586,8 @@ class ConnectionDisco:
iq.setID(id)
# Wait the answer during 30 secondes
self.awaiting_timeouts[gajim.idlequeue.current_time() + 30] = (id,
_('Registration information for transport %s has not arrived in time' % \
agent))
_('Registration information for transport %s has not arrived in time') % \
agent)
self.connection.SendAndCallForResponse(iq, self._ReceivedRegInfo,
{'agent': agent})
@ -726,17 +732,28 @@ class ConnectionDisco:
qc = iq_obj.getQueryChildren()
if not qc:
qc = []
is_muc = False
transport_type = ''
for i in qc:
if i.getName() == 'identity':
attr = {}
for key in i.getAttrs().keys():
attr[key] = i.getAttr(key)
if attr.has_key('category') and attr['category'] in ('gateway', 'headline')\
and attr.has_key('type'):
transport_type = attr['type']
if attr.has_key('category') and attr['category'] == 'conference' \
and attr.has_key('type') and attr['type'] == 'text':
is_muc = True
identities.append(attr)
elif i.getName() == 'feature':
features.append(i.getAttr('var'))
elif i.getName() == 'x' and i.getAttr('xmlns') == common.xmpp.NS_DATA:
elif i.getName() == 'x' and i.getNamespace() == common.xmpp.NS_DATA:
data.append(common.xmpp.DataForm(node=i))
jid = helpers.get_full_jid_from_iq(iq_obj)
if transport_type and jid not in gajim.transport_type:
gajim.transport_type[jid] = transport_type
gajim.logger.save_transport_type(jid, transport_type)
id = iq_obj.getID()
if not identities: # ejabberd doesn't send identities when we browse online users
#FIXME: see http://www.jabber.ru/bugzilla/show_bug.cgi?id=225
@ -744,6 +761,14 @@ class ConnectionDisco:
if id[0] == 'p':
if features.__contains__(common.xmpp.NS_BYTESTREAM):
gajim.proxy65_manager.resolve(jid, self.connection, self.name)
if features.__contains__(common.xmpp.NS_MUC) and is_muc:
type_ = transport_type or 'jabber'
self.muc_jid[type_] = jid
if transport_type:
if self.available_transports.has_key(transport_type):
self.available_transports[transport_type].append(jid)
else:
self.available_transports[transport_type] = [jid]
self.dispatch('AGENT_INFO_INFO', (jid, node, identities,
features, data))
@ -851,7 +876,10 @@ class ConnectionVcard:
id = self.connection.getAnID()
iq.setID(id)
self.awaiting_answers[id] = (VCARD_ARRIVED, jid)
j = jid
if not j:
j = gajim.get_jid_from_account(self.name)
self.awaiting_answers[id] = (VCARD_ARRIVED, j)
if is_fake_jid:
room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
if not room_jid in self.room_jids:
@ -939,9 +967,12 @@ class ConnectionVcard:
elif self.awaiting_answers[id][0] == VCARD_ARRIVED:
# If vcard is empty, we send to the interface an empty vcard so that
# it knows it arrived
if not iq_obj.getTag('vCard'):
jid = self.awaiting_answers[id][1]
our_jid = gajim.get_jid_from_account(self.name)
jid = self.awaiting_answers[id][1]
our_jid = gajim.get_jid_from_account(self.name)
if iq_obj.getType() == 'error' and jid == our_jid:
# our server doesn't support vcard
self.vcard_supported = False
if not iq_obj.getTag('vCard') or iq_obj.getType() == 'error':
if jid and jid != our_jid:
# Write an empty file
self.save_vcard_to_hd(jid, '')
@ -951,6 +982,29 @@ class ConnectionVcard:
elif self.awaiting_answers[id][0] == AGENT_REMOVED:
jid = self.awaiting_answers[id][1]
self.dispatch('AGENT_REMOVED', jid)
elif self.awaiting_answers[id][0] == METACONTACTS_ARRIVED:
if iq_obj.getType() == 'result':
# Metacontact tags
# http://www.jabber.org/jeps/jep-XXXX.html
meta_list = {}
query = iq_obj.getTag('query')
storage = query.getTag('storage')
metas = storage.getTags('meta')
for meta in metas:
jid = meta.getAttr('jid')
tag = meta.getAttr('tag')
data = {'jid': jid}
order = meta.getAttr('order')
if order != None:
data['order'] = order
if meta_list.has_key(tag):
meta_list[tag].append(data)
else:
meta_list[tag] = [data]
self.dispatch('METACONTACTS', meta_list)
# We can now continue connection by requesting the roster
self.connection.initRoster()
del self.awaiting_answers[id]
def _vCardCB(self, con, vc):
@ -1051,6 +1105,9 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# keep the jids we auto added (transports contacts) to not send the
# SUBSCRIBED event to gui
self.automatically_added = []
# keep the latest subscribed event for each jid to prevent loop when we
# acknoledge presences
self.subscribed_events = {}
try:
idle.init()
except:
@ -1077,7 +1134,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
raise common.xmpp.NodeProcessed
def _ErrorCB(self, con, iq_obj):
errmsg = iq_obj.getError()
errmsg = iq_obj.getErrorMsg()
errcode = iq_obj.getErrorCode()
jid_from = helpers.get_full_jid_from_iq(iq_obj)
id = unicode(iq_obj.getID())
@ -1113,26 +1170,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
self.bookmarks.append(bm)
self.dispatch('BOOKMARKS', self.bookmarks)
elif ns == 'storage:metacontacts':
# Metacontact tags
# http://www.jabber.org/jeps/jep-XXXX.html
meta_list = {}
metas = storage.getTags('meta')
for meta in metas:
jid = meta.getAttr('jid')
tag = meta.getAttr('tag')
data = {'jid': jid}
order = meta.getAttr('order')
if order != None:
data['order'] = order
if meta_list.has_key(tag):
meta_list[tag].append(data)
else:
meta_list[tag] = [data]
self.dispatch('METACONTACTS', meta_list)
# We can now continue connection by requesting the roster
self.connection.initRoster()
elif ns == 'gajim:prefs':
# Preferences data
# http://www.jabber.org/jeps/jep-0049.html
@ -1215,6 +1252,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
self.dispatch('OS_INFO', (jid_stripped, resource, client_info, os_info))
def _TimeCB(self, con, iq_obj):
gajim.log.debug('TimeCB')
iq_obj = iq_obj.buildReply('result')
qp = iq_obj.getTag('query')
qp.setTagData('utc', strftime("%Y%m%dT%T", gmtime()))
qp.setTagData('tz', strftime("%Z", gmtime()))
qp.setTagData('display', strftime("%c", localtime()))
self.connection.send(iq_obj)
raise common.xmpp.NodeProcessed
def _gMailNewMailCB(self, con, gm):
'''Called when we get notified of new mail messages in gmail account'''
@ -1239,9 +1286,20 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
newmsgs = gm.getTag('mailbox').getAttr('total-matched')
if newmsgs != '0':
# there are new messages
gmail_messages_list = []
if gm.getTag('mailbox').getTag('mail-thread-info'):
gmail_messages = gm.getTag('mailbox').getTags('mail-thread-info')
for gmessage in gmail_messages:
gmail_from = gmessage.getTag('senders').getTag('sender').getAttr('address')
gmail_subject = gmessage.getTag('subject').getData()
gmail_snippet = gmessage.getTag('snippet').getData()
gmail_messages_list.append({ \
'From': gmail_from, \
'Subject': gmail_subject, \
'Snippet': gmail_snippet})
jid = gajim.get_jid_from_account(self.name)
gajim.log.debug(('You have %s new gmail e-mails on %s.') % (newmsgs, jid))
self.dispatch('GMAIL_NOTIFY', (jid, newmsgs))
self.dispatch('GMAIL_NOTIFY', (jid, newmsgs, gmail_messages_list))
raise common.xmpp.NodeProcessed
def _messageCB(self, con, msg):
@ -1295,7 +1353,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
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()
@ -1325,9 +1387,11 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# 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))
if self.name not in no_log_for and jid in self.last_history_line \
and not int(float(time.mktime(tim))) <= \
has_timestamp = False
if msg.timestamp:
has_timestamp = True
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp))
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)
elif mtype == 'chat': # it's type 'chat'
@ -1338,7 +1402,7 @@ 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))
chatstate, msg_id, composing_jep, user_nick))
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,
@ -1352,7 +1416,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
else:
self.dispatch('MSG', (frm, msgtxt, tim, encrypted, 'normal',
subject, chatstate, msg_id, composing_jep))
subject, chatstate, msg_id, composing_jep, user_nick))
# END messageCB
def _presenceCB(self, con, prs):
@ -1361,27 +1425,42 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
if ptype == 'available':
ptype = None
gajim.log.debug('PresenceCB: %s' % ptype)
who = helpers.get_full_jid_from_iq(prs)
try:
who = helpers.get_full_jid_from_iq(prs)
except:
if prs.getTag('error').getTag('jid-malformed'):
# wrong jid, we probably tried to change our nick in a room to a non valid
# 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, False))
return
jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who)
timestamp = None
is_gc = False # is it a GC presence ?
sigTag = None
avatar_sha = None
# JEP-0172 User Nickname
user_nick = prs.getTagData('nick')
if not user_nick:
user_nick = ''
transport_auto_auth = False
xtags = prs.getTags('x')
for x in xtags:
if x.getNamespace().startswith(common.xmpp.NS_MUC):
namespace = x.getNamespace()
if namespace.startswith(common.xmpp.NS_MUC):
is_gc = True
if x.getNamespace() == common.xmpp.NS_SIGNED:
if namespace == common.xmpp.NS_SIGNED:
sigTag = x
if x.getNamespace() == common.xmpp.NS_VCARD_UPDATE:
if namespace == common.xmpp.NS_VCARD_UPDATE:
avatar_sha = x.getTagData('photo')
if x.getNamespace() == common.xmpp.NS_DELAY:
if namespace == common.xmpp.NS_DELAY:
# JEP-0091
tim = prs.getTimestamp()
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
timestamp = time.localtime(timegm(tim))
if x.getNamespace() == 'http://delx.cjb.net/protocol/roster-subsync':
if namespace == 'http://delx.cjb.net/protocol/roster-subsync':
# see http://trac.gajim.org/ticket/326
agent = gajim.get_server_from_jid(jid_stripped)
if self.connection.getRoster().getItem(agent): # to be sure it's a transport contact
@ -1389,7 +1468,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
no_log_for = gajim.config.get_per('accounts', self.name,
'no_log_for').split()
status = prs.getStatus()
status = prs.getStatus() or ''
show = prs.getShow()
if not show in STATUS_LIST:
show = '' # We ignore unknown show
@ -1448,7 +1527,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
if not ptype or ptype == 'unavailable':
if gajim.config.get('log_contact_status_changes') and self.name\
not in no_log_for and jid_stripped not in no_log_for:
gajim.logger.write('gcstatus', who, status, show)
gc_c = gajim.contacts.get_gc_contact(self.name, jid_stripped, resource)
st = status or ''
if gc_c:
jid = gc_c.jid
else:
jid = prs.getJid()
if jid:
# we know real jid, save it in db
st += ' (%s)' % jid
gajim.logger.write('gcstatus', who, st, show)
if avatar_sha:
if self.vcard_shas.has_key(who):
if avatar_sha != self.vcard_shas[who]:
@ -1475,23 +1563,49 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
resource, prio, keyID, timestamp))
if transport_auto_auth:
self.automatically_added.append(jid_stripped)
self.request_subscription(jid_stripped)
self.request_subscription(jid_stripped, name = user_nick)
else:
if not status:
status = _('I would like to add you to my roster.')
self.dispatch('SUBSCRIBE', (who, status))
self.dispatch('SUBSCRIBE', (who, status, user_nick))
elif ptype == 'subscribed':
if jid_stripped in self.automatically_added:
self.automatically_added.remove(jid_stripped)
else:
self.dispatch('SUBSCRIBED', (jid_stripped, resource))
# detect a subscription loop
if not self.subscribed_events.has_key(jid_stripped):
self.subscribed_events[jid_stripped] = []
self.subscribed_events[jid_stripped].append(time.time())
block = False
if len(self.subscribed_events[jid_stripped]) > 5:
if time.time() - self.subscribed_events[jid_stripped][0] < 5:
block = True
self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
if block:
gajim.config.set_per('account', self.name,
'dont_ack_subscription', True)
else:
self.dispatch('SUBSCRIBED', (jid_stripped, resource))
# BE CAREFUL: no con.updateRosterItem() in a callback
gajim.log.debug(_('we are now subscribed to %s') % who)
elif ptype == 'unsubscribe':
gajim.log.debug(_('unsubscribe request from %s') % who)
elif ptype == 'unsubscribed':
gajim.log.debug(_('we are now unsubscribed from %s') % who)
self.dispatch('UNSUBSCRIBED', jid_stripped)
# detect a unsubscription loop
if not self.subscribed_events.has_key(jid_stripped):
self.subscribed_events[jid_stripped] = []
self.subscribed_events[jid_stripped].append(time.time())
block = False
if len(self.subscribed_events[jid_stripped]) > 5:
if time.time() - self.subscribed_events[jid_stripped][0] < 5:
block = True
self.subscribed_events[jid_stripped] = self.subscribed_events[jid_stripped][1:]
if block:
gajim.config.set_per('account', self.name, 'dont_ack_subscription',
True)
else:
self.dispatch('UNSUBSCRIBED', jid_stripped)
elif ptype == 'error':
errmsg = prs.getError()
errcode = prs.getErrorCode()
@ -1650,13 +1764,18 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
print >> sys.stderr, _('JID %s is not RFC compliant. It will not be added to your roster. Use roster management tools such as http://jru.jabberstudio.org/ to remove it') % jid
else:
infos = raw_roster[jid]
if jid != our_jid and (not infos['subscription'] or infos['subscription'] == \
'none') and (not infos['ask'] or infos['ask'] == 'none') and not infos['name'] \
and not infos['groups']:
if jid != our_jid and (not infos['subscription'] or \
infos['subscription'] == 'none') and (not infos['ask'] or \
infos['ask'] == 'none') and not infos['name'] and \
not infos['groups']:
# remove this useless item, it won't be shown in roster anyway
self.connection.getRoster().delItem(jid)
elif jid != our_jid: # don't add our jid
roster[j] = raw_roster[jid]
if gajim.jid_is_transport(jid) and \
not gajim.get_transport_name_from_jid(jid):
# we can't determine which iconset to use
self.discoverInfo(jid)
self.dispatch('ROSTER', roster)
@ -1694,7 +1813,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
# If it's a gmail account,
# inform the server that we want e-mail notifications
if gajim.get_server_from_jid(our_jid) == 'gmail.com':
if gajim.get_server_from_jid(our_jid) in gajim.gmail_domains:
gajim.log.debug(('%s is a gmail account. Setting option '
'to get e-mail notifications on the server.') % (our_jid))
iq = common.xmpp.Iq(typ = 'set', to = our_jid)
@ -1748,6 +1867,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
common.xmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._VersionCB, 'get',
common.xmpp.NS_VERSION)
con.RegisterHandler('iq', self._TimeCB, 'get',
common.xmpp.NS_TIME)
con.RegisterHandler('iq', self._LastCB, 'get',
common.xmpp.NS_LAST)
con.RegisterHandler('iq', self._LastResultCB, 'result',
@ -1762,8 +1883,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
common.xmpp.NS_ROSTER)
con.RegisterHandler('iq', self._PrivateCB, 'result',
common.xmpp.NS_PRIVATE)
con.RegisterHandler('iq', self._PrivateErrorCB, 'error',
common.xmpp.NS_PRIVATE)
con.RegisterHandler('iq', self._HttpAuthCB, 'get',
common.xmpp.NS_HTTP_AUTH)
con.RegisterHandler('iq', self._gMailNewMailCB, 'set',

View File

@ -210,7 +210,7 @@ class Contacts:
contacts = self.get_contacts_from_jid(account, jid)
if not contacts and '/' in jid:
# jid may be a fake jid, try it
room, nick = jid.split('/')
room, nick = jid.split('/', 1)
contact = self.get_gc_contact(account, room, nick)
return contact
return self.get_highest_prio_contact_from_contacts(contacts)
@ -324,8 +324,11 @@ class Contacts:
max_order = data_['order']
contact = self.get_contact_with_highest_priority(account, jid)
score = (max_order - order)*10000
if not common.gajim.jid_is_transport(jid):
score += contact.priority*10
if common.gajim.get_transport_name_from_jid(jid) is None:
score += 10
if contact.priority > 0:
score += contact.priority * 10
score += ['not in roster', 'error', 'offline', 'invisible', 'dnd', 'xa',
'away', 'chat', 'online', 'requested', 'message'].index(contact.show)
return score

233
src/common/events.py Normal file
View File

@ -0,0 +1,233 @@
## common/events.py
##
## Contributors for this file:
## - Yann Le Boulanger <asterix@lagaule.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>
##
## 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 time
import gajim
class Event:
'''Information concerning each event'''
def __init__(self, type_, time_, parameters, show_in_roster = False,
show_in_systray = True):
''' type_ in chat, normal, file-request, file-error, file-completed,
file-request-error, file-send-error, file-stopped, gc_msg, pm,
printed_chat, printed_gc_msg, printed_pm
parameters is (per type_):
chat, normal: [message, subject, kind, time, encrypted, resource,
msg_id]
where kind in error, incoming
file-*: file_props
gc_msg: None
printed_*: None
messages that are already printed in chat, but not read'''
self.type_ = type_
self.time_ = time_
self.parameters = parameters
self.show_in_roster = show_in_roster
self.show_in_systray = show_in_systray
class Events:
'''Information concerning all events'''
def __init__(self):
self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
def change_account_name(self, old_name, new_name):
if self._events.has_key(old_name):
self._events[new_name] = self._events[old_name]
del self._events[old_name]
def add_account(self, account):
self._events[account] = {}
def get_accounts(self):
return self._events.keys()
def remove_account(self, account):
del self._events[account]
def create_event(self, type_, parameters, time_ = time.time(),
show_in_roster = False, show_in_systray = True):
return Event(type_, time_, parameters, show_in_roster,
show_in_systray)
def add_event(self, account, jid, event):
# No such account before ?
if not self._events.has_key(account):
self._events[account] = {jid: [event]}
# no such jid before ?
elif not self._events[account].has_key(jid):
self._events[account][jid] = [event]
else:
self._events[account][jid].append(event)
if event.show_in_systray and gajim.interface.systray_capabilities:
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,
optionnaly only from given type
return True if no such event found'''
if not self._events.has_key(account):
return True
if not self._events[account].has_key(jid):
return True
if event: # remove only one event
if event in self._events[account][jid]:
if len(self._events[account][jid]) == 1:
del self._events[account][jid]
else:
self._events[account][jid].remove(event)
if event.show_in_systray and gajim.interface.systray_capabilities:
gajim.interface.systray.set_img()
return
else:
return True
if types:
new_list = [] # list of events to keep
for ev in self._events[account][jid]:
if ev.type_ not in types:
new_list.append(ev)
if len(new_list) == len(self._events[account][jid]):
return True
if new_list:
self._events[account][jid] = new_list
else:
del self._events[account][jid]
if gajim.interface.systray_capabilities:
gajim.interface.systray.set_img()
return
# no event nor type given, remove them all
del self._events[account][jid]
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_events(self, account, jid = None, types = []):
'''if event is not speficied, remove all events from this jid,
optionnaly only from given type'''
if not self._events.has_key(account):
return []
if not jid:
return self._events[account]
if not self._events[account].has_key(jid):
return []
events_list = [] # list of events
for ev in self._events[account][jid]:
if not types or ev.type_ in types:
events_list.append(ev)
return events_list
def get_first_event(self, account, jid = None, type_ = None):
'''Return the first event of type type_ if given'''
events_list = self.get_events(account, jid, type_)
# be sure it's bigger than latest event
first_event_time = time.time() + 1
first_event = None
for event in events_list:
if event.time_ < first_event_time:
first_event_time = event.time_
first_event = event
return first_event
def _get_nb_events(self, account = None, jid = None, attribute = None, types = []):
'''return the number of events'''
nb = 0
if account:
accounts = [account]
else:
accounts = self._events.keys()
for acct in accounts:
if not self._events.has_key(acct):
continue
if jid:
jids = [jid]
else:
jids = self._events[acct].keys()
for j in jids:
if not self._events[acct].has_key(j):
continue
for event in self._events[acct][j]:
if types and event.type_ not in types:
continue
if not attribute or \
attribute == 'systray' and event.show_in_systray or \
attribute == 'roster' and event.show_in_roster:
nb += 1
return nb
def _get_some_events(self, attribute):
'''attribute in systray, roster'''
events = {}
for account in self._events:
events[account] = {}
for jid in self._events[account]:
events[account][jid] = []
for event in self._events[account][jid]:
if attribute == 'systray' and event.show_in_systray or \
attribute == 'roster' and event.show_in_roster:
events[account][jid].append(event)
if not events[account][jid]:
del events[account][jid]
if not events[account]:
del events[account]
return events
def _get_first_event_with_attribute(self, events):
'''get the first event
events is in the form {account1: {jid1: [ev1, ev2], },. }'''
# be sure it's bigger than latest event
first_event_time = time.time() + 1
first_account = None
first_jid = None
first_event = None
for account in events:
for jid in events[account]:
for event in events[account][jid]:
if event.time_ < first_event_time:
first_event_time = event.time_
first_account = account
first_jid = jid
first_event = event
return first_account, first_jid, first_event
def get_nb_systray_events(self, types = []):
'''returns the number of events displayedin roster'''
return self._get_nb_events(attribute = 'systray', types = types)
def get_systray_events(self):
'''return all events that must be displayed in systray:
{account1: {jid1: [ev1, ev2], },. }'''
return self._get_some_events('systray')
def get_first_systray_event(self):
events = self.get_systray_events()
return self._get_first_event_with_attribute(events)
def get_nb_roster_events(self, account = None, jid = None, types = []):
'''returns the number of events displayedin roster'''
return self._get_nb_events(attribute = 'roster', account = account,
jid = jid, types = types)
def get_roster_events(self):
'''return all events that must be displayed in roster:
{account1: {jid1: [ev1, ev2], },. }'''
return self._get_some_events('roster')

View File

@ -23,9 +23,6 @@
## GNU General Public License for more details.
##
from common import i18n
_ = i18n._
class PysqliteNotAvailable(Exception):
'''sqlite2 is not installed or python bindings are missing'''
def __init__(self):

134
src/common/fuzzyclock.py Executable file
View File

@ -0,0 +1,134 @@
## fuzzyclock.py
##
## Contributors for this file:
##
## - Yann Le Boulanger <asterix@lagaule.org>
## - Christoph Neuroth <delmonico@gmx.net>
##
## Copyright (C) 2006 Christoph Neuroth <delmonico@gmx.net>
## Yann Le Boulanger <asterix@lagaule.org>
## Dimitur Kirov <dkirov@gmail.com>
## 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
## 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.
##
'''
Python class to show a "fuzzy clock".
Homepage: http://home.gna.org/fuzzyclock/
Project Page: http://gna.org/projects/fuzzyclock
The class has been ported from PHP code by
Henrique Recidive <henrique at recidive.com> which was
in turn based on the Fuzzy Clock Applet of Frerich Raabe (KDE).
So most of the credit goes to this guys, thanks :-)
'''
import time
class FuzzyClock:
def __init__(self):
self.__hour = 0
self.__minute = 0
self.__dayOfWeek = 0
self.__hourNames = [ _('one'), _('two'), _('three'), _('four'), _('five'), _('six'),
_('seven'), _('eight'), _('nine'), _('ten'), _('eleven'),
_('twelve')]
#Strings to use for the output. %0 will be replaced with the preceding hour (e.g. "x PAST %0"), %1 with the coming hour (e.g. "x TO %1). '''
self.__normalFuzzy = [ _("%0 o'clock"), _('five past %0'), _('ten past %0'),
_('quarter past %0'), _('twenty past %0'),
_('twenty five past %0'), _('half past %0'),
_('twenty five to %1'), _('twenty to %1'),
_('quarter to %1'), _('ten to %1'), _('five to %1'),
_("%1 o'clock") ]
#A "singular-form". It is used when talking about hour 0
self.__normalFuzzyOne = [ _("%0 o'clock"), _('five past %0'),
_('ten past %0'), _('quarter past %0'),
_('twenty past %0'), _('twenty five past %0'),
_('half past %0'), _('twenty five to %1'),
_('twenty to %1'), _('quarter to %1'),
_('ten to %1'), _('five to %1'), _("%1 o'clock") ]
self.__dayTime = [ _('Night'), _('Early morning'), _('Morning'), _('Almost noon'),
_('Noon'), _('Afternoon'), _('Evening'), _('Late evening') ]
self.__fuzzyWeek = [ _('Start of week'), _('Middle of week'), _('End of week'),
_('Weekend!') ]
self.setCurrent()
def setHour(self,hour):
self.__hour = int(hour)
def setMinute(self,minute):
self.__minute=int(minute)
def setDayOfWeek(self,day):
self.__dayOfWeek=int(day)
def setTime(self,time):
timeArray = time.split(":")
self.setHour(timeArray[0])
self.setMinute(timeArray[1])
def setCurrent(self):
hour=time.strftime("%H")
minute=time.strftime("%M")
day=time.strftime("%w")
self.setTime(hour+":"+minute)
self.setDayOfWeek(day)
def getFuzzyTime(self, fuzzyness = 1):
sector = 0
realHour = 0
if fuzzyness == 1 or fuzzyness == 2:
if fuzzyness == 1:
if self.__minute >2:
sector = (self.__minute - 3) / 5 +1
else:
if self.__minute > 6:
sector = ((self.__minute - 7) / 15 + 1) * 3
newTimeStr = self.__normalFuzzy[sector]
#%0 or %1?
deltaHour = int(newTimeStr[newTimeStr.find("%")+1])
if (self.__hour + deltaHour) % 12 > 0:
realHour = (self.__hour + deltaHour) % 12 - 1
else:
realHour = 12 - ((self.__hour + deltaHour) % 12 + 1)
if realHour == 0:
newTimeStr = self.__normalFuzzyOne[sector]
newTimeStr = newTimeStr.replace("%"+str(deltaHour),
self.__hourNames[realHour])
elif fuzzyness == 3:
newTimeStr = self.__dayTime[self.__hour / 3]
else:
dayOfWeek = self.__dayOfWeek
if dayOfWeek == 1:
newTimeStr = self.__fuzzyWeek[0]
elif dayOfWeek >= 2 and dayOfWeek <= 4:
newTimeStr = self.__fuzzyWeek[1]
elif dayOfWeek == 5:
newTimeStr = self.__fuzzyWeek[2]
else:
newTimeStr = self.__fuzzyWeek[3]
return newTimeStr

View File

@ -23,10 +23,11 @@ import locale
import config
from contacts import Contacts
from events import Events
interface = None # The actual interface (the gtk one for the moment)
version = '0.10'
config = config.Config()
version = config.get('version')
connections = {}
verbose = False
@ -81,6 +82,10 @@ if LANG is None:
else:
LANG = LANG[:2] # en, fr, el etc..
gmail_domains = ['gmail.com', 'googlemail.com']
transport_type = {} # list the type of transport
last_message_time = {} # list of time of the latest incomming message
# {acct1: {jid1: time1, jid2: time2}, }
encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
@ -88,23 +93,20 @@ encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..}
contacts = Contacts()
gc_connected = {} # tell if we are connected to the room or not {acct: {room_jid: True}}
gc_passwords = {} # list of the pass required to enter a room {room_jid: password}
automatic_rooms = {} # list of rooms that must be automaticaly configured and for which we have a list of invities {account: {room_jid: {'invities': []}}}
groups = {} # list of groups
newly_added = {} # list of contacts that has just signed in
to_be_removed = {} # list of contacts that has just signed out
awaiting_events = {} # list of messages/FT reveived but not printed
# awaiting_events[jid] = (type, (data1, data2, ...))
# if type in ('chat', 'normal'): data = (message, subject, kind, time,
# encrypted, resource)
# kind can be (incoming, error)
# if type in file-request, file-request-error, file-send-error, file-error,
# file-completed, file-stopped:
# data = file_props
events = Events()
nicks = {} # list of our nick names in each account
# should we block 'contact signed in' notifications for this account?
# this is only for the first 30 seconds after we change our show
# to something else than offline
# can also contain account/transport_jid to block notifications for contacts
# from this transport
block_signed_in_notifications = {}
con_types = {} # type of each connection (ssl, tls, tcp, ...)
@ -218,8 +220,11 @@ def get_transport_name_from_jid(jid, use_config_setting = True):
# jid was None. Yann why?
if not jid or (use_config_setting and not config.get('use_transports_iconsets')):
return
host = get_server_from_jid(jid)
if host in transport_type:
return transport_type[host]
# host is now f.e. icq.foo.org or just icq (sometimes on hacky transports)
host_splitted = host.split('.')
if len(host_splitted) != 0:
@ -229,7 +234,7 @@ def get_transport_name_from_jid(jid, use_config_setting = True):
if host == 'aim':
return 'aim'
elif host == 'gg':
return 'gadugadu'
return 'gadu-gadu'
elif host == 'irc':
return 'irc'
elif host == 'icq':
@ -277,18 +282,6 @@ def get_hostname_from_account(account_name, use_srv = False):
return config.get_per('accounts', account_name, 'custom_host')
return config.get_per('accounts', account_name, 'hostname')
def get_first_event(account, jid, typ = None):
'''returns the first event of the given type from the awaiting_events queue'''
if not awaiting_events[account].has_key(jid):
return None
q = awaiting_events[account][jid]
if not typ:
return q[0]
for ev in q:
if ev[0] == typ:
return ev
return None
def get_notification_image_prefix(jid):
'''returns the prefix for the notification images'''
transport_name = get_transport_name_from_jid(jid)

View File

@ -18,6 +18,7 @@
import sre
import os
import subprocess
import urllib
import errno
import select
@ -26,7 +27,7 @@ import sha
from encodings.punycode import punycode_encode
import gajim
import i18n
from i18n import Q_
from xmpp_stringprep import nodeprep, resourceprep, nameprep
try:
@ -36,9 +37,6 @@ try:
except:
pass
_ = i18n._
Q_ = i18n.Q_
special_groups = (_('Transports'), _('Not in Roster'), _('Observers'))
class InvalidFormat(Exception):
@ -363,6 +361,11 @@ def is_in_path(name_of_command, return_abs_path = False):
else:
return is_in_dir
def exec_command(command):
'''command is a string that contain arguments'''
# os.system(command)
subprocess.Popen(command.split())
def launch_browser_mailer(kind, uri):
#kind = 'url' or 'mail'
if os.name == 'nt':
@ -386,11 +389,9 @@ def launch_browser_mailer(kind, uri):
command = gajim.config.get('custommailapp')
if command == '': # if no app is configured
return
# we add the uri in "" so we have good parsing from shell
uri = uri.replace('"', '\\"') # escape "
command = command + ' "' + uri + '" &'
try: #FIXME: when we require python2.4+ use subprocess module
os.system(command)
command = command + ' ' + uri
try:
exec_command(command)
except:
pass
@ -409,11 +410,9 @@ def launch_file_manager(path_to_open):
command = gajim.config.get('custom_file_manager')
if command == '': # if no app is configured
return
# we add the path in "" so we have good parsing from shell
path_to_open = path_to_open.replace('"', '\\"') # escape "
command = command + ' "' + path_to_open + '" &'
try: #FIXME: when we require python2.4+ use subprocess module
os.system(command)
command = command + ' ' + path_to_open
try:
exec_command(command)
except:
pass
@ -421,6 +420,9 @@ def play_sound(event):
if not gajim.config.get('sounds_on'):
return
path_to_soundfile = gajim.config.get_per('soundevents', event, 'path')
play_sound_file(path_to_soundfile)
def play_sound_file(path_to_soundfile):
if path_to_soundfile == 'beep':
print '\a' # make a speaker beep
return
@ -436,11 +438,8 @@ def play_sound(event):
if gajim.config.get('soundplayer') == '':
return
player = gajim.config.get('soundplayer')
# we add the path in "" so we have good parsing from shell
path_to_soundfile = path_to_soundfile.replace('"', '\\"') # escape "
command = player + ' "' + path_to_soundfile + '" &'
#FIXME: when we require 2.4+ use subprocess module
os.system(command)
command = player + ' ' + path_to_soundfile
exec_command(command)
def get_file_path_from_dnd_dropped_uri(uri):
path = urllib.url2pathname(uri) # escape special chars
@ -464,14 +463,6 @@ def from_xs_boolean_to_python_boolean(value):
return val
def ensure_unicode_string(s):
# py23 u'abc'.decode('utf-8') raises
# python24 does not. if python23 is ooold we can remove this func
# FIXME: remove this when we abandon py23
if isinstance(s, str):
s = s.decode('utf-8')
return s
def get_xmpp_show(show):
if show in ('online', 'offline'):
return None
@ -514,9 +505,9 @@ def get_global_status():
def get_icon_name_to_show(contact, account = None):
'''Get the icon name to show in online, away, requested, ...'''
if account and gajim.awaiting_events[account].has_key(contact.jid):
if account and gajim.events.get_nb_roster_events(account, contact.jid):
return 'message'
if account and gajim.awaiting_events[account].has_key(
if account and gajim.events.get_nb_roster_events(account,
contact.get_full_jid()):
return 'message'
if contact.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
@ -543,6 +534,14 @@ def decode_string(string):
return string
def ensure_utf8_string(string):
'''make sure string is in UTF-8'''
try:
string = decode_string(string).encode('utf-8')
except:
pass
return string
def get_windows_reg_env(varname, default=''):
'''asks for paths commonly used but not exposed as ENVs
in english Windows 2003 those are:
@ -692,8 +691,11 @@ def get_os_info():
text = fd.readline().strip() # get only first line
fd.close()
if path_to_file.endswith('version'):
# sourcemage_version has all the info we need
if not os.path.basename(path_to_file).startswith('sourcemage'):
# sourcemage_version and slackware-version files
# have all the info we need (name and version of distro)
if not os.path.basename(path_to_file).startswith(
'sourcemage') or not\
os.path.basename(path_to_file).startswith('slackware'):
text = distro_name + ' ' + text
elif path_to_file.endswith('aurox-release'):
# file doesn't have version
@ -724,20 +726,73 @@ def sanitize_filename(filename):
return filename
def allow_showing_notification(account):
def allow_showing_notification(account, type = None, advanced_notif_num = None,
first = True):
'''is it allowed to show nofication?
check OUR status and if we allow notifications for that status'''
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:
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):
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
return True
return False
def allow_popup_window(account):
def allow_popup_window(account, advanced_notif_num = None):
'''is it allowed to popup windows?'''
if advanced_notif_num != None:
popup = gajim.config.get_per('notifications', str(advanced_notif_num),
'auto_open')
if popup == 'yes':
return True
if popup == 'no':
return False
autopopup = gajim.config.get('autopopup')
autopopupaway = gajim.config.get('autopopupaway')
if autopopup and (autopopupaway or \
gajim.connections[account].connected in (2, 3)): # we're online or chat
return True
return False
def allow_sound_notification(sound_event, advanced_notif_num = None):
if advanced_notif_num != None:
sound = gajim.config.get_per('notifications', str(advanced_notif_num),
'sound')
if sound == 'yes':
return True
if sound == 'no':
return False
if gajim.config.get_per('soundevents', sound_event, 'enabled'):
return True
return False
def get_chat_control(account, contact):
full_jid_with_resource = contact.jid
if contact.resource:
full_jid_with_resource += '/' + contact.resource
highest_contact = gajim.contacts.get_contact_with_highest_priority(
account, contact.jid)
# Look for a chat control that has the given resource, or default to
# one without resource
ctrl = gajim.interface.msg_win_mgr.get_control(full_jid_with_resource,
account)
if ctrl:
return ctrl
elif not highest_contact or not highest_contact.resource:
# unknow contact or offline message
return gajim.interface.msg_win_mgr.get_control(contact.jid, account)
elif highest_contact and contact.resource != \
highest_contact.resource:
return None
return gajim.interface.msg_win_mgr.get_control(contact.jid, account)

View File

@ -48,21 +48,11 @@ if os.name == 'nt':
if lang:
os.environ['LANG'] = lang
_translation = None
def init():
global _translation
try:
_translation = gettext.translation(APP, DIR)
except IOError:
_translation = gettext.NullTranslations()
init()
def _(s):
if s == '':
return s
return _translation.ugettext(s)
gettext.install(APP, DIR, unicode = True)
if gettext._translations:
_translation = gettext._translations.values()[0]
else:
_translation = gettext.NullTranslations()
def Q_(s):
# Qualified translatable strings

View File

@ -29,8 +29,7 @@ import time
import datetime
import exceptions
import i18n
_ = i18n._
import gajim
try:
from pysqlite2 import dbapi2 as sqlite
@ -79,30 +78,50 @@ class Constants:
self.SHOW_OFFLINE
) = range(6)
(
self.TYPE_AIM,
self.TYPE_GG,
self.TYPE_HTTP_WS,
self.TYPE_ICQ,
self.TYPE_MSN,
self.TYPE_QQ,
self.TYPE_SMS,
self.TYPE_SMTP,
self.TYPE_TLEN,
self.TYPE_YAHOO,
self.TYPE_NEWMAIL,
self.TYPE_RSS,
self.TYPE_WEATHER,
) = range(13)
constants = Constants()
class Logger:
def __init__(self):
self.jids_already_in = [] # holds jids that we already have in DB
self.con = None
if not os.path.exists(LOG_DB_PATH):
# this can happen only the first time (the time we create the db)
# db is not created here but in src/common/checks_paths.py
return
self.init_vars()
def init_vars(self):
# if locked, wait up to 20 sec to unlock
# before raise (hopefully should be enough)
if self.con:
self.con.close()
self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
isolation_level = 'IMMEDIATE')
self.cur = self.con.cursor()
self.get_jids_already_in_db()
def get_jids_already_in_db(self):
self.cur.execute('SELECT jid FROM jids')
rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
self.jids_already_in = []
for row in rows:
# row[0] is first item of row (the only result here, the jid)
self.jids_already_in.append(row[0])
@ -193,7 +212,66 @@ class Logger:
show_col = 'UNKNOWN'
return kind_col, show_col
def convert_human_transport_type_to_db_api_values(self, type_):
'''converts from string style to constant ints for db'''
if type_ == 'aim':
return constants.TYPE_AIM
if type_ == 'gadu-gadu':
return constants.TYPE_GG
if type_ == 'http-ws':
return constants.TYPE_HTTP_WS
if type_ == 'icq':
return constants.TYPE_ICQ
if type_ == 'msn':
return constants.TYPE_MSN
if type_ == 'qq':
return constants.TYPE_QQ
if type_ == 'sms':
return constants.TYPE_SMS
if type_ == 'smtp':
return constants.TYPE_SMTP
if type_ == 'tlen':
return constants.TYPE_TLEN
if type_ == 'yahoo':
return constants.TYPE_YAHOO
if type_ == 'newmail':
return constants.TYPE_NEWMAIL
if type_ == 'rss':
return constants.TYPE_RSS
if type_ == 'weather':
return constants.TYPE_WEATHER
return None
def convert_api_values_to_human_transport_type(self, type_id):
'''converts from constant ints for db to string style'''
if type_id == constants.TYPE_AIM:
return 'aim'
if type_id == constants.TYPE_GG:
return 'gadu-gadu'
if type_id == constants.TYPE_HTTP_WS:
return 'http-ws'
if type_id == constants.TYPE_ICQ:
return 'icq'
if type_id == constants.TYPE_MSN:
return 'msn'
if type_id == constants.TYPE_QQ:
return 'qq'
if type_id == constants.TYPE_SMS:
return 'sms'
if type_id == constants.TYPE_SMTP:
return 'smtp'
if type_id == constants.TYPE_TLEN:
return 'tlen'
if type_id == constants.TYPE_YAHOO:
return 'yahoo'
if type_id == constants.TYPE_NEWMAIL:
return 'newmail'
if type_id == constants.TYPE_RSS:
return 'rss'
if type_id == constants.TYPE_WEATHER:
return 'weather'
def commit_to_db(self, values, write_unread = False):
#print 'saving', values
sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)'
@ -239,25 +317,8 @@ class Logger:
self.cur.execute(
'SELECT message_id from unread_messages WHERE jid_id = %d' % jid_id)
results = self.cur.fetchall()
# Remove before 0.10
except:
try:
self.cur.executescript('DROP TABLE unread_messages;')
self.con.commit()
except:
pass
try:
self.cur.executescript('''CREATE TABLE unread_messages(
message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid_id INTEGER
);''')
self.con.commit()
except:
pass
self.con.close()
self.jids_already_in = []
self.init_vars()
return []
pass
for message in results:
msg_id = message[0]
@ -340,7 +401,7 @@ class Logger:
return self.commit_to_db(values, write_unread)
def get_last_conversation_lines(self, jid, restore_how_many_rows,
pending_how_many, timeout):
pending_how_many, timeout, account):
'''accepts how many rows to restore and when to time them out (in minutes)
(mark them as too old) and number of messages that are in queue
and are already logged but pending to be viewed,
@ -348,15 +409,17 @@ class Logger:
list with empty tupple if nothing found to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
where_sql = self._build_contact_where(account, jid)
now = int(float(time.time()))
timed_out = now - (timeout * 60) # before that they are too old
# so if we ask last 5 lines and we have 2 pending we get
# 3 - 8 (we avoid the last 2 lines but we still return 5 asked)
self.cur.execute('''
SELECT time, kind, message FROM logs
WHERE jid_id = %d AND kind IN (%d, %d, %d, %d) AND time > %d
WHERE (%s) AND kind IN (%d, %d, %d, %d) AND time > %d
ORDER BY time DESC LIMIT %d OFFSET %d
''' % (jid_id, constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV,
''' % (where_sql, constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV,
constants.KIND_SINGLE_MSG_SENT, constants.KIND_CHAT_MSG_SENT,
timed_out, restore_how_many_rows, pending_how_many)
)
@ -374,35 +437,36 @@ class Logger:
start_of_day = int(time.mktime(local_time)) # we have time since epoch baby :)
return start_of_day
def get_conversation_for_date(self, jid, year, month, day):
def get_conversation_for_date(self, jid, year, month, day, account):
'''returns contact_name, time, kind, show, message
for each row in a list of tupples,
returns list with empty tupple if we found nothing to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
where_sql = self._build_contact_where(account, jid)
start_of_day = self.get_unix_time_from_date(year, month, day)
seconds_in_a_day = 86400 # 60 * 60 * 24
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
SELECT contact_name, time, kind, show, message FROM logs
WHERE jid_id = %d
WHERE (%s)
AND time BETWEEN %d AND %d
ORDER BY time
''' % (jid_id, start_of_day, last_second_of_day))
''' % (where_sql, start_of_day, last_second_of_day))
results = self.cur.fetchall()
return results
def get_search_results_for_query(self, jid, query):
def get_search_results_for_query(self, jid, query, account):
'''returns contact_name, time, kind, show, message
for each row in a list of tupples,
returns list with empty tupple if we found nothing to meet our demands'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
if False: #query.startswith('SELECT '): # it's SQL query
if False: #query.startswith('SELECT '): # it's SQL query (FIXME)
try:
self.cur.execute(query)
except sqlite.OperationalError, e:
@ -410,22 +474,24 @@ class Logger:
return results
else: # user just typed something, we search in message column
where_sql = self._build_contact_where(account, jid)
like_sql = '%' + query + '%'
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject FROM logs
WHERE jid_id = ? AND message LIKE ?
WHERE (%s) AND message LIKE '%s'
ORDER BY time
''', (jid_id, like_sql))
''' % (where_sql, like_sql))
results = self.cur.fetchall()
return results
def get_days_with_logs(self, jid, year, month, max_day):
def get_days_with_logs(self, jid, year, month, max_day, account):
'''returns the list of days that have logs (not status messages)'''
jid = jid.lower()
jid_id = self.get_jid_id(jid)
days_with_logs = []
where_sql = self._build_contact_where(account, jid)
# First select all date of month whith logs we want
start_of_month = self.get_unix_time_from_date(year, month, 1)
seconds_in_a_day = 86400 # 60 * 60 * 24
@ -433,11 +499,11 @@ class Logger:
self.cur.execute('''
SELECT time FROM logs
WHERE jid_id = %d
WHERE (%s)
AND time BETWEEN %d AND %d
AND kind NOT IN (%d, %d)
ORDER BY time
''' % (jid_id, start_of_month, last_second_of_month,
''' % (where_sql, start_of_month, last_second_of_month,
constants.KIND_STATUS, constants.KIND_GCSTATUS))
result = self.cur.fetchall()
@ -468,17 +534,23 @@ class Logger:
result = self.cur.fetchone()
return days_with_logs
def get_last_date_that_has_logs(self, jid, is_room = False):
def get_last_date_that_has_logs(self, jid, account = None, is_room = False):
'''returns last time (in seconds since EPOCH) for which
we had logs (excluding statuses)'''
jid = jid.lower()
jid_id = self.get_jid_id(jid, 'ROOM')
where_sql = ''
if not is_room:
where_sql = self._build_contact_where(account, jid)
else:
jid_id = self.get_jid_id(jid, 'ROOM')
where_sql = 'jid_id = %s' % jid_id
self.cur.execute('''
SELECT time FROM logs
WHERE jid_id = ?
AND kind NOT IN (?, ?)
WHERE (%s)
AND kind NOT IN (%d, %d)
ORDER BY time DESC LIMIT 1
''', (jid_id, constants.KIND_STATUS, constants.KIND_GCSTATUS))
''' % (where_sql, constants.KIND_STATUS, constants.KIND_GCSTATUS))
results = self.cur.fetchone()
if results is not None:
@ -487,3 +559,61 @@ class Logger:
result = None
return result
def _build_contact_where(self, account, jid):
'''build the where clause for a jid, including metacontacts
jid(s) if any'''
where_sql = ''
# will return empty list if jid is not associated with
# any metacontacts
family = gajim.contacts.get_metacontacts_family(account, jid)
if family:
for user in family:
jid_id = self.get_jid_id(user['jid'])
where_sql += 'jid_id = %s' % jid_id
if user != family[-1]:
where_sql += ' OR '
else: # if jid was not associated with metacontacts
jid_id = self.get_jid_id(jid)
where_sql = 'jid_id = %s' % jid_id
return where_sql
def save_transport_type(self, jid, type_):
'''save the type of the transport in DB'''
type_id = self.convert_human_transport_type_to_db_api_values(type_)
if not type_id:
# unknown type
return
self.cur.execute(
'SELECT type from transports_cache WHERE transport = "%s"' % jid)
results = self.cur.fetchall()
if results:
result = results[0][0]
if result == type_id:
return
self.cur.execute(
'UPDATE transports_cache SET type = %d WHERE transport = "%s"' % (type_id,
jid))
try:
self.con.commit()
except sqlite.OperationalError, e:
print >> sys.stderr, str(e)
return
self.cur.execute(
'INSERT INTO transports_cache VALUES ("%s", %d)' % (jid, type_id))
try:
self.con.commit()
except sqlite.OperationalError, e:
print >> sys.stderr, str(e)
def get_transports_type(self):
'''return all the type of the transports in DB'''
self.cur.execute(
'SELECT * from transports_cache')
results = self.cur.fetchall()
if not results:
return {}
answer = {}
for result in results:
answer[result[0]] = self.convert_api_values_to_human_transport_type(result[1])
return answer

View File

@ -26,8 +26,6 @@ import os
import sys
import locale
from common import gajim
from common import i18n
_ = i18n._
class OptionsParser:
def __init__(self, filename):
@ -129,21 +127,28 @@ class OptionsParser:
def update_config(self, old_version, new_version):
# Convert '0.x.y' to (0, x, y)
old_version = old_version.split('.')
old_version_list = old_version.split('.')
old = []
while len(old_version):
old.append(int(old_version.pop(0)))
new_version = new_version.split('.')
while len(old_version_list):
old.append(int(old_version_list.pop(0)))
new_version_list = new_version.split('.')
new = []
while len(new_version):
new.append(int(new_version.pop(0)))
while len(new_version_list):
new.append(int(new_version_list.pop(0)))
if old < [0, 9] and new >= [0, 9]:
self.update_config_x_to_09()
if old < [0, 10] and new >= [0, 10]:
self.update_config_09_to_010()
if old < [0, 10, 0, 1] and new >= [0, 10, 0, 1]:
self.update_config_to_01001()
if old < [0, 10, 1, 1] and new >= [0, 10, 1, 1]:
self.update_config_to_01011()
if old < [0, 10, 1, 2] and new >= [0, 10, 1, 2]:
self.update_config_to_01012()
if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]:
self.update_config_to_01013()
gajim.logger.init_vars()
gajim.config.set('version', new_version)
def update_config_x_to_09(self):
# Var name that changed:
@ -258,6 +263,41 @@ class OptionsParser:
gajim.config.set('version', '0.10')
def update_config_to_01001(self):
gajim.config.set('print_status_in_muc', 'in_and_out')
gajim.config.set('version', '0.10.0.1')
def update_config_to_01011(self):
if self.old_values.has_key('print_status_in_muc') and \
self.old_values['print_status_in_muc'] in (True, False):
gajim.config.set('print_status_in_muc', 'in_and_out')
gajim.config.set('version', '0.10.1.1')
def update_config_to_01012(self):
# See [6456]
if self.old_values.has_key('emoticons_theme') and \
self.old_values['emoticons_theme'] == 'Disabled':
gajim.config.set('emoticons_theme', '')
gajim.config.set('version', '0.10.1.2')
def update_config_to_01013(self):
'''create table transports_cache if there is no such table'''
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:
cur.executescript(
'''
CREATE TABLE transports_cache (
transport TEXT UNIQUE,
type INTEGER
);
'''
)
con.commit()
except sqlite.OperationalError, e:
pass
con.close()
gajim.config.set('version', '0.10.1.3')

View File

@ -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()

View File

@ -32,6 +32,7 @@ import os
import struct
import sha
import time
from dialogs import BindPortError
from errno import EWOULDBLOCK
from errno import ENOBUFS
@ -84,12 +85,9 @@ class SocksQueue:
self.listener.bind()
if self.listener.started is False:
self.listener = None
import sys
print >> sys.stderr, '================================================='
print >> sys.stderr, 'Unable to bind to port %s.' % port
print >> sys.stderr, 'Maybe you have another running instance of Gajim.'
print >> sys.stderr, 'File Transfer will be canceled.'
print >> sys.stderr, '================================================='
# We cannot bind port, call error
# dialog from dialogs.py and fail
BindPortError(port)
return None
self.connected += 1
return self.listener
@ -352,7 +350,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

View File

@ -126,12 +126,11 @@ class NBCommonClient(CommonClient):
def _on_connected(self):
self.connected = 'tcp'
if (self._Ssl is None and self.Connection.getPort() in (5223, 443)) or self._Ssl:
try:
transports_nb.NonBlockingTLS().PlugIn(self, now=1)
self.connected = 'ssl'
except socket.sslerror:
if self._Ssl:
transports_nb.NonBlockingTLS().PlugIn(self, now=1)
if not self.Connection: # ssl error, stream is closed
return
self.connected = 'ssl'
self.onreceive(self._on_receive_document_attrs)
dispatcher_nb.Dispatcher().PlugIn(self)
@ -194,6 +193,8 @@ class NonBlockingClient(NBCommonClient):
self.isplugged = True
self.onreceive(None)
transports_nb.NonBlockingTLS().PlugIn(self)
if not self.Connection: # ssl error, stream is closed
return True
if not self.Dispatcher.Stream._document_attrs.has_key('version') or \
not self.Dispatcher.Stream._document_attrs['version']=='1.0':
self._is_connected()

View File

@ -134,6 +134,7 @@ class Dispatcher(PlugIn):
return 0
except ExpatError:
sys.exc_clear()
self.DEBUG('Invalid XML received from server. Forcing disconnect.')
self._owner.Connection.pollend()
return 0
if len(self._pendingExceptions) > 0:

View File

@ -27,6 +27,9 @@ All these methods takes 'disp' first argument that should be already connected
from protocol import *
REGISTER_DATA_RECEIVED='REGISTER DATA RECEIVED'
PRIVACY_LISTS_RECEIVED='PRIVACY LISTS RECEIVED'
PRIVACY_LIST_RECEIVED='PRIVACY LIST RECEIVED'
PRIVACY_LISTS_ACTIVE_DEFAULT='PRIVACY LISTS ACTIVE DEFAULT'
### DISCO ### http://jabber.org/protocol/disco ### JEP-0030 ####################
### Browse ### jabber:iq:browse ### JEP-0030 ###################################

View File

@ -14,7 +14,7 @@
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
from features import REGISTER_DATA_RECEIVED
from features import REGISTER_DATA_RECEIVED, PRIVACY_LISTS_RECEIVED, PRIVACY_LIST_RECEIVED, PRIVACY_LISTS_ACTIVE_DEFAULT
from protocol import *
def _on_default_response(disp, iq, cb):
@ -146,9 +146,9 @@ def register(disp, host, info, cb):
attributes lastErrNode, lastErr and lastErrCode.
"""
iq=Iq('set', NS_REGISTER, to=host)
if not isinstance(info, dict):
if not isinstance(info, dict):
info=info.asDict()
for i in info.keys():
for i in info.keys():
iq.setTag('query').setTagData(i,info[i])
disp.SendAndCallForResponse(iq, cb)
@ -172,37 +172,46 @@ def changePasswordTo(disp, newpassword, host=None, cb = None):
#type=[jid|group|subscription]
#action=[allow|deny]
def getPrivacyLists(disp, cb):
def getPrivacyLists(disp):
""" Requests privacy lists from connected server.
Returns dictionary of existing lists on success."""
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict = {'lists': []}
try:
if not isResultNode(resp):
cb(False)
return
for list in resp.getQueryPayload():
if list.getName()=='list':
dict['lists'].append(list.getAttr('name'))
else:
dict[list.getName()]=list.getAttr('name')
cb(dict)
except:
pass
cb(False)
disp.SendAndCallForResponse(iq, _on_respons)
if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (False))
return
for list in resp.getQueryPayload():
if list.getName()=='list':
dict['lists'].append(list.getAttr('name'))
else:
dict[list.getName()]=list.getAttr('name')
disp.Event(NS_PRIVACY, PRIVACY_LISTS_RECEIVED, (dict))
disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname, cb):
def getActiveAndDefaultPrivacyLists(disp):
iq = Iq('get', NS_PRIVACY)
def _on_response(resp):
dict = {'active': '', 'default': ''}
if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (False))
return
for list in resp.getQueryPayload():
if list.getName() == 'active':
dict['active'] = list.getAttr('name')
elif list.getName() == 'default':
dict['default'] = list.getAttr('name')
disp.Event(NS_PRIVACY, PRIVACY_LISTS_ACTIVE_DEFAULT, (dict))
disp.SendAndCallForResponse(iq, _on_response)
def getPrivacyList(disp, listname):
""" Requests specific privacy list listname. Returns list of XML nodes (rules)
taken from the server responce."""
def _on_response(resp):
try:
if isResultNode(resp):
return cb(resp.getQueryPayload()[0])
except:
pass
cb(False)
if not isResultNode(resp):
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False))
return
disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (resp))
iq = Iq('get', NS_PRIVACY, payload=[Node('list', {'name': listname})])
disp.SendAndCallForResponse(iq, _on_response)
@ -220,14 +229,25 @@ def setDefaultPrivacyList(disp, listname=None):
""" Sets the default privacy list as 'listname'. Returns true on success."""
return setActivePrivacyList(disp, listname,'default')
def setPrivacyList(disp, list, cb):
def setPrivacyList(disp, listname, tags):
""" Set the ruleset. 'list' should be the simpleXML node formatted
according to RFC 3921 (XMPP-IM) (I.e. Node('list',{'name':listname},payload=[...]) )
Returns true on success."""
iq=Iq('set',NS_PRIVACY,payload=[list])
_on_default_response(disp, iq, cb)
iq = Iq('set', NS_PRIVACY, xmlns = '')
list_query = iq.getTag('query').setTag('list', {'name': listname})
for item in tags:
if item.has_key('type') and item.has_key('value'):
item_tag = list_query.setTag('item', {'action': item['action'],
'order': item['order'], 'type': item['type'], 'value': item['value']})
else:
item_tag = list_query.setTag('item', {'action': item['action'],
'order': item['order']})
if item.has_key('child'):
for child_tag in item['child']:
item_tag.setTag(child_tag)
_on_default_response(disp, iq, None)
def delPrivacyList(disp,listname, cb):
def delPrivacyList(disp,listname):
""" Deletes privacy list 'listname'. Returns true on success."""
iq = Iq('set',NS_PRIVACY,payload=[Node('list',{'name':listname})])
_on_default_response(disp, iq, cb)
_on_default_response(disp, iq, None)

View File

@ -62,11 +62,13 @@ NS_MUC ='http://jabber.org/protocol/muc'
NS_MUC_USER =NS_MUC+'#user'
NS_MUC_ADMIN =NS_MUC+'#admin'
NS_MUC_OWNER =NS_MUC+'#owner'
NS_NICK ='http://jabber.org/protocol/nick' # JEP-0172
NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # JEP-0013
NS_PHYSLOC ='http://jabber.org/protocol/physloc' # JEP-0112
NS_PRESENCE ='presence' # Jabberd2
NS_PRIVACY ='jabber:iq:privacy'
NS_PRIVATE ='jabber:iq:private'
NS_PROFILE ='http://jabber.org/protocol/profile' # JEP-0154
NS_PUBSUB ='http://jabber.org/protocol/pubsub' # JEP-0060
NS_REGISTER ='jabber:iq:register'
NS_ROSTER ='jabber:iq:roster'
@ -82,7 +84,7 @@ NS_SIGNED ='jabber:x:signed' # JEP-00
NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
NS_STREAM ='http://affinix.com/jabber/stream'
NS_STREAMS ='http://etherx.jabber.org/streams'
NS_TIME ='jabber:iq:time'
NS_TIME ='jabber:iq:time' # JEP-0900
NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
NS_VACATION ='http://jabber.org/protocol/vacation'
NS_VCARD ='vcard-temp'
@ -346,6 +348,13 @@ 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 self.getTagAttr('error','code')

View File

@ -371,8 +371,12 @@ class NonBlockingTLS(PlugIn):
PlugIn.PlugIn(self, owner)
DBG_LINE='NonBlockingTLS'
self.on_tls_start = on_tls_start
if now:
res = self._startSSL()
if now:
try:
res = self._startSSL()
except Exception, e:
self._owner.socket.pollend()
return
self.tls_start()
return res
if self._owner.Dispatcher.Stream.features:
@ -434,7 +438,11 @@ class NonBlockingTLS(PlugIn):
self.DEBUG('Got starttls response: ' + self.starttls,'error')
return
self.DEBUG('Got starttls proceed response. Switching to TLS/SSL...','ok')
self._startSSL()
try:
self._startSSL()
except Exception, e:
self._owner.socket.pollend()
return
self._owner.Dispatcher.PlugOut()
dispatcher_nb.Dispatcher().PlugIn(self._owner)

View File

@ -33,8 +33,6 @@ import common.xmpp
from common import GnuPG
from common import helpers
from common import gajim
from common import i18n
_ = i18n._
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']

View File

@ -48,9 +48,6 @@ from connection_handlers_zeroconf import *
USE_GPG = GnuPG.USE_GPG
from common import i18n
_ = i18n._
class ConnectionZeroconf(ConnectionHandlersZeroconf):
'''Connection class'''
def __init__(self, name):

View File

@ -16,7 +16,6 @@
##
import gtk
import gtk.glade
import gobject
import os
import common.config
@ -38,12 +37,6 @@ except:
from common import helpers
from common import gajim
from common import connection
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain (APP, i18n.DIR)
gtk.glade.textdomain (APP)
#---------- PreferencesWindow class -------------#
class PreferencesWindow:
@ -60,6 +53,7 @@ class PreferencesWindow:
'''Initialize Preferences window'''
self.xml = gtkgui_helpers.get_glade('preferences_window.glade')
self.window = self.xml.get_widget('preferences_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.iconset_combobox = self.xml.get_widget('iconset_combobox')
self.notify_on_new_message_radiobutton = self.xml.get_widget(
'notify_on_new_message_radiobutton')
@ -124,7 +118,7 @@ class PreferencesWindow:
for dir in emoticons_list:
if dir != '.svn':
l.append(dir)
l.append('Disabled')
l.append(_('Disabled'))
for i in xrange(len(l)):
model.append([l[i]])
if gajim.config.get('emoticons_theme') == l[i]:
@ -256,11 +250,6 @@ class PreferencesWindow:
# try to set default font for the current desktop env
fontbutton = self.xml.get_widget('conversation_fontbutton')
if font == '':
font = gtkgui_helpers.get_default_font()
if font is not None:
font = 'Sans 10'
gajim.config.set('conversation_font', font)
fontbutton.set_font_name(font)
fontbutton.set_sensitive(False)
self.xml.get_widget('default_chat_font').set_active(True)
else:
@ -322,6 +311,8 @@ class PreferencesWindow:
commands = ('aplay', 'play', 'esdplay', 'artsplay')
for command in commands:
if helpers.is_in_path(command):
if command == 'aplay':
command += ' -q'
self.xml.get_widget('soundplayer_entry').set_text(command)
gajim.config.set('soundplayer', command)
break
@ -385,6 +376,32 @@ class PreferencesWindow:
self.xml.get_widget('prompt_offline_status_message_checkbutton').\
set_active(st)
# Default Status messages
self.default_msg_tree = self.xml.get_widget('default_msg_treeview')
# (status, translated_status, message, enabled)
model = gtk.ListStore(str, str, str, bool)
self.default_msg_tree.set_model(model)
col = gtk.TreeViewColumn('Status')
self.default_msg_tree.append_column(col)
renderer = gtk.CellRendererText()
col.pack_start(renderer, False)
col.set_attributes(renderer, text = 1)
col = gtk.TreeViewColumn('Message')
self.default_msg_tree.append_column(col)
renderer = gtk.CellRendererText()
col.pack_start(renderer, True)
col.set_attributes(renderer, text = 2)
renderer.connect('edited', self.on_default_msg_cell_edited)
renderer.set_property('editable', True)
col = gtk.TreeViewColumn('Enabled')
self.default_msg_tree.append_column(col)
renderer = gtk.CellRendererToggle()
col.pack_start(renderer, False)
col.set_attributes(renderer, active = 3)
renderer.set_property('activatable', True)
renderer.connect('toggled', self.default_msg_toggled_cb)
self.fill_default_msg_treeview()
#Status messages
self.msg_tree = self.xml.get_widget('msg_treeview')
model = gtk.ListStore(str, str)
@ -440,17 +457,22 @@ class PreferencesWindow:
# Notify user of new gmail e-mail messages,
# only show checkbox if user has a gtalk account
frame_gmail = self.xml.get_widget('frame_gmail')
notify_gmail_checkbutton = self.xml.get_widget('notify_gmail_checkbutton')
notify_gmail_checkbutton.set_no_show_all(True)
notify_gmail_extra_checkbutton = self.xml.get_widget('notify_gmail_extra_checkbutton')
frame_gmail.set_no_show_all(True)
for account in gajim.config.get_per('accounts'):
jid = gajim.get_jid_from_account(account)
if gajim.get_server_from_jid(jid) == 'gmail.com':
if gajim.get_server_from_jid(jid) in gajim.gmail_domains:
frame_gmail.show_all()
st = gajim.config.get('notify_on_new_gmail_email')
notify_gmail_checkbutton.set_active(st)
notify_gmail_checkbutton.show()
st = gajim.config.get('notify_on_new_gmail_email_extra')
notify_gmail_extra_checkbutton.set_active(st)
break
else:
notify_gmail_checkbutton.hide()
frame_gmail.hide()
self.xml.signal_autoconnect(self)
@ -460,11 +482,14 @@ class PreferencesWindow:
self.on_msg_treemodel_row_changed)
self.msg_tree.get_model().connect('row-deleted',
self.on_msg_treemodel_row_deleted)
self.default_msg_tree.get_model().connect('row-changed',
self.on_default_msg_treemodel_row_changed)
self.theme_preferences = None
self.notebook.set_current_page(0)
self.window.show_all()
gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
def on_preferences_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
@ -595,7 +620,23 @@ class PreferencesWindow:
gajim.config.set('use_speller', active)
gajim.interface.save_config()
if active:
self.apply_speller()
lang = gajim.config.get('speller_language')
if not lang:
lang = gajim.LANG
tv = gtk.TextView()
try:
spell = gtkspell.Spell(tv, lang)
except:
dialogs.ErrorDialog(
_('Dictionary for lang %s not available') % lang,
_('You have to install %s dictionary to use spellchecking, or '
'choose another language by setting the speller_language option.'
) % lang)
gajim.config.set('use_speller', False)
widget.set_active(False)
else:
gajim.config.set('speller_language', lang)
self.apply_speller()
else:
self.remove_speller()
@ -792,6 +833,36 @@ class PreferencesWindow:
def on_auto_xa_message_entry_changed(self, widget):
gajim.config.set('autoxa_message', widget.get_text().decode('utf-8'))
def fill_default_msg_treeview(self):
model = self.default_msg_tree.get_model()
model.clear()
status = []
for status_ in gajim.config.get_per('defaultstatusmsg'):
status.append(status_)
status.sort()
for status_ in status:
msg = gajim.config.get_per('defaultstatusmsg', status_, 'message')
enabled = gajim.config.get_per('defaultstatusmsg', status_, 'enabled')
iter = model.append()
uf_show = helpers.get_uf_show(status_)
model.set(iter, 0, status_, 1, uf_show, 2, msg, 3, enabled)
def on_default_msg_cell_edited(self, cell, row, new_text):
model = self.default_msg_tree.get_model()
iter = model.get_iter_from_string(row)
model.set_value(iter, 2, new_text)
def default_msg_toggled_cb(self, cell, path):
model = self.default_msg_tree.get_model()
model[path][3] = not model[path][3]
def on_default_msg_treemodel_row_changed(self, model, path, iter):
status = model[iter][0]
message = model[iter][2].decode('utf-8')
gajim.config.set_per('defaultstatusmsg', status, 'enabled',
model[iter][3])
gajim.config.set_per('defaultstatusmsg', status, 'message', message)
def save_status_messages(self, model):
for msg in gajim.config.get_per('statusmsg'):
gajim.config.del_per('statusmsg', msg)
@ -846,18 +917,25 @@ class PreferencesWindow:
def on_send_os_info_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'send_os_info')
def on_notify_gmail_checkbutton_toggled(self, widget):
def on_notify_gmail_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email')
def on_notify_gmail_extra_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'notify_on_new_gmail_email_extra')
def fill_msg_treeview(self):
self.xml.get_widget('delete_msg_button').set_sensitive(False)
model = self.msg_tree.get_model()
model.clear()
for msg in gajim.config.get_per('statusmsg'):
preset_status = []
for msg_name in gajim.config.get_per('statusmsg'):
preset_status.append(msg_name)
preset_status.sort()
for msg_name in preset_status:
msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
msg_text = helpers.from_one_line(msg_text)
iter = model.append()
val = gajim.config.get_per('statusmsg', msg, 'message')
val = helpers.from_one_line(val)
model.set(iter, 0, msg, 1, val)
model.set(iter, 0, msg_name, 1, msg_text)
def on_msg_cell_edited(self, cell, row, new_text):
model = self.msg_tree.get_model()
@ -970,7 +1048,6 @@ class PreferencesWindow:
path_to_snd_file = widget.get_text()
model, iter = self.sound_tree.get_selection().get_selected()
model[iter][2] = path_to_snd_file # set new path to sounds_model
model[iter][0] = True # set the sound to enabled
def on_play_button_clicked(self, widget):
model, iter = self.sound_tree.get_selection().get_selected()
@ -1004,6 +1081,7 @@ class AccountModificationWindow:
def __init__(self, account):
self.xml = gtkgui_helpers.get_glade('account_modification_window.glade')
self.window = self.xml.get_widget('account_modification_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.account = account
# init proxy list
@ -1152,7 +1230,7 @@ class AccountModificationWindow:
_('You are currently connected to the server'),
_('To change the account name, you must be disconnected.'))
return
if len(gajim.awaiting_events[self.account]):
if len(gajim.events.get_events(self.account)):
dialogs.ErrorDialog(_('Unread events'),
_('To change the account name, you must read all pending '
'events.'))
@ -1261,12 +1339,12 @@ class AccountModificationWindow:
if name != self.account:
#update variables
gajim.interface.instances[name] = gajim.interface.instances[self.account]
gajim.awaiting_events[name] = gajim.awaiting_events[self.account]
gajim.nicks[name] = gajim.nicks[self.account]
gajim.block_signed_in_notifications[name] = \
gajim.block_signed_in_notifications[self.account]
gajim.groups[name] = gajim.groups[self.account]
gajim.gc_connected[name] = gajim.gc_connected[self.account]
gajim.automatic_rooms[name] = gajim.automatic_rooms[self.account]
gajim.newly_added[name] = gajim.newly_added[self.account]
gajim.to_be_removed[name] = gajim.to_be_removed[self.account]
gajim.sleeper_state[name] = gajim.sleeper_state[self.account]
@ -1277,27 +1355,25 @@ class AccountModificationWindow:
gajim.status_before_autoaway[self.account]
gajim.contacts.change_account_name(self.account, name)
gajim.events.change_account_name(self.account, name)
#upgrade account variable in opened windows
for kind in ('infos', 'disco', 'chats', 'gc', 'gc_config'):
# change account variable for chat / gc controls
for ctrl in gajim.interface.msg_win_mgr.get_controls():
ctrl.account = name
# upgrade account variable in opened windows
for kind in ('infos', 'disco', 'gc_config'):
for j in gajim.interface.instances[name][kind]:
gajim.interface.instances[name][kind][j].account = name
#upgrade account in systray
if gajim.interface.systray_enabled:
for list in gajim.interface.systray.jids:
if list[0] == self.account:
list[0] = name
# ServiceCache object keep old property account
if hasattr(gajim.connections[self.account], 'services_cache'):
gajim.connections[self.account].services_cache.account = name
del gajim.interface.instances[self.account]
del gajim.awaiting_events[self.account]
del gajim.nicks[self.account]
del gajim.block_signed_in_notifications[self.account]
del gajim.groups[self.account]
del gajim.gc_connected[self.account]
del gajim.automatic_rooms[self.account]
del gajim.newly_added[self.account]
del gajim.to_be_removed[self.account]
del gajim.sleeper_state[self.account]
@ -1316,11 +1392,13 @@ class AccountModificationWindow:
relogin_needed = False
else: # we're connected to the account we want to apply changes
# check if relogin is needed
relogin_needed = self.options_changed_need_relogin(config,
relogin_needed = False
if self.options_changed_need_relogin(config,
('resource', 'proxy', 'usessl', 'keyname',
'use_custom_host', 'custom_host'))
'use_custom_host', 'custom_host')):
relogin_needed = True
if config['use_custom_host'] and (self.option_changed(config,
elif config['use_custom_host'] and (self.option_changed(config,
'custom_host') or self.option_changed(config, 'custom_port')):
relogin_needed = True
@ -1400,7 +1478,6 @@ class AccountModificationWindow:
dialogs.ErrorDialog(_('No such account available'),
_('You must create your account before editing your personal information.'))
return
jid = self.xml.get_widget('jid_entry').get_text().decode('utf-8')
# show error dialog if account is newly created (not in gajim.connections)
if not gajim.connections.has_key(self.account) or \
@ -1409,12 +1486,12 @@ class AccountModificationWindow:
_('Without a connection, you can not edit your personal information.'))
return
# in infos the key jid is OUR jid so we save the vcardwindow instance there
if gajim.interface.instances[self.account]['infos'].has_key(jid):
gajim.interface.instances[self.account]['infos'][jid].window.present()
else:
gajim.interface.instances[self.account]['infos'][jid] = \
vcard.VcardWindow(jid, self.account, True)
if not gajim.connections[self.account].vcard_supported:
dialogs.ErrorDialog(_("Your server doesn't support Vcard"),
_("Your server can't save your personal information."))
return
gajim.interface.edit_own_details(self.account)
def on_manage_proxies_button_clicked(self, widget):
if gajim.interface.instances.has_key('manage_proxies'):
@ -1438,7 +1515,7 @@ class AccountModificationWindow:
dialogs.ErrorDialog(_('Failed to get secret keys'),
_('There was a problem retrieving your OpenPGP secret keys.'))
return
secret_keys['None'] = 'None'
secret_keys[_('None')] = _('None')
instance = dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'),
_('Choose your OpenPGP key'), secret_keys)
keyID = instance.run()
@ -1447,7 +1524,7 @@ class AccountModificationWindow:
checkbutton = self.xml.get_widget('gpg_save_password_checkbutton')
gpg_key_label = self.xml.get_widget('gpg_key_label')
gpg_name_label = self.xml.get_widget('gpg_name_label')
if keyID[0] == 'None':
if keyID[0] == _('None'):
gpg_key_label.set_text(_('No key selected'))
gpg_name_label.set_text('')
checkbutton.set_sensitive(False)
@ -1495,6 +1572,7 @@ class ManageProxiesWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('manage_proxies_window.glade')
self.window = self.xml.get_widget('manage_proxies_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.proxies_treeview = self.xml.get_widget('proxies_treeview')
self.proxyname_entry = self.xml.get_widget('proxyname_entry')
self.init_list()
@ -1659,6 +1737,7 @@ class AccountsWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('accounts_window.glade')
self.window = self.xml.get_widget('accounts_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.accounts_treeview = self.xml.get_widget('accounts_treeview')
self.modify_button = self.xml.get_widget('modify_button')
self.remove_button = self.xml.get_widget('remove_button')
@ -1714,7 +1793,7 @@ class AccountsWindow:
if not iter:
return
account = model.get_value(iter, 0).decode('utf-8')
if len(gajim.awaiting_events[account]):
if len(gajim.events.get_events(account)):
dialogs.ErrorDialog(_('Unread events'),
_('Read all pending events before removing this account.'))
return
@ -1761,6 +1840,7 @@ class DataFormWindow:
self.config = config
self.xml = gtkgui_helpers.get_glade('data_form_window.glade')
self.window = self.xml.get_widget('data_form_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.config_vbox = self.xml.get_widget('config_vbox')
if config:
self.fill_vbox()
@ -1910,10 +1990,11 @@ class ServiceRegistrationWindow(DataFormWindow):
else:
self.xml = gtkgui_helpers.get_glade('service_registration_window.glade')
self.window = self.xml.get_widget('service_registration_window')
self.window.set_transient_for(gajim.interface.roster.window)
if infos.has_key('registered'):
self.window.set_title(_('Edit %s' % service))
self.window.set_title(_('Edit %s') % service)
else:
self.window.set_title(_('Register to %s' % service))
self.window.set_title(_('Register to %s') % service)
self.xml.get_widget('label').set_text(infos['instructions'])
self.entries = {}
self.draw_table()
@ -1983,7 +2064,7 @@ class GroupchatConfigWindow(DataFormWindow):
self.room_jid = room_jid
self.remove_button = {}
self.affiliation_treeview = {}
self.removed_jid = {}
self.list_init = {} # list at the begining
ui_list = {'outcast': _('Ban List'),
'member': _('Member List'),
'owner': _('Owner List'),
@ -1993,7 +2074,7 @@ class GroupchatConfigWindow(DataFormWindow):
add_on_vbox = self.xml.get_widget('add_on_vbox')
for affiliation in ('outcast', 'member', 'owner', 'admin'):
self.removed_jid[affiliation] = []
self.list_init[affiliation] = {}
hbox = gtk.HBox(spacing = 5)
add_on_vbox.pack_start(hbox, False)
@ -2086,8 +2167,6 @@ class GroupchatConfigWindow(DataFormWindow):
return
model = self.affiliation_treeview[affiliation].get_model()
model.append((jid,'', '', ''))
if jid in self.removed_jid[affiliation]:
self.removed_jid[affiliation].remove(jid)
def on_remove_button_clicked(self, widget, affiliation):
selection = self.affiliation_treeview[affiliation].get_selection()
@ -2100,7 +2179,6 @@ class GroupchatConfigWindow(DataFormWindow):
iter = model.get_iter(path)
jid = model[iter][0]
model.remove(iter)
self.removed_jid[affiliation].append(jid)
self.remove_button[affiliation].set_sensitive(False)
def on_affiliation_treeview_cursor_changed(self, widget, affiliation):
@ -2108,6 +2186,7 @@ class GroupchatConfigWindow(DataFormWindow):
def affiliation_list_received(self, affiliation, list):
'''Fill the affiliation treeview'''
self.list_init[affiliation] = list
if not affiliation:
return
tv = self.affiliation_treeview[affiliation]
@ -2134,18 +2213,28 @@ class GroupchatConfigWindow(DataFormWindow):
self.config)
for affiliation in ('outcast', 'member', 'owner', 'admin'):
list = {}
actual_jid_list = []
model = self.affiliation_treeview[affiliation].get_model()
iter = model.get_iter_first()
# add new jid
while iter:
jid = model[iter][0].decode('utf-8')
list[jid] = {'affiliation': affiliation}
if affiliation == 'outcast':
list[jid]['reason'] = model[iter][1].decode('utf-8')
actual_jid_list.append(jid)
if jid not in self.list_init[affiliation] or \
(affiliation == 'outcast' and self.list_init[affiliation]\
[jid].has_key('reason') and self.list_init[affiliation][jid]\
['reason'] != model[iter][1].decode('utf-8')):
list[jid] = {'affiliation': affiliation}
if affiliation == 'outcast':
list[jid]['reason'] = model[iter][1].decode('utf-8')
iter = model.iter_next(iter)
for jid in self.removed_jid[affiliation]:
list[jid] = {'affiliation': 'none'}
gajim.connections[self.account].send_gc_affiliation_list(self.room_jid,
list)
# remove removed one
for jid in self.list_init[affiliation]:
if jid not in actual_jid_list:
list[jid] = {'affiliation': 'none'}
if list:
gajim.connections[self.account].send_gc_affiliation_list(
self.room_jid, list)
self.window.destroy()
#---------- RemoveAccountWindow class -------------#
@ -2164,8 +2253,9 @@ class RemoveAccountWindow:
self.account = account
xml = gtkgui_helpers.get_glade('remove_account_window.glade')
self.window = xml.get_widget('remove_account_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.remove_and_unregister_radiobutton = xml.get_widget(
'remove_and_unregister_radiobutton')
'remove_and_unregister_radiobutton')
self.window.set_title(_('Removing %s account') % self.account)
xml.signal_autoconnect(self)
self.window.show_all()
@ -2198,7 +2288,7 @@ class RemoveAccountWindow:
self.dialog = None
if gajim.connections[self.account].connected:
self.dialog = dialogs.ConfirmationDialog(
_('Account "%s" is connected to the server' % self.account),
_('Account "%s" is connected to the server') % self.account,
_('If you remove it, the connection will be lost.'),
on_response_ok = remove)
else:
@ -2210,18 +2300,18 @@ class RemoveAccountWindow:
if not res:
return
# Close all opened windows
gajim.interface.roster.close_all(gajim.interface.instances[self.account])
gajim.interface.roster.close_all(self.account)
gajim.connections[self.account].disconnect(on_purpose = True)
del gajim.connections[self.account]
gajim.config.del_per('accounts', self.account)
gajim.interface.save_config()
del gajim.interface.instances[self.account]
del gajim.awaiting_events[self.account]
del gajim.nicks[self.account]
del gajim.block_signed_in_notifications[self.account]
del gajim.groups[self.account]
gajim.contacts.remove_account(self.account)
del gajim.gc_connected[self.account]
del gajim.automatic_rooms[self.account]
del gajim.to_be_removed[self.account]
del gajim.newly_added[self.account]
del gajim.sleeper_state[self.account]
@ -2243,6 +2333,7 @@ class ManageBookmarksWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('manage_bookmarks_window.glade')
self.window = self.xml.get_widget('manage_bookmarks_window')
self.window.set_transient_for(gajim.interface.roster.window)
#Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status
self.treestore = gtk.TreeStore(str, str, str, bool, str, str, str)
@ -2541,8 +2632,11 @@ class AccountCreationWizardWindow:
# Connect events from comboboxentry.child
server_comboboxentry = self.xml.get_widget('server_comboboxentry')
server_comboboxentry.child.connect('key_press_event',
self.on_server_comboboxentry_key_press_event)
entry = server_comboboxentry.child
entry.connect('key_press_event',
self.on_server_comboboxentry_key_press_event)
completion = gtk.EntryCompletion()
entry.set_completion(completion)
# parse servers.xml
servers_xml = os.path.join(gajim.DATA_DIR, 'other', 'servers.xml')
@ -2551,6 +2645,9 @@ class AccountCreationWizardWindow:
for server in servers:
servers_model.append((str(server[0]), int(server[1])))
completion.set_model(servers_model)
completion.set_text_column(0)
# Put servers into comboboxentries
server_comboboxentry.set_model(servers_model)
server_comboboxentry.set_text_column(0)
@ -2563,7 +2660,8 @@ class AccountCreationWizardWindow:
self.finish_button = self.xml.get_widget('finish_button')
self.advanced_button = self.xml.get_widget('advanced_button')
self.finish_label = self.xml.get_widget('finish_label')
self.go_online_checkbutton = self.xml.get_widget('go_online_checkbutton')
self.go_online_checkbutton = self.xml.get_widget(
'go_online_checkbutton')
self.show_vcard_checkbutton = self.xml.get_widget(
'show_vcard_checkbutton')
self.progressbar = self.xml.get_widget('progressbar')
@ -2581,7 +2679,8 @@ class AccountCreationWizardWindow:
del gajim.interface.instances['account_creation_wizard']
def on_register_server_features_button_clicked(self, widget):
helpers.launch_browser_mailer('url', 'http://www.jabber.org/network/oldnetwork.shtml')
helpers.launch_browser_mailer('url',
'http://www.jabber.org/network/oldnetwork.shtml')
def on_save_password_checkbutton_toggled(self, widget):
self.xml.get_widget('pass1_entry').grab_focus()
@ -2837,12 +2936,12 @@ _('You can set advanced account options by pressing Advanced button, or later by
# update variables
gajim.interface.instances[self.account] = {'infos': {}, 'disco': {},
'chats': {}, 'gc': {}, 'gc_config': {}}
gajim.awaiting_events[self.account] = {}
'gc_config': {}}
gajim.connections[self.account].connected = 0
gajim.groups[self.account] = {}
gajim.contacts.add_account(self.account)
gajim.gc_connected[self.account] = {}
gajim.automatic_rooms[self.account] = {}
gajim.newly_added[self.account] = []
gajim.to_be_removed[self.account] = []
gajim.nicks[self.account] = config['name']

View File

@ -24,11 +24,11 @@
##
import gtk
import gtk.glade
import pango
import gobject
import time
import sys
import os
import tooltips
import dialogs
import locale
@ -36,13 +36,8 @@ import locale
import gtkgui_helpers
from common import gajim
from common import helpers
from common import i18n
from calendar import timegm
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
from common.fuzzyclock import FuzzyClock
class ConversationTextview:
'''Class for the conversation textview (where user reads already said messages)
@ -56,7 +51,7 @@ class ConversationTextview:
self.tv.set_accepts_tab(True)
self.tv.set_editable(False)
self.tv.set_cursor_visible(False)
self.tv.set_wrap_mode(gtk.WRAP_WORD)
self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.tv.set_left_margin(2)
self.tv.set_right_margin(2)
self.handlers = {}
@ -89,6 +84,14 @@ class ConversationTextview:
color = gajim.config.get('statusmsgcolor')
self.tagStatus.set_property('foreground', color)
colors = gajim.config.get('gc_nicknames_colors')
colors = colors.split(':')
for color in xrange(len(colors)):
tagname = 'gc_nickname_color_' + str(color)
tag = buffer.create_tag(tagname)
color = colors[color]
tag.set_property('foreground', color)
tag = buffer.create_tag('marked')
color = gajim.config.get('markedmsgcolor')
tag.set_property('foreground', color)
@ -130,6 +133,10 @@ class ConversationTextview:
buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER)
self.allow_focus_out_line = True
# holds the iter's offset which points to the end of --- line
self.focus_out_end_iter_offset = None
self.line_tooltip = tooltips.BaseTooltip()
def del_handlers(self):
@ -180,9 +187,73 @@ class ConversationTextview:
def scroll_to_end_iter(self):
buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter()
if not end_iter:
return False
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
def show_focus_out_line(self):
if not self.allow_focus_out_line:
# if room did not receive focus-in from the last time we added
# --- line then do not readd
return
print_focus_out_line = False
buffer = self.tv.get_buffer()
if self.focus_out_end_iter_offset is None:
# this happens only first time we focus out on this room
print_focus_out_line = True
else:
if self.focus_out_end_iter_offset != buffer.get_end_iter().\
get_offset():
# this means after last-focus something was printed
# (else end_iter's offset is the same as before)
# only then print ---- line (eg. we avoid printing many following
# ---- lines)
print_focus_out_line = True
if print_focus_out_line and buffer.get_char_count() > 0:
buffer.begin_user_action()
# remove previous focus out line if such focus out line exists
if self.focus_out_end_iter_offset is not None:
end_iter_for_previous_line = buffer.get_iter_at_offset(
self.focus_out_end_iter_offset)
begin_iter_for_previous_line = end_iter_for_previous_line.copy()
# img_char+1 (the '\n')
begin_iter_for_previous_line.backward_chars(2)
# remove focus out line
buffer.delete(begin_iter_for_previous_line,
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)
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
# update the iter we hold to make comparison the next time
self.focus_out_end_iter_offset = buffer.get_end_iter().get_offset()
buffer.end_user_action()
# scroll to the end (via idle in case the scrollbar has appeared)
gobject.idle_add(self.scroll_to_end)
def show_line_tooltip(self):
pointer = self.tv.get_pointer()
x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
@ -237,6 +308,7 @@ class ConversationTextview:
buffer = self.tv.get_buffer()
start, end = buffer.get_bounds()
buffer.delete(start, end)
self.focus_out_end_iter_offset = None
def visit_url_from_menuitem(self, widget, link):
'''basically it filters out the widget instance'''
@ -572,9 +644,6 @@ class ConversationTextview:
other_tags_for_name = [], other_tags_for_time = [],
other_tags_for_text = [], subject = None, old_kind = None):
'''prints 'chat' type messages'''
# kind = info, we print things as if it was a status: same color, ...
if kind == 'info':
kind = 'status'
buffer = self.tv.get_buffer()
buffer.begin_user_action()
end_iter = buffer.get_end_iter()
@ -593,7 +662,7 @@ class ConversationTextview:
# We don't have tim for outgoing messages...
tim = time.localtime()
current_print_time = gajim.config.get('print_time')
if current_print_time == 'always':
if current_print_time == 'always' and kind != 'info':
before_str = gajim.config.get('before_time')
after_str = gajim.config.get('after_time')
# get difference in days since epoch (86400 = 24*3600)
@ -612,25 +681,38 @@ class ConversationTextview:
if day_str:
format += day_str + ' '
format += '%X' + after_str
# format comes as unicode, because of day_str.
# we convert it to the encoding that we want
tim_format = time.strftime(format, tim).encode('utf-8')
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')
buffer.insert_with_tags_by_name(end_iter, tim_format + ' ',
*other_tags_for_time)
elif current_print_time == 'sometimes':
elif current_print_time == 'sometimes' and kind != 'info':
every_foo_seconds = 60 * gajim.config.get(
'print_ichat_every_foo_minutes')
seconds_passed = time.mktime(tim) - self.last_time_printout
if seconds_passed > every_foo_seconds:
self.last_time_printout = time.mktime(tim)
end_iter = buffer.get_end_iter()
tim_format = time.strftime('%H:%M', tim).decode(
locale.getpreferredencoding())
if gajim.config.get('print_time_fuzzy') > 0:
fc = FuzzyClock()
fc.setTime(time.strftime('%H:%M', tim))
ft = fc.getFuzzyTime(gajim.config.get('print_time_fuzzy'))
tim_format = ft.decode(locale.getpreferredencoding())
else:
tim_format = time.strftime('%H:%M', tim).decode(
locale.getpreferredencoding())
buffer.insert_with_tags_by_name(end_iter, tim_format + '\n',
'time_sometimes')
# kind = info, we print things as if it was a status: same color, ...
if kind == 'info':
kind = 'status'
other_text_tag = self.detect_other_text_tag(text, kind)
text_tags = other_tags_for_text[:] # create a new list
if other_text_tag:
# note that color of /me may be overwritten in gc_control
text_tags.append(other_text_tag)
else: # not status nor /me
if gajim.config.get(

View File

@ -20,8 +20,6 @@ import sys
from common import gajim
from common import exceptions
from common import i18n
_ = i18n._
try:
import dbus

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,6 @@ import inspect
import weakref
import gobject
import gtk
import gtk.glade
import pango
import dialogs
@ -60,12 +59,6 @@ import gtkgui_helpers
from common import gajim
from common import xmpp
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain (APP, i18n.DIR)
gtk.glade.textdomain (APP)
# Dictionary mapping category, type pairs to browser class, image pairs.
# This is a function, so we can call it after the classes are declared.
@ -92,7 +85,7 @@ def _gen_agent_type_info():
('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy
# Transports
('conference', 'irc'): (False, 'irc.png'),
('conference', 'irc'): (ToplevelAgentBrowser, 'irc.png'),
('_jid', 'irc'): (False, 'irc.png'),
('gateway', 'aim'): (False, 'aim.png'),
('_jid', 'aim'): (False, 'aim.png'),
@ -140,7 +133,8 @@ class CacheDictionary:
def _expire_timeout(self, key):
'''The timeout has expired, remove the object.'''
del self.cache[key]
if key in self.cache:
del self.cache[key]
return False
def _refresh_timeout(self, key):
@ -278,7 +272,7 @@ class ServicesCache:
except KeyError:
continue
browser = info[0]
if browser is not None:
if browser:
break
# Note: possible outcome here is browser=False
if browser is None:
@ -449,7 +443,7 @@ _('Without a connection, you can not browse available services'))
# Address combobox
self.address_comboboxentry = None
address_hbox = self.xml.get_widget('address_hbox')
address_table = self.xml.get_widget('address_table')
if address_entry:
self.address_comboboxentry = self.xml.get_widget(
'address_comboboxentry')
@ -461,7 +455,6 @@ _('Without a connection, you can not browse available services'))
self.address_comboboxentry.set_text_column(0)
self.latest_addresses = gajim.config.get(
'latest_disco_addresses').split()
jid = gajim.get_hostname_from_account(self.account)
if jid in self.latest_addresses:
self.latest_addresses.remove(jid)
self.latest_addresses.insert(0, jid)
@ -472,8 +465,8 @@ _('Without a connection, you can not browse available services'))
self.address_comboboxentry.child.set_text(jid)
else:
# Don't show it at all if we didn't ask for it
address_hbox.set_no_show_all(True)
address_hbox.hide()
address_table.set_no_show_all(True)
address_table.hide()
self._initial_state()
self.xml.signal_autoconnect(self)
@ -1205,7 +1198,10 @@ class ToplevelAgentBrowser(AgentBrowser):
else:
room = ''
if not gajim.interface.instances[self.account].has_key('join_gc'):
dialogs.JoinGroupchatWindow(self.account, service, room)
try:
dialogs.JoinGroupchatWindow(self.account, service, room)
except RuntimeError:
pass
else:
gajim.interface.instances[self.account]['join_gc'].window.present()
self.window.destroy(chain = True)
@ -1535,7 +1531,10 @@ class MucBrowser(AgentBrowser):
else:
room = model[iter][1].decode('utf-8')
if not gajim.interface.instances[self.account].has_key('join_gc'):
dialogs.JoinGroupchatWindow(self.account, service, room)
try:
dialogs.JoinGroupchatWindow(self.account, service, room)
except RuntimeError:
pass
else:
gajim.interface.instances[self.account]['join_gc'].window.present()
self.window.destroy(chain = True)

View File

@ -18,7 +18,6 @@
##
import gtk
import gtk.glade
import gobject
import pango
import os
@ -30,12 +29,6 @@ import dialogs
from common import gajim
from common import helpers
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain (APP, i18n.DIR)
gtk.glade.textdomain (APP)
C_IMAGE = 0
C_LABELS = 1
@ -153,9 +146,8 @@ class FileTransfersWindow:
''' show a dialog saying that file (file_props) has been transferred'''
self.window.present()
self.window.window.focus()
def on_open(widget, file_props):
self.dialog.destroy()
dialog.destroy()
if not file_props.has_key('file-name'):
return
(path, file) = os.path.split(file_props['file-name'])
@ -192,17 +184,17 @@ class FileTransfersWindow:
sectext += recipient
if file_props['type'] == 'r':
sectext += '\n\t' +_('Saved in: %s') % file_path
self.dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
dialog = dialogs.HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_NONE,
_('File transfer completed'), sectext)
if file_props['type'] == 'r':
button = gtk.Button(_('_Open Containing Folder'))
button.connect('clicked', on_open, file_props)
self.dialog.action_area.pack_start(button)
ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
dialog.action_area.pack_start(button)
ok_button = dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
def on_ok(widget):
self.dialog.destroy()
dialog.destroy()
ok_button.connect('clicked', on_ok)
self.dialog.show_all()
dialog.show_all()
def show_request_error(self, file_props):
''' show error dialog to the recipient saying that transfer
@ -221,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':
@ -229,7 +221,9 @@ _('Connection with peer cannot be established.'))
else:
file_name = file_props['name']
sectext = '\t' + _('Filename: %s') % file_name
sectext += '\n\t' + _('Sender: %s') % jid
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()
@ -237,7 +231,7 @@ _('Connection with peer cannot be established.'))
def show_file_send_request(self, account, contact):
def on_ok(widget):
file_dir = None
files_path_list = self.dialog.get_filenames()
files_path_list = dialog.get_filenames()
files_path_list = gtkgui_helpers.decode_filechooser_file_paths(
files_path_list)
for file_path in files_path_list:
@ -245,16 +239,16 @@ _('Connection with peer cannot be established.'))
file_dir = os.path.dirname(file_path)
if file_dir:
gajim.config.set('last_send_dir', file_dir)
self.dialog.destroy()
dialog.destroy()
self.dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
dialog = dialogs.FileChooserDialog(_('Choose File to Send...'),
gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
gtk.RESPONSE_OK,
True, # select multiple true as we can select many files to send
gajim.config.get('last_send_dir'),
)
btn = self.dialog.add_button(_('_Send'), gtk.RESPONSE_OK)
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)
@ -313,6 +307,12 @@ _('Connection with peer cannot be established.'))
file_path = gtkgui_helpers.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
stat = os.stat(file_path)
dl_size = stat.st_size
file_size = file_props['size']
@ -327,6 +327,11 @@ _('Connection with peer cannot be established.'))
return
elif response == 100:
file_props['offset'] = dl_size
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
dialog2.destroy()
self._start_receive(file_path, account, contact, file_props)
@ -442,12 +447,11 @@ _('Connection with peer cannot be established.'))
jid = gajim.get_jid_without_resource(other)
else: # It's a Contact instance
jid = other.jid
if gajim.awaiting_events[account].has_key(jid):
for event in gajim.awaiting_events[account][jid]:
if event[0] in ('file-error', 'file-completed',
'file-request-error', 'file-send-error', 'file-stopped') and \
event[1]['sid'] == file_props['sid']:
gajim.interface.remove_event(account, jid, event)
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']:
gajim.events.remove_events(account, jid, event)
del(self.files_props[sid[0]][sid[1:]])
del(file_props)
@ -832,9 +836,9 @@ _('Connection with peer cannot be established.'))
self.set_buttons_sensitive(path, True)
event_button = gtkgui_helpers.get_possible_button_event(event)
self.file_transfers_menu.show_all()
self.file_transfers_menu.popup(None, self.tree, None,
event_button, event.time)
self.file_transfers_menu.show_all()
def on_transfers_list_key_press_event(self, widget, event):
'''when a key is pressed in the treeviews'''

View File

@ -38,8 +38,6 @@ signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
from common import exceptions
from common import i18n
_ = i18n._
i18n.init()
try:
PREFERRED_ENCODING = locale.getpreferredencoding()
except:
@ -68,7 +66,7 @@ BASENAME = 'gajim-remote'
class GajimRemote:
def __init__(self):
self.argv_len = len(sys.argv)
# define commands dict. Prototype :
@ -81,7 +79,7 @@ class GajimRemote:
#
self.commands = {
'help':[
_('shows a help on specific command'),
_('Shows a help on specific command'),
[
#User gets help for the command, specified by this parameter
(_('command'),
@ -101,7 +99,7 @@ class GajimRemote:
[
(_('account'), _('show only contacts of the given account'), False)
]
],
'list_accounts': [
_('Prints a list of registered accounts'),
@ -110,6 +108,7 @@ class GajimRemote:
'change_status': [
_('Changes the status of account or accounts'),
[
#offline, online, chat, away, xa, dnd, invisible should not be translated
(_('status'), _('one of: offline, online, chat, away, xa, dnd, invisible '), True),
(_('message'), _('status message'), False),
(_('account'), _('change status of account "account". '
@ -127,7 +126,7 @@ class GajimRemote:
]
],
'send_message':[
_('Sends new message to a contact in the roster. Both OpenPGP key '
_('Sends new chat message to a contact in the roster. Both OpenPGP key '
'and account are optional. If you want to set only \'account\', '
'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
[
@ -138,6 +137,20 @@ class GajimRemote:
(_('account'), _('if specified, the message will be sent '
'using this account'), False),
]
],
'send_single_message':[
_('Sends new single message to a contact in the roster. Both OpenPGP key '
'and account are optional. If you want to set only \'account\', '
'without \'OpenPGP key\', just set \'OpenPGP key\' to \'\'.'),
[
('jid', _('JID of the contact that will receive the message'), True),
(_('subject'), _('message subject'), True),
(_('message'), _('message contents'), True),
(_('pgp key'), _('if specified, the message will be encrypted '
'using this public key'), False),
(_('account'), _('if specified, the message will be sent '
'using this account'), False),
]
],
'contact_info': [
_('Gets detailed info on a contact'),
@ -188,7 +201,7 @@ class GajimRemote:
('jid', _('JID of the contact'), True),
(_('account'), _('if specified, contact is taken from the '
'contact list of this account'), False)
]
],
'add_contact': [
@ -198,14 +211,14 @@ class GajimRemote:
(_('account'), _('Adds new contact to this account'), False)
]
],
'get_status': [
_('Returns current status (the global one unless account is specified)'),
[
(_('account'), _(''), False)
]
],
'get_status_message': [
_('Returns current status message(the global one unless account is specified)'),
[
@ -218,11 +231,20 @@ class GajimRemote:
[ ]
],
'start_chat': [
_('Open \'Start Chat\' dialog'),
_('Opens \'Start Chat\' dialog'),
[
(_('account'), _('Starts chat, using this account'), True)
]
],
'send_xml': [
_('Sends custom XML'),
[
('xml', _('XML to send'), True),
('account', _('Account in which the xml will be sent; '
'if not specified, xml will be sent to all accounts'),
False)
]
],
}
if self.argv_len < 2 or \
sys.argv[1] not in self.commands.keys(): # no args or bad args
@ -234,14 +256,14 @@ class GajimRemote:
else:
print self.compose_help().encode(PREFERRED_ENCODING)
sys.exit(0)
self.init_connection()
self.check_arguments()
if self.command == 'contact_info':
if self.argv_len < 3:
send_error(_('Missing argument "contact_jid"'))
try:
res = self.call_remote_method()
except exceptions.ServiceNotAvailable:
@ -249,14 +271,14 @@ class GajimRemote:
sys.exit(1)
else:
self.print_result(res)
def print_result(self, res):
''' Print retrieved result to the output '''
if res is not None:
if self.command in ('open_chat', 'send_message', 'start_chat'):
if self.command == 'send_message':
if self.command in ('open_chat', 'send_message', 'send_single_message', 'start_chat'):
if self.command in ('send_message', 'send_single_message'):
self.argv_len -= 2
if res is False:
if self.argv_len < 4:
send_error(_('\'%s\' is not in your roster.\n'
@ -289,7 +311,7 @@ class GajimRemote:
print self.print_info(0, res, True)
elif res:
print unicode(res).encode(PREFERRED_ENCODING)
def init_connection(self):
''' create the onnection to the session dbus,
or exit if it is not possible '''
@ -297,7 +319,7 @@ class GajimRemote:
self.sbus = dbus.SessionBus()
except:
raise exceptions.SessionBusNotPresent
if _version[1] >= 30:
obj = self.sbus.get_object(SERVICE, OBJ_PATH)
interface = dbus.Interface(obj, INTERFACE)
@ -306,10 +328,10 @@ class GajimRemote:
interface = self.service.get_object(OBJ_PATH, INTERFACE)
else:
send_error(_('Unknown D-Bus version: %s') % _version[1])
# get the function asked
self.method = interface.__getattr__(self.command)
def make_arguments_row(self, args):
''' return arguments list. Mandatory arguments are enclosed with:
'<', '>', optional arguments - with '[', ']' '''
@ -326,7 +348,7 @@ class GajimRemote:
else:
str += ']'
return str
def help_on_command(self, command):
''' return help message for a given command '''
if command in self.commands:
@ -340,7 +362,7 @@ class GajimRemote:
str += ' ' + argument[0] + ' - ' + argument[1] + '\n'
return str
send_error(_('%s not found') % command)
def compose_help(self):
''' print usage, and list available commands '''
str = _('Usage: %s command [arguments]\nCommand is one of:\n' ) % BASENAME
@ -361,7 +383,7 @@ class GajimRemote:
str += ']'
str += '\n'
return str
def print_info(self, level, prop_dict, encode_return = False):
''' return formated string from data structure '''
if prop_dict is None or not isinstance(prop_dict, (dict, list, tuple)):
@ -410,7 +432,7 @@ class GajimRemote:
except:
pass
return ret_str
def check_arguments(self):
''' Make check if all necessary arguments are given '''
argv_len = self.argv_len - 2
@ -420,7 +442,7 @@ class GajimRemote:
send_error(_('Argument "%s" is not specified. \n'
'Type "%s help %s" for more info') %
(args[argv_len][0], BASENAME, self.command))
def call_remote_method(self):
''' calls self.method with arguments from sys.argv[2:] '''
args = sys.argv[2:]

View File

@ -33,16 +33,22 @@ import sys
import os
import urllib
from common import i18n
import message_control
from chat_control import ChatControlBase
from common import exceptions
from common import i18n
from common.zeroconf import connection_zeroconf
i18n.init()
_ = i18n._
if os.name == 'posix': # dl module is Unix Only
try: # rename the process name to gajim
import dl
libc = dl.open('/lib/libc.so.6')
libc.call('prctl', 15, 'gajim\0', 0, 0, 0)
except:
pass
try:
import gtk
@ -73,6 +79,15 @@ except exceptions.PysqliteNotAvailable, e:
pritext = _('Gajim needs PySQLite2 to run')
sectext = str(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,
@ -129,40 +144,60 @@ 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'
if profile:
config_filename += '.%s' % profile
pid_filename += '.%s' % profile
pid_filename += '.pid'
import dialogs
if os.path.exists(pid_filename):
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
gtk.window_set_default_icon(pix) # set the icon to all newly opened wind
pritext = _('Gajim is already running')
sectext = _('Another instance of Gajim seems to be running\nRun anyway?')
dialog = dialogs.YesNoDialog(pritext, sectext)
if dialog.get_response() != gtk.RESPONSE_YES:
sys.exit(3)
if os.path.exists(pid_filename):
os.remove(pid_filename)
dialog.destroy()
# Create .gajim dir
pid_dir = os.path.dirname(pid_filename)
if not os.path.exists(pid_dir):
check_paths.create_path(pid_dir)
# Create pid file
f = open(pid_filename, 'a')
f.close()
def on_exit():
# delete pid file on normal exit
if os.path.exists(pid_filename):
os.remove(pid_filename)
import atexit
atexit.register(on_exit)
parser = optparser.OptionsParser(config_filename)
import roster_window
import systray
import dialogs
import vcard
import profile_window
import config
class MigrateCommand(nslookup.IdleCommand):
def __init__(self, on_result):
nslookup.IdleCommand.__init__(self, on_result)
self.commandtimeout = 10
def _compose_command_args(self):
return ['python', 'migrate_logs_to_dot9_db.py', 'dont_wait']
def _return_result(self):
print self.result
if self.result_handler:
self.result_handler(self.result)
self.result_handler = None
class GlibIdleQueue(idlequeue.IdleQueue):
'''
Extends IdleQueue to use glib io_add_wath, instead of select/poll
@ -239,7 +274,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
@ -251,7 +286,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
@ -275,6 +310,14 @@ class Interface:
gajim.con_types[account] = con_type
self.roster.draw_account(account)
def handle_event_connection_lost(self, account, array):
# ('CONNECTION_LOST', account, [title, text])
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'connection_lost.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
notify.popup(_('Connection Failed'), account, account,
'connection_failed', path, array[0], array[1])
def unblock_signed_in_notifications(self, account):
gajim.block_signed_in_notifications[account] = False
@ -314,9 +357,9 @@ class Interface:
def edit_own_details(self, account):
jid = gajim.get_jid_from_account(account)
if not self.instances[account]['infos'].has_key(jid):
self.instances[account]['infos'][jid] = \
vcard.VcardWindow(jid, account, True)
if not self.instances[account].has_key('profile'):
self.instances[account]['profile'] = \
profile_window.ProfileWindow(account)
gajim.connections[account].request_vcard(jid)
def handle_event_notify(self, account, array):
@ -346,7 +389,7 @@ class Interface:
# Update contact
jid_list = gajim.contacts.get_jid_list(account)
if ji in jid_list:
if ji in jid_list or jid == gajim.get_jid_from_account(account):
lcontact = gajim.contacts.get_contacts_from_jid(account, ji)
contact1 = None
resources = []
@ -363,7 +406,20 @@ class Interface:
return
else:
contact1 = gajim.contacts.get_first_contact_from_jid(account, ji)
if contact1.show in statuss:
if not contact1:
# presence of another resource of our jid
if resource == gajim.connections[account].server_resource:
return
contact1 = gajim.contacts.create_contact(jid = ji,
name = gajim.nicks[account], groups = [],
show = array[1], status = status_message, sub = 'both',
ask = 'none', priority = priority, keyID = keyID,
resource = resource)
old_show = 0
gajim.contacts.add_contact(account, contact1)
lcontact.append(contact1)
self.roster.add_self_contact(account)
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:
@ -403,9 +459,21 @@ class Interface:
if ji in jid_list:
# Update existing iter
self.roster.draw_contact(ji, account)
elif jid == gajim.get_jid_from_account(account):
# It's another of our resources. We don't need to see that!
return
# transport just signed in/out, don't show popup notifications
# for 30s
account_ji = account + '/' + ji
gajim.block_signed_in_notifications[account_ji] = True
gobject.timeout_add(30000, self.unblock_signed_in_notifications,
account_ji)
locations = (self.instances, self.instances[account])
for location in locations:
if location.has_key('add_contact'):
if old_show == 0 and new_show > 1:
location['add_contact'].transport_signed_in(jid)
break
elif old_show > 1 and new_show == 0:
location['add_contact'].transport_signed_out(jid)
break
elif ji in jid_list:
# It isn't an agent
# reset chatstate if needed:
@ -416,20 +484,21 @@ class Interface:
gajim.connections[account].remove_transfers_for_contact(contact1)
self.roster.chg_contact_status(contact1, array[1], status_message,
account)
# play sound
# Notifications
if old_show < 2 and new_show > 1:
notify.notify('contact_connected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactPresence',
(account, array))
elif old_show > 1 and new_show < 2:
notify.notify('contact_disconnected', jid, account, status_message)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('ContactAbsence', (account, array))
# FIXME: stop non active file transfers
elif new_show > 1: # Status change (not connected/disconnected or error (<1))
notify.notify('status_change', jid, account, [new_show, status_message])
notify.notify('status_change', jid, account, [new_show,
status_message])
else:
# FIXME: Msn transport (CMSN1.2.1 and PyMSN0.10) doesn't follow the JEP
# remove in 2007
@ -440,7 +509,7 @@ class Interface:
def handle_event_msg(self, account, array):
# 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject,
# chatstate))
# chatstate, msg_id, composing_jep, user_nick)) user_nick is JEP-0172
full_jid_with_resource = array[0]
jid = gajim.get_jid_without_resource(full_jid_with_resource)
@ -448,6 +517,7 @@ class Interface:
message = array[1]
msg_type = array[4]
subject = array[5]
chatstate = array[6]
msg_id = array[7]
composing_jep = array[8]
@ -513,10 +583,13 @@ class Interface:
not gajim.contacts.get_contact(account, jid) and not pm:
return
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.awaiting_events[account].has_key(
jid_of_control):
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
first = True
@ -527,10 +600,14 @@ class Interface:
else:
# array: (jid, msg, time, encrypted, msg_type, subject)
self.roster.on_message(jid, message, array[2], account, array[3],
msg_type, array[5], resource, msg_id)
msg_type, subject, resource, msg_id, array[9], advanced_notif_num)
nickname = gajim.get_name_from_jid(account, jid)
# Check and do wanted notifications
notify.notify('new_message', jid, account, [msg_type, first, nickname, message])
msg = message
if subject:
msg = _('Subject: %s') % subject + '\n' + msg
notify.notify('new_message', jid, account, [msg_type, first, nickname,
msg], advanced_notif_num)
if self.remote_ctrl:
self.remote_ctrl.raise_signal('NewMessage', (account, array))
@ -583,8 +660,8 @@ class Interface:
helpers.play_sound('message_sent')
def handle_event_subscribe(self, account, array):
#('SUBSCRIBE', account, (jid, text))
dialogs.SubscriptionRequestWindow(array[0], array[1], account)
#('SUBSCRIBE', account, (jid, text, user_nick)) user_nick is JEP-0172
dialogs.SubscriptionRequestWindow(array[0], array[1], account, array[2])
if self.remote_ctrl:
self.remote_ctrl.raise_signal('Subscribe', (account, array))
@ -664,8 +741,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))
@ -705,8 +782,8 @@ class Interface:
nick = array['NICKNAME']
if nick:
gajim.nicks[account] = nick
if self.instances[account]['infos'].has_key(array['jid']):
win = self.instances[account]['infos'][array['jid']]
if self.instances[account].has_key('profile'):
win = self.instances[account]['profile']
win.set_values(array)
if account in self.show_vcard_when_connect:
self.show_vcard_when_connect.remove(account)
@ -763,7 +840,7 @@ class Interface:
c = gajim.contacts.get_contact(account, array[0], array[1])
# c is a list when no resource is given. it probably means that contact
# is offline, so only on Contact instance
if isinstance(c, list):
if isinstance(c, list) and len(c):
c = c[0]
if c: # c can be none if it's a gc contact
c.last_status_time = time.localtime(time.time() - array[2])
@ -811,14 +888,13 @@ class Interface:
uf_show = helpers.get_uf_show(show)
ctrl.print_conversation(_('%s is now %s (%s)') % (nick, uf_show, status),
'status')
ctrl.draw_banner()
ctrl.parent_win.redraw_tab(ctrl)
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))
jids = array[0].split('/', 1)
room_jid = jids[0]
gc_control = self.msg_win_mgr.get_control(room_jid, account)
@ -830,7 +906,7 @@ class Interface:
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])
if self.remote_ctrl:
self.remote_ctrl.raise_signal('GCMessage', (account, array))
@ -851,10 +927,18 @@ class Interface:
def handle_event_gc_config(self, account, array):
#('GC_CONFIG', account, (jid, config)) config is a dict
jid = array[0].split('/')[0]
if not self.instances[account]['gc_config'].has_key(jid):
self.instances[account]['gc_config'][jid] = \
config.GroupchatConfigWindow(account, jid, array[1])
room_jid = array[0].split('/')[0]
if room_jid in gajim.automatic_rooms[account]:
# use default configuration
gajim.connections[account].send_gc_config(room_jid, array[1])
# invite contacts
if gajim.automatic_rooms[account][room_jid].has_key('invities'):
for jid in gajim.automatic_rooms[account][room_jid]['invities']:
gajim.connections[account].send_invite(room_jid, jid)
del gajim.automatic_rooms[account][room_jid]
elif not self.instances[account]['gc_config'].has_key(room_jid):
self.instances[account]['gc_config'][room_jid] = \
config.GroupchatConfigWindow(account, room_jid, array[1])
def handle_event_gc_affiliation(self, account, array):
#('GC_AFFILIATION', account, (room_jid, affiliation, list)) list is list
@ -875,8 +959,7 @@ class Interface:
self.add_event(account, jid, 'gc-invitation', (room_jid, array[2],
array[3]))
if gajim.config.get('notify_on_new_message') and \
helpers.allow_showing_notification(account):
if helpers.allow_showing_notification(account, 'notify_on_new_message'):
path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'gc_invitation.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(path)
@ -908,7 +991,7 @@ class Interface:
c = contacts[0]
self.roster.remove_contact(c, account)
gajim.contacts.remove_jid(account, jid)
if gajim.awaiting_events[account].has_key(c.jid):
if gajim.events.get_events(account, c.jid):
keyID = ''
attached_keys = gajim.config.get_per('accounts', account,
'attached_gpg_keys').split()
@ -989,12 +1072,20 @@ class Interface:
def handle_event_gmail_notify(self, account, array):
jid = array[0]
gmail_new_messages = int(array[1])
gmail_messages_list = array[2]
if gajim.config.get('notify_on_new_gmail_email'):
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') % \
{'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)
if gajim.config.get('notify_on_new_gmail_email_extra'):
for gmessage in gmail_messages_list:
# each message has a 'From', 'Subject' and 'Snippet' field
text += _('\nFrom: %(from_address)s') % \
{'from_address': gmessage['From']}
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
notify.popup(_('New E-mail'), jid, account, 'gmail',
path_to_image = path, title = title, text = text)
@ -1035,71 +1126,59 @@ class Interface:
path_to_bw_file = path_to_file + '_notif_size_bw.png'
bwbuf.save(path_to_bw_file, 'png')
def add_event(self, account, jid, typ, args):
'''add an event to the awaiting_events var'''
# We add it to the awaiting_events queue
def add_event(self, account, jid, type_, args):
'''add an event to the gajim.events var'''
# We add it to the gajim.events queue
# Do we have a queue?
jid = gajim.get_jid_without_resource(jid)
qs = gajim.awaiting_events[account]
no_queue = False
if not qs.has_key(jid):
no_queue = True
qs[jid] = []
qs[jid].append((typ, args))
self.roster.nb_unread += 1
no_queue = len(gajim.events.get_events(account, jid)) == 0
event_type = None
# type_ can be gc-invitation file-send-error file-error file-request-error
# file-request file-completed file-stopped
# event_type can be in advancedNotificationWindow.events_list
event_types = {'file-request': 'ft_request',
'file-completed': 'ft_finished'}
if type_ in event_types:
event_type = event_types[type_]
show_in_roster = notify.get_show_in_roster(event_type, account, jid)
show_in_systray = notify.get_show_in_systray(event_type, account, jid)
event = gajim.events.create_event(type_, args,
show_in_roster = show_in_roster,
show_in_systray = show_in_systray)
gajim.events.add_event(account, jid, event)
self.roster.show_title()
if no_queue: # We didn't have a queue: we change icons
self.roster.draw_contact(jid, account)
if self.systray_enabled:
self.systray.add_jid(jid, account, typ)
def redraw_roster_systray(self, account, jid, typ = None):
self.roster.nb_unread -= 1
self.roster.show_title()
self.roster.draw_contact(jid, account)
if self.systray_enabled:
self.systray.remove_jid(jid, account, typ)
def remove_first_event(self, account, jid, type_ = None):
event = gajim.events.get_first_event(account, jid, type_)
self.remove_event(account, jid, event)
def remove_first_event(self, account, jid, typ = None):
qs = gajim.awaiting_events[account]
event = gajim.get_first_event(account, jid, typ)
qs[jid].remove(event)
# Is it the last event?
if not len(qs[jid]):
del qs[jid]
def remove_event(self, account, jid, event):
if gajim.events.remove_events(account, jid, event):
# No such event found
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)
self.redraw_roster_systray(account, jid, typ)
def remove_event(self, account, jid, event):
qs = gajim.awaiting_events[account]
if not event in qs[jid]:
return
qs[jid].remove(event)
# Is it the last event?
if not len(qs[jid]):
del qs[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)
self.redraw_roster_systray(account, jid, event[0])
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
@ -1114,7 +1193,7 @@ class Interface:
if helpers.allow_showing_notification(account):
# check if we should be notified
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'ft_error.png')
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
event_type = _('File Transfer Error')
notify.popup(event_type, jid, account, msg_type, path,
@ -1137,7 +1216,8 @@ class Interface:
if helpers.allow_showing_notification(account):
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'ft_request.png')
txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(account, jid)
txt = _('%s wants to send you a file.') % gajim.get_name_from_jid(
account, jid)
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
event_type = _('File Transfer Request')
notify.popup(event_type, jid, account, 'file-request',
@ -1146,7 +1226,7 @@ class Interface:
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'])
def handle_event_file_rcv_completed(self, account, file_props):
ft = self.instances['file_transfers']
if file_props['error'] == 0:
@ -1172,13 +1252,14 @@ class Interface:
msg_type = ''
event_type = ''
if file_props['error'] == 0 and gajim.config.get('notify_on_file_complete'):
if file_props['error'] == 0 and gajim.config.get(
'notify_on_file_complete'):
msg_type = 'file-completed'
event_type = _('File Transfer Completed')
elif file_props['error'] == -1:
msg_type = 'file-stopped'
event_type = _('File Transfer Stopped')
if event_type == '':
# FIXME: ugly workaround (this can happen Gajim sent, Gaim recvs)
# this should never happen but it does. see process_result() in socks5.py
@ -1188,7 +1269,7 @@ class Interface:
if msg_type:
self.add_event(account, jid, msg_type, file_props)
if file_props is not None:
if file_props['type'] == 'r':
# get the name of the sender, as it is in the roster
@ -1260,6 +1341,8 @@ class Interface:
def handle_event_signed_in(self, account, empty):
'''SIGNED_IN event is emitted when we sign in, so handle it'''
# block signed in notifications for 30 seconds
gajim.block_signed_in_notifications[account] = True
self.roster.actions_menu_needs_rebuild = True
if gajim.interface.sleeper.getState() != common.sleepy.STATE_UNKNOWN and \
gajim.connections[account].connected in (2, 3):
@ -1289,6 +1372,31 @@ class Interface:
def handle_event_metacontacts(self, account, tags_list):
gajim.contacts.define_metacontacts(account, tags_list)
def handle_event_privacy_lists_received(self, account, data):
# ('PRIVACY_LISTS_RECEIVED', account, list)
if not self.instances.has_key(account):
return
if self.instances[account].has_key('privacy_lists'):
self.instances[account]['privacy_lists'].privacy_lists_received(data)
def handle_event_privacy_list_received(self, account, data):
# ('PRIVACY_LISTS_RECEIVED', account, (name, rules))
if not self.instances.has_key(account):
return
name = data[0]
rules = data[1]
if self.instances[account].has_key('privacy_list_%s' % name):
self.instances[account]['privacy_list_%s' % name].\
privacy_list_received(rules)
def handle_event_privacy_lists_active_default(self, account, data):
if not data:
return
# Send to all privacy_list_* windows as we can't know which one asked
for win in self.instances[account]:
if win.startswith('privacy_list_'):
self.instances[account][win].check_active_default(data)
def read_sleepy(self):
'''Check idle status and change that status if needed'''
if not self.sleeper.poll():
@ -1342,12 +1450,12 @@ class Interface:
return False
def show_systray(self):
self.systray.show_icon()
self.systray_enabled = True
self.systray.show_icon()
def hide_systray(self):
self.systray.hide_icon()
self.systray_enabled = False
self.systray.hide_icon()
def image_is_ok(self, image):
if not os.path.exists(image):
@ -1577,6 +1685,7 @@ class Interface:
'ROSTER_INFO': self.handle_event_roster_info,
'BOOKMARKS': self.handle_event_bookmarks,
'CON_TYPE': self.handle_event_con_type,
'CONNECTION_LOST': self.handle_event_connection_lost,
'FILE_REQUEST': self.handle_event_file_request,
'GMAIL_NOTIFY': self.handle_event_gmail_notify,
'FILE_REQUEST_ERROR': self.handle_event_file_request_error,
@ -1589,6 +1698,10 @@ class Interface:
'ASK_NEW_NICK': self.handle_event_ask_new_nick,
'SIGNED_IN': self.handle_event_signed_in,
'METACONTACTS': self.handle_event_metacontacts,
'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received,
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
self.handle_event_privacy_lists_active_default,
}
gajim.handlers = self.handlers
@ -1608,14 +1721,14 @@ class Interface:
err_str)
sys.exit()
def handle_event(self, account, jid, typ):
def handle_event(self, account, jid, type_):
w = None
fjid = jid
resource = gajim.get_resource_from_jid(jid)
jid = gajim.get_jid_without_resource(jid)
if typ == message_control.TYPE_GC:
if type_ in ('printed_gc_msg', 'gc_msg'):
w = self.msg_win_mgr.get_window(jid, account)
elif typ == message_control.TYPE_CHAT:
elif type_ in ('printed_chat', 'chat'):
if self.msg_win_mgr.has_window(fjid, account):
w = self.msg_win_mgr.get_window(fjid, account)
else:
@ -1625,30 +1738,30 @@ class Interface:
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
elif typ == message_control.TYPE_PM:
elif type_ in ('printed_pm', 'pm'):
if self.msg_win_mgr.has_window(fjid, account):
w = self.msg_win_mgr.get_window(fjid, account)
else:
room_jid = jid
nick = resource
gc_contact = gajim.contacts.get_gc_contact(account, room_jid,
nick)
nick)
if gc_contact:
show = gc_contact.show
else:
show = 'offline'
gc_contact = gajim.contacts.create_gc_contact(room_jid = room_jid,
name = nick, show = show)
gc_contact = gajim.contacts.create_gc_contact(
room_jid = room_jid, name = nick, show = show)
c = gajim.contacts.contact_from_gc_contact(gc_contact)
self.roster.new_chat(c, account, private_chat = True)
w = self.msg_win_mgr.get_window(fjid, account)
elif typ in ('normal', 'file-request', 'file-request-error',
'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
elif type_ in ('normal', 'file-request', 'file-request-error',
'file-send-error', 'file-error', 'file-stopped', 'file-completed'):
# Get the first single message event
ev = gajim.get_first_event(account, jid, typ)
event = gajim.events.get_first_event(account, jid, type_)
# Open the window
self.roster.open_event(account, jid, ev)
elif typ == 'gmail':
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')),
@ -1656,12 +1769,12 @@ class Interface:
else:
url = ('http://mail.google.com/')
helpers.launch_browser_mailer('url', url)
elif typ == 'gc-invitation':
ev = gajim.get_first_event(account, jid, typ)
data = ev[1]
elif type_ == 'gc-invitation':
event = gajim.events.get_first_event(account, jid, type_)
data = event.parameters
dialogs.InvitationReceivedDialog(account, data[0], jid, data[2],
data[1])
self.remove_first_event(account, jid, typ)
gajim.events.remove_events(account, jid, event)
if w:
w.set_active_tab(fjid, account)
w.window.present()
@ -1753,14 +1866,13 @@ class Interface:
self.instances = {'logs': {}}
for a in gajim.connections:
self.instances[a] = {'infos': {}, 'disco': {}, 'chats': {},
'gc': {}, 'gc_config': {}}
self.instances[a] = {'infos': {}, 'disco': {}, 'gc_config': {}}
gajim.contacts.add_account(a)
gajim.groups[a] = {}
gajim.gc_connected[a] = {}
gajim.automatic_rooms[a] = {}
gajim.newly_added[a] = []
gajim.to_be_removed[a] = []
gajim.awaiting_events[a] = {}
gajim.nicks[a] = gajim.config.get_per('accounts', a, 'name')
gajim.block_signed_in_notifications[a] = True
gajim.sleeper_state[a] = 0
@ -1814,12 +1926,34 @@ class Interface:
# get instances for windows/dialogs that will show_all()/hide()
self.instances['file_transfers'] = dialogs.FileTransfersWindow()
# get transports type from DB
gajim.transport_type = gajim.logger.get_transports_type()
# test is dictionnary is present for speller
if gajim.config.get('use_speller'):
lang = gajim.config.get('speller_language')
if not lang:
lang = gajim.LANG
tv = gtk.TextView()
try:
import gtkspell
spell = gtkspell.Spell(tv, lang)
except:
dialogs.ErrorDialog(
_('Dictionary for lang %s not available') % lang,
_('You have to install %s dictionary to use spellchecking, or '
'choose another language by setting the speller_language option.'
) % lang)
gajim.config.set('use_speller', False)
gobject.timeout_add(100, self.autoconnect)
gobject.timeout_add(200, self.process_connections)
gobject.timeout_add(500, self.read_sleepy)
if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
def sigint_cb(num, stack):
sys.exit(5)
# ^C exits the application normally to delete pid file
signal.signal(signal.SIGINT, sigint_cb)
if os.name != 'nt':
# Session Management support
@ -1847,33 +1981,7 @@ if __name__ == '__main__':
cli.set_restart_command(len(argv), argv)
gtkgui_helpers.possibly_set_gajim_as_xmpp_handler()
# Migrate old logs if we have such olds logs
from common import logger
from migrate_logs_to_dot9_db import PATH_TO_LOGS_BASE_DIR
LOG_DB_PATH = logger.LOG_DB_PATH
if not os.path.exists(LOG_DB_PATH) and os.path.isdir(PATH_TO_LOGS_BASE_DIR):
import Queue
q = Queue.Queue(100)
dialog = dialogs.ProgressDialog(_('Migrating Logs...'),
_('Please wait while logs are being migrated...'), q)
if os.name == 'nt' and gtk.pygtk_version > (2, 8, 0):
idlequeue = idlequeue.SelectIdleQueue()
else:
idlequeue = GlibIdleQueue()
def on_result(*arg):
dialog.dialog.destroy()
dialog.dialog = None
gobject.source_remove(dialog.update_progressbar_timeout_id)
gajim.logger.init_vars()
check_paths.check_and_possibly_create_paths()
Interface()
m = MigrateCommand(on_result)
m.set_idlequeue(idlequeue)
m.start()
else:
check_paths.check_and_possibly_create_paths()
Interface()
check_paths.check_and_possibly_create_paths()
Interface()
gtk.main()

View File

@ -25,23 +25,18 @@
##
import gtk
import gtk.glade
import pango
import dialogs
import gtkgui_helpers
from common import gajim
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain (APP, i18n.DIR)
gtk.glade.textdomain (APP)
class GajimThemesWindow:
def __init__(self):
self.xml = gtkgui_helpers.get_glade('gajim_themes_window.glade')
self.window = self.xml.get_widget('gajim_themes_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.options = ['account', 'group', 'contact', 'banner']
self.options_combobox = self.xml.get_widget('options_combobox')

View File

@ -23,7 +23,6 @@
import os
import time
import gtk
import gtk.glade
import pango
import gobject
import gtkgui_helpers
@ -40,13 +39,6 @@ from common import helpers
from chat_control import ChatControl
from chat_control import ChatControlBase
from conversation_textview import ConversationTextview
from common import i18n
_ = i18n._
Q_ = i18n.Q_
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
#(status_image, type, nick, shown_nick)
(
@ -127,6 +119,13 @@ class PrivateChatControl(ChatControl):
return
ChatControl.send_message(self, message)
def update_ui(self):
if self.contact.show == 'offline':
self.got_disconnected()
else:
self.got_connected()
ChatControl.update_ui(self)
class GroupchatControl(ChatControlBase):
@ -183,7 +182,7 @@ class GroupchatControl(ChatControlBase):
# alphanum sorted
self.muc_cmds = ['ban', 'chat', 'query', 'clear', 'close', 'compact',
'help', 'invite', 'join', 'kick', 'leave', 'me', 'msg', 'nick', 'part',
'say', 'topic']
'names', 'say', 'topic']
# muc attention flag (when we are mentioned in a muc)
# if True, the room has mentioned us
self.attention_flag = False
@ -197,10 +196,6 @@ class GroupchatControl(ChatControlBase):
self.tooltip = tooltips.GCTooltip()
self.allow_focus_out_line = True
# holds the iter's offset which points to the end of --- line
self.focus_out_end_iter_offset = None
# connect the menuitems to their respective functions
xm = gtkgui_helpers.get_glade('gc_control_popup_menu.glade')
@ -292,12 +287,9 @@ class GroupchatControl(ChatControlBase):
column.set_visible(False)
self.list_treeview.set_expander_column(column)
id = self.msg_textview.connect('populate_popup',
self.on_msg_textview_populate_popup)
self.handlers[id] = self.msg_textview
# set an empty subject to show the room_jid
self.set_subject('')
self.got_disconnected() #init some variables
self.got_disconnected() # init some variables
self.update_ui()
self.conv_textview.tv.grab_focus()
@ -306,20 +298,18 @@ class GroupchatControl(ChatControlBase):
def on_msg_textview_populate_popup(self, textview, menu):
'''we override the default context menu and we prepend Clear
and the ability to insert a nick'''
ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
item = gtk.SeparatorMenuItem()
menu.prepend(item)
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menu.prepend(item)
id = item.connect('activate', self.msg_textview.clear)
self.handlers[id] = item
item = gtk.MenuItem(_('Insert Nickname'))
menu.prepend(item)
submenu = gtk.Menu()
item.set_submenu(submenu)
for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
item = gtk.MenuItem(nick)
for nick in sorted(gajim.contacts.get_nick_list(self.account,
self.room_jid)):
item = gtk.MenuItem(nick, use_underline = False)
submenu.append(item)
id = item.connect('activate', self.append_nick_in_msg_textview, nick)
self.handlers[id] = item
@ -333,7 +323,7 @@ class GroupchatControl(ChatControlBase):
def _on_window_focus_in_event(self, widget, event):
'''When window gets focus'''
if self.parent_win.get_active_jid() == self.room_jid:
self.allow_focus_out_line = True
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'''
@ -440,21 +430,21 @@ 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):
if not nick:
# message from server
self.print_conversation(msg, tim = tim)
else:
# message from someone
self.print_conversation(msg, nick, tim)
if has_timestamp:
self.print_old_conversation(msg, nick, tim)
else:
self.print_conversation(msg, nick, tim)
def on_private_message(self, nick, msg, tim):
# Do we have a queue?
fjid = self.room_jid + '/' + nick
qs = gajim.awaiting_events[self.account]
no_queue = True
if qs.has_key(fjid):
no_queue = False
no_queue = len(gajim.events.get_events(self.account, fjid)) == 0
# We print if window is opened
pm_control = gajim.interface.msg_win_mgr.get_control(fjid, self.account)
@ -462,9 +452,9 @@ class GroupchatControl(ChatControlBase):
pm_control.print_conversation(msg, tim = tim)
return
if no_queue:
qs[fjid] = []
qs[fjid].append(('chat', (msg, '', 'incoming', tim, False, '')))
event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
False, '', None))
gajim.events.add_event(self.account, fjid, event)
autopopup = gajim.config.get('autopopup')
autopopupaway = gajim.config.get('autopopupaway')
@ -479,8 +469,6 @@ class GroupchatControl(ChatControlBase):
self.room_jid, icon_name = 'message')
image = state_images['message']
model[iter][C_IMG] = image
if gajim.interface.systray_enabled:
gajim.interface.systray.add_jid(fjid, self.account, 'pm')
self.parent_win.show_title()
else:
self._start_private_message(nick)
@ -511,6 +499,24 @@ class GroupchatControl(ChatControlBase):
fin = True
return None
gc_count_nicknames_colors = 0
gc_custom_colors = {}
def print_old_conversation(self, text, contact, tim = None):
if isinstance(text, str):
text = unicode(text, 'utf-8')
if contact == self.nick: # it's us
kind = 'outgoing'
else:
kind = 'incoming'
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'])
def print_conversation(self, text, contact = '', tim = None):
'''Print a line in the conversation:
if contact is set: it's a message from someone or an info message (contact
@ -536,6 +542,19 @@ class GroupchatControl(ChatControlBase):
if kind == 'incoming': # it's a message NOT from us
# highlighting and sounds
(highlight, sound) = self.highlighting_for_message(text, tim)
if self.gc_custom_colors.has_key(contact):
other_tags_for_name.append('gc_nickname_color_' + \
str(self.gc_custom_colors[contact]))
else:
self.gc_count_nicknames_colors += 1
number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
split(':'))
if self.gc_count_nicknames_colors == number_of_colors:
self.gc_count_nicknames_colors = 0
self.gc_custom_colors[contact] = \
self.gc_count_nicknames_colors
other_tags_for_name.append('gc_nickname_color_' + \
str(self.gc_count_nicknames_colors))
if highlight:
# muc-specific chatstate
self.parent_win.redraw_tab(self, 'attention')
@ -545,12 +564,23 @@ class GroupchatControl(ChatControlBase):
helpers.play_sound('muc_message_received')
elif sound == 'highlight':
helpers.play_sound('muc_message_highlight')
if text.startswith('/me ') or text.startswith('/me\n'):
other_tags_for_text.append('gc_nickname_color_' + \
str(self.gc_custom_colors[contact]))
self.check_and_possibly_add_focus_out_line()
ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
other_tags_for_name, [], other_tags_for_text)
def get_nb_unread(self):
nb = len(gajim.events.get_events(self.account, self.room_jid,
['printed_gc_msg']))
for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
nb += len(gajim.events.get_events(self.account, self.room_jid + '/' + \
nick, ['pm']))
return nb
def highlighting_for_message(self, text, tim):
'''Returns a 2-Tuple. The first says whether or not to highlight the
text, the second, what sound to play.'''
@ -587,64 +617,7 @@ class GroupchatControl(ChatControlBase):
# we have full focus (we are reading it!)
return
if not self.allow_focus_out_line:
# if room did not receive focus-in from the last time we added
# --- line then do not readd
return
print_focus_out_line = False
buffer = self.conv_textview.tv.get_buffer()
if self.focus_out_end_iter_offset is None:
# this happens only first time we focus out on this room
print_focus_out_line = True
else:
if self.focus_out_end_iter_offset != buffer.get_end_iter().get_offset():
# this means after last-focus something was printed
# (else end_iter's offset is the same as before)
# only then print ---- line (eg. we avoid printing many following
# ---- lines)
print_focus_out_line = True
if print_focus_out_line and buffer.get_char_count() > 0:
buffer.begin_user_action()
# remove previous focus out line if such focus out line exists
if self.focus_out_end_iter_offset is not None:
end_iter_for_previous_line = buffer.get_iter_at_offset(
self.focus_out_end_iter_offset)
begin_iter_for_previous_line = end_iter_for_previous_line.copy()
begin_iter_for_previous_line.backward_chars(2) # img_char+1 (the '\n')
# remove focus out line
buffer.delete(begin_iter_for_previous_line,
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)
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
# update the iter we hold to make comparison the next time
self.focus_out_end_iter_offset = buffer.get_end_iter().get_offset()
buffer.end_user_action()
# scroll to the end (via idle in case the scrollbar has appeared)
gobject.idle_add(self.conv_textview.scroll_to_end)
self.conv_textview.show_focus_out_line()
def needs_visual_notification(self, text):
'''checks text to see whether any of the words in (muc_highlight_words
@ -740,7 +713,7 @@ class GroupchatControl(ChatControlBase):
model = self.list_treeview.get_model()
gc_contact = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
state_images = gajim.interface.roster.jabber_state_images['16']
if gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
if len(gajim.events.get_events(self.account, self.room_jid + '/' + nick)):
image = state_images['message']
else:
image = state_images[gc_contact.show]
@ -823,9 +796,29 @@ class GroupchatControl(ChatControlBase):
# after that, but that doesn't hurt
self.add_contact_to_roster(new_nick, show, role, affiliation,
status, jid)
# keep nickname color
if nick in self.gc_custom_colors:
self.gc_custom_colors[new_nick] = self.gc_custom_colors[nick]
# rename vcard / avatar
puny_jid = helpers.sanitize_filename(self.room_jid)
puny_nick = helpers.sanitize_filename(nick)
puny_new_nick = helpers.sanitize_filename(new_nick)
old_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_nick)
new_path = os.path.join(gajim.VCARD_PATH, puny_jid, puny_new_nick)
files = {old_path: new_path}
path = os.path.join(gajim.AVATAR_PATH, puny_jid)
# possible extensions
for ext in ('.png', '.jpeg', '_notif_size_bw.png',
'_notif_size_colored.png'):
files[os.path.join(path, puny_nick + ext)] = \
os.path.join(path, puny_new_nick + ext)
for old_file in files:
if os.path.exists(old_file):
os.rename(old_file, files[old_file])
self.print_conversation(s, 'info')
if not gajim.awaiting_events[self.account].has_key(self.room_jid + '/' + nick):
if len(gajim.events.get_events(self.account,
self.room_jid + '/' + nick)) == 0:
self.remove_contact(nick)
else:
c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)
@ -870,15 +863,18 @@ class GroupchatControl(ChatControlBase):
break
if print_status is None:
print_status = gajim.config.get('print_status_in_muc')
nick_jid = nick
if jid:
nick_jid += ' (%s)' % jid
if show == 'offline' and print_status in ('all', 'in_and_out'):
st = _('%s has left') % nick
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
st = _('%s has joined the room') % nick_jid
elif print_status == 'all':
st = _('%s is now %s') % (nick, helpers.get_uf_show(show))
st = _('%s is now %s') % (nick_jid, helpers.get_uf_show(show))
if st:
if status:
st += ' (' + status + ')'
@ -974,7 +970,7 @@ class GroupchatControl(ChatControlBase):
if command == 'nick':
# example: /nick foo
if len(message_array):
if len(message_array) and message_array[0] != self.nick:
nick = message_array[0]
gajim.connections[self.account].change_gc_nick(self.room_jid, nick)
self.clear(self.msg_textview)
@ -985,14 +981,19 @@ class GroupchatControl(ChatControlBase):
# Open a chat window to the specified nick
# example: /query foo
if len(message_array):
nick = message_array.pop(0)
nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
if nick in nicks:
self.on_send_pm(nick = nick)
self.clear(self.msg_textview)
nick0 = message_array.pop(0)
if nick0[-1] == ' ':
nick1 = nick0[:-1]
else:
self.print_conversation(_('Nickname not found: %s') % nick,
'info')
nick1 = nick0
nicks = gajim.contacts.get_nick_list(self.account, self.room_jid)
for nick in [nick0, nick1]:
if nick in nicks:
self.on_send_pm(nick = nick)
self.clear(self.msg_textview)
return True
self.print_conversation(_('Nickname not found: %s') % \
nick0, 'info')
else:
self.get_command_help(command)
return True
@ -1002,7 +1003,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)
if nick in room_nicks:
privmsg = ' '.join(message_array)
self.on_send_pm(nick=nick, msg=privmsg)
@ -1122,6 +1124,21 @@ class GroupchatControl(ChatControlBase):
else:
self.get_command_help(command)
return True
elif command == 'names':
# print the list of participants
nicklist=''
i=0
for contact in self.iter_contact_rows():
nicklist += '[ %-12.12s ] ' % (contact[C_NICK].decode('utf-8'))
i=i+1
if i == 3:
i=0
self.print_conversation(nicklist, 'info')
nicklist=''
if nicklist:
self.print_conversation(nicklist, 'info')
self.clear(self.msg_textview)
return True
elif command == 'help':
if len(message_array):
subcommand = message_array.pop(0)
@ -1205,6 +1222,10 @@ class GroupchatControl(ChatControlBase):
s = _('Usage: /%s <nickname>, changes your nickname in current room.')\
% command
self.print_conversation(s, 'info')
elif command == 'names':
s = _('Usage: /%s , display the names of room 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')
@ -1287,9 +1308,8 @@ class GroupchatControl(ChatControlBase):
nb = 0
for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
fjid = self.room_jid + '/' + nick
if gajim.awaiting_events[self.account].has_key(fjid):
# gc can only have messages as event
nb += len(gajim.awaiting_events[self.account][fjid])
nb += len(gajim.events.get_events(self.account, fjid))
# gc can only have messages as event
return nb
def _on_change_subject_menuitem_activate(self, widget):
@ -1559,8 +1579,8 @@ class GroupchatControl(ChatControlBase):
# show the popup now!
menu = xml.get_widget('gc_occupants_menu')
menu.popup(None, None, None, event.button, event.time)
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
def _start_private_message(self, nick):
gc_c = gajim.contacts.get_gc_contact(self.account, self.room_jid, nick)

View File

@ -27,6 +27,7 @@
##
import sys
import os
import traceback
import threading
@ -36,9 +37,7 @@ import dialogs
from cStringIO import StringIO
from common import helpers
from common import i18n
_ = i18n._
_exception_in_progress = threading.Lock()
def _info(type, value, tb):
@ -102,7 +101,8 @@ def _info(type, value, tb):
_exception_in_progress.release()
if not sys.stderr.isatty(): # gdb/kdm etc if we use startx this is not True
# gdb/kdm etc if we use startx this is not True
if os.name == 'nt' or not sys.stderr.isatty():
#FIXME: maybe always show dialog?
_excepthook_save = sys.excepthook
sys.excepthook = _info

View File

@ -19,12 +19,14 @@
import xml.sax.saxutils
import gtk
import gtk.glade
import gobject
import pango
import os
import sys
import vcard
import dialogs
HAS_PYWIN32 = True
@ -37,11 +39,12 @@ if os.name == 'nt':
HAS_PYWIN32 = False
from common import i18n
i18n.init()
_ = i18n._
from common import gajim
from common import helpers
gtk.glade.bindtextdomain(i18n.APP, i18n.DIR)
gtk.glade.textdomain(i18n.APP)
screen_w = gtk.gdk.screen_width()
screen_h = gtk.gdk.screen_height()
@ -123,8 +126,8 @@ def get_default_font():
# in try because daemon may not be there
client = gconf.client_get_default()
return helpers.ensure_unicode_string(
client.get_string('/desktop/gnome/interface/font_name'))
return client.get_string('/desktop/gnome/interface/font_name'
).decode('utf-8')
except:
pass
@ -144,8 +147,7 @@ def get_default_font():
for line in file(xfce_config_file):
if line.find('name="Gtk/FontName"') != -1:
start = line.find('value="') + 7
return helpers.ensure_unicode_string(
line[start:line.find('"', start)])
return line[start:line.find('"', start)].decode('utf-8')
except:
#we talk about file
print >> sys.stderr, _('Error: cannot open %s for reading') % xfce_config_file
@ -160,7 +162,7 @@ def get_default_font():
font_name = values[0]
font_size = values[1]
font_string = '%s %s' % (font_name, font_size) # Verdana 9
return helpers.ensure_unicode_string(font_string)
return font_string.decode('utf-8')
except:
#we talk about file
print >> sys.stderr, _('Error: cannot open %s for reading') % kde_config_file
@ -403,7 +405,7 @@ def possibly_move_window_in_current_desktop(window):
if current_virtual_desktop_no != window_virtual_desktop:
# we are in another VD that the window was
# so show it in current VD
window.show()
window.present()
def file_is_locked(path_to_file):
'''returns True if file is locked (WINDOWS ONLY)'''
@ -455,7 +457,7 @@ def _get_fade_color(treeview, selected, focused):
def get_scaled_pixbuf(pixbuf, kind):
'''returns scaled pixbuf, keeping ratio etc or None
kind is either "chat" or "roster" or "notification" or "tooltip"'''
kind is either "chat", "roster", "notification", "tooltip", "vcard"'''
# resize to a width / height for the avatar not to have distortion
# (keep aspect ratio)
@ -666,3 +668,70 @@ def get_possible_button_event(event):
def destroy_widget(widget):
widget.destroy()
def on_avatar_save_as_menuitem_activate(widget, jid, account,
default_name = ''):
def on_ok(widget):
def on_ok2(widget, file_path, pixbuf):
pixbuf.save(file_path, 'jpeg')
dialog2.destroy()
dialog.destroy()
file_path = dialog.get_filename()
file_path = decode_filechooser_file_paths((file_path,))[0]
if os.path.exists(file_path):
dialog2 = dialogs.FTOverwriteConfirmationDialog(
_('This file already exists'), _('What do you want to do?'),
False)
dialog2.set_transient_for(dialog)
dialog2.set_destroy_with_parent(True)
response = dialog2.get_response()
if response < 0:
return
# Get pixbuf
pixbuf = None
is_fake = False
if account and gajim.contacts.is_pm_from_jid(account, jid):
is_fake = True
pixbuf = get_avatar_pixbuf_from_cache(jid, is_fake)
ext = file_path.split('.')[-1]
type_ = ''
if not ext:
# Silently save as Jpeg image
file_path += '.jpeg'
type_ = 'jpeg'
elif ext == 'jpg':
type_ = 'jpeg'
else:
type_ = ext
# Save image
try:
pixbuf.save(file_path, type_)
except:
#XXX Check for permissions
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},
on_response_ok = (on_ok2, new_file_path, pixbuf))
else:
dialog.destroy()
def on_cancel(widget):
dialog.destroy()
dialog = dialogs.FileChooserDialog(
title_text = _('Save Image as...'),
action = gtk.FILE_CHOOSER_ACTION_SAVE,
buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK),
default_response = gtk.RESPONSE_OK,
current_folder = gajim.config.get('last_save_dir'),
on_response_ok = on_ok,
on_response_cancel = on_cancel)
dialog.set_current_name(default_name)
dialog.connect('delete-event', lambda widget, event:
on_cancel(widget))

View File

@ -24,21 +24,17 @@ import sys
import os
import signal
import gtk
import gtk.glade
import time
import locale
from common import i18n
import exceptions
import dialogs
import gtkgui_helpers
from common.logger import LOG_DB_PATH, constants
from common import gajim
from common import i18n
from common import helpers
_ = i18n._
gtk.glade.bindtextdomain(i18n.APP, i18n.DIR)
gtk.glade.textdomain(i18n.APP)
# time, message, subject
(
@ -468,8 +464,8 @@ class HistoryManager:
except ValueError:
pass
file_.write(_('%(who)s on %(time)s said: %(message)s\n' % {'who': who,
'time': time_, 'message': message}))
file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {'who': who,
'time': time_, 'message': message})
def _delete_jid_logs(self, liststore, list_of_paths):
paths_len = len(list_of_paths)

View File

@ -24,7 +24,6 @@
##
import gtk
import gtk.glade
import gobject
import time
import calendar
@ -34,17 +33,11 @@ import conversation_textview
from common import gajim
from common import helpers
from common import i18n
from common.logger import Constants
constants = Constants()
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
# contact_name, date, message, time
(
C_CONTACT_NAME,
@ -120,7 +113,7 @@ class HistoryWindow:
# select and show logs for last date we have logs with contact
# and if we don't have logs at all, default to today
result = gajim.logger.get_last_date_that_has_logs(self.jid)
result = gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
if result is None:
date = time.localtime()
else:
@ -157,7 +150,7 @@ class HistoryWindow:
asks for days in this month if they have logs it bolds them (marks them)'''
weekday, days_in_this_month = calendar.monthrange(year, month)
log_days = gajim.logger.get_days_with_logs(self.jid, year,
month, days_in_this_month)
month, days_in_this_month, self.account)
for day in log_days:
widget.mark_day(day)
yield True
@ -197,7 +190,8 @@ class HistoryWindow:
'''adds all the lines for given date in textbuffer'''
self.history_buffer.set_text('') # clear the buffer first
self.last_time_printout = 0
lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day)
lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
# lines holds list with tupples that have:
# contact_name, time, kind, show, message
for line in lines:
@ -325,7 +319,8 @@ class HistoryWindow:
if text == '':
return
# contact_name, time, kind, show, message, subject
results = gajim.logger.get_search_results_for_query(self.jid, text)
results = gajim.logger.get_search_results_for_query(
self.jid, text, self.account)
#FIXME:
# add "subject: | message: " in message column if kind is single
# also do we need show at all? (we do not search on subject)

View File

@ -11,8 +11,6 @@
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
import gtk
import gtk.glade
import gtkgui_helpers
from common import gajim
@ -22,12 +20,6 @@ TYPE_CHAT = 'chat'
TYPE_GC = 'gc'
TYPE_PM = 'pm'
####################
# FIXME: Can't this stuff happen once?
from common import i18n
_ = i18n._
APP = i18n.APP
####################
class MessageControl:
@ -47,7 +39,6 @@ class MessageControl:
self.account = account
self.hide_chat_buttons_always = False
self.hide_chat_buttons_current = False
self.nb_unread = 0
self.resource = resource
gajim.last_message_time[self.account][self.get_full_jid()] = 0
@ -125,16 +116,15 @@ class MessageControl:
pass
def get_specific_unread(self):
n = 0
if gajim.awaiting_events[self.account].has_key(self.contact.jid):
n = len(gajim.awaiting_events[self.account][self.contact.jid])
return n
return len(gajim.events.get_events(self.account, self.contact.jid))
def send_message(self, message, keyID = '', type = 'chat',
chatstate = None, msg_id = None, composing_jep = None, resource = None):
chatstate = None, msg_id = None, composing_jep = None, resource = None,
user_nick = None):
'''Send the given message to the active tab'''
jid = self.contact.jid
# Send and update history
gajim.connections[self.account].send_message(jid, message, keyID,
type = type, chatstate = chatstate, msg_id = msg_id,
composing_jep = composing_jep, resource = self.resource)
type = type, chatstate = chatstate, msg_id = msg_id,
composing_jep = composing_jep, resource = self.resource,
user_nick = user_nick)

View File

@ -44,12 +44,14 @@ class MessageTextView(gtk.TextView):
self.set_accepts_tab(True)
self.set_editable(True)
self.set_cursor_visible(True)
self.set_wrap_mode(gtk.WRAP_WORD)
self.set_wrap_mode(gtk.WRAP_WORD_CHAR)
self.set_left_margin(2)
self.set_right_margin(2)
self.set_pixels_above_lines(2)
self.set_pixels_below_lines(2)
self.lang = None # Lang used for spell checking
def destroy(self):
import gc
gobject.idle_add(lambda:gc.collect())

View File

@ -22,7 +22,7 @@
##
import gtk
import gtk.glade
import gobject
import common
import gtkgui_helpers
@ -31,12 +31,6 @@ from chat_control import ChatControlBase
from common import gajim
####################
# FIXME: Can't this stuff happen once?
from common import i18n
_ = i18n._
APP = i18n.APP
####################
class MessageWindow:
@ -156,8 +150,17 @@ class MessageWindow:
fjid = control.get_full_jid()
self._controls[control.account][fjid] = control
if self.get_num_controls() > 1:
if self.get_num_controls() == 2:
# is first conversation_textview scrolled down ?
scrolled = False
first_widget = self.notebook.get_nth_page(0)
ctrl = self._widget_to_control(first_widget)
conv_textview = ctrl.conv_textview
if conv_textview.at_the_end():
scrolled = True
self.notebook.set_show_tabs(True)
if scrolled:
gobject.idle_add(conv_textview.scroll_to_end_iter)
self.alignment.set_property('top-padding', 2)
# Add notebook page and connect up to the tab's close button
@ -221,7 +224,7 @@ class MessageWindow:
gajim.config.get('notify_on_all_muc_messages') and not \
ctrl.attention_flag:
continue
unread += ctrl.nb_unread
unread += ctrl.get_nb_unread()
unread_str = ''
if unread > 1:
@ -277,9 +280,8 @@ class MessageWindow:
ctrl.shutdown()
# Update external state
if gajim.interface.systray_enabled:
gajim.interface.systray.remove_jid(ctrl.get_full_jid(), ctrl.account,
ctrl.type_id)
gajim.events.remove_events(ctrl.account, ctrl.get_full_jid,
types = ['printed_msg', 'chat', 'gc_msg'])
del gajim.last_message_time[ctrl.account][ctrl.get_full_jid()]
self.disconnect_tab_dnd(ctrl.widget)
@ -416,6 +418,8 @@ class MessageWindow:
ind = self.notebook.get_current_page()
current = ind
found = False
first_composing_ind = -1 # id of first composing ctrl to switch to
# if no others controls have awaiting events
# loop until finding an unread tab or having done a complete cycle
while True:
if forward == True: # look for the first unread tab on the right
@ -426,15 +430,22 @@ class MessageWindow:
ind = ind - 1
if ind < 0:
ind = self.notebook.get_n_pages() - 1
if ind == current:
break # a complete cycle without finding an unread tab
ctrl = self.get_control(ind, None)
if ctrl.nb_unread > 0:
if ctrl.get_nb_unread() > 0:
found = True
break # found
elif gajim.config.get('ctrl_tab_go_to_next_composing') : # Search for a composing contact
contact = ctrl.contact
if first_composing_ind == -1 and contact.chatstate == 'composing':
# If no composing contact found yet, check if this one is composing
first_composing_ind = ind
if ind == current:
break # a complete cycle without finding an unread tab
if found:
self.notebook.set_current_page(ind)
else: # not found
elif first_composing_ind != -1:
self.notebook.set_current_page(first_composing_ind)
else: # not found and nobody composing
if forward: # CTRL + TAB
if current < (self.notebook.get_n_pages() - 1):
self.notebook.next_page()
@ -641,13 +652,13 @@ class MessageWindowMgr:
if not gajim.config.get('saveposition'):
return
if self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_ALWAYS):
if self.mode == self.ONE_MSG_WINDOW_ALWAYS:
size = (gajim.config.get('msgwin-width'),
gajim.config.get('msgwin-height'))
elif self.mode == self.ONE_MSG_WINDOW_PERACCT:
size = (gajim.config.get_per('accounts', acct, 'msgwin-width'),
gajim.config.get_per('accounts', acct, 'msgwin-height'))
elif self.mode == self.ONE_MSG_WINDOW_PERTYPE:
elif self.mode in (self.ONE_MSG_WINDOW_NEVER, self.ONE_MSG_WINDOW_PERTYPE):
if type == message_control.TYPE_PM:
type = message_control.TYPE_CHAT
opt_width = type + '-msgwin-width'
@ -705,6 +716,7 @@ class MessageWindowMgr:
win_type = type
win_role = type
elif self.mode == self.ONE_MSG_WINDOW_NEVER:
win_type = type
win_role = contact.jid
win = None
@ -792,6 +804,10 @@ class MessageWindowMgr:
pos_y_key = type + '-msgwin-y-position'
size_width_key = type + '-msgwin-width'
size_height_key = type + '-msgwin-height'
elif self.mode == self.ONE_MSG_WINDOW_NEVER:
type = msg_win.type
size_width_key = type + '-msgwin-width'
size_height_key = type + '-msgwin-height'
if acct:
gajim.config.set_per('accounts', acct, size_width_key, width)

View File

@ -1,271 +0,0 @@
#!/bin/sh
''':'
exec python -OOt "$0" ${1+"$@"}
' '''
## 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>
##
## 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 sre
import sys
import time
import signal
from common import logger
from common import i18n
_ = i18n._
try:
PREFERRED_ENCODING = sys.getpreferredencoding()
except:
PREFERRED_ENCODING = 'utf-8'
from common.helpers import from_one_line, decode_string
signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
from pysqlite2 import dbapi2 as sqlite
if os.name == 'nt':
try:
PATH_TO_LOGS_BASE_DIR = os.path.join(os.environ['appdata'], 'Gajim', 'Logs')
PATH_TO_DB = os.path.join(os.environ['appdata'], 'Gajim', 'logs.db') # database is called logs.db
except KeyError:
# win9x
PATH_TO_LOGS_BASE_DIR = '../src/Logs'
PATH_TO_DB = '../src/logs.db'
else:
PATH_TO_LOGS_BASE_DIR = os.path.expanduser('~/.gajim/logs')
PATH_TO_DB = os.path.expanduser('~/.gajim/logs.db') # database is called logs.db
class Migration:
def __init__(self):
self.constants = logger.Constants()
self.DONE = False
self.PROCESSING = False
if os.path.exists(PATH_TO_DB):
print '%s already exists. Exiting..' % PATH_TO_DB
sys.exit()
self.jids_already_in = [] # jid we already put in DB
def get_jid(self, dirname, filename):
# jids.jid text column will be JID if TC-related, room_jid if GC-related,
# ROOM_JID/nick if pm-related. Here I get names from filenames
if dirname.endswith('logs') or dirname.endswith('Logs'):
# we have file (not dir) in logs base dir, so it's TC
jid = filename # file is JID
else:
# we are in a room folder (so it can be either pm or message in room)
if filename == os.path.basename(dirname): # room/room
jid = dirname # filename is ROOM_JID
else: #room/nick it's pm
jid = dirname + '/' + filename
if jid.startswith('/'):
p = len(PATH_TO_LOGS_BASE_DIR)
jid = jid[p+1:]
jid = jid.lower()
return jid
def decode_jid(self, string):
'''try to decode (to make it Unicode instance) given jid'''
string = decode_string(string)
if isinstance(string, str):
return None # decode failed
return string
def visit(self, arg, dirname, filenames):
s = _('Visiting %s') % dirname
if self.queue:
self.queue.put(s)
else:
print s
for filename in filenames:
# Don't take this file into account, this is dup info
# notifications are also in contact log file
if filename in ('notify.log', 'README'):
continue
path_to_text_file = os.path.join(dirname, filename)
if os.path.isdir(path_to_text_file):
continue
jid = self.get_jid(dirname, filename)
jid = self.decode_jid(jid)
if not jid:
continue
if filename == os.path.basename(dirname): # gajim@conf/gajim@conf then gajim@conf is type room
jid_type = self.constants.JID_ROOM_TYPE
#Type of log
typ = 'room'
else:
jid_type = self.constants.JID_NORMAL_TYPE
#Type of log
typ = _('normal')
s = _('Processing %s of type %s') % (jid, typ)
if self.queue:
self.queue.put(s.encode(PREFERRED_ENCODING))
else:
print s.encode(PREFERRED_ENCODING)
JID_ID = None
f = open(path_to_text_file, 'r')
lines = f.readlines()
for line in lines:
line = from_one_line(line)
splitted_line = line.split(':')
if len(splitted_line) > 2:
# type in logs is one of
# 'gc', 'gcstatus', 'recv', 'sent' and if nothing of those
# it is status
# new db has:
# status, gcstatus, gc_msg, (we only recv those 3),
# single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
# to meet all our needs
# here I convert
# gc ==> gc_msg, gcstatus ==> gcstatus, recv ==> chat_msg_recv
# sent ==> chat_msg_sent, status ==> status
typ = splitted_line[1] # line[1] has type of logged message
message_data = splitted_line[2:] # line[2:] has message data
# line[0] is date,
# some lines can be fucked up, just drop them
try:
tim = int(float(splitted_line[0]))
except:
continue
contact_name = None
show = None
if typ == 'gc':
contact_name = message_data[0]
message = ':'.join(message_data[1:])
kind = self.constants.KIND_GC_MSG
elif typ == 'gcstatus':
contact_name = message_data[0]
show = message_data[1]
message = ':'.join(message_data[2:]) # status msg
kind = self.constants.KIND_GCSTATUS
elif typ == 'recv':
message = ':'.join(message_data[0:])
kind = self.constants.KIND_CHAT_MSG_RECV
elif typ == 'sent':
message = ':'.join(message_data[0:])
kind = self.constants.KIND_CHAT_MSG_SENT
else: # status
kind = self.constants.KIND_STATUS
show = message_data[0]
message = ':'.join(message_data[1:]) # status msg
message = message[:-1] # remove last \n
if not message:
continue
# jid is already in the DB, don't create a new row, just get his jid_id
if not JID_ID:
if jid in self.jids_already_in:
self.cur.execute('SELECT jid_id FROM jids WHERE jid = "%s"' % jid)
JID_ID = self.cur.fetchone()[0]
else:
self.jids_already_in.append(jid)
self.cur.execute('INSERT INTO jids (jid, type) VALUES (?, ?)',
(jid, jid_type))
self.con.commit()
JID_ID = self.cur.lastrowid
sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message) '\
'VALUES (?, ?, ?, ?, ?, ?)'
values = (JID_ID, contact_name, tim, kind, show, message)
self.cur.execute(sql, values)
self.con.commit()
def migrate(self, queue = None):
self.queue = queue
self.con = sqlite.connect(PATH_TO_DB)
os.chmod(PATH_TO_DB, 0600) # rw only for us
self.cur = self.con.cursor()
# create the tables
# kind can be
# status, gcstatus, gc_msg, (we only recv for those 3),
# single_msg_recv, chat_msg_recv, chat_msg_sent, single_msg_sent
# to meet all our needs
# logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code
self.cur.executescript(
'''
CREATE TABLE jids(
jid_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid TEXT UNIQUE,
type INTEGER
);
CREATE TABLE unread_messages(
message_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid_id INTEGER
);
CREATE TABLE logs(
log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
jid_id INTEGER,
contact_name TEXT,
time INTEGER,
kind INTEGER,
show INTEGER,
message TEXT,
subject TEXT
);
'''
)
self.con.commit()
self.PROCESSING = True
os.path.walk(PATH_TO_LOGS_BASE_DIR, self.visit, None)
s = '''
We do not use plain-text files anymore, because they do not meet our needs.
Those files here are logs for Gajim up until 0.8.2
We now use an sqlite database called logs.db found in %s
You can now safely remove your %s folder
Thank you''' % (os.path.dirname(PATH_TO_LOGS_BASE_DIR), PATH_TO_LOGS_BASE_DIR)
f = open(os.path.join(PATH_TO_LOGS_BASE_DIR, 'README'), 'w')
f.write(s)
f.close()
if queue:
queue.put(s)
self.DONE = True
if __name__ == '__main__':
# magic argumen 'dont_wait' tells us that script is run from Gajim
if len(sys.argv) < 2 or sys.argv[1] != 'dont_wait':
print 'IMPORTNANT: PLEASE READ http://trac.gajim.org/wiki/MigrateLogToDot9DB'
print 'Migration will start in 40 seconds unless you press Ctrl+C'
time.sleep(40) # give the user time to act
print
print 'Starting Logs Migration'
print '======================='
print 'Please do NOT run Gajim until this script is over'
m = Migration()
m.migrate()

View File

@ -23,10 +23,7 @@ import dialogs
import gtkgui_helpers
from common import gajim
from common import i18n
from common import helpers
i18n.init()
_ = i18n._
import dbus_support
if dbus_support.supported:
@ -35,29 +32,112 @@ if dbus_support.supported:
import dbus.glib
import dbus.service
def notify(event, jid, account, parameters):
def get_show_in_roster(event, account, contact):
'''Return True if this event must be shown in roster, else False'''
num = get_advanced_notification(event, account, contact)
if num != None:
if gajim.config.get_per('notifications', str(num), 'roster') == 'yes':
return True
if gajim.config.get_per('notifications', str(num), 'roster') == 'no':
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
def get_show_in_systray(event, account, contact):
'''Return True if this event must be shown in roster, 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
def get_advanced_notification(event, account, contact):
'''Returns the number of the first advanced notification or None'''
num = 0
notif = gajim.config.get_per('notifications', str(num))
while notif:
recipient_ok = False
status_ok = False
tab_opened_ok = False
# test event
if gajim.config.get_per('notifications', str(num), 'event') == event:
# test recipient
recipient_type = gajim.config.get_per('notifications', str(num),
'recipient_type')
recipients = gajim.config.get_per('notifications', str(num),
'recipients').split()
if recipient_type == 'all':
recipient_ok = True
elif recipient_type == 'contact' and contact.jid in recipients:
recipient_ok = True
elif recipient_type == 'group':
for group in contact.groups:
if group in contact.groups:
recipient_ok = True
break
if recipient_ok:
# test status
our_status = gajim.SHOW_LIST[gajim.connections[account].connected]
status = gajim.config.get_per('notifications', str(num), 'status')
if status == 'all' or our_status in status.split():
status_ok = True
if status_ok:
# test window_opened
tab_opened = gajim.config.get_per('notifications', str(num),
'tab_opened')
if tab_opened == 'both':
tab_opened_ok = True
else:
chat_control = helper.get_chat_control(account, contact)
if (chat_control and tab_opened == 'yes') or (not chat_control and \
tab_opened == 'no'):
tab_opened_ok = True
if tab_opened_ok:
return num
num += 1
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'''
# First, find what notifications we want
do_popup = False
do_sound = False
do_cmd = False
if (event == 'status_change'):
new_show = parameters[0]
status_message = parameters[1]
# Default : No popup for status change
elif (event == 'contact_connected'):
status_message = parameters
if gajim.config.get('notify_on_signin') and \
not gajim.block_signed_in_notifications[account]\
and helpers.allow_showing_notification(account):
j = gajim.get_jid_without_resource(jid)
server = gajim.get_server_from_jid(j)
account_server = account + '/' + server
block_transport = False
if account_server in gajim.block_signed_in_notifications and \
gajim.block_signed_in_notifications[account_server]:
block_transport = True
if helpers.allow_showing_notification(account, 'notify_on_signin') and \
not gajim.block_signed_in_notifications[account] and not block_transport:
do_popup = True
if gajim.config.get_per('soundevents', 'contact_connected',
'enabled') and not gajim.block_signed_in_notifications[account]:
'enabled') and not gajim.block_signed_in_notifications[account] and \
not block_transport:
do_sound = True
elif (event == 'contact_disconnected'):
status_message = parameters
if gajim.config.get('notify_on_signout') \
and helpers.allow_showing_notification(account):
if helpers.allow_showing_notification(account, 'notify_on_signout'):
do_popup = True
if gajim.config.get_per('soundevents', 'contact_disconnected',
'enabled'):
@ -67,17 +147,21 @@ def notify(event, jid, account, parameters):
first = parameters[1]
nickname = parameters[2]
message = parameters[3]
if gajim.config.get('notify_on_new_message') and \
helpers.allow_showing_notification(account) and first:
if helpers.allow_showing_notification(account, 'notify_on_new_message',
advanced_notif_num, first):
do_popup = True
if first and gajim.config.get_per('soundevents', 'first_message_received',
'enabled'):
if first and helpers.allow_sound_notification('first_message_received',
advanced_notif_num):
do_sound = True
elif not first and gajim.config.get_per('soundevents', 'next_message_received',
'enabled'):
elif not first 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',
str(advanced_notif_num), 'run_command'):
do_cmd = True
# Do the wanted notifications
if (do_popup):
@ -138,8 +222,7 @@ def notify(event, jid, account, parameters):
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, t = gajim.get_room_name_and_server_from_room_jid(jid)
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'priv_msg_recv.png')
title = _('New Private Message from room %s') % room_name
@ -151,20 +234,40 @@ def notify(event, jid, account, parameters):
'chat_msg_recv.png')
title = _('New Message from %(nickname)s') % \
{'nickname': nickname}
text = message
text = message
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
popup(event_type, jid, account, message_type,
path_to_image = path, title = title, text = text)
if (do_sound):
snd_file = None
snd_event = None # If not snd_file, play the event
if (event == 'new_message'):
if first:
helpers.play_sound('first_message_received')
if advanced_notif_num != 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(
'notifications', str(advanced_notif_num), 'sound') == 'no':
pass # do not set snd_event
elif first:
snd_event = 'first_message_received'
else:
helpers.play_sound('next_message_received')
elif (event == 'contact_connected' or event == 'contact_disconnected'):
helpers.play_sound(event)
snd_event = 'next_message_received'
elif event in ('contact_connected', 'contact_disconnected'):
snd_event = event
if snd_file:
helpers.play_sound_file(snd_file)
if snd_event:
helpers.play_sound(snd_event)
if do_cmd:
command = gajim.config.get_per('notifications', str(advanced_notif_num),
'command')
try:
helpers.exec_command(command)
except:
pass
def popup(event_type, jid, account, msg_type = '', path_to_image = None,
title = None, text = None):
@ -279,6 +382,8 @@ class DesktopNotification:
ntype = 'im.invitation'
elif event_type == _('Contact Changed Status'):
ntype = 'presence.status'
elif event_type == _('Connection Failed'):
ntype = 'connection.failed'
else:
# default failsafe values
self.path_to_image = os.path.abspath(

283
src/profile_window.py Normal file
View File

@ -0,0 +1,283 @@
## profile_window.py
##
## Copyright (C) 2003-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
## 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 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_
def get_avatar_pixbuf_encoded_mime(photo):
'''return the pixbuf of the image
photo is a dictionary containing PHOTO information'''
if not isinstance(photo, dict):
return None, None, None
img_decoded = None
avatar_encoded = None
avatar_mime_type = None
if photo.has_key('BINVAL'):
img_encoded = photo['BINVAL']
avatar_encoded = img_encoded
try:
img_decoded = base64.decodestring(img_encoded)
except:
pass
if img_decoded:
if photo.has_key('TYPE'):
avatar_mime_type = photo['TYPE']
pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
else:
pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
img_decoded, want_type=True)
else:
pixbuf = None
return pixbuf, avatar_encoded, avatar_mime_type
class ProfileWindow:
'''Class for our information window'''
def __init__(self, account):
self.xml = gtkgui_helpers.get_glade('profile_window.glade')
self.window = self.xml.get_widget('profile_window')
self.account = account
self.jid = gajim.get_jid_from_account(account)
self.avatar_mime_type = None
self.avatar_encoded = None
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_profile_window_destroy(self, widget):
del gajim.interface.instances[self.account]['profile']
def on_profile_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
self.window.destroy()
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)
self.avatar_encoded = None
self.avatar_mime_type = None
def on_set_avatar_button_clicked(self, widget):
f = None
def on_ok(widget, path_to_file):
filesize = os.path.getsize(path_to_file) # in bytes
#FIXME: use messages for invalid file for 0.11
invalid_file = False
msg = ''
if os.path.isfile(path_to_file):
stat = os.stat(path_to_file)
if stat[6] == 0:
invalid_file = True
else:
invalid_file = True
if not invalid_file and filesize > 16384: # 16 kb
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
# get the image at 'notification size'
# and use that user did not specify in ACE crazy size
scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
'tooltip')
except gobject.GError, msg: # unknown format
# msg should be string, not object instance
msg = str(msg)
invalid_file = True
if invalid_file:
if True: # keep identation
dialogs.ErrorDialog(_('Could not load image'), msg)
return
if filesize > 16384:
if scaled_pixbuf:
path_to_file = os.path.join(gajim.TMP,
'avatar_scaled.png')
scaled_pixbuf.save(path_to_file, 'png')
self.dialog.destroy()
fd = open(path_to_file, 'rb')
data = fd.read()
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')
image.set_from_pixbuf(pixbuf)
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_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)
# show clear
menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menuitem.connect('activate', self.on_clear_button_clicked)
menu.append(menuitem)
menu.connect('selection-done', lambda w:w.destroy())
# show the menu
menu.show_all()
menu.popup(None, None, None, event.button, event.time)
elif event.button == 1: # left click
self.on_set_avatar_button_clicked(widget)
def set_value(self, entry_name, value):
try:
self.xml.get_widget(entry_name).set_text(value)
except AttributeError:
pass
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)
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')
if not pixbuf:
image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG)
continue
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
image.set_from_pixbuf(pixbuf)
continue
if i == 'ADR' or i == 'TEL' or i == 'EMAIL':
for entry in vcard[i]:
add_on = '_HOME'
if 'WORK' in entry:
add_on = '_WORK'
for j in entry.keys():
self.set_value(i + add_on + '_' + j + '_entry', entry[j])
if isinstance(vcard[i], dict):
for j in vcard[i].keys():
self.set_value(i + '_' + j + '_entry', vcard[i][j])
else:
if i == 'DESC':
self.xml.get_widget('DESC_textview').get_buffer().set_text(
vcard[i], 0)
else:
self.set_value(i + '_entry', vcard[i])
def add_to_vcard(self, vcard, entry, txt):
'''Add an information to the vCard dictionary'''
entries = entry.split('_')
loc = vcard
if len(entries) == 3: # We need to use lists
if not loc.has_key(entries[0]):
loc[entries[0]] = []
found = False
for e in loc[entries[0]]:
if entries[1] in e:
found = True
break
if found:
e[entries[2]] = txt
else:
loc[entries[0]].append({entries[1]: '', entries[2]: txt})
return vcard
while len(entries) > 1:
if not loc.has_key(entries[0]):
loc[entries[0]] = {}
loc = loc[entries[0]]
del entries[0]
loc[entries[0]] = txt
return vcard
def make_vcard(self):
'''make the vCard dictionary'''
entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
vcard = {}
for e in entries:
txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8')
if txt != '':
vcard = self.add_to_vcard(vcard, e, txt)
# DESC textview
buff = self.xml.get_widget('DESC_textview').get_buffer()
start_iter = buff.get_start_iter()
end_iter = buff.get_end_iter()
txt = buff.get_text(start_iter, end_iter, 0)
if txt != '':
vcard['DESC'] = txt.decode('utf-8')
# Avatar
if self.avatar_encoded:
vcard['PHOTO'] = {'BINVAL': self.avatar_encoded}
if self.avatar_mime_type:
vcard['PHOTO']['TYPE'] = self.avatar_mime_type
return vcard
def on_publish_button_clicked(self, widget):
if gajim.connections[self.account].connected < 2:
dialogs.ErrorDialog(_('You are not connected to the server'),
_('Without a connection you can not publish your contact '
'information.'))
return
vcard = self.make_vcard()
nick = ''
if vcard.has_key('NICKNAME'):
nick = vcard['NICKNAME']
if nick == '':
nick = gajim.config.get_per('accounts', self.account, 'name')
gajim.nicks[self.account] = nick
gajim.connections[self.account].send_vcard(vcard)
def on_retrieve_button_clicked(self, widget):
entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
'ORG_ORGUNIT', 'TITLE', 'ROLE', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR',
'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE',
'ADR_WORK_CTRY']
if gajim.connections[self.account].connected > 1:
# clear all entries
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)
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.'))

View File

@ -31,9 +31,7 @@ import os
from common import gajim
from common import helpers
from time import time
from common import i18n
from dialogs import AddNewContactWindow, NewChatDialog
_ = i18n._
import dbus_support
if dbus_support.supported:
@ -56,7 +54,7 @@ ident = lambda e: e
if dbus_support.version[1] >= 43:
# in most cases it is a utf-8 string
DBUS_STRING = dbus.String
# general type (for use in dicts,
# where all values should have the same type)
DBUS_VARIANT = dbus.Variant
@ -69,7 +67,7 @@ if dbus_support.version[1] >= 43:
DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
# empty type
DBUS_NONE = lambda : dbus.Variant(0)
else: # 33, 35, 36
DBUS_DICT_SV = lambda : {}
DBUS_DICT_SS = lambda : {}
@ -124,7 +122,7 @@ class Remote:
def __init__(self):
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)
@ -141,7 +139,7 @@ class Remote:
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. '''
def __init__(self, service):
self.first_show = True
self.vcard_account = None
@ -161,6 +159,7 @@ class SignalObject(DbusPrototype):
self.change_status,
self.open_chat,
self.send_message,
self.send_single_message,
self.contact_info,
self.send_file,
self.prefs_list,
@ -172,6 +171,7 @@ class SignalObject(DbusPrototype):
self.get_status,
self.get_status_message,
self.start_chat,
self.send_xml,
])
def raise_signal(self, signal, arg):
@ -181,7 +181,7 @@ class SignalObject(DbusPrototype):
i = message.get_iter(True)
i.append(arg)
self._connection.send(message)
def get_status(self, *args):
'''get_status(account = None)
returns status (show to be exact) which is the global one
@ -194,7 +194,7 @@ class SignalObject(DbusPrototype):
# return show for the given account
index = gajim.connections[account].connected
return DBUS_STRING(STATUS_LIST[index])
def get_status_message(self, *args):
'''get_status(account = None)
returns status which is the global one
@ -207,7 +207,7 @@ class SignalObject(DbusPrototype):
# return show for the given account
status = gajim.connections[account].status
return DBUS_STRING(status)
def get_account_and_contact(self, account, jid):
''' get the account (if not given) and contact instance from jid'''
@ -233,7 +233,7 @@ class SignalObject(DbusPrototype):
break
if not contact:
contact = jid
return connected_account, contact
def send_file(self, *args):
@ -241,34 +241,50 @@ class SignalObject(DbusPrototype):
send file, located at 'file_path' to 'jid', using account
(optional) 'account' '''
file_path, jid, account = self._get_real_arguments(args, 3)
jid = self._get_real_jid(jid, account)
connected_account, contact = self.get_account_and_contact(account, jid)
if connected_account:
if file_path[:7] == 'file://':
file_path=file_path[7:]
if os.path.isfile(file_path): # is it file?
gajim.interface.instances['file_transfers'].send_file(
connected_account, contact, file_path)
return True
return False
def send_message(self, *args):
''' send_message(jid, message, keyID=None, account=None)
send '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)
def _send_message(self, jid, message, keyID, account, type = 'chat', subject = None):
''' can be called from send_chat_message (default when send_message)
or send_single_message'''
if not jid or not message:
return None # or raise error
if not keyID:
keyID = ''
connected_account, contact = self.get_account_and_contact(account, jid)
if connected_account:
connection = gajim.connections[connected_account]
res = connection.send_message(jid, message, keyID)
res = connection.send_message(jid, message, keyID, type, subject)
return True
return False
def send_chat_message(self, *args):
''' 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)
def send_single_message(self, *args):
''' 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)
def open_chat(self, *args):
''' start_chat(jid, account=None) -> shows the tabbed window for new
message to 'jid', using account(optional) 'account' '''
@ -276,9 +292,8 @@ class SignalObject(DbusPrototype):
if not jid:
# FIXME: raise exception for missing argument (dbus0.35+)
return None
if jid.startswith('xmpp:'):
jid = jid[5:] # len('xmpp:') = 5
jid = self._get_real_jid(jid, account)
if account:
accounts = [account]
else:
@ -303,11 +318,11 @@ class SignalObject(DbusPrototype):
connected_account = acct
elif first_connected_acct is None:
first_connected_acct = acct
# if jid is not a conntact, open-chat with first connected account
if connected_account is None and first_connected_acct:
connected_account = first_connected_acct
if connected_account:
gajim.interface.roster.new_chat_from_jid(connected_account, jid)
# preserve the 'steal focus preservation'
@ -316,7 +331,7 @@ class SignalObject(DbusPrototype):
win.window.focus()
return True
return False
def change_status(self, *args, **keywords):
''' change_status(status, message, account). account is optional -
if not specified status is changed for all accounts. '''
@ -331,16 +346,17 @@ 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. '''
#FIXME: when systray is disabled this method does nothing.
if len(gajim.interface.systray.jids) != 0:
if gajim.events.get_nb_events():
gajim.interface.systray.handle_first_event()
def contact_info(self, *args):
''' get vcard info for a contact. Return cached value of the vcard.
'''
@ -350,14 +366,15 @@ class SignalObject(DbusPrototype):
if not jid:
# FIXME: raise exception for missing argument (0.3+)
return None
jid = self._get_real_jid(jid, account)
cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
if cached_vcard:
return get_dbus_struct(cached_vcard)
# return empty dict
return DBUS_DICT_SV()
def list_accounts(self, *args):
''' list register accounts '''
result = gajim.contacts.get_accounts()
@ -367,7 +384,7 @@ class SignalObject(DbusPrototype):
result_array.append(DBUS_STRING(account))
return result_array
return None
def account_info(self, *args):
''' show info on account: resource, jid, nick, prio, message '''
[for_account] = self._get_real_arguments(args, 1)
@ -386,7 +403,7 @@ class SignalObject(DbusPrototype):
result['resource'] = DBUS_STRING(unicode(gajim.config.get_per('accounts',
account.name, 'resource')))
return result
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 '''
@ -410,7 +427,7 @@ class SignalObject(DbusPrototype):
if result == []:
return None
return result
def toggle_roster_appearance(self, *args):
''' shows/hides the roster window '''
win = gajim.interface.roster.window
@ -437,14 +454,14 @@ class SignalObject(DbusPrototype):
prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
gajim.config.foreach(get_prefs)
return prefs_dict
def prefs_store(self, *args):
try:
gajim.interface.save_config()
except Exception, e:
return False
return True
def prefs_del(self, *args):
[key] = self._get_real_arguments(args, 1)
if not key:
@ -457,7 +474,7 @@ class SignalObject(DbusPrototype):
else:
gajim.config.del_per(key_path[0], key_path[1], key_path[2])
return True
def prefs_put(self, *args):
[key] = self._get_real_arguments(args, 1)
if not key:
@ -470,7 +487,7 @@ class SignalObject(DbusPrototype):
subname, value = key_path[2].split('=', 1)
gajim.config.set_per(key_path[0], key_path[1], subname, value)
return True
def add_contact(self, *args):
[jid, account] = self._get_real_arguments(args, 2)
if account:
@ -485,11 +502,12 @@ class SignalObject(DbusPrototype):
# if account is not given, show account combobox
AddNewContactWindow(account = None, jid = jid)
return True
def remove_contact(self, *args):
[jid, account] = self._get_real_arguments(args, 2)
jid = self._get_real_jid(jid, account)
accounts = gajim.contacts.get_accounts()
# if there is only one account in roster, take it as default
if account:
accounts = [account]
@ -503,7 +521,7 @@ class SignalObject(DbusPrototype):
gajim.contacts.remove_jid(account, jid)
contact_exists = True
return contact_exists
def _is_first(self):
if self.first_show:
self.first_show = False
@ -523,7 +541,35 @@ class SignalObject(DbusPrototype):
args.extend([None] * (desired_length - len(args)))
args = args[:desired_length]
return args
def _get_real_jid(self, jid, account = None):
'''get the real jid from the given one: removes xmpp: or get jid from nick
if account is specified, search only in this account
'''
if account:
accounts = [account]
else:
accounts = gajim.connections.keys()
if jid.startswith('xmpp:'):
return jid[5:] # len('xmpp:') = 5
nick_in_roster = None # Is jid a nick ?
for account in accounts:
# Does jid exists in roster of one account ?
if gajim.contacts.get_contacts_from_jid(account, jid):
return jid
if not nick_in_roster:
# look in all contact if one has jid as nick
for jid_ in gajim.contacts.get_jid_list(account):
c = gajim.contacts.get_contacts_from_jid(account, jid_)
if c[0].name == jid:
nick_in_roster = jid_
break
if nick_in_roster:
# We have not found jid in roster, but we found is as a nick
return nick_in_roster
# We have not found it as jid nor as nick, probably a not in roster jid
return jid
def _contacts_as_dbus_structure(self, contacts):
''' get info from list of Contact objects and create dbus dict '''
if not contacts:
@ -552,7 +598,7 @@ class SignalObject(DbusPrototype):
return contact_dict
def get_unread_msgs_number(self, *args):
return str(gajim.interface.roster.nb_unread)
return str(gajim.events.get_nb_events)
def start_chat(self, *args):
[account] = self._get_real_arguments(args, 1)
@ -562,13 +608,21 @@ class SignalObject(DbusPrototype):
NewChatDialog(account)
return True
def send_xml(self, *args):
xml, account = self._get_real_arguments(args, 2)
if account:
gajim.connections[account[0]].send_stanza(xml)
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
@ -579,7 +633,8 @@ class SignalObject(DbusPrototype):
change_status = method(INTERFACE)(change_status)
open_chat = method(INTERFACE)(open_chat)
contact_info = method(INTERFACE)(contact_info)
send_message = method(INTERFACE)(send_message)
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)
@ -592,3 +647,4 @@ class SignalObject(DbusPrototype):
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)

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,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>
@ -18,7 +18,6 @@
##
import gtk
import gtk.glade
import gobject
import os
@ -29,7 +28,6 @@ import gtkgui_helpers
from common import gajim
from common import helpers
from common import i18n
HAS_SYSTRAY_CAPABILITIES = True
@ -42,19 +40,13 @@ except:
gajim.log.debug('No trayicon module available')
HAS_SYSTRAY_CAPABILITIES = False
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
class Systray:
'''Class for icon in the notification area
This class is both base class (for systraywin32.py) and normal class
for trayicon in GNU/Linux'''
def __init__(self):
self.jids = [] # Contain things like [account, jid, type_of_msg]
self.single_message_handler_id = None
self.new_chat_handler_id = None
self.t = None
@ -66,7 +58,9 @@ class Systray:
self.popup_menus = []
def set_img(self):
if len(self.jids) > 0:
if not gajim.interface.systray_enabled:
return
if gajim.events.get_nb_systray_events():
state = 'message'
else:
state = self.status
@ -76,26 +70,13 @@ class Systray:
elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
self.img_tray.set_from_pixbuf(image.get_pixbuf())
def add_jid(self, jid, account, typ):
l = [account, jid, typ]
# We can keep several single message 'cause we open them one by one
if not l in self.jids or typ == 'normal':
self.jids.append(l)
self.set_img()
def remove_jid(self, jid, account, typ):
l = [account, jid, typ]
if l in self.jids:
self.jids.remove(l)
self.set_img()
def change_status(self, global_status):
''' set tray image to 'global_status' '''
# change image and status, only if it is different
if global_status is not None and self.status != global_status:
self.status = global_status
self.set_img()
def start_chat(self, widget, account, jid):
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
if gajim.interface.msg_win_mgr.has_window(jid, account):
@ -106,13 +87,13 @@ class Systray:
gajim.interface.roster.new_chat(contact, account)
gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
jid, account)
def on_single_message_menuitem_activate(self, widget, account):
dialogs.SingleMessageWindow(account, action = 'send')
def on_new_chat(self, widget, account):
dialogs.NewChatDialog(account)
def make_menu(self, event = None):
'''create chat with and new message (sub) menus/menuitems
event is None when we're in Windows
@ -125,7 +106,7 @@ class Systray:
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')
if self.single_message_handler_id:
single_message_menuitem.handler_disconnect(
self.single_message_handler_id)
@ -137,7 +118,7 @@ class Systray:
sub_menu = gtk.Menu()
self.popup_menus.append(sub_menu)
status_menuitem.set_submenu(sub_menu)
gc_sub_menu = gtk.Menu() # gc is always a submenu
join_gc_menuitem.set_submenu(gc_sub_menu)
@ -182,7 +163,7 @@ class Systray:
chat_with_menuitem.set_sensitive(iskey)
single_message_menuitem.set_sensitive(iskey)
join_gc_menuitem.set_sensitive(iskey)
if connected_accounts >= 2: # 2 or more connections? make submenus
account_menu_for_chat_with = gtk.Menu()
chat_with_menuitem.set_submenu(account_menu_for_chat_with)
@ -191,7 +172,7 @@ class Systray:
account_menu_for_single_message = gtk.Menu()
single_message_menuitem.set_submenu(account_menu_for_single_message)
self.popup_menus.append(account_menu_for_single_message)
accounts_list = gajim.contacts.get_accounts()
accounts_list.sort()
for account in accounts_list:
@ -215,7 +196,7 @@ class Systray:
gc_item.add(label)
gc_sub_menu.append(gc_item)
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'
for account in gajim.connections:
@ -230,7 +211,7 @@ class Systray:
# join gc
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:
@ -238,14 +219,18 @@ class Systray:
item = gtk.MenuItem(_('Hide this menu'))
self.systray_context_menu.prepend(item)
self.added_hide_menuitem = True
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):
for i in range(len(self.jids)):
self.handle_first_event()
events = gajim.events.get_systray_events()
for account in events:
for jid in events[account]:
for event in events[account][jid]:
gajim.interface.handle_event(account, jid, event.type_)
def on_show_roster_menuitem_activate(self, widget):
win = gajim.interface.roster.window
@ -262,11 +247,11 @@ class Systray:
def on_left_click(self):
win = gajim.interface.roster.window
if len(self.jids) == 0:
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)
@ -276,10 +261,8 @@ class Systray:
self.handle_first_event()
def handle_first_event(self):
account = self.jids[0][0]
jid = self.jids[0][1]
typ = self.jids[0][2]
gajim.interface.handle_event(account, jid, typ)
account, jid, event = gajim.events.get_first_systray_event()
gajim.interface.handle_event(account, jid, event.type_)
def on_middle_click(self):
'''middle click raises window to have complete focus (fe. get kbd events)
@ -292,13 +275,13 @@ class Systray:
def on_clicked(self, widget, event):
self.on_tray_leave_notify_event(widget, None)
if event.button == 1: # Left click
if event.type == gtk.gdk.BUTTON_PRESS and 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)
def on_show_menuitem_activate(self, widget, show):
# we all add some fake (we cannot select those nor have them as show)
# but this helps to align with roster's status_combobox index positions
@ -327,7 +310,7 @@ class Systray:
if self.tooltip.id == position:
size = widget.window.get_size()
self.tooltip.show_tooltip('', size[1], position[1])
def on_tray_motion_notify_event(self, widget, event):
wireq=widget.size_request()
position = widget.window.get_origin()
@ -339,16 +322,23 @@ class Systray:
self.tooltip.id = position
self.tooltip.timeout = gobject.timeout_add(500,
self.show_tooltip, widget)
def on_tray_leave_notify_event(self, widget, event):
position = widget.window.get_origin()
if self.tooltip.timeout > 0 and \
self.tooltip.id == position:
self.tooltip.hide_tooltip()
def on_tray_destroyed(self, widget):
'''re-add trayicon when systray is destroyed'''
self.t = None
if gajim.interface.systray_enabled:
self.show_icon()
def show_icon(self):
if not self.t:
self.t = trayicon.TrayIcon('Gajim')
self.t.connect('destroy', self.on_tray_destroyed)
eb = gtk.EventBox()
# avoid draw seperate bg color in some gtk themes
eb.set_visible_window(False)
@ -363,7 +353,7 @@ class Systray:
self.t.add(eb)
self.set_img()
self.t.show_all()
def hide_icon(self):
if self.t:
self.t.destroy()

View File

@ -42,10 +42,6 @@ WM_TRAYMESSAGE = win32con.WM_USER + 20
import gtkgui_helpers
from common import gajim
from common import i18n
_ = i18n._
APP = i18n.APP
gtk.glade.bindtextdomain(APP, i18n.DIR)
gtk.glade.textdomain(APP)
class SystrayWINAPI:
def __init__(self, gtk_window):
@ -249,36 +245,25 @@ class SystrayWin32(systray.Systray):
elif lparam == win32con.WM_LBUTTONUP: # Left click
self.on_left_click()
def add_jid(self, jid, account, typ):
systray.Systray.add_jid(self, jid, account, typ)
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
nb = gajim.interface.roster.nb_unread
for acct in gajim.connections:
# in chat / groupchat windows
for kind in ('chats', 'gc'):
jids = gajim.interface.instances[acct][kind]
for jid in jids:
if jid != 'tabbed':
nb += jids[jid].nb_unread[jid]
text = i18n.ngettext(
'Gajim - %d unread message',
'Gajim - %d unread messages',
nb, nb, nb)
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.notify_icon.set_tooltip(text)
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
def remove_jid(self, jid, account, typ):
systray.Systray.remove_jid(self, jid, account, typ)
nb = gajim.events.get_nb_systray_events()
nb = gajim.interface.roster.nb_unread
for acct in gajim.connections:
# in chat / groupchat windows
for kind in ('chats', 'gc'):
for jid in gajim.interface.instances[acct][kind]:
if jid != 'tabbed':
nb += gajim.interface.instances[acct][kind][jid].nb_unread[jid]
if nb > 0:
text = i18n.ngettext(
'Gajim - %d unread message',
@ -288,23 +273,6 @@ class SystrayWin32(systray.Systray):
text = 'Gajim'
self.systray_winapi.notify_icon.set_tooltip(text)
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
def load_icos(self):
'''load .ico files and return them to a dic of SHOW --> img_obj'''
iconset = str(gajim.config.get('iconset'))

View File

@ -27,9 +27,6 @@ from common import gajim
from common import helpers
from common import i18n
_ = i18n._
APP = i18n.APP
class BaseTooltip:
''' Base Tooltip class;
Usage:
@ -285,34 +282,14 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable):
self.table.set_property('column-spacing', 1)
text, single_line = '', ''
unread_chat = gajim.interface.roster.nb_unread
unread_single_chat = 0
unread_gc = 0
unread_pm = 0
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()
for acct in gajim.connections:
# Count unread chat messages
chat_t = message_control.TYPE_CHAT
for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
unread_chat += ctrl.nb_unread
# Count unread PM messages for which we have a control
chat_t = message_control.TYPE_PM
for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
unread_pm += ctrl.nb_unread
# we count unread gc/pm messages
chat_t = message_control.TYPE_GC
for ctrl in gajim.interface.msg_win_mgr.get_controls(chat_t, acct):
# These are PMs for which the PrivateChatControl has not yet been
# created
pm_msgs = ctrl.get_specific_unread()
unread_gc += ctrl.nb_unread
unread_gc -= pm_msgs
unread_pm += pm_msgs
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
@ -391,8 +368,9 @@ class GCTooltip(BaseTooltip):
if contact.jid.strip() != '':
jid_markup = '<span weight="bold">' + contact.jid + '</span>'
else:
jid_markup = '<span weight="bold">' + contact.get_shown_name() + \
'</span>'
jid_markup = '<span weight="bold">' + \
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()))
@ -510,11 +488,12 @@ class RosterTooltip(NotificationAreaTooltip):
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())))
prim_contact.get_shown_name())))
if prim_contact.sub:
properties.append(( _('Subscription: '),
gtkgui_helpers.escape_for_pango_markup(prim_contact.sub)))
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:
@ -525,17 +504,27 @@ class RosterTooltip(NotificationAreaTooltip):
properties.append((_('OpenPGP: '),
gtkgui_helpers.escape_for_pango_markup(keyID)))
num_resources = 0
# put contacts in dict, where key is priority
contacts_dict = {}
for contact in contacts:
if contact.resource:
num_resources += 1
if num_resources== 1 and contact.resource:
properties.append((_('Resource: '), gtkgui_helpers.escape_for_pango_markup(
contact.resource) + ' (' + unicode(contact.priority) + ')'))
if contact.priority in contacts_dict:
contacts_dict[contact.priority].append(contact)
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: '), ' '))
for contact in contacts:
if contact.resource:
contact_keys = contacts_dict.keys()
contact_keys.sort()
contact_keys.reverse()
for priority in contact_keys:
for contact in contacts_dict[priority]:
status_line = self.get_status_info(contact.resource,
contact.priority, contact.show, contact.status)

View File

@ -14,7 +14,6 @@
##
import gtk
import gtk.glade
import gobject
import base64
import mimetypes
@ -27,12 +26,7 @@ import dialogs
from common import helpers
from common import gajim
from common import i18n
_ = i18n._
Q_ = i18n.Q_
APP = i18n.APP
gtk.glade.bindtextdomain (APP, i18n.DIR)
gtk.glade.textdomain (APP)
from common.i18n import Q_
def get_avatar_pixbuf_encoded_mime(photo):
'''return the pixbuf of the image
@ -42,16 +36,20 @@ def get_avatar_pixbuf_encoded_mime(photo):
img_decoded = None
avatar_encoded = None
avatar_mime_type = None
if photo.has_key('BINVAL') and photo.has_key('TYPE'):
if photo.has_key('BINVAL'):
img_encoded = photo['BINVAL']
avatar_encoded = img_encoded
avatar_mime_type = photo['TYPE']
try:
img_decoded = base64.decodestring(img_encoded)
except:
pass
if img_decoded:
pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
if photo.has_key('TYPE'):
avatar_mime_type = photo['TYPE']
pixbuf = gtkgui_helpers.get_pixbuf_from_data(img_decoded)
else:
pixbuf, avatar_mime_type = gtkgui_helpers.get_pixbuf_from_data(
img_decoded, want_type=True)
else:
pixbuf = None
return pixbuf, avatar_encoded, avatar_mime_type
@ -59,50 +57,25 @@ def get_avatar_pixbuf_encoded_mime(photo):
class VcardWindow:
'''Class for contact's information window'''
def __init__(self, contact, account, vcard = False, is_fake = False):
def __init__(self, contact, account, is_fake = False):
# 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.publish_button = self.xml.get_widget('publish_button')
self.retrieve_button = self.xml.get_widget('retrieve_button')
self.nickname_entry = self.xml.get_widget('nickname_entry')
if not vcard: # Maybe gc_vcard ?
self.nickname_entry.set_property('editable', False)
self.publish_button.set_no_show_all(True)
self.retrieve_button.set_no_show_all(True)
self.xml.get_widget('photo_vbuttonbox').set_no_show_all(True)
self.contact = contact # don't use it if vcard is true
self.contact = contact
self.account = account
self.vcard = vcard
self.is_fake = is_fake
self.avatar_mime_type = None
self.avatar_encoded = None
if vcard: # we view/edit our own vcard
self.jid = contact
# remove Jabber tab & show publish/retrieve/close/set_avatar buttons
# and make entries and textview editable
self.change_to_vcard()
else: # we see someone else's vcard
self.publish_button.hide()
self.retrieve_button.hide()
self.jid = contact.jid
self.fill_jabber_page()
# if we are editing our own vcard publish button should publish
# vcard data we have typed including nickname, it's why we connect only
# here (when we see someone else's vcard)
self.nickname_entry.connect('focus-out-event',
self.on_nickname_entry_focus_out_event)
self.fill_jabber_page()
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_vcard_information_window_destroy(self, widget):
del gajim.interface.instances[self.account]['infos'][self.jid]
del gajim.interface.instances[self.account]['infos'][self.contact.jid]
def on_vcard_information_window_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Escape:
@ -123,86 +96,20 @@ class VcardWindow:
if oldlog != log:
gajim.config.set_per('accounts', self.account, 'no_log_for',
' '.join(no_log_for))
def on_nickname_entry_focus_out_event(self, widget, event):
'''Save contact information and update
the roster item on the Jabber server'''
new_name = self.nickname_entry.get_text().decode('utf-8')
# update contact.name with new nickname if that is not ''
if new_name != self.contact.name and new_name != '':
self.contact.name = new_name
# update roster model
model = gajim.interface.roster.tree.get_model()
for iter_ in gajim.interface.roster.get_contact_iter(self.contact.jid,
self.account):
model[iter_][1] = new_name
gajim.connections[self.account].update_contact(self.contact.jid,
self.contact.name, self.contact.groups)
# update opened chat window
ctrl = gajim.interface.msg_win_mgr.get_control(self.contact.jid,
self.account)
if ctrl:
ctrl.update_ui()
win = gajim.interface.msg_win_mgr.get_window(self.contact.jid,
self.account)
win.redraw_tab(ctrl)
win.show_title()
def on_close_button_clicked(self, widget):
self.window.destroy()
def on_clear_button_clicked(self, widget):
# empty the image
self.xml.get_widget('PHOTO_image').set_from_pixbuf(None)
self.avatar_encoded = None
def on_set_avatar_button_clicked(self, widget):
f = None
def on_ok(widget, path_to_file):
filesize = os.path.getsize(path_to_file) # in bytes
#FIXME: use messages for invalid file for 0.11
invalid_file = False
msg = ''
if os.path.isfile(path_to_file):
stat = os.stat(path_to_file)
if stat[6] == 0:
invalid_file = True
else:
invalid_file = True
if not invalid_file and filesize > 16384: # 16 kb
try:
pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file)
# get the image at 'notification size'
# and use that user did not specify in ACE crazy size
scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf,
'tooltip')
except gobject.GError, msg: # unknown format
# msg should be string, not object instance
msg = str(msg)
invalid_file = True
if invalid_file:
if True: # keep identation
dialogs.ErrorDialog(_('Could not load image'), msg)
return
if filesize > 16384:
if scaled_pixbuf:
path_to_file = os.path.join(gajim.TMP,
'avatar_scaled.png')
scaled_pixbuf.save(path_to_file, 'png')
self.dialog.destroy()
fd = open(path_to_file, 'rb')
data = fd.read()
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')
image.set_from_pixbuf(pixbuf)
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_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.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:
@ -215,9 +122,11 @@ class VcardWindow:
if i == 'PHOTO':
pixbuf, self.avatar_encoded, self.avatar_mime_type = \
get_avatar_pixbuf_encoded_mime(vcard[i])
if not pixbuf:
continue
image = self.xml.get_widget('PHOTO_image')
if not pixbuf:
image.set_from_icon_name('stock_person',
gtk.ICON_SIZE_DIALOG)
continue
pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard')
image.set_from_pixbuf(pixbuf)
continue
@ -227,21 +136,23 @@ class VcardWindow:
if 'WORK' in entry:
add_on = '_WORK'
for j in entry.keys():
self.set_value(i + add_on + '_' + j + '_entry', entry[j])
self.set_value(i + add_on + '_' + j + '_label', entry[j])
if isinstance(vcard[i], dict):
for j in vcard[i].keys():
self.set_value(i + '_' + j + '_entry', vcard[i][j])
self.set_value(i + '_' + j + '_label', vcard[i][j])
else:
if i == 'DESC':
self.xml.get_widget('DESC_textview').get_buffer().set_text(
vcard[i], 0)
else:
self.set_value(i + '_entry', vcard[i])
self.set_value(i + '_label', vcard[i])
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:
return
i = 0
client = ''
os = ''
@ -265,6 +176,8 @@ class VcardWindow:
self.xml.get_widget('os_label').set_text(os)
def fill_status_label(self):
if self.xml.get_widget('information_notebook').get_n_pages() < 4:
return
contact_list = gajim.contacts.get_contact(self.account, self.contact.jid)
# stats holds show and status message
stats = ''
@ -280,7 +193,7 @@ class VcardWindow:
stats += '\n' + _('since %s') % time.strftime('%c',
c.last_status_time).decode(locale.getpreferredencoding())
one = False
elif not self.vcard: # Maybe gc_vcard ?
else: # Maybe gc_vcard ?
stats = helpers.get_uf_show(self.contact.show)
if self.contact.status:
stats += ': ' + self.contact.status
@ -294,8 +207,10 @@ class VcardWindow:
def fill_jabber_page(self):
tooltips = gtk.Tooltips()
self.xml.get_widget('nickname_label').set_text(
self.contact.get_shown_name())
self.xml.get_widget('nickname_label').set_markup(
'<b><span size="x-large">' +
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)
@ -317,7 +232,6 @@ class VcardWindow:
if self.contact.ask == 'subscribe':
tooltips.set_tip(eb,
_("You are waiting contact's answer about your subscription request"))
self.nickname_entry.set_text(self.contact.name)
log = True
if self.contact.jid in gajim.config.get_per('accounts', self.account,
'no_log_for').split(' '):
@ -339,8 +253,8 @@ class VcardWindow:
# Request os info in contact is connected
if self.contact.show not in ('offline', 'error'):
gajim.connections[self.account].request_os_info(self.contact.jid,
self.contact.resource)
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': '',
'os': ''}}
i = 1
@ -353,7 +267,8 @@ class VcardWindow:
uf_resources += '\n' + c.resource + \
_(' resource with priority ') + unicode(c.priority)
if c.show not in ('offline', 'error'):
gajim.connections[self.account].request_os_info(c.jid,
gobject.idle_add(
gajim.connections[self.account].request_os_info, c.jid,
c.resource)
gajim.connections[self.account].request_last_status_time(c.jid,
c.resource)
@ -368,117 +283,3 @@ class VcardWindow:
self.fill_status_label()
gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake)
def add_to_vcard(self, vcard, entry, txt):
'''Add an information to the vCard dictionary'''
entries = entry.split('_')
loc = vcard
if len(entries) == 3: # We need to use lists
if not loc.has_key(entries[0]):
loc[entries[0]] = []
found = False
for e in loc[entries[0]]:
if entries[1] in e:
found = True
break
if found:
e[entries[2]] = txt
else:
loc[entries[0]].append({entries[1]: '', entries[2]: txt})
return vcard
while len(entries) > 1:
if not loc.has_key(entries[0]):
loc[entries[0]] = {}
loc = loc[entries[0]]
del entries[0]
loc[entries[0]] = txt
return vcard
def make_vcard(self):
'''make the vCard dictionary'''
entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
vcard = {}
for e in entries:
txt = self.xml.get_widget(e + '_entry').get_text().decode('utf-8')
if txt != '':
vcard = self.add_to_vcard(vcard, e, txt)
# DESC textview
buff = self.xml.get_widget('DESC_textview').get_buffer()
start_iter = buff.get_start_iter()
end_iter = buff.get_end_iter()
txt = buff.get_text(start_iter, end_iter, 0)
if txt != '':
vcard['DESC'] = txt.decode('utf-8')
# Avatar
if self.avatar_encoded:
vcard['PHOTO'] = {'BINVAL': self.avatar_encoded}
if self.avatar_mime_type:
vcard['PHOTO']['TYPE'] = self.avatar_mime_type
return vcard
def on_publish_button_clicked(self, widget):
if gajim.connections[self.account].connected < 2:
dialogs.ErrorDialog(_('You are not connected to the server'),
_('Without a connection you can not publish your contact '
'information.'))
return
vcard = self.make_vcard()
nick = ''
if vcard.has_key('NICKNAME'):
nick = vcard['NICKNAME']
if nick == '':
nick = gajim.config.get_per('accounts', self.account, 'name')
gajim.nicks[self.account] = nick
gajim.connections[self.account].send_vcard(vcard)
def on_retrieve_button_clicked(self, widget):
entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
'ORG_ORGUNIT', 'TITLE', 'ROLE', 'ADR_WORK_STREET', 'ADR_WORK_EXTADR',
'ADR_WORK_LOCALITY', 'ADR_WORK_REGION', 'ADR_WORK_PCODE',
'ADR_WORK_CTRY']
if gajim.connections[self.account].connected > 1:
# clear all entries
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_pixbuf(None)
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.'))
def change_to_vcard(self):
self.xml.get_widget('information_notebook').remove_page(0)
self.xml.get_widget('nickname_label').set_text(_('Personal details'))
self.publish_button.show()
self.retrieve_button.show()
#photo_vbuttonbox visible
self.xml.get_widget('photo_vbuttonbox').show()
#make all entries editable
entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL',
'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX',
'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY',
'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME',
'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID',
'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY',
'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY']
for e in entries:
self.xml.get_widget(e + '_entry').set_property('editable', True)
description_textview = self.xml.get_widget('DESC_textview')
description_textview.set_editable(True)
description_textview.set_cursor_visible(True)