Commit cc215851 authored by Martin's avatar Martin

Merge tag 'upstream/1.1.0' into debian/master

parents 3c1cf770 a90f7405
[flake8]
ignore =
exclude = .git,__pycache__,.gitlab
max-complexity = 15
builtins=_
\ No newline at end of file
......@@ -24,6 +24,11 @@ run-pylint:
- pylint --version
- scripts/dev/pylint-ci.sh --jobs=2 gajim
run-appdata:
stage: test
script:
- appstream-util validate data/org.gajim.Gajim.appdata.xml.in
run-build:
stage: build
script:
......
Gajim 1.0.99.1 (13 October 2018)
Gajim 1.1.0 (06 November 2018)
New
* Remove support for XEP-0091
Bug fixes
* 8968 Windows: Gajim loads DLLs from wrong location
* 9322 Error when adding contact
* 9357 Acquire sleep inhibtor correctly after sleep
* 9385 Ignore invalid bookmarks
* 9386 Discovery: Browsing nodes without identity
* 9393 Error when parsing invalid timestamps
* 9398 Error on jingle file transfer
Gajim 1.0.99.1 (07 October 2018)
New
......
......@@ -11,7 +11,7 @@
- python3-openssl (>=0.14)
- python3-cssutils (>=1.0.2)
- python3-keyring
- python3-precis-i18n
### Optional Runtime Requirements
......@@ -25,7 +25,6 @@
- gir1.2-gupnpigd-1.0 for better NAT traversing
- gir1.2-networkmanager-1.0 for network lose detection
- gir1.2-geoclue-2.0 for sharing your location
- python3-idna and python3-precis-i18n for correctly parsing JIDs
### Compile-time Requirements
......
......@@ -18,6 +18,8 @@ clone_depth: 1
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
build_script:
- del C:\Windows\System32\libssl-*.dll C:\Windows\system32\libcrypto-*.dll
- del C:\Windows\SysWOW64\libssl-*.dll C:\Windows\SysWOW64\libcrypto-*.dll
- C:\msys64\usr\bin\pacman -Syuu --needed --noconfirm --noprogressbar --overwrite \\*
- ps: |
$env:TIME_STRING=(get-date -UFormat "%Y-%m-%d").ToString()
......
......@@ -69,54 +69,6 @@ modules:
url: https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz
sha256: e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
- name: python3-six
buildsystem: simple
build-commands:
- pip3 install --prefix=/app six-1.11.0-py2.py3-none-any.whl
sources:
- type: file
url: https://pypi.python.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
sha256: 832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb
- name: python3-pyparsing
buildsystem: simple
build-commands:
- pip3 install --prefix=/app pyparsing-2.2.2-py2.py3-none-any.whl
sources:
- type: file
url: https://files.pythonhosted.org/packages/2b/4a/f06b45ab9690d4c37641ec776f7ad691974f4cf6943a73267475b05cbfca/pyparsing-2.2.2-py2.py3-none-any.whl
sha256: d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401
- name: python3-packaging
buildsystem: simple
build-commands:
- pip3 install --prefix=/app packaging-18.0-py2.py3-none-any.whl
sources:
- type: file
url: https://files.pythonhosted.org/packages/89/d1/92e6df2e503a69df9faab187c684585f0136662c12bb1f36901d426f3fab/packaging-18.0-py2.py3-none-any.whl
sha256: f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9
- name: python3-appdirs
buildsystem: simple
build-commands:
- pip3 install --prefix=/app appdirs-1.4.3-py2.py3-none-any.whl
sources:
- type: file
url: https://pypi.python.org/packages/56/eb/810e700ed1349edde4cbdc1b2a21e28cdf115f9faf263f6bbf8447c1abf3/appdirs-1.4.3-py2.py3-none-any.whl
sha256: d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e
- name: python3-setuptools
ensure-writable:
- easy-install.pth
- setuptools.pth
buildsystem: simple
build-commands:
- python3 setup.py install --prefix=/app
sources:
- type: archive
url: https://files.pythonhosted.org/packages/6e/9c/6a003320b00ef237f94aa74e4ad66c57a7618f6c79d67527136e2544b728/setuptools-40.4.3.zip
sha256: acbc5740dd63f243f46c2b4b8e2c7fd92259c2ddb55a4115b16418a2ed371b15
- name: python3-asn1crypto
buildsystem: simple
build-commands:
......@@ -213,6 +165,15 @@ modules:
url: https://files.pythonhosted.org/packages/6b/15/a9fb9010f58d1c55dd0b7779db2334feb9a572d407024f39a60f44293861/cssutils-1.0.2-py3-none-any.whl
sha256: c74dbe19c92f5052774eadb15136263548dd013250f1ed1027988e7fef125c8d
- name: python3-precis_i18n
buildsystem: simple
build-commands:
- pip3 install --prefix=/app precis_i18n-1.0.0-py3-none-any.whl
sources:
- type: file
url: https://files.pythonhosted.org/packages/08/9d/d32f1b7c6d280c82a786b629ba89bd9e3e8409b9e23a7309f76a78e46971/precis_i18n-1.0.0-py3-none-any.whl
sha256: f6942bbffec698d0d7e30c42f589f3f6da13e723d047997d0761a41facf2986f
- name: gspell
cleanup:
- /bin
......@@ -220,14 +181,6 @@ modules:
- type: archive
url: https://download.gnome.org/sources/gspell/1.8/gspell-1.8.1.tar.xz
sha256: 819a1d23c7603000e73f5e738bdd284342e0cd345fb0c7650999c31ec741bbe5
modules:
- name: enchant2
cleanup:
- /bin
sources:
- type: archive
url: https://github.com/AbiWord/enchant/releases/download/v2.2.3/enchant-2.2.3.tar.gz
sha256: abd8e915675cff54c0d4da5029d95c528362266557c61c7149d53fa069b8076d
- name: python3-nbxmpp
buildsystem: simple
......
import os
import subprocess
__version__ = "1.0.99.1"
__version__ = "1.1.0"
IS_FLATPAK = False
if os.path.exists('/app/share/run-as-flatpak'):
......
......@@ -82,6 +82,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
except TypeError:
keycode_c = 54
except IndexError:
# FIXME
# On some keyboard layouts there is no keyval for KEY_c
keycode_c = None
try:
keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
except TypeError:
......
......@@ -145,6 +145,8 @@ ZEROCONF_ACC_NAME = 'Local'
idlequeue = None # type: nbxmpp.idlequeue.IdleQueue
socks5queue = None
gupnp_igd = None
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
nbxmpp.NS_MUC, nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC_ADMIN, nbxmpp.NS_MUC_OWNER,
......@@ -275,7 +277,8 @@ def detect_dependencies():
try:
gi.require_version('GUPnPIgd', '1.0')
from gi.repository import GUPnPIgd
GUPnPIgd.SimpleIgd()
global gupnp_igd
gupnp_igd = GUPnPIgd.SimpleIgd()
_dependencies['UPNP'] = True
except ValueError:
pass
......
......@@ -79,7 +79,7 @@ class Config:
'autopopupaway': [opt_bool, False],
'autopopup_chat_opened': [opt_bool, False, _('Show desktop notification even when a chat window is opened for this contact and does not have focus')],
'sounddnd': [opt_bool, False, _('Play sound when user is busy')],
'showoffline': [opt_bool, False],
'showoffline': [opt_bool, True],
'show_only_chat_and_online': [opt_bool, False, _('Show only online and free for chat contacts in roster.')],
'show_transports_group': [opt_bool, True],
'autoaway': [opt_bool, True],
......@@ -209,7 +209,6 @@ class Config:
'tabs_always_visible': [opt_bool, False, _('Show tab when only one conversation?')],
'tabs_border': [opt_bool, False, _('Show tabbed notebook border in chat windows?')],
'tabs_close_button': [opt_bool, True, _('Show close button in tab?')],
'esession_modp': [opt_str, '15,16,14', _('A list of modp groups to use in a Diffie-Hellman, highest preference first, separated by commas. Valid groups are 1, 2, 5, 14, 15, 16, 17 and 18. Higher numbers are more secure, but take longer to calculate when you start a session.')],
'tooltip_status_online_color': [opt_color, '#73D216'],
'tooltip_status_free_for_chat_color': [opt_color, '#3465A4'],
'tooltip_status_away_color': [opt_color, '#EDD400'],
......
......@@ -197,6 +197,7 @@ class ConfigPaths:
('SECRETS_FILE', 'secrets', PathLocation.DATA, PathType.FILE),
('MY_PEER_CERTS', 'certs', PathLocation.DATA, PathType.FOLDER),
('DEBUG', 'debug', PathLocation.DATA, PathType.FOLDER),
('PLUGINS_DATA', 'plugins_data', PathLocation.DATA, PathType.FOLDER),
# Config paths
('CONFIG_FILE', 'config', PathLocation.CONFIG, PathType.FILE),
......
......@@ -17,7 +17,6 @@
# pylint: disable=no-init
# pylint: disable=attribute-defined-outside-init
from calendar import timegm
import logging
from time import time as time_time
......@@ -30,6 +29,8 @@ from gajim.common import app
from gajim.common import i18n
from gajim.common.i18n import _
from gajim.common.modules import dataforms
from gajim.common.modules.misc import parse_idle
from gajim.common.modules.misc import parse_delay
from gajim.common.const import KindConstant, SSLError
from gajim.common.pep import SUPPORTED_PERSONAL_USER_EVENTS
from gajim.common.jingle_transport import JingleTransportSocks5
......@@ -87,18 +88,6 @@ class HelperEvent:
minimized = app.interface.minimized_controls[self.conn.name]
self.gc_control = minimized.get(self.jid)
def _generate_timestamp(self, tag):
# Make sure we use only int/float Epoch time
if tag is None:
self.timestamp = time_time()
return
try:
tim = helpers.datetime_tuple(tag)
self.timestamp = timegm(tim)
except Exception:
log.error('wrong timestamp, ignoring it: %s', tag)
self.timestamp = time_time()
def get_oob_data(self, stanza):
oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
if oob_node is not None:
......@@ -249,17 +238,14 @@ PresenceHelperEvent):
self.user_nick = self.stanza.getTagData('nick') or ''
self.contact_nickname = None
self.transport_auto_auth = False
# XEP-0203
delay_tag = self.stanza.getTag('delay', namespace=nbxmpp.NS_DELAY2)
if delay_tag:
self._generate_timestamp(self.stanza.timestamp)
self.timestamp = parse_delay(self.stanza)
if self.timestamp is None:
self.timestamp = time_time()
# XEP-0319
self.idle_time = None
idle_tag = self.stanza.getTag('idle', namespace=nbxmpp.NS_IDLE)
if idle_tag:
time_str = idle_tag.getAttr('since')
tim = helpers.datetime_tuple(time_str)
self.idle_time = timegm(tim)
self.idle_time = parse_idle(self.stanza)
xtags = self.stanza.getTags('x')
for x in xtags:
......@@ -268,9 +254,6 @@ PresenceHelperEvent):
self.is_gc = True
elif namespace == nbxmpp.NS_SIGNED:
sig_tag = x
elif namespace == nbxmpp.NS_DELAY and not self.timestamp:
# XEP-0091
self._generate_timestamp(self.stanza.timestamp)
self.status = self.stanza.getStatus() or ''
self._generate_show()
......
......@@ -14,17 +14,17 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
import logging
from datetime import datetime
from gi.repository import GLib
from gajim.common import app
import gi
gi.require_version('Geoclue', '2.0')
from gi.repository import Geoclue
from gi.repository import GLib
if app.is_installed('GEOCLUE'):
from gi.repository import Geoclue
log = logging.getLogger('gajim.c.location_listener')
log = logging.getLogger('gajim.c.dbus.location')
class LocationListener:
......@@ -102,5 +102,9 @@ class LocationListener:
def enable():
if not app.is_installed('GEOCLUE'):
log.warning('GeoClue not installed')
return
listener = LocationListener.get()
listener.start()
# Copyright (C) 2014 Kamil Paral <kamil.paral AT gmail.com>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
'''
Watch for system sleep using systemd-logind.
Documentation: http://www.freedesktop.org/wiki/Software/systemd/inhibit
'''
import os
import logging
from gi.repository import Gio
from gi.repository import GLib
from gajim.common import app
from gajim.common.i18n import _
log = logging.getLogger('gajim.c.dbus.logind')
class LogindListener:
_instance = None
@classmethod
def get(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
# file descriptor object of the inhibitor
self._inhibit_fd = None
Gio.bus_watch_name(
Gio.BusType.SYSTEM,
'org.freedesktop.login1',
Gio.BusNameWatcherFlags.NONE,
self._on_appear_logind,
self._on_vanish_logind)
def _on_prepare_for_sleep(self, connection, _sender_name, _object_path,
interface_name, signal_name, parameters,
*_user_data):
'''Signal handler for PrepareForSleep event'''
log.debug('Received signal %s.%s%s', interface_name, signal_name, parameters)
before = parameters[0] # Signal is either before or after sleep occurs
if before:
warn = self._inhibit_fd is None
log.log(
logging.WARNING if warn else logging.INFO,
'Preparing for sleep by disconnecting from network%s',
', without holding a sleep inhibitor' if warn else '')
for name, conn in app.connections.items():
if app.account_is_connected(name):
conn.old_show = app.SHOW_LIST[conn.connected]
st = conn.status
conn.change_status('offline', _('Machine is going to sleep'))
conn.status = st
conn.time_to_reconnect = 5
self._disinhibit_sleep()
else:
self._inhibit_sleep(connection)
for conn in app.connections.values():
if conn.connected <= 0 and conn.time_to_reconnect:
conn.reconnect()
def _inhibit_sleep(self, connection):
'''Obtain a sleep delay inhibitor from logind'''
if self._inhibit_fd is not None:
# Something is wrong, we have an inhibitor fd, and we are asking for
# yet another one.
log.warning('Trying to obtain a sleep inhibitor '
'while already holding one.')
ret, ret_fdlist = connection.call_with_unix_fd_list_sync(
'org.freedesktop.login1',
'/org/freedesktop/login1',
'org.freedesktop.login1.Manager',
'Inhibit',
GLib.Variant('(ssss)', (
'sleep', 'org.gajim.Gajim', _('Disconnect from the network'),
'delay' # Inhibitor will delay but not block sleep
)),
GLib.VariantType.new('(h)'),
Gio.DBusCallFlags.NONE, -1, None, None)
log.info('Inhibit sleep')
self._inhibit_fd = ret_fdlist.get(ret.unpack()[0])
def _disinhibit_sleep(self):
'''Relinquish our sleep delay inhibitor'''
if self._inhibit_fd is not None:
os.close(self._inhibit_fd)
self._inhibit_fd = None
log.info('Disinhibit sleep')
def _on_appear_logind(self, connection, name, name_owner, *_user_data):
'''Use signal and locks provided by org.freedesktop.login1'''
log.info('Name %s appeared, owned by %s', name, name_owner)
connection.signal_subscribe(
'org.freedesktop.login1',
'org.freedesktop.login1.Manager',
'PrepareForSleep',
'/org/freedesktop/login1',
None,
Gio.DBusSignalFlags.NONE,
self._on_prepare_for_sleep,
None)
self._inhibit_sleep(connection)
def _on_vanish_logind(self, _connection, name, *_user_data):
'''Release remaining resources related to org.freedesktop.login1'''
log.info('Name %s vanished', name)
self._disinhibit_sleep()
def enable():
LogindListener.get()
......@@ -24,7 +24,7 @@ import logging
from gi.repository import GObject
from gi.repository import Gio, GLib
log = logging.getLogger('gajim.music_track_listener')
log = logging.getLogger('gajim.c.dbus.music_track')
MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.'
......
# Copyright (C) 2018 André Apitzsch <git AT apitzsch.eu>
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import logging
from gi.repository import Gio
from gajim.common import app
log = logging.getLogger('gajim.c.dbus.screensaver')
class ScreensaverListener:
_instance = None
@classmethod
def get(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
Gio.bus_watch_name(
Gio.BusType.SESSION,
'org.gnome.ScreenSaver',
Gio.BusNameWatcherFlags.NONE,
self._appeared,
None)
@staticmethod
def _signal_received(_connection, _sender_name, _object_path,
interface_name, _signal_name, parameters, *_user_data):
'''Signal handler for screensaver active change'''
log.info('Signal received: %s - %s', interface_name, parameters)
roster = app.interface.roster
if not parameters[0]:
for account in app.connections:
if app.account_is_connected(account) and \
app.sleeper_state[account] == 'autoaway-forced':
# We came back online after screensaver
# autoaway
roster.send_status(account, 'online',
app.status_before_autoaway[account])
app.status_before_autoaway[account] = ''
app.sleeper_state[account] = 'online'
return
if not app.config.get('autoaway'):
# Don't go auto away if user disabled the option
return
for account in app.connections:
if account not in app.sleeper_state or not app.sleeper_state[account]:
continue
if app.sleeper_state[account] == 'online':
if not app.account_is_connected(account):
continue
# we save our online status
app.status_before_autoaway[account] = \
app.connections[account].status
# we go away (no auto status) [we pass True to auto param]
auto_message = app.config.get('autoaway_message')
if not auto_message:
auto_message = app.connections[account].status
else:
auto_message = auto_message.replace('$S', '%(status)s')
auto_message = auto_message.replace('$T', '%(time)s')
auto_message = auto_message % {
'status': app.status_before_autoaway[account],
'time': app.config.get('autoxatime')}
roster.send_status(account, 'away', auto_message, auto=True)
app.sleeper_state[account] = 'autoaway-forced'
def _appeared(self, connection, name, _name_owner, *_user_data):
'''Set up a listener for screensaver signals'''
log.info('%s appeared', name)
connection.signal_subscribe(
'org.gnome.ScreenSaver',
'org.gnome.ScreenSaver',
'ActiveChanged',
'/org/gnome/ScreenSaver',
None,
Gio.DBusSignalFlags.NONE,
self._signal_received,
None)
def enable():
ScreensaverListener.get()
......@@ -39,6 +39,7 @@ import shlex
import socket
import time
import logging
import json
from datetime import datetime, timedelta
from distutils.version import LooseVersion as V
from encodings.punycode import punycode_encode
......@@ -1492,3 +1493,15 @@ def get_sync_threshold(jid, archive_info):
app.logger.set_archive_infos(jid, sync_threshold=threshold)
return threshold
return archive_info.sync_threshold
def load_json(path, key=None, default=None):
try:
with open(path, 'r') as file:
json_dict = json.loads(file.read())
except Exception:
log.exception('Parsing error')
return default
if key is None:
return json_dict
return json_dict.get(key, default)
......@@ -166,6 +166,7 @@ class Bookmarks:
autojoin_val = conf.getAttr('autojoin')
if not autojoin_val: # not there (it's optional)
autojoin_val = False
minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM)
if not minimize_val: # not there, try old Gajim behaviour
minimize_val = conf.getAttr('minimize')
......@@ -189,19 +190,27 @@ class Bookmarks:
conf.getAttr('jid'))
continue
bookmark = {
'name': conf.getAttr('name'),
'password': conf.getTagData('password'),
'nick': conf.getTagData('nick'),
'print_status': print_status
}
try:
bookmark['autojoin'] = from_xs_boolean(autojoin_val)
bookmark['minimize'] = from_xs_boolean(minimize_val)
except ValueError as error:
log.warning(error)
continue
if check_merge:
if jid in self.bookmarks:
continue
merged = True
log.debug('Found Bookmark: %s', jid)
self.bookmarks[jid] = {
'name': conf.getAttr('name'),
'autojoin': from_xs_boolean(autojoin_val),
'minimize': from_xs_boolean(minimize_val),
'password': conf.getTagData('password'),
'nick': conf.getTagData('nick'),
'print_status': print_status}
self.bookmarks[jid] = bookmark
return merged
......
......@@ -163,6 +163,7 @@ class Message:
# TODO: why is this here?
if stanza.getTag('html'):
stanza.delChild('html')
type_ = 'groupchat'
session = None
if type_ != 'groupchat':
......
......@@ -139,3 +139,24 @@ def parse_xhtml(stanza):
if app.config.get('ignore_incoming_xhtml'):
return None
return stanza.getXHTML()
# XEP-0319: Last User Interaction in Presence
def parse_idle(stanza):
idle_tag = stanza.getTag('idle', namespace=nbxmpp.NS_IDLE)
if idle_tag is None:
return
since = idle_tag.getAttr('since')
if since is None:
log.warning('No since attr in idle node')
log.warning(stanza)
return
timestamp = parse_datetime(since, convert='utc', epoch=True)
if timestamp is None:
log.warning('Invalid timestamp received: %s', since)
log.warning(stanza)
return timestamp
......@@ -59,26 +59,26 @@ class MUC:
presence = self._con.get_module('Presence').get_presence(
*args, **kwargs)
muc_x = presence.setTag(nbxmpp.NS_MUC + ' x')
if room_jid is not None:
self._add_history_query(presence, room_jid, rejoin)
self._add_history_query(muc_x, room_jid, rejoin)
if password is not None:
presence.setTagData('password', password)
muc_x.setTagData('password', password)
log.debug('Send MUC join presence:\n%s', presence)
self._con.connection.send(presence)
def _add_history_query(self, presence, room_jid, rejoin):
def _add_history_query(self, muc_x, room_jid, rejoin):
last_date = app.logger.get_room_last_message_time(
self._account, room_jid)
if not last_date:
last_date = 0
history = presence.setTag(nbxmpp.NS_MUC + ' x')
if muc_caps_cache.has_mam(room_jid):
# The room is MAM capable dont get MUC History