Merge branch 'master' into 'gnotification'

# Conflicts:
#   gajim/gajim.py
This commit is contained in:
Yann Leboulanger 2017-09-21 20:44:18 +02:00
commit 66e9bc4e03
2869 changed files with 1813 additions and 3429 deletions

View file

@ -3,7 +3,7 @@
CONF_ARGS="" CONF_ARGS=""
echo "[encoding: UTF-8]" > po/POTFILES.in \ echo "[encoding: UTF-8]" > po/POTFILES.in \
&& for p in `ls data/gui/*.ui`; do echo "[type: gettext/glade]$p" >> \ && for p in `ls gajim/data/gui/*.ui`; do echo "[type: gettext/glade]$p" >> \
po/POTFILES.in; done \ po/POTFILES.in; done \
&& ls -1 data/org.gajim.Gajim.appdata.xml.in data/org.gajim.Gajim.desktop.in.in data/gajim-remote.desktop.in.in \ && ls -1 data/org.gajim.Gajim.appdata.xml.in data/org.gajim.Gajim.desktop.in.in data/gajim-remote.desktop.in.in \
gajim/*.py gajim/common/*.py gajim/command_system/*.py gajim/command_system/implementation/*.py gajim/common/zeroconf/*.py gajim/plugins/*.py | grep -v ipython_view.py >> \ gajim/*.py gajim/common/*.py gajim/command_system/*.py gajim/command_system/implementation/*.py gajim/common/zeroconf/*.py gajim/plugins/*.py | grep -v ipython_view.py >> \

View file

@ -54,12 +54,13 @@ AC_SUBST(PACKAGE)
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
data/Makefile data/Makefile
data/gui/Makefile gajim/data/Makefile
data/emoticons/Makefile gajim/data/gui/Makefile
gajim/data/emoticons/Makefile
data/pixmaps/Makefile data/pixmaps/Makefile
data/iconsets/Makefile gajim/data/iconsets/Makefile
data/moods/Makefile gajim/data/moods/Makefile
data/activities/Makefile gajim/data/activities/Makefile
icons/Makefile icons/Makefile
data/org.gajim.Gajim.appdata.xml data/org.gajim.Gajim.appdata.xml
data/org.gajim.Gajim.desktop.in data/org.gajim.Gajim.desktop.in

View file

@ -1,4 +1,4 @@
SUBDIRS = gui emoticons pixmaps iconsets moods activities SUBDIRS = pixmaps
@INTLTOOL_DESKTOP_RULE@ @INTLTOOL_DESKTOP_RULE@
appstreamdir = $(datadir)/metainfo/ appstreamdir = $(datadir)/metainfo/
@ -13,25 +13,12 @@ desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
installdefsdir = $(gajim_srcdir)/common installdefsdir = $(gajim_srcdir)/common
installdefs_DATA = defs.py installdefs_DATA = defs.py
soundsdir = $(pkgdatadir)/data/sounds
sounds_DATA = $(srcdir)/sounds/*.wav
styledir = $(pkgdatadir)/data/style
style_DATA = $(srcdir)/style/*.css
otherdir = $(pkgdatadir)/data/other
other_DATA = other/servers.xml other/dh4096.pem
# other/cacert.pem is used only on Windows. On Unix platforms
# use CA certificates installed in /etc/ssl/certs
man_MANS = gajim.1 gajim-remote.1 gajim-history-manager.1 man_MANS = gajim.1 gajim-remote.1 gajim-history-manager.1
EXTRA_DIST = $(appstream_in_files) \ EXTRA_DIST = $(appstream_in_files) \
$(desktop_in_files) \ $(desktop_in_files) \
$(sounds_DATA) \
$(style_DATA) \
$(other_DATA) \
$(man_MANS) \ $(man_MANS) \
defs.py.in defs.py.in

View file

@ -27,7 +27,7 @@ Where to look for logs file
.El .El
.Sh FILES .Sh FILES
.Bl -tag -width Ds .Bl -tag -width Ds
.It $XDG_DATA_DIR/gajim/logs.db .It $XDG_DATA_HOME/gajim/logs.db
The history database log file used when The history database log file used when
.Op Fl c .Op Fl c
is not specified. is not specified.

View file

@ -66,19 +66,19 @@ Where to look for configuration files
.El .El
.Sh FILES .Sh FILES
.Bl -tag -width Ds .Bl -tag -width Ds
.It $XDG_CACHE_DIR/gajim/cache.db .It $XDG_CACHE_HOME/gajim/cache.db
Cache database file of transports, caps, roster entry, and roster group. Cache database file of transports, caps, roster entry, and roster group.
.It $XDG_CACHE_DIR/gajim/avatars/ .It $XDG_CACHE_HOME/gajim/avatars/
Cache directory of avatars. Cache directory of avatars.
.It $XDG_CACHE_DIR/gajim/vcards/ .It $XDG_CACHE_HOME/gajim/vcards/
Cache directory of vCards (virtual cards). Cache directory of vCards (virtual cards).
.It $XDG_CONFIG_DIR/gajim/ .It $XDG_CONFIG_HOME/gajim/
The config-path used when The config-path used when
.Op Fl c .Op Fl c
is not specified. is not specified.
.It $XDG_DATA_DIR/gajim/certs/ .It $XDG_DATA_HOME/gajim/certs/
Directory where certificates are stored. Directory where certificates are stored.
.It $XDG_DATA_DIR/gajim/logs.db .It $XDG_DATA_HOME/gajim/logs.db
The history database log file. The history database log file.
For more information on database logs see For more information on database logs see
<https://dev.gajim.org/gajim/gajim/wikis/development/LogsDatabase>. <https://dev.gajim.org/gajim/gajim/wikis/development/LogsDatabase>.

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
SUBDIRS = data
INCLUDES = \ INCLUDES = \
$(PYTHON_INCLUDES) $(PYTHON_INCLUDES)
export MACOSX_DEPLOYMENT_TARGET=10.4 export MACOSX_DEPLOYMENT_TARGET=10.4

698
gajim/accounts_window.py Normal file
View file

@ -0,0 +1,698 @@
from functools import partial
from gi.repository import Gtk, Gio, GLib, Gdk
from gajim.common import app
from gajim.gtkgui_helpers import get_image_button
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim.common import passwords
from gajim import dialogs
from gajim import config
from gajim.common import helpers
from gajim.common.connection import Connection
from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf
from gajim.options_dialog import OptionsDialog, OptionsBox
from gajim.common.const import Option, OptionKind, OptionType
class AccountsWindow(Gtk.ApplicationWindow):
def __init__(self):
Gtk.ApplicationWindow.__init__(self)
self.set_application(app.app)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_show_menubar(False)
self.set_name('AccountsWindow')
self.set_size_request(500, -1)
self.set_resizable(False)
self.need_relogin = {}
glade_objects = [
'stack', 'box', 'actionbar', 'headerbar', 'back_button',
'menu_button', 'account_page', 'account_list']
self.builder = gtkgui_helpers.get_gtk_builder('accounts_window.ui')
for obj in glade_objects:
setattr(self, obj, self.builder.get_object(obj))
self.set_titlebar(self.headerbar)
menu = Gio.Menu()
menu.append('Merge Accounts', 'app.merge')
menu.append('Use PGP Agent', 'app.agent')
self.menu_button.set_menu_model(menu)
button = get_image_button('list-add-symbolic', 'Add')
button.set_action_name('app.add-account')
self.actionbar.pack_start(button)
accounts = app.config.get_per('accounts')
accounts.sort()
for account in accounts:
self.need_relogin[account] = self.get_relogin_options(account)
account_item = Account(account, self)
self.account_list.add(account_item)
account_item.set_activatable()
self.add(self.box)
self.builder.connect_signals(self)
self.connect('destroy', self.on_destroy)
self.connect('key-press-event', self.on_key_press)
self.show_all()
def on_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
def on_destroy(self, *args):
self.check_relogin()
del app.interface.instances['accounts']
def on_child_visible(self, stack, *args):
page = stack.get_visible_child_name()
if page is None:
return
if page == 'main':
self.menu_button.show()
self.back_button.hide()
self.check_relogin()
else:
self.back_button.show()
self.menu_button.hide()
def on_back_button(self, *args):
page = self.stack.get_visible_child_name()
child = self.stack.get_visible_child()
self.remove_all_pages()
if page == 'account':
child.toggle.set_active(False)
self.stack.add_named(self.account_page, 'main')
self.stack.set_visible_child_name('main')
self.update_accounts()
else:
self.stack.add_named(child.parent, 'account')
self.stack.set_visible_child_name('account')
def update_accounts(self):
for row in self.account_list.get_children():
row.get_child().update()
@staticmethod
def on_row_activated(listbox, row):
row.get_child().on_row_activated()
def remove_all_pages(self):
for page in self.stack.get_children():
self.stack.remove(page)
def set_page(self, page, name):
self.remove_all_pages()
self.stack.add_named(page, name)
page.update()
page.show_all()
self.stack.set_visible_child(page)
def update_proxy_list(self):
page = self.stack.get_child_by_name('connetion')
if page is None:
return
page.options['proxy'].update_values()
def check_relogin(self):
for account in self.need_relogin:
options = self.get_relogin_options(account)
active = app.config.get_per('accounts', account, 'active')
if options != self.need_relogin[account]:
self.need_relogin[account] = options
if active:
self.relog(account)
break
def relog(self, account):
if app.connections[account].connected == 0:
return
if account == app.ZEROCONF_ACC_NAME:
app.connections[app.ZEROCONF_ACC_NAME].update_details()
return
def login(account, show_before, status_before):
"""
Login with previous status
"""
# first make sure connection is really closed,
# 0.5 may not be enough
app.connections[account].disconnect(True)
app.interface.roster.send_status(
account, show_before, status_before)
def relog(account):
show_before = app.SHOW_LIST[app.connections[account].connected]
status_before = app.connections[account].status
app.interface.roster.send_status(
account, 'offline', _('Be right back.'))
GLib.timeout_add(500, login, account, show_before, status_before)
dialogs.YesNoDialog(
_('Relogin now?'),
_('If you want all the changes to apply instantly, '
'you must relogin.'),
transient_for=self,
on_response_yes=lambda *args: relog(account))
@staticmethod
def get_relogin_options(account):
if account == app.ZEROCONF_ACC_NAME:
options = ['zeroconf_first_name', 'zeroconf_last_name',
'zeroconf_jabber_id', 'zeroconf_email', 'keyid']
else:
options = ['client_cert', 'proxy', 'resource',
'use_custom_host', 'custom_host', 'custom_port',
'keyid']
values = []
for option in options:
values.append(app.config.get_per('accounts', account, option))
return values
def on_remove_account(self, button, account):
if app.events.get_events(account):
dialogs.ErrorDialog(
_('Unread events'),
_('Read all pending events before removing this account.'),
transient_for=self)
return
if app.config.get_per('accounts', account, 'is_zeroconf'):
# Should never happen as button is insensitive
return
win_opened = False
if app.interface.msg_win_mgr.get_controls(acct=account):
win_opened = True
elif account in app.interface.instances:
for key in app.interface.instances[account]:
if (app.interface.instances[account][key] and
key != 'remove_account'):
win_opened = True
break
# Detect if we have opened windows for this account
def remove(account):
if (account in app.interface.instances and
'remove_account' in app.interface.instances[account]):
dialog = app.interface.instances[account]['remove_account']
dialog.window.present()
else:
if account not in app.interface.instances:
app.interface.instances[account] = {}
app.interface.instances[account]['remove_account'] = \
config.RemoveAccountWindow(account)
if win_opened:
dialogs.ConfirmationDialog(
_('You have opened chat in account %s') % account,
_('All chat and groupchat windows will be closed. '
'Do you want to continue?'),
on_response_ok=(remove, account))
else:
remove(account)
def remove_account(self, account):
for row in self.account_list.get_children():
if row.get_child().account == account:
self.account_list.remove(row)
del self.need_relogin[account]
break
def add_account(self, account):
account_item = Account(account, self)
self.account_list.add(account_item)
account_item.set_activatable()
self.account_list.show_all()
self.stack.show_all()
self.need_relogin[account] = self.get_relogin_options(account)
def select_account(self, account):
for row in self.account_list.get_children():
if row.get_child().account == account:
self.account_list.emit('row-activated', row)
break
@staticmethod
def enable_account(account):
if account == app.ZEROCONF_ACC_NAME:
app.connections[account] = ConnectionZeroconf(account)
else:
app.connections[account] = Connection(account)
# update variables
app.interface.instances[account] = {
'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {},
'online_dialog': {}, 'sub_request': {}}
app.interface.minimized_controls[account] = {}
app.connections[account].connected = 0
app.groups[account] = {}
app.contacts.add_account(account)
app.gc_connected[account] = {}
app.automatic_rooms[account] = {}
app.newly_added[account] = []
app.to_be_removed[account] = []
if account == app.ZEROCONF_ACC_NAME:
app.nicks[account] = app.ZEROCONF_ACC_NAME
else:
app.nicks[account] = app.config.get_per(
'accounts', account, 'name')
app.block_signed_in_notifications[account] = True
app.sleeper_state[account] = 'off'
app.encrypted_chats[account] = []
app.last_message_time[account] = {}
app.status_before_autoaway[account] = ''
app.transport_avatar[account] = {}
app.gajim_optional_features[account] = []
app.caps_hash[account] = ''
helpers.update_optional_features(account)
# refresh roster
if len(app.connections) >= 2:
# Do not merge accounts if only one exists
app.interface.roster.regroup = app.config.get('mergeaccounts')
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
gui_menu_builder.build_accounts_menu()
@staticmethod
def disable_account(account):
app.interface.roster.close_all(account)
if account == app.ZEROCONF_ACC_NAME:
app.connections[account].disable_account()
app.connections[account].cleanup()
del app.connections[account]
del app.interface.instances[account]
del app.interface.minimized_controls[account]
del app.nicks[account]
del app.block_signed_in_notifications[account]
del app.groups[account]
app.contacts.remove_account(account)
del app.gc_connected[account]
del app.automatic_rooms[account]
del app.to_be_removed[account]
del app.newly_added[account]
del app.sleeper_state[account]
del app.encrypted_chats[account]
del app.last_message_time[account]
del app.status_before_autoaway[account]
del app.transport_avatar[account]
del app.gajim_optional_features[account]
del app.caps_hash[account]
if len(app.connections) >= 2:
# Do not merge accounts if only one exists
app.interface.roster.regroup = app.config.get('mergeaccounts')
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
gui_menu_builder.build_accounts_menu()
class Account(Gtk.Box):
def __init__(self, account, parent):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL,
spacing=12)
self.account = account
if account == app.ZEROCONF_ACC_NAME:
self.options = ZeroConfPage(account)
else:
self.options = AccountPage(account)
self.parent = parent
switch = Gtk.Switch()
switch.set_active(app.config.get_per('accounts', account, 'active'))
switch.set_vexpand(False)
switch.set_valign(Gtk.Align.CENTER)
switch.set_halign(Gtk.Align.START)
if account == app.ZEROCONF_ACC_NAME and not app.HAVE_ZEROCONF:
switch.set_sensitive(False)
switch.set_active(False)
switch.connect('notify::active', self.on_switch, self.account)
account_label = app.config.get_per('accounts', account, 'account_label')
self.label = Gtk.Label(label=account_label or account)
self.label.set_halign(Gtk.Align.START)
self.label.set_hexpand(True)
self.add(switch)
self.add(self.label)
if account != app.ZEROCONF_ACC_NAME:
button = get_image_button('list-remove-symbolic', 'Remove')
button.connect('clicked', parent.on_remove_account, account)
self.add(button)
def set_activatable(self):
if self.account == app.ZEROCONF_ACC_NAME:
self.get_parent().set_activatable(app.HAVE_ZEROCONF)
def on_switch(self, switch, param, account):
old_state = app.config.get_per('accounts', account, 'active')
state = switch.get_active()
if old_state == state:
return
if (account in app.connections and
app.connections[account].connected > 0):
# connecting or connected
dialogs.ErrorDialog(
_('You are currently connected to the server'),
_('To disable the account, you must be disconnected.'),
transient_for=self.parent)
switch.set_active(not state)
return
if state:
self.parent.enable_account(account)
else:
self.parent.disable_account(account)
app.config.set_per('accounts', account, 'active', state)
def on_row_activated(self):
self.options.update_states()
self.parent.set_page(self.options, 'account')
def update(self):
account_label = app.config.get_per(
'accounts', self.account, 'account_label')
self.label.set_text(account_label or self.account)
class GenericOptionPage(Gtk.Box):
def __init__(self, account, parent, options):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=12)
self.account = account
self.parent = parent
self.toggle = get_image_button('document-edit-symbolic',
_('Rename account label'), toggle=True)
self.toggle.connect('toggled', self.set_entry_text)
self.entry = Gtk.Entry()
self.entry.set_sensitive(False)
self.entry.set_name('AccountNameEntry')
self.set_entry_text(self.toggle, update=True)
box = Gtk.Box()
if isinstance(self, AccountPage):
box.pack_start(self.toggle, False, True, 0)
box.pack_start(self.entry, True, True, 0)
self.listbox = OptionsBox(account)
self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
for option in options:
self.listbox.add_option(option)
self.listbox.update_states()
self.pack_start(box, False, False, 0)
self.pack_start(self.listbox, True, True, 0)
self.listbox.connect('row-activated', self.on_row_activated)
def update_states(self):
self.listbox.update_states()
def on_row_activated(self, listbox, row):
self.toggle.set_active(False)
row.get_child().on_row_activated()
def set_entry_text(self, toggle, update=False):
account_label = app.config.get_per(
'accounts', self.account, 'account_label')
if update:
self.entry.set_text(account_label or self.account)
return
if toggle.get_active():
self.entry.set_sensitive(True)
self.entry.grab_focus()
else:
self.entry.set_sensitive(False)
value = self.entry.get_text()
if not value:
value = account_label or self.account
app.config.set_per('accounts', self.account,
'account_label', value or self.account)
if app.config.get_per('accounts', self.account, 'active'):
app.interface.roster.draw_account(self.account)
def update(self):
self.set_entry_text(self.toggle, update=True)
def set_page(self, options, name):
options.update_states()
self.get_toplevel().set_page(options, name)
class AccountPage(GenericOptionPage):
def __init__(self, account, parent=None):
general = partial(
self.set_page, GeneralPage(account, self), 'general')
connection = partial(
self.set_page, ConnectionPage(account, self), 'connection')
options = [
Option(OptionKind.LOGIN, _('Login'), OptionType.DIALOG,
props={'dialog': LoginDialog}),
Option(OptionKind.ACTION, _('Profile'), OptionType.ACTION,
'-profile', props={'action_args': account}),
Option(OptionKind.CALLBACK, _('General'),
name='general', props={'callback': general}),
Option(OptionKind.CALLBACK, _('Connection'),
name='connection', props={'callback': connection}),
Option(OptionKind.ACTION, _('Import Contacts'), OptionType.ACTION,
'-import-contacts', props={'action_args': account}),
Option(OptionKind.DIALOG, _('Client Certificate'),
OptionType.DIALOG, props={'dialog': CertificateDialog}),
Option(OptionKind.GPG, _('OpenPGP Key'), OptionType.DIALOG,
props={'dialog': None}),
]
GenericOptionPage.__init__(self, account, parent, options)
class GeneralPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.SWITCH, _('Connect on startup'),
OptionType.ACCOUNT_CONFIG, 'autoconnect'),
Option(OptionKind.SWITCH, _('Reconnect when connection is lost'),
OptionType.ACCOUNT_CONFIG, 'autoreconnect'),
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
OptionType.ACCOUNT_CONFIG, 'no_log_for',
desc=_('Store conversations on the harddrive')),
Option(OptionKind.SWITCH, _('Server Message Archive'),
OptionType.ACCOUNT_CONFIG, 'sync_logs_with_server',
desc=_('Messages get stored on the server.\n'
'The archive is used to sync messages\n'
'between multiple devices.\n'
'XEP-0313')),
Option(OptionKind.SWITCH, _('Global Status'),
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
desc=_('Synchronise the status of all accounts')),
Option(OptionKind.SWITCH, _('Message Carbons'),
OptionType.ACCOUNT_CONFIG, 'enable_message_carbons',
desc=_('All your other online devices get copies\n'
'of sent and received messages.\n'
'XEP-0280')),
Option(OptionKind.SWITCH, _('Use file transfer proxies'),
OptionType.ACCOUNT_CONFIG, 'use_ft_proxies'),
]
GenericOptionPage.__init__(self, account, parent, options)
class ConnectionPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.SWITCH, 'HTTP_PROXY',
OptionType.ACCOUNT_CONFIG, 'use_env_http_proxy',
desc=_('Use environment variable')),
Option(OptionKind.PROXY, _('Proxy'),
OptionType.ACCOUNT_CONFIG, 'proxy', name='proxy'),
Option(OptionKind.SWITCH, _('Warn on insecure connection'),
OptionType.ACCOUNT_CONFIG,
'warn_when_insecure_ssl_connection'),
Option(OptionKind.SWITCH, _('Send keep-alive packets'),
OptionType.ACCOUNT_CONFIG, 'keep_alives_enabled'),
Option(OptionKind.HOSTNAME, _('Hostname'), OptionType.DIALOG,
desc=_('Manually set the hostname for the server'),
props={'dialog': CutstomHostnameDialog}),
Option(OptionKind.ENTRY, _('Resource'),
OptionType.ACCOUNT_CONFIG, 'resource'),
Option(OptionKind.PRIORITY, _('Priority'),
OptionType.DIALOG, props={'dialog': PriorityDialog}),
]
GenericOptionPage.__init__(self, account, parent, options)
class ZeroConfPage(GenericOptionPage):
def __init__(self, account, parent=None):
options = [
Option(OptionKind.DIALOG, _('Credentials'),
OptionType.DIALOG, props={'dialog': CredentialsDialog}),
Option(OptionKind.SWITCH, _('Connect on startup'),
OptionType.ACCOUNT_CONFIG, 'autoconnect',
desc=_('Use environment variable')),
Option(OptionKind.SWITCH, _('Save conversations for all contacts'),
OptionType.ACCOUNT_CONFIG, 'no_log_for',
desc=_('Store conversations on the harddrive')),
Option(OptionKind.SWITCH, _('Global Status'),
OptionType.ACCOUNT_CONFIG, 'sync_with_global_status',
desc=_('Synchronize the status of all accounts')),
Option(OptionKind.GPG, _('OpenPGP Key'),
OptionType.DIALOG, props={'dialog': None}),
]
GenericOptionPage.__init__(self, account, parent, options)
class CredentialsDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.ENTRY, _('First Name'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_first_name'),
Option(OptionKind.ENTRY, _('Last Name'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_last_name'),
Option(OptionKind.ENTRY, _('Jabber ID'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_jabber_id'),
Option(OptionKind.ENTRY, _('Email'),
OptionType.ACCOUNT_CONFIG, 'zeroconf_email'),
]
OptionsDialog.__init__(self, parent, _('Credential Options'),
Gtk.DialogFlags.MODAL, options, account)
class PriorityDialog(OptionsDialog):
def __init__(self, account, parent):
neg_priority = app.config.get('enable_negative_priority')
if neg_priority:
range_ = (-128, 127)
else:
range_ = (0, 127)
options = [
Option(OptionKind.SWITCH, _('Adjust to status'),
OptionType.ACCOUNT_CONFIG, 'adjust_priority_with_status',
'adjust'),
Option(OptionKind.SPIN, _('Priority'),
OptionType.ACCOUNT_CONFIG, 'priority',
enabledif=('adjust', False), props={'range_': range_}),
]
OptionsDialog.__init__(self, parent, _('Priority'),
Gtk.DialogFlags.MODAL, options, account)
self.connect('destroy', self.on_destroy)
def on_destroy(self, *args):
# Update priority
if self.account not in app.connections:
return
show = app.SHOW_LIST[app.connections[self.account].connected]
status = app.connections[self.account].status
app.connections[self.account].change_status(show, status)
class CutstomHostnameDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.SWITCH, _('Enable'),
OptionType.ACCOUNT_CONFIG, 'use_custom_host', name='custom'),
Option(OptionKind.ENTRY, _('Hostname'),
OptionType.ACCOUNT_CONFIG, 'custom_host',
enabledif=('custom', True)),
Option(OptionKind.ENTRY, _('Port'),
OptionType.ACCOUNT_CONFIG, 'custom_port',
enabledif=('custom', True)),
]
OptionsDialog.__init__(self, parent, _('Connection Options'),
Gtk.DialogFlags.MODAL, options, account)
class CertificateDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.FILECHOOSER, _('Client Certificate'),
OptionType.ACCOUNT_CONFIG, 'client_cert',
props={'filefilter': (_('PKCS12 Files'), '*.p12')}),
Option(OptionKind.SWITCH, _('Encrypted Certificate'),
OptionType.ACCOUNT_CONFIG, 'client_cert_encrypted'),
]
OptionsDialog.__init__(self, parent, _('Certificate Options'),
Gtk.DialogFlags.MODAL, options, account)
class LoginDialog(OptionsDialog):
def __init__(self, account, parent):
options = [
Option(OptionKind.ENTRY, _('Password'),
OptionType.ACCOUNT_CONFIG, 'password', name='password',
enabledif=('savepass', True)),
Option(OptionKind.SWITCH, _('Save Password'),
OptionType.ACCOUNT_CONFIG, 'savepass', name='savepass'),
Option(OptionKind.CHANGEPASSWORD, _('Change Password'),
OptionType.DIALOG, callback=self.on_password_change,
props={'dialog': None}),
]
OptionsDialog.__init__(self, parent, _('Login Options'),
Gtk.DialogFlags.MODAL, options, account)
self.connect('destroy', self.on_destroy)
def on_password_change(self, new_password, data):
self.get_option('password').entry.set_text(new_password)
def on_destroy(self, *args):
savepass = app.config.get_per('accounts', self.account, 'savepass')
if not savepass:
passwords.save_password(self.account, '')

View file

@ -25,10 +25,12 @@ from gajim.common.exceptions import GajimGeneralException
from gi.repository import Gtk from gi.repository import Gtk
import sys import sys
import os import os
from gajim import config from gajim import config
from gajim import dialogs from gajim import dialogs
from gajim import features_window from gajim import features_window
from gajim import shortcuts_window from gajim import shortcuts_window
from gajim import accounts_window
import gajim.plugins.gui import gajim.plugins.gui
from gajim import history_window from gajim import history_window
from gajim import disco from gajim import disco
@ -57,10 +59,10 @@ class AppActions():
interface.instances['plugins'] = gajim.plugins.gui.PluginsWindow() interface.instances['plugins'] = gajim.plugins.gui.PluginsWindow()
def on_accounts(self, action, param): def on_accounts(self, action, param):
if 'accounts' in interface.instances: if 'accounts' in app.interface.instances:
interface.instances['accounts'].window.present() app.interface.instances['accounts'].present()
else: else:
interface.instances['accounts'] = config.AccountsWindow() app.interface.instances['accounts'] = accounts_window.AccountsWindow()
def on_history_manager(self, action, param): def on_history_manager(self, action, param):
from gajim.history_manager import HistoryManager from gajim.history_manager import HistoryManager
@ -131,6 +133,35 @@ class AppActions():
def on_single_message(self, action, param): def on_single_message(self, action, param):
dialogs.SingleMessageWindow(param.get_string(), action='send') dialogs.SingleMessageWindow(param.get_string(), action='send')
def on_merge_accounts(self, action, param):
action.set_state(param)
value = param.get_boolean()
app.config.set('mergeaccounts', value)
if len(app.connections) >= 2: # Do not merge accounts if only one active
app.interface.roster.regroup = value
else:
app.interface.roster.regroup = False
app.interface.roster.setup_and_draw_roster()
def on_use_pgp_agent(self, action, param):
action.set_state(param)
app.config.set('use_gpg_agent', param.get_boolean())
def on_add_account(self, action, param):
if 'account_creation_wizard' in app.interface.instances:
app.interface.instances['account_creation_wizard'].window.present()
else:
app.interface.instances['account_creation_wizard'] = \
config.AccountCreationWizardWindow()
def on_import_contacts(self, action, param):
account = param.get_string()
if 'import_contacts' in app.interface.instances:
app.interface.instances['import_contacts'].dialog.present()
else:
app.interface.instances['import_contacts'] = \
dialogs.SynchroniseSelectAccountDialog(account)
# Advanced Actions # Advanced Actions
def on_archiving_preferences(self, action, param): def on_archiving_preferences(self, action, param):
@ -174,6 +205,13 @@ class AppActions():
interface.instances[account]['xml_console'] = \ interface.instances[account]['xml_console'] = \
dialogs.XMLConsoleWindow(account) dialogs.XMLConsoleWindow(account)
def on_manage_proxies(self, action, param):
if 'manage_proxies' in app.interface.instances:
app.interface.instances['manage_proxies'].window.present()
else:
app.interface.instances['manage_proxies'] = \
config.ManageProxiesWindow(interface.roster.window)
# Admin Actions # Admin Actions
def on_set_motd(self, action, param): def on_set_motd(self, action, param):

View file

@ -298,7 +298,8 @@ class StandardGroupChatCommands(CommandContainer):
@doc(_("Invite a user to a room for a reason")) @doc(_("Invite a user to a room for a reason"))
def invite(self, jid, reason): def invite(self, jid, reason):
self.connection.send_invite(self.room_jid, jid, reason) self.connection.send_invite(self.room_jid, jid, reason)
return _("Invited %s to %s") % (jid, self.room_jid) return _("Invited %(jid)s to %(room_jid)s") % {'jid': jid,
'room_jid': self.room_jid}
@command(raw=True, empty=True) @command(raw=True, empty=True)
@doc(_("Join a group chat given by a jid, optionally using given nickname")) @doc(_("Join a group chat given by a jid, optionally using given nickname"))

View file

@ -53,7 +53,7 @@ ged = ged_module.GlobalEventsDispatcher() # Global Events Dispatcher
nec = None # Network Events Controller nec = None # Network Events Controller
plugin_manager = None # Plugins Manager plugin_manager = None # Plugins Manager
log = logging.getLogger('gajim') glog = logging.getLogger('gajim')
logger = None logger = None
@ -88,8 +88,6 @@ else:
os_info = None # used to cache os information os_info = None # used to cache os information
gmail_domains = ['gmail.com', 'googlemail.com']
transport_type = {} # list the type of transport transport_type = {} # list the type of transport
last_message_time = {} # list of time of the latest incomming message last_message_time = {} # list of time of the latest incomming message
@ -176,7 +174,7 @@ try:
''' '''
v_gnupg = gnupg.__version__ v_gnupg = gnupg.__version__
if V(v_gnupg) < V('0.3.8') or V(v_gnupg) > V('1.0.0'): if V(v_gnupg) < V('0.3.8') or V(v_gnupg) > V('1.0.0'):
log.info('Gajim needs python-gnupg >= 0.3.8') glog.info('Gajim needs python-gnupg >= 0.3.8')
HAVE_GPG = False HAVE_GPG = False
except ImportError: except ImportError:
HAVE_GPG = False HAVE_GPG = False
@ -247,7 +245,7 @@ try:
if sleepy.SUPPORTED: if sleepy.SUPPORTED:
HAVE_IDLE = True HAVE_IDLE = True
except Exception: except Exception:
log.debug(_('Unable to load idle module')) glog.info(_('Unable to load idle module'))
HAVE_IDLE = False HAVE_IDLE = False
@ -261,7 +259,8 @@ gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK, nbxmpp.NS_SSN, nbxmpp.NS_MOOD, nbxmpp.NS_ACTIVITY, nbxmpp.NS_NICK,
nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2, nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256, nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT] nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT,
nbxmpp.NS_EME]
# Optional features gajim supports per account # Optional features gajim supports per account
gajim_optional_features = {} gajim_optional_features = {}
@ -493,3 +492,7 @@ def get_priority(account, show):
elif prio > 127: elif prio > 127:
prio = 127 prio = 127
return prio return prio
def log(domain):
root = 'gajim.'
return logging.getLogger(root + domain)

View file

@ -250,7 +250,7 @@ def check_and_possibly_move_config():
continue continue
if not os.path.exists(src): if not os.path.exists(src):
continue continue
print(_('moving %s to %s') % (src, dst)) print(_('moving %(src)s to %(dst)s') % {'src': src, 'dst': dst})
shutil.move(src, dst) shutil.move(src, dst)
app.logger.init_vars() app.logger.init_vars()
app.logger.attach_cache_database() app.logger.attach_cache_database()

View file

@ -123,6 +123,7 @@ class Config:
'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': [ opt_str, 'always', _('\'always\' - print time for every message.\n\'sometimes\' - print time every print_ichat_every_foo_minutes minute.\n\'never\' - never print time.')],
'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ], 'print_time_fuzzy': [ opt_int, 0, _('Print time in chats using Fuzzy Clock. Value of fuzziness from 1 to 4, or 0 to disable fuzzyclock. 1 is the most precise clock, 4 the least precise one. This is used only if print_time is \'sometimes\'.') ],
'emoticons_theme': [opt_str, 'noto-emoticons', '', True ], 'emoticons_theme': [opt_str, 'noto-emoticons', '', True ],
'ascii_emoticons': [opt_bool, True, _('Enable ASCII emoticons'), True],
'ascii_formatting': [ opt_bool, True, 'ascii_formatting': [ opt_bool, True,
_('Treat * / _ pairs as possible formatting characters.'), True], _('Treat * / _ pairs as possible formatting characters.'), True],
'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not ' 'show_ascii_formatting_chars': [ opt_bool, True, _('If True, do not '
@ -176,9 +177,6 @@ class Config:
'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ],
'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ],
'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ],
'notify_on_new_gmail_email': [ opt_bool, True ],
'notify_on_new_gmail_email_extra': [ opt_bool, False ],
'notify_on_new_gmail_email_command': [ opt_str, '', _('Specify the command to run when new mail arrives, e.g.: /usr/bin/getmail -q') ],
'use_gpg_agent': [ opt_bool, False ], 'use_gpg_agent': [ opt_bool, False ],
'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')], 'change_roster_title': [ opt_bool, True, _('Add * and [n] in roster title?')],
'restore_lines': [opt_int, 10, _('How many history messages should be restored when a chat tab/window is reopened?')], 'restore_lines': [opt_int, 10, _('How many history messages should be restored when a chat tab/window is reopened?')],
@ -192,7 +190,7 @@ class Config:
'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')],
'version': [ opt_str, defs.version ], # which version created the config 'version': [ opt_str, defs.version ], # which version created the config
'search_engine': [opt_str, 'https://www.google.com/search?&q=%s&sourceid=gajim'], 'search_engine': [opt_str, 'https://www.google.com/search?&q=%s&sourceid=gajim'],
'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %%s in it where %%s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")],
'always_english_wikipedia': [opt_bool, False], 'always_english_wikipedia': [opt_bool, False],
'always_english_wiktionary': [opt_bool, True], 'always_english_wiktionary': [opt_bool, True],
'remote_control': [opt_bool, False, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True], 'remote_control': [opt_bool, False, _('If checked, Gajim can be controlled remotely using gajim-remote.'), True],
@ -323,6 +321,7 @@ class Config:
__options_per_key = { __options_per_key = {
'accounts': ({ 'accounts': ({
'name': [ opt_str, '', '', True ], 'name': [ opt_str, '', '', True ],
'account_label': [ opt_str, '', '', False ],
'hostname': [ opt_str, '', '', True ], 'hostname': [ opt_str, '', '', True ],
'anonymous_auth': [ opt_bool, False ], 'anonymous_auth': [ opt_bool, False ],
'client_cert': [ opt_str, '', '', True ], 'client_cert': [ opt_str, '', '', True ],
@ -535,7 +534,6 @@ class Config:
'message_sent': [ False, 'sent.wav' ], 'message_sent': [ False, 'sent.wav' ],
'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')], 'muc_message_highlight': [ True, 'gc_message1.wav', _('Sound to play when a group chat message contains one of the words in muc_highlight_words, or when a group chat message contains your nickname.')],
'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ], 'muc_message_received': [ False, 'gc_message2.wav', _('Sound to play when any MUC message arrives.') ],
'gmail_received': [ False, 'message1.wav' ],
} }
themes_default = { themes_default = {

View file

@ -98,9 +98,11 @@ class ConfigPaths:
base = expand('~/.local/share') base = expand('~/.local/share')
self.data_root = os.path.join(base, 'gajim') self.data_root = os.path.join(base, 'gajim')
basedir = os.environ.get('GAJIM_BASEDIR', defs.basedir) import pkg_resources
basedir = pkg_resources.resource_filename("gajim", ".")
self.add('DATA', None, os.path.join(basedir, 'data')) self.add('DATA', None, os.path.join(basedir, 'data'))
self.add('GUI', None, os.path.join(basedir, 'data', 'gui')) self.add('GUI', None, os.path.join(basedir, 'data', 'gui'))
basedir = os.environ.get('GAJIM_BASEDIR', defs.basedir)
self.add('ICONS', None, os.path.join(basedir, 'icons')) self.add('ICONS', None, os.path.join(basedir, 'icons'))
self.add('HOME', None, os.path.expanduser('~')) self.add('HOME', None, os.path.expanduser('~'))
self.add('PLUGINS_BASE', None, os.path.join(basedir, 'plugins')) self.add('PLUGINS_BASE', None, os.path.join(basedir, 'plugins'))
@ -142,8 +144,7 @@ class ConfigPaths:
d = {'LOG_DB': 'logs.db', 'MY_CACERTS': 'cacerts.pem', d = {'LOG_DB': 'logs.db', 'MY_CACERTS': 'cacerts.pem',
'MY_EMOTS': 'emoticons', 'MY_ICONSETS': 'iconsets', 'MY_EMOTS': 'emoticons', 'MY_ICONSETS': 'iconsets',
'MY_MOOD_ICONSETS': 'moods', 'MY_ACTIVITY_ICONSETS': 'activities', 'MY_MOOD_ICONSETS': 'moods', 'MY_ACTIVITY_ICONSETS': 'activities',
'PLUGINS_USER': 'plugins', 'PLUGINS_USER': 'plugins'}
'RNG_SEED': 'rng_seed'}
for name in d: for name in d:
d[name] += profile d[name] += profile
self.add(name, Type.DATA, windowsify(d[name])) self.add(name, Type.DATA, windowsify(d[name]))

View file

@ -1083,7 +1083,8 @@ class Connection(CommonConnection, ConnectionHandlers):
self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10, self._hosts = [ {'host': h, 'port': p, 'ssl_port': ssl_p, 'prio': 10,
'weight': 10} ] 'weight': 10} ]
self._hostname = hostname self._hostname = hostname
if use_srv:
if use_srv and self._proxy is None:
# add request for srv query to the resolve, on result '_on_resolve' # add request for srv query to the resolve, on result '_on_resolve'
# will be called # will be called
app.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii( app.resolver.resolve('_xmpp-client._tcp.' + helpers.idn_to_ascii(
@ -1451,7 +1452,9 @@ class Connection(CommonConnection, ConnectionHandlers):
return return
if hasattr(con, 'Resource'): if hasattr(con, 'Resource'):
self.server_resource = con.Resource self.server_resource = con.Resource
self.registered_name = con._registered_name if con._registered_name is not None:
log.info('Bound JID: %s', con._registered_name)
self.registered_name = con._registered_name
if app.config.get_per('accounts', self.name, 'anonymous_auth'): if app.config.get_per('accounts', self.name, 'anonymous_auth'):
# Get jid given by server # Get jid given by server
old_jid = app.get_jid_from_account(self.name) old_jid = app.get_jid_from_account(self.name)
@ -1821,9 +1824,10 @@ class Connection(CommonConnection, ConnectionHandlers):
self.sm.resuming = False # back to previous state self.sm.resuming = False # back to previous state
# Discover Stun server(s) # Discover Stun server(s)
hostname = app.config.get_per('accounts', self.name, 'hostname') if self._proxy is None:
app.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(hostname), hostname = app.config.get_per('accounts', self.name, 'hostname')
self._on_stun_resolved) app.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(hostname),
self._on_stun_resolved)
def _on_stun_resolved(self, host, result_array): def _on_stun_resolved(self, host, result_array):
if len(result_array) != 0: if len(result_array) != 0:
@ -1911,9 +1915,6 @@ class Connection(CommonConnection, ConnectionHandlers):
get_action(self.name + '-archive').set_enabled(True) get_action(self.name + '-archive').set_enabled(True)
if obj.fjid == hostname: if obj.fjid == hostname:
if nbxmpp.NS_GMAILNOTIFY in obj.features:
app.gmail_domains.append(obj.fjid)
self.request_gmail_notifications()
if nbxmpp.NS_SECLABEL in obj.features: if nbxmpp.NS_SECLABEL in obj.features:
self.seclabel_supported = True self.seclabel_supported = True
for identity in obj.identities: for identity in obj.identities:
@ -2074,8 +2075,9 @@ class Connection(CommonConnection, ConnectionHandlers):
return return
if type_ == 'message': if type_ == 'message':
if len(contacts) == 1: if len(contacts) == 1:
msg = _('Sent contact: "%s" (%s)') % (contacts[0].get_full_jid(), msg = _('Sent contact: "%(jid)s" (%(name)s)') % {
contacts[0].get_shown_name()) 'jid': contacts[0].get_full_jid(),
'name': contacts[0].get_shown_name()}
else: else:
msg = _('Sent contacts:') msg = _('Sent contacts:')
for contact in contacts: for contact in contacts:

View file

@ -1080,6 +1080,27 @@ class ConnectionHandlersBase:
app.plugin_manager.extension_point( app.plugin_manager.extension_point(
'decrypt', self, obj, self._on_message_received) 'decrypt', self, obj, self._on_message_received)
if not obj.encrypted: if not obj.encrypted:
# XEP-0380
enc_tag = obj.stanza.getTag('encryption', namespace=nbxmpp.NS_EME)
if enc_tag:
ns = enc_tag.getAttr('namespace')
if ns:
if ns == 'urn:xmpp:otr:0':
obj.msgtxt = _('This message was encrypted with OTR '
'and could not be decrypted.')
elif ns == 'jabber:x:encrypted':
obj.msgtxt = _('This message was encrypted with Legacy '
'OpenPGP and could not be decrypted. You can install '
'the PGP plugin to handle those messages.')
elif ns == 'urn:xmpp:openpgp:0':
obj.msgtxt = _('This message was encrypted with '
'OpenPGP for XMPP and could not be decrypted.')
else:
enc_name = enc_tag.getAttr('name')
if not enc_name:
enc_name = ns
obj.msgtxt = _('This message was encrypted with %s '
'and could not be decrypted.') % enc_name
self._on_message_received(obj) self._on_message_received(obj)
def _on_message_received(self, obj): def _on_message_received(self, obj):
@ -1155,6 +1176,8 @@ class ConnectionHandlersBase:
return True return True
def _nec_gc_message_received(self, obj): def _nec_gc_message_received(self, obj):
if obj.conn.name != self.name:
return
if app.config.should_log(obj.conn.name, obj.jid) and not \ if app.config.should_log(obj.conn.name, obj.jid) and not \
obj.timestamp < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \ obj.timestamp < obj.conn.last_history_time[obj.jid] and obj.msgtxt and \
obj.nick: obj.nick:
@ -1356,7 +1379,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
client_caps_factory=capscache.create_suitable_client_caps) client_caps_factory=capscache.create_suitable_client_caps)
ConnectionJingle.__init__(self) ConnectionJingle.__init__(self)
ConnectionHandlersBase.__init__(self) ConnectionHandlersBase.__init__(self)
self.gmail_url = None
# keep the latest subscribed event for each jid to prevent loop when we # keep the latest subscribed event for each jid to prevent loop when we
# acknowledge presences # acknowledge presences
@ -1375,9 +1397,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
self.privacy_default_list = None self.privacy_default_list = None
self.gmail_last_tid = None
self.gmail_last_time = None
app.nec.register_incoming_event(PrivateStorageBookmarksReceivedEvent) app.nec.register_incoming_event(PrivateStorageBookmarksReceivedEvent)
app.nec.register_incoming_event(BookmarksReceivedEvent) app.nec.register_incoming_event(BookmarksReceivedEvent)
app.nec.register_incoming_event( app.nec.register_incoming_event(
@ -1415,8 +1434,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
self._nec_roster_received) self._nec_roster_received)
app.ged.register_event_handler('iq-error-received', ged.CORE, app.ged.register_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received) self._nec_iq_error_received)
app.ged.register_event_handler('gmail-new-mail-received', ged.CORE,
self._nec_gmail_new_mail_received)
app.ged.register_event_handler('ping-received', ged.CORE, app.ged.register_event_handler('ping-received', ged.CORE,
self._nec_ping_received) self._nec_ping_received)
app.ged.register_event_handler('subscribe-presence-received', app.ged.register_event_handler('subscribe-presence-received',
@ -1461,8 +1478,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
self._nec_roster_received) self._nec_roster_received)
app.ged.remove_event_handler('iq-error-received', ged.CORE, app.ged.remove_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received) self._nec_iq_error_received)
app.ged.remove_event_handler('gmail-new-mail-received', ged.CORE,
self._nec_gmail_new_mail_received)
app.ged.remove_event_handler('ping-received', ged.CORE, app.ged.remove_event_handler('ping-received', ged.CORE,
self._nec_ping_received) self._nec_ping_received)
app.ged.remove_event_handler('subscribe-presence-received', app.ged.remove_event_handler('subscribe-presence-received',
@ -1719,44 +1734,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
app.nec.push_incoming_event(TimeResultReceivedEvent(None, conn=self, app.nec.push_incoming_event(TimeResultReceivedEvent(None, conn=self,
stanza=iq_obj)) stanza=iq_obj))
def _gMailNewMailCB(self, con, iq_obj):
"""
Called when we get notified of new mail messages in gmail account
"""
log.debug('gMailNewMailCB')
app.nec.push_incoming_event(GmailNewMailReceivedEvent(None, conn=self,
stanza=iq_obj))
raise nbxmpp.NodeProcessed
def _nec_gmail_new_mail_received(self, obj):
if obj.conn.name != self.name:
return
if not self.connection or self.connected < 2:
return
# we'll now ask the server for the exact number of new messages
jid = app.get_jid_from_account(self.name)
log.debug('Got notification of new gmail e-mail on %s. Asking the '
'server for more info.' % jid)
iq = nbxmpp.Iq(typ='get')
query = iq.setTag('query')
query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
# we want only be notified about newer mails
if self.gmail_last_tid:
query.setAttr('newer-than-tid', self.gmail_last_tid)
if self.gmail_last_time:
query.setAttr('newer-than-time', self.gmail_last_time)
self.connection.send(iq)
def _gMailQueryCB(self, con, iq_obj):
"""
Called when we receive results from Querying the server for mail messages
in gmail account
"""
log.debug('gMailQueryCB')
app.nec.push_incoming_event(GMailQueryReceivedEvent(None, conn=self,
stanza=iq_obj))
raise nbxmpp.NodeProcessed
def _rosterItemExchangeCB(self, con, msg): def _rosterItemExchangeCB(self, con, msg):
""" """
XEP-0144 Roster Item Echange XEP-0144 Roster Item Echange
@ -2121,28 +2098,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
# hashes of already received messages # hashes of already received messages
self.received_message_hashes = [] self.received_message_hashes = []
def request_gmail_notifications(self):
if not self.connection or self.connected < 2:
return
# It's a gmail account,
# inform the server that we want e-mail notifications
our_jid = helpers.parse_jid(app.get_jid_from_account(self.name))
log.debug(('%s is a gmail account. Setting option '
'to get e-mail notifications on the server.') % (our_jid))
iq = nbxmpp.Iq(typ='set', to=our_jid)
iq.setAttr('id', 'MailNotify')
query = iq.setTag('usersetting')
query.setNamespace(nbxmpp.NS_GTALKSETTING)
query = query.setTag('mailnotifications')
query.setAttr('value', 'true')
self.connection.send(iq)
# Ask how many messages there are now
iq = nbxmpp.Iq(typ='get')
iq.setID(self.connection.getAnID())
query = iq.setTag('query')
query.setNamespace(nbxmpp.NS_GMAILNOTIFY)
self.connection.send(iq)
def _SearchCB(self, con, iq_obj): def _SearchCB(self, con, iq_obj):
log.debug('SearchCB') log.debug('SearchCB')
app.nec.push_incoming_event(SearchFormReceivedEvent(None, app.nec.push_incoming_event(SearchFormReceivedEvent(None,
@ -2258,10 +2213,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
con.RegisterHandler('iq', self._HttpAuthCB, 'get', nbxmpp.NS_HTTP_AUTH) con.RegisterHandler('iq', self._HttpAuthCB, 'get', nbxmpp.NS_HTTP_AUTH)
con.RegisterHandler('iq', self._CommandExecuteCB, 'set', con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
nbxmpp.NS_COMMANDS) nbxmpp.NS_COMMANDS)
con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
nbxmpp.NS_GMAILNOTIFY)
con.RegisterHandler('iq', self._gMailQueryCB, 'result',
nbxmpp.NS_GMAILNOTIFY)
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
nbxmpp.NS_DISCO_INFO) nbxmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',

View file

@ -232,61 +232,6 @@ class TimeResultReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.time_info = t.astimezone(contact_tz()).strftime('%c') self.time_info = t.astimezone(contact_tz()).strftime('%c')
return True return True
class GMailQueryReceivedEvent(nec.NetworkIncomingEvent):
name = 'gmail-notify'
base_network_events = []
def generate(self):
if not self.stanza.getTag('mailbox'):
return
mb = self.stanza.getTag('mailbox')
if not mb.getAttr('url'):
return
self.conn.gmail_url = mb.getAttr('url')
if mb.getNamespace() != nbxmpp.NS_GMAILNOTIFY:
return
self.newmsgs = mb.getAttr('total-matched')
if not self.newmsgs:
return
if self.newmsgs == '0':
return
# there are new messages
self.gmail_messages_list = []
if mb.getTag('mail-thread-info'):
gmail_messages = mb.getTags('mail-thread-info')
for gmessage in gmail_messages:
unread_senders = []
for sender in gmessage.getTag('senders').getTags('sender'):
if sender.getAttr('unread') != '1':
continue
if sender.getAttr('name'):
unread_senders.append(sender.getAttr('name') + \
'< ' + sender.getAttr('address') + '>')
else:
unread_senders.append(sender.getAttr('address'))
if not unread_senders:
continue
gmail_subject = gmessage.getTag('subject').getData()
gmail_snippet = gmessage.getTag('snippet').getData()
tid = int(gmessage.getAttr('tid'))
if not self.conn.gmail_last_tid or \
tid > self.conn.gmail_last_tid:
self.conn.gmail_last_tid = tid
self.gmail_messages_list.append({
'From': unread_senders,
'Subject': gmail_subject,
'Snippet': gmail_snippet,
'url': gmessage.getAttr('url'),
'participation': gmessage.getAttr('participation'),
'messages': gmessage.getAttr('messages'),
'date': gmessage.getAttr('date')})
self.conn.gmail_last_time = int(mb.getAttr('result-time'))
self.jid = app.get_jid_from_account(self.name)
log.debug('You have %s new gmail e-mails on %s.', self.newmsgs, self.jid)
return True
class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent): class RosterItemExchangeEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'roster-item-exchange-received' name = 'roster-item-exchange-received'
base_network_events = [] base_network_events = []
@ -671,18 +616,6 @@ class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
self.errcode = self.stanza.getErrorCode() self.errcode = self.stanza.getErrorCode()
return True return True
class GmailNewMailReceivedEvent(nec.NetworkIncomingEvent):
name = 'gmail-new-mail-received'
base_network_events = []
def generate(self):
if not self.stanza.getTag('new-mail'):
return
if self.stanza.getTag('new-mail').getNamespace() != \
nbxmpp.NS_GMAILNOTIFY:
return
return True
class PingReceivedEvent(nec.NetworkIncomingEvent): class PingReceivedEvent(nec.NetworkIncomingEvent):
name = 'ping-received' name = 'ping-received'
base_network_events = [] base_network_events = []

View file

@ -1,3 +1,34 @@
from enum import IntEnum, unique
from collections import namedtuple
Option = namedtuple('Option', 'kind label type value name callback data desc enabledif props')
Option.__new__.__defaults__ = (None,) * len(Option._fields)
@unique
class OptionKind(IntEnum):
ENTRY = 0
SWITCH = 1
SPIN = 2
ACTION = 3
LOGIN = 4
DIALOG = 5
CALLBACK = 6
PROXY = 7
HOSTNAME = 8
PRIORITY = 9
FILECHOOSER = 10
CHANGEPASSWORD = 11
GPG = 12
@unique
class OptionType(IntEnum):
ACCOUNT_CONFIG = 0
CONFIG = 1
BOOL = 2
ACTION = 3
DIALOG = 4
THANKS = u"""\ THANKS = u"""\
Alexander Futász Alexander Futász
Alexander V. Butenko Alexander V. Butenko

View file

@ -76,54 +76,8 @@ def base28(n):
else: else:
return base28_chr[n] return base28_chr[n]
def add_entropy_sources_OpenSSL():
# Other possibly variable data. This are very low quality sources of
# entropy, but some of them are installation dependent and can be hard
# to guess for the attacker.
# Data available on all platforms Unix, Windows
sources = [sys.argv, sys.builtin_module_names,
sys.copyright, sys.getfilesystemencoding(), sys.hexversion,
sys.modules, sys.path, sys.version, sys.api_version,
os.environ, os.getcwd(), os.getpid()]
for s in sources:
OpenSSL.rand.add(str(s).encode('utf-8'), 1)
# On Windows add the current contents of the screen to the PRNG state.
# if os.name == 'nt':
# OpenSSL.rand.screen()
# The /proc filesystem on POSIX systems contains many random variables:
# memory statistics, interrupt counts, network packet counts
if os.name == 'posix':
dirs = ['/proc', '/proc/net', '/proc/self']
for d in dirs:
if os.access(d, os.R_OK):
for filename in os.listdir(d):
OpenSSL.rand.add(filename.encode('utf-8'), 0)
try:
with open(d + os.sep + filename, "r") as fp:
# Limit the ammount of read bytes, in case a memory
# file was opened
OpenSSL.rand.add(str(fp.read(5000)).encode('utf-8'),
1)
except:
# Ignore all read and access errors
pass
PYOPENSSL_PRNG_PRESENT = False
try:
import OpenSSL.rand
PYOPENSSL_PRNG_PRESENT = True
except ImportError:
# PyOpenSSL PRNG not available
pass
def random_bytes(bytes_): def random_bytes(bytes_):
if PYOPENSSL_PRNG_PRESENT: return os.urandom(bytes_)
OpenSSL.rand.add(os.urandom(bytes_), bytes_)
return OpenSSL.rand.bytes(bytes_)
else:
return os.urandom(bytes_)
def generate_nonce(): def generate_nonce():
return random_bytes(8) return random_bytes(8)

View file

@ -121,8 +121,10 @@ class JingleRTPContent(JingleContent):
InformationEvent( InformationEvent(
None, conn=self.session.connection, level='error', None, conn=self.session.connection, level='error',
pri_txt=_('%s configuration error') % text.capitalize(), pri_txt=_('%s configuration error') % text.capitalize(),
sec_txt=_('Couldnt setup %s. Check your configuration.\n\n' sec_txt=_('Couldnt setup %(text)s. Check your '
'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e)))) 'configuration.\n\nPipeline was:\n%(pipeline)s\n\n'
'Error was:\n%(error)s') % {'text': text,
'pipeline': pipeline, 'error': str(e)}))
raise JingleContentSetupException raise JingleContentSetupException
def add_remote_candidates(self, candidates): def add_remote_candidates(self, candidates):
@ -228,9 +230,9 @@ class JingleRTPContent(JingleContent):
InformationEvent( InformationEvent(
None, conn=self.session.connection, level='error', None, conn=self.session.connection, level='error',
pri_txt=_('GStreamer error'), pri_txt=_('GStreamer error'),
sec_txt=_('Error: %s\nDebug: %s' % sec_txt=_('Error: %(error)s\nDebug: %(debug)s' % {
(message.get_structure().get_value('gerror'), 'error': message.get_structure().get_value('gerror'),
message.get_structure().get_value('debug'))))) 'debug': message.get_structure().get_value('debug')})))
sink_pad = self.p2psession.get_property('sink-pad') sink_pad = self.p2psession.get_property('sink-pad')

View file

@ -27,6 +27,9 @@
import os import os
import logging import logging
import gi import gi
from gi.repository import GLib
from gajim.common import app from gajim.common import app
__all__ = ['get_password', 'save_password'] __all__ = ['get_password', 'save_password']
@ -78,10 +81,15 @@ class LibSecretPasswordStorage(PasswordStorage):
def save_password(self, account_name, password, update=True): def save_password(self, account_name, password, update=True):
server = app.config.get_per('accounts', account_name, 'hostname') server = app.config.get_per('accounts', account_name, 'hostname')
user = app.config.get_per('accounts', account_name, 'name') user = app.config.get_per('accounts', account_name, 'name')
display_name = _('XMPP account %s@%s') % (user, server) display_name = _('XMPP account %s') % user + '@' + server
attributes = {'user': user, 'server': server, 'protocol': 'xmpp'} attributes = {'user': user, 'server': server, 'protocol': 'xmpp'}
return self.Secret.password_store_sync(self.GAJIM_SCHEMA, attributes, try:
self.Secret.COLLECTION_DEFAULT, display_name, password or '', None) return self.Secret.password_store_sync(
self.GAJIM_SCHEMA, attributes, self.Secret.COLLECTION_DEFAULT,
display_name, password or '', None)
except GLib.Error as error:
log.error(error)
return False
class SecretWindowsPasswordStorage(PasswordStorage): class SecretWindowsPasswordStorage(PasswordStorage):

View file

@ -23,7 +23,7 @@ import functools
log = logging.getLogger('gajim.c.resolver') log = logging.getLogger('gajim.c.resolver')
if __name__ == '__main__': if __name__ == '__main__':
sys.path.append('..') sys.path.append('../..')
from gajim.common import i18n from gajim.common import i18n
from gajim.common import configpaths from gajim.common import configpaths
configpaths.gajimpaths.init(None) configpaths.gajimpaths.init(None)

File diff suppressed because it is too large Load diff

View file

@ -918,13 +918,12 @@ class ConversationTextview(GObject.GObject):
if special_text.startswith(scheme): if special_text.startswith(scheme):
text_is_valid_uri = True text_is_valid_uri = True
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
if iter_: if iter_:
end_iter = iter_ end_iter = iter_
else: else:
end_iter = buffer_.get_end_iter() end_iter = buffer_.get_end_iter()
pixbuf = emoticons.get_pixbuf(possible_emot_ascii_caps) pixbuf = emoticons.get_pixbuf(special_text)
if app.config.get('emoticons_theme') and pixbuf and graphics: if app.config.get('emoticons_theme') and pixbuf and graphics:
# it's an emoticon # it's an emoticon
anchor = buffer_.create_child_anchor(end_iter) anchor = buffer_.create_child_anchor(end_iter)

19
gajim/data/Makefile.am Normal file
View file

@ -0,0 +1,19 @@
SUBDIRS = gui emoticons iconsets moods activities
@INTLTOOL_DESKTOP_RULE@
soundsdir = $(gajim_srcdir)/data/sounds
sounds_DATA = $(srcdir)/sounds/*.wav
styledir = $(gajim_srcdir)/data/style
style_DATA = $(srcdir)/style/*.css
otherdir = $(gajim_srcdir)/data/other
other_DATA = other/servers.xml other/dh4096.pem
# other/cacert.pem is used only on Windows. On Unix platforms
# use CA certificates installed in /etc/ssl/certs
EXTRA_DIST = $(sounds_DATA) \
$(style_DATA) \
$(other_DATA)
MAINTAINERCLEANFILES = Makefile.in

View file

@ -1,4 +1,4 @@
activitiesdir = $(pkgdatadir)/data/activities activitiesdir = $(gajim_srcdir)/data/activities
nobase_dist_activities_DATA = $(srcdir)/*/*/* nobase_dist_activities_DATA = $(srcdir)/*/*/*
MAINTAINERCLEANFILES = Makefile.in MAINTAINERCLEANFILES = Makefile.in

View file

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 909 B

View file

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 854 B

View file

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 842 B

View file

Before

Width:  |  Height:  |  Size: 1,005 B

After

Width:  |  Height:  |  Size: 1,005 B

View file

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 957 B

View file

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 916 B

View file

Before

Width:  |  Height:  |  Size: 808 B

After

Width:  |  Height:  |  Size: 808 B

View file

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 749 B

View file

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 980 B

View file

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 891 B

View file

Before

Width:  |  Height:  |  Size: 706 B

After

Width:  |  Height:  |  Size: 706 B

View file

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

View file

Before

Width:  |  Height:  |  Size: 918 B

After

Width:  |  Height:  |  Size: 918 B

View file

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 712 B

View file

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 868 B

View file

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

View file

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View file

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 969 B

View file

Before

Width:  |  Height:  |  Size: 1,016 B

After

Width:  |  Height:  |  Size: 1,016 B

View file

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 912 B

View file

Before

Width:  |  Height:  |  Size: 901 B

After

Width:  |  Height:  |  Size: 901 B

View file

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 957 B

View file

Before

Width:  |  Height:  |  Size: 723 B

After

Width:  |  Height:  |  Size: 723 B

View file

Before

Width:  |  Height:  |  Size: 625 B

After

Width:  |  Height:  |  Size: 625 B

View file

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 854 B

View file

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 900 B

View file

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 665 B

View file

Before

Width:  |  Height:  |  Size: 1,013 B

After

Width:  |  Height:  |  Size: 1,013 B

View file

Before

Width:  |  Height:  |  Size: 759 B

After

Width:  |  Height:  |  Size: 759 B

View file

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 793 B

View file

Before

Width:  |  Height:  |  Size: 1,012 B

After

Width:  |  Height:  |  Size: 1,012 B

View file

Before

Width:  |  Height:  |  Size: 981 B

After

Width:  |  Height:  |  Size: 981 B

View file

Before

Width:  |  Height:  |  Size: 589 B

After

Width:  |  Height:  |  Size: 589 B

View file

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 907 B

View file

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 752 B

View file

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View file

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 882 B

View file

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 703 B

View file

Before

Width:  |  Height:  |  Size: 997 B

After

Width:  |  Height:  |  Size: 997 B

View file

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 967 B

View file

Before

Width:  |  Height:  |  Size: 839 B

After

Width:  |  Height:  |  Size: 839 B

View file

Before

Width:  |  Height:  |  Size: 719 B

After

Width:  |  Height:  |  Size: 719 B

View file

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 866 B

View file

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 793 B

View file

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 990 B

View file

Before

Width:  |  Height:  |  Size: 913 B

After

Width:  |  Height:  |  Size: 913 B

View file

Before

Width:  |  Height:  |  Size: 921 B

After

Width:  |  Height:  |  Size: 921 B

View file

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 964 B

View file

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View file

Before

Width:  |  Height:  |  Size: 956 B

After

Width:  |  Height:  |  Size: 956 B

View file

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 958 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View file

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

View file

Before

Width:  |  Height:  |  Size: 792 B

After

Width:  |  Height:  |  Size: 792 B

View file

Before

Width:  |  Height:  |  Size: 651 B

After

Width:  |  Height:  |  Size: 651 B

View file

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 865 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 868 B

View file

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 982 B

View file

Before

Width:  |  Height:  |  Size: 960 B

After

Width:  |  Height:  |  Size: 960 B

View file

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View file

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 897 B

View file

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 773 B

View file

Before

Width:  |  Height:  |  Size: 825 B

After

Width:  |  Height:  |  Size: 825 B

View file

Before

Width:  |  Height:  |  Size: 962 B

After

Width:  |  Height:  |  Size: 962 B

View file

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View file

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

Some files were not shown because too many files have changed in this diff Show more