Merge branch 'master' into 'gnotification'
# Conflicts: # gajim/gajim.py
|
@ -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 >> \
|
||||||
|
|
11
configure.ac
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
12
data/gajim.1
|
@ -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>.
|
||||||
|
|
|
@ -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
|
@ -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, '')
|
|
@ -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):
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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=_('Couldn’t setup %s. Check your configuration.\n\n'
|
sec_txt=_('Couldn’t 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')
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
1268
gajim/config.py
|
@ -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
|
@ -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
|
|
@ -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
|
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 909 B |
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 842 B |
Before Width: | Height: | Size: 1,005 B After Width: | Height: | Size: 1,005 B |
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 916 B |
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 808 B |
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 980 B |
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 906 B |
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 712 B After Width: | Height: | Size: 712 B |
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 906 B |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 969 B |
Before Width: | Height: | Size: 1,016 B After Width: | Height: | Size: 1,016 B |
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 912 B |
Before Width: | Height: | Size: 901 B After Width: | Height: | Size: 901 B |
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 723 B After Width: | Height: | Size: 723 B |
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 900 B After Width: | Height: | Size: 900 B |
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 846 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
Before Width: | Height: | Size: 1,013 B After Width: | Height: | Size: 1,013 B |
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 759 B |
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 689 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 1,012 B After Width: | Height: | Size: 1,012 B |
Before Width: | Height: | Size: 981 B After Width: | Height: | Size: 981 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 907 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 997 B After Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 967 B After Width: | Height: | Size: 967 B |
Before Width: | Height: | Size: 839 B After Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
Before Width: | Height: | Size: 866 B After Width: | Height: | Size: 866 B |
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 990 B After Width: | Height: | Size: 990 B |
Before Width: | Height: | Size: 913 B After Width: | Height: | Size: 913 B |
Before Width: | Height: | Size: 921 B After Width: | Height: | Size: 921 B |
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 964 B |
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 956 B After Width: | Height: | Size: 956 B |
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 958 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 955 B |
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
Before Width: | Height: | Size: 792 B After Width: | Height: | Size: 792 B |
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 865 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 868 B |
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 960 B After Width: | Height: | Size: 960 B |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 897 B After Width: | Height: | Size: 897 B |
Before Width: | Height: | Size: 773 B After Width: | Height: | Size: 773 B |
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 797 B |
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |