diff options
37 files changed, 40 insertions, 3726 deletions
@@ -21,7 +21,6 @@ ### Optional Runtime Requirements - python3-pil (pillow) for support of webp avatars -- gir1.2-avahi-0.6 for zeroconf on Linux or [pybonjour](https://dev.gajim.org/lovetox/pybonjour-python3) on Windows/macOS - gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc - gir1.2-secret-1 for GNOME Keyring or KDE support as password storage - D-Bus running to have gajim-remote working diff --git a/data/org.gajim.Gajim.appdata.xml.in b/data/org.gajim.Gajim.appdata.xml.in index 7e9cd255e..480415620 100644 --- a/data/org.gajim.Gajim.appdata.xml.in +++ b/data/org.gajim.Gajim.appdata.xml.in @@ -30,7 +30,6 @@ <li>Support for multiple accounts</li> <li>Group multiple contacts from one friend to a single Meta-Contact</li> <li>XML console to see what's happening on the protocol layer</li> - <li>Serverless messaging (Bonjour/Zeroconf)</li> <li>Support for service discovery including nodes and search for users</li> <li>Even more features via plugins</li> </ul> diff --git a/data/org.gajim.Gajim.desktop.in b/data/org.gajim.Gajim.desktop.in index c68cc657b..a718cf977 100644 --- a/data/org.gajim.Gajim.desktop.in +++ b/data/org.gajim.Gajim.desktop.in @@ -4,7 +4,7 @@ Name=Gajim GenericName=XMPP Chat Client Comment=A fully-featured XMPP chat client #Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! -Keywords=chat;messaging;im;xmpp;bonjour;voip; +Keywords=chat;messaging;im;xmpp;voip; Exec=gajim %u #Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=org.gajim.Gajim diff --git a/gajim/command_system/implementation/standard.py b/gajim/command_system/implementation/standard.py index bd4c28dd3..e20064e45 100644 --- a/gajim/command_system/implementation/standard.py +++ b/gajim/command_system/implementation/standard.py @@ -196,9 +196,6 @@ class StandardCommonChatCommands(CommandContainer): @command @doc(_("Send a ping to the contact")) def ping(self): - if self.account == app.ZEROCONF_ACC_NAME: - raise CommandError( - _('Command is not supported for zeroconf accounts')) app.connections[self.account].get_module('Ping').send_ping(self.contact) @command @@ -451,10 +448,6 @@ class StandardGroupChatCommands(CommandContainer): @command @doc(_("Send a ping to the contact")) def ping(self, nick): - if self.account == app.ZEROCONF_ACC_NAME: - raise CommandError( - _('Command is not supported for zeroconf accounts')) - client = app.get_client(self.account) groupchat_contact = client.get_module('Contacts').get_contact( self.room_jid, groupchat=True) diff --git a/gajim/common/app.py b/gajim/common/app.py index 88a6e5c14..146c42cf0 100644 --- a/gajim/common/app.py +++ b/gajim/common/app.py @@ -117,9 +117,6 @@ cert_store = cast('CertificateStore', None) task_manager = None -# zeroconf account name -ZEROCONF_ACC_NAME = 'Local' - # These will be set in app.gui_interface. idlequeue = cast(IdleQueue, None) socks5queue = None @@ -129,8 +126,6 @@ gupnp_igd = None gsound_ctx = None _dependencies = { - 'AVAHI': False, - 'PYBONJOUR': False, 'FARSTREAM': False, 'GST': False, 'AV': False, @@ -161,9 +156,6 @@ def get_client(account: str) -> types.Client: def is_installed(dependency: str) -> bool: - if dependency == 'ZEROCONF': - # Alias for checking zeroconf libs - return _dependencies['AVAHI'] or _dependencies['PYBONJOUR'] return _dependencies[dependency] @@ -195,20 +187,6 @@ def disable_dependency(dependency: str) -> None: def detect_dependencies() -> None: import gi - # ZEROCONF - try: - import pybonjour # pylint: disable=unused-import - _dependencies['PYBONJOUR'] = True - except Exception: - pass - - try: - gi.require_version('Avahi', '0.6') - from gi.repository import Avahi # pylint: disable=unused-import - _dependencies['AVAHI'] = True - except Exception: - pass - try: gi.require_version('Gst', '1.0') gi.require_version('GstPbutils', '1.0') @@ -397,14 +375,10 @@ def get_accounts_sorted() -> list[str]: ''' account_list = settings.get_accounts() account_list.sort(key=str.lower) - if 'Local' in account_list: - account_list.remove('Local') - account_list.insert(0, 'Local') return account_list def get_enabled_accounts_with_labels( - exclude_local: bool = True, connected_only: bool = False, private_storage_only: bool = False) -> list[list[str]]: """ @@ -413,8 +387,6 @@ def get_enabled_accounts_with_labels( """ accounts: list[list[str]] = [] for acc in connections: - if exclude_local and account_is_zeroconf(acc): - continue if connected_only and not account_is_connected(acc): continue if private_storage_only and not account_supports_private_storage(acc): @@ -430,10 +402,6 @@ def get_account_label(account: str) -> str: return settings.get_account_setting(account, 'account_label') or account -def account_is_zeroconf(account: str) -> bool: - return connections[account].is_zeroconf - - def account_supports_private_storage(account: str) -> bool: # If Delimiter module is not available we can assume # Private Storage is not available @@ -457,11 +425,6 @@ def account_is_disconnected(account: str) -> bool: return not account_is_connected(account) -def zeroconf_is_connected() -> bool: - return account_is_connected(ZEROCONF_ACC_NAME) and \ - settings.get_account_setting(ZEROCONF_ACC_NAME, 'is_zeroconf') - - def get_transport_name_from_jid( jid: str, use_config_setting: bool = True) -> Optional[str]: diff --git a/gajim/common/client.py b/gajim/common/client.py index 5a484c963..1032c399c 100644 --- a/gajim/common/client.py +++ b/gajim/common/client.py @@ -81,7 +81,6 @@ class Client(Observable): self._connect_machine_calls = 0 self.addressing_supported = False - self.is_zeroconf = False self.pep = {} self.roster_supported = True diff --git a/gajim/common/events.py b/gajim/common/events.py index 56a09f273..be0d8a665 100644 --- a/gajim/common/events.py +++ b/gajim/common/events.py @@ -583,13 +583,6 @@ class SecCatalogReceived(ApplicationEvent): @dataclass -class RawPresenceReceived(ApplicationEvent): - name: str = field(init=False, default='raw-pres-received') - conn: 'Client' - stanza: Any - - -@dataclass class PresenceReceived(ApplicationEvent): name: str = field(init=False, default='presence-received') account: str diff --git a/gajim/common/modules/bytestream.py b/gajim/common/modules/bytestream.py index a7ea908a4..09634c65b 100644 --- a/gajim/common/modules/bytestream.py +++ b/gajim/common/modules/bytestream.py @@ -114,19 +114,16 @@ class Bytestream(BaseModule): default=self._account, testit=testit) raise nbxmpp.NodeProcessed - def _ft_get_receiver_jid(self, file_props): - if self._account == 'Local': - return file_props.receiver.jid + @staticmethod + def _ft_get_receiver_jid(file_props): return file_props.receiver.jid + '/' + file_props.receiver.resource - def _ft_get_from(self, iq_obj): - if self._account == 'Local': - return iq_obj.getFrom() + @staticmethod + def _ft_get_from(iq_obj): return helpers.get_full_jid_from_iq(iq_obj) - def _ft_get_streamhost_jid_attr(self, streamhost): - if self._account == 'Local': - return streamhost.getAttr('jid') + @staticmethod + def _ft_get_streamhost_jid_attr(streamhost): return helpers.parse_jid(streamhost.getAttr('jid')) def send_file_approval(self, file_props): diff --git a/gajim/common/modules/presence.py b/gajim/common/modules/presence.py index f23bc8f1f..1d8e97628 100644 --- a/gajim/common/modules/presence.py +++ b/gajim/common/modules/presence.py @@ -24,7 +24,6 @@ from nbxmpp.const import PresenceType from gajim.common import app from gajim.common import idle from gajim.common.events import PresenceReceived -from gajim.common.events import RawPresenceReceived from gajim.common.events import ShowChanged from gajim.common.events import SubscribePresenceReceived from gajim.common.events import SubscribedPresenceReceived @@ -101,12 +100,6 @@ class Presence(BaseModule): self._log.info('Error: %s %s', properties.jid, properties.error) return - if self._account == 'Local': - app.ged.raise_event( - RawPresenceReceived(conn=self._con, - stanza=stanza)) - return - if properties.is_self_presence: app.ged.raise_event(ShowChanged(account=self._account, show=properties.show.value)) diff --git a/gajim/common/settings.py b/gajim/common/settings.py index f2320d8e6..26eb0ddc7 100644 --- a/gajim/common/settings.py +++ b/gajim/common/settings.py @@ -310,6 +310,9 @@ class Settings: group_chat_settings) for account, settings in account_settings.items(): + if account == 'Local': + # Zeroconf support was dropped so don’t migrate the account + continue self.add_account(account) self._account_settings[account]['account'] = settings self._account_settings[account]['contact'] = contact_settings diff --git a/gajim/common/types.py b/gajim/common/types.py index 0f39629ea..6a10bef11 100644 --- a/gajim/common/types.py +++ b/gajim/common/types.py @@ -35,7 +35,6 @@ if TYPE_CHECKING: # pylint: disable=unused-import from gajim.common.client import Client from nbxmpp.client import Client as xmppClient - from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf from gajim.common.modules.contacts import CommonContact from gajim.common.modules.contacts import BareContact from gajim.common.modules.contacts import ResourceContact @@ -49,7 +48,7 @@ if TYPE_CHECKING: InterfaceT = Union['Interface'] -ConnectionT = Union['Client', 'ConnectionZeroconf'] +ConnectionT = Union['Client'] CSSConfigT = Union['CSSConfig'] # PEP diff --git a/gajim/common/zeroconf/__init__.py b/gajim/common/zeroconf/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/gajim/common/zeroconf/__init__.py +++ /dev/null diff --git a/gajim/common/zeroconf/client_zeroconf.py b/gajim/common/zeroconf/client_zeroconf.py deleted file mode 100644 index 35231236d..000000000 --- a/gajim/common/zeroconf/client_zeroconf.py +++ /dev/null @@ -1,857 +0,0 @@ -# Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> -# 2006 Dimitur Kirov <dkirov@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/>. - -import socket -import ssl -import errno -import sys -import os -import logging -from unittest.mock import Mock - -import nbxmpp -from nbxmpp import old_dispatcher as dispatcher -from nbxmpp import simplexml -from nbxmpp.namespaces import Namespace -from nbxmpp.structs import StanzaHandler -from nbxmpp.plugin import PlugIn -from nbxmpp.idlequeue import IdleObject -from nbxmpp.util import generate_id - -from gajim.common import app -from gajim.common.zeroconf import zeroconf -from gajim.common.zeroconf import roster_zeroconf - -log = logging.getLogger('gajim.c.z.client_zeroconf') - - -MAX_BUFF_LEN = 65536 -TYPE_SERVER, TYPE_CLIENT = range(2) - -# wait XX sec to establish a connection -CONNECT_TIMEOUT_SECONDS = 10 - -# after XX sec with no activity, close the stream -ACTIVITY_TIMEOUT_SECONDS = 30 - -class ZeroconfListener(IdleObject): - def __init__(self, port, conn_holder): - """ - Handle all incoming connections on ('0.0.0.0', port) - """ - self.port = port - self.queue_idx = -1 - #~ self.queue = None - self.started = False - self._sock = None - self.fd = -1 - self.caller = conn_holder.caller - self.conn_holder = conn_holder - - def bind(self): - flags = socket.AI_PASSIVE - if hasattr(socket, 'AI_ADDRCONFIG'): - flags |= socket.AI_ADDRCONFIG - ai = socket.getaddrinfo(None, self.port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, flags)[0] - self._serv = socket.socket(ai[0], ai[1]) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._serv.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self._serv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - if os.name == 'nt': - if sys.getwindowsversion().major >= 6: # Win Vista + - # 47 is socket.IPPROTO_IPV6 - # 27 is socket.IPV6_V6ONLY under windows, but not defined ... - self._serv.setsockopt(41, 27, 0) - # will fail when port is busy, or we don't have rights to bind - try: - self._serv.bind((ai[4][0], self.port)) - except Exception: - # unable to bind, show error dialog - return None - self._serv.listen(socket.SOMAXCONN) - self._serv.setblocking(False) - self.fd = self._serv.fileno() - app.idlequeue.plug_idle(self, False, True) - self.started = True - - def pollend(self): - """ - Called when we stop listening on (host, port) - """ - self.disconnect() - - def pollin(self): - """ - Accept a new incoming connection and notify queue - """ - sock = self.accept_conn() - # loop through roster to find who has connected to us - from_jid = None - ipaddr = sock[1][0] - for jid in self.conn_holder.getRoster().keys(): - entry = self.conn_holder.getRoster().getItem(jid) - for address in entry['addresses']: - if address['address'] == ipaddr: - from_jid = jid - break - P2PClient(sock[0], [{'host': ipaddr, 'address': ipaddr, 'port': sock[1][1]}], self.conn_holder, [], from_jid) - - def disconnect(self, message=''): - """ - Free all resources, we are not listening anymore - """ - log.info('Disconnecting ZeroconfListener: %s', message) - app.idlequeue.remove_timeout(self.fd) - app.idlequeue.unplug_idle(self.fd) - self.fd = -1 - self.started = False - try: - self._serv.close() - except socket.error: - pass - self.conn_holder.kill_all_connections() - - def accept_conn(self): - """ - Accept a new incoming connection - """ - _sock = self._serv.accept() - _sock[0].setblocking(False) - return _sock - -class P2PClient(IdleObject): - def __init__(self, _sock, addresses, conn_holder, stanzaqueue, to=None, - on_ok=None, on_not_ok=None): - self._owner = self - self.Namespace = 'jabber:client' - self.protocol_type = 'XMPP' - self.defaultNamespace = self.Namespace - self.Smacks = Mock() - self._component = 0 - self._registered_name = None - self._caller = conn_holder.caller - self.conn_holder = conn_holder - self.stanzaqueue = stanzaqueue - self.to = to - #self.Server = addresses[0]['host'] - self.on_ok = on_ok - self.on_not_ok = on_not_ok - self.Connection = None - self.sock_hash = None - if _sock: - self.sock_type = TYPE_SERVER - else: - self.sock_type = TYPE_CLIENT - self.fd = -1 - conn = P2PConnection('', _sock, addresses, self._caller, - self.on_connect, self) - self.Server = conn.host # set Server to the last host name / address tried - if not self.conn_holder: - # An error occurred, disconnect() has been called - if on_not_ok: - on_not_ok('Connection to host could not be established.') - return - self.sock_hash = conn._sock.__hash__ - self.fd = conn.fd - self.conn_holder.add_connection(self, self.Server, conn.port, self.to) - # count messages in queue - for val in self.stanzaqueue: - stanza, is_message = val - if is_message: - if self.fd == -1: - if on_not_ok: - on_not_ok( - 'Connection to host could not be established.') - return - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = generate_id() - if self.fd in self.conn_holder.ids_of_awaiting_messages: - self.conn_holder.ids_of_awaiting_messages[self.fd].append(( - id_, thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] - - self.on_responses = {} - - def get_bound_jid(self): - return self._caller.get_own_jid() - - def add_stanza(self, stanza, is_message=False): - if self.Connection: - if self.Connection.state == -1: - return False - self.send(stanza, is_message) - else: - self.stanzaqueue.append((stanza, is_message)) - - if is_message: - thread_id = stanza.getThread() - id_ = stanza.getID() - if not id_: - id_ = generate_id() - if self.fd in self.conn_holder.ids_of_awaiting_messages: - self.conn_holder.ids_of_awaiting_messages[self.fd].append((id_, - thread_id)) - else: - self.conn_holder.ids_of_awaiting_messages[self.fd] = [(id_, - thread_id)] - - return True - - def on_message_sent(self, connection_id): - id_, _thread_id = \ - self.conn_holder.ids_of_awaiting_messages[connection_id].pop(0) - if self.on_ok: - self.on_ok(id_) - # use on_ok only on first message. For others it's called in - # ClientZeroconf - self.on_ok = None - - def on_connect(self, conn): - self.Connection = conn - self.Connection.PlugIn(self) - dispatcher.Dispatcher().PlugIn(self) - self._register_handlers() - - def StreamInit(self): - """ - Send an initial stream header - """ - self.Dispatcher.Stream = simplexml.NodeBuilder() - self.Dispatcher.Stream._dispatch_depth = 2 - self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch - self.Dispatcher.Stream.stream_header_received = self._check_stream_start - self.Dispatcher.Stream.features = None - if self.sock_type == TYPE_CLIENT: - self.send_stream_header() - - def send_stream_header(self): - self.Dispatcher._metastream = nbxmpp.Node('stream:stream') - self.Dispatcher._metastream.setNamespace(self.Namespace) - self.Dispatcher._metastream.setAttr('version', '1.0') - self.Dispatcher._metastream.setAttr('xmlns:stream', Namespace.STREAMS) - self.Dispatcher._metastream.setAttr('from', - self.conn_holder.zeroconf.name) - if self.to: - self.Dispatcher._metastream.setAttr('to', self.to) - self.Dispatcher.send("<?xml version='1.0'?>%s>" % str( - self.Dispatcher._metastream)[:-2]) - - def _check_stream_start(self, ns, tag, attrs): - if ns != Namespace.STREAMS or tag != 'stream': - log.error('Incorrect stream start: (%s,%s).Terminating!', - tag, ns) - self.Connection.disconnect() - if self.on_not_ok: - self.on_not_ok('Connection to host could not be established: ' - 'Incorrect answer from server.') - return - if self.sock_type == TYPE_SERVER: - if 'from' in attrs: - self.to = attrs['from'] - self.send_stream_header() - if 'version' in attrs and attrs['version'] == '1.0': - # other part supports stream features - features = nbxmpp.Node('stream:features') - self.Dispatcher.send(features) - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) - elif self.sock_type == TYPE_CLIENT: - while self.stanzaqueue: - stanza, is_message = self.stanzaqueue.pop(0) - self.send(stanza, is_message) - - def on_disconnect(self): - if self.conn_holder: - if self.fd in self.conn_holder.ids_of_awaiting_messages: - del self.conn_holder.ids_of_awaiting_messages[self.fd] - self.conn_holder.remove_connection(self.sock_hash) - if 'Dispatcher' in self.__dict__: - self._caller._unregister_new_handlers(self) - self.Dispatcher.PlugOut() - if 'P2PConnection' in self.__dict__: - self.P2PConnection.PlugOut() - self.Connection = None - self._caller = None - self.conn_holder = None - - def force_disconnect(self): - if self.Connection: - self.disconnect() - else: - self.on_disconnect() - - def _on_receive_document_attrs(self, data): - if data: - self.Dispatcher.ProcessNonBlocking(data) - if not hasattr(self, 'Dispatcher') or \ - self.Dispatcher.Stream._document_attrs is None: - return - self.onreceive(None) - if 'version' in self.Dispatcher.Stream._document_attrs and \ - self.Dispatcher.Stream._document_attrs['version'] == '1.0': - #~ self.onreceive(self._on_receive_stream_features) - #XXX continue with TLS - return - self.onreceive(None) - return True - - def remove_timeout(self): - pass - - def _register_handlers(self): - self._caller.peerhost = self.Connection._sock.getsockname() - - self.RegisterHandler(*StanzaHandler(name='message', - callback=self._caller._messageCB)) - self.RegisterHandler(*StanzaHandler(name='message', - typ='error', - callback=self._caller._message_error_received)) - - self._caller._register_new_handlers(self) - - -class P2PConnection(IdleObject, PlugIn): - def __init__(self, sock_hash, _sock, addresses=None, caller=None, - on_connect=None, client=None): - IdleObject.__init__(self) - self._owner = client - PlugIn.__init__(self) - self.sendqueue = [] - self.sendbuff = None - self.buff_is_message = False - self._sock = _sock - self.sock_hash = None - self.addresses = addresses - self.on_connect = on_connect - self.client = client - self.writable = False - self.readable = False - self._exported_methods = [self.send, self.disconnect, self.onreceive] - self.on_receive = None - if _sock: - self.host = addresses[0]['host'] - self.port = addresses[0]['port'] - self._sock = _sock - self.state = 1 - self._sock.setblocking(False) - self.fd = self._sock.fileno() - self.on_connect(self) - else: - self.state = 0 - self.addresses_ = self.addresses - self.get_next_addrinfo() - - def get_next_addrinfo(self): - address = self.addresses_.pop(0) - self.host = address['host'] - self.port = address['port'] - try: - self.ais = socket.getaddrinfo(address['host'], address['port'], socket.AF_UNSPEC, - socket.SOCK_STREAM) - except socket.gaierror as e: - log.info('Lookup failure for %s: %s[%s]', self.host, e[1], - repr(e[0]), exc_info=True) - if self.addresses_: - return self.get_next_addrinfo() - else: - self.connect_to_next_ip() - - def connect_to_next_ip(self): - if not self.ais: - log.error('Connection failure to %s', str(self.host), exc_info=True) - if self.addresses_: - return self.get_next_addrinfo() - self.disconnect() - return - ai = self.ais.pop(0) - log.info('Trying to connect to %s through %s:%s', - str(self.host), ai[4][0], ai[4][1], exc_info=True) - try: - self._sock = socket.socket(*ai[:3]) - self._sock.setblocking(False) - self._server = ai[4] - except socket.error: - if sys.exc_value[0] != errno.EINPROGRESS: - # for all errors, we try other addresses - self.connect_to_next_ip() - return - self.fd = self._sock.fileno() - app.idlequeue.plug_idle(self, True, False) - self.set_timeout(CONNECT_TIMEOUT_SECONDS) - self.do_connect() - - def set_timeout(self, timeout): - app.idlequeue.remove_timeout(self.fd) - if self.state >= 0: - app.idlequeue.set_read_timeout(self.fd, timeout) - - def plugin(self, owner): - self.onreceive(owner._on_receive_document_attrs) - self._plug_idle() - return True - - def plugout(self): - """ - Disconnect from the remote server and unregister self.disconnected - method from the owner's dispatcher - """ - self.disconnect() - self._owner = None - - def onreceive(self, recv_handler): - if not recv_handler: - if hasattr(self._owner, 'Dispatcher'): - self.on_receive = self._owner.Dispatcher.ProcessNonBlocking - else: - self.on_receive = None - return - _tmp = self.on_receive - # make sure this cb is not overridden by recursive calls - if not recv_handler(None) and _tmp == self.on_receive: - self.on_receive = recv_handler - - def send(self, packet, is_message=False, now=False): - """ - Append stanza to the queue of messages to be send if now is False, else - send it instantly - """ - if self.state <= 0: - return - - r = str(packet).encode('utf-8') - - if now: - self.sendqueue.insert(0, (r, is_message)) - self._do_send() - else: - self.sendqueue.append((r, is_message)) - self._plug_idle() - - def read_timeout(self): - ids = self.client.conn_holder.ids_of_awaiting_messages - if self.fd in ids and ids[self.fd]: - for (_id, thread_id) in ids[self.fd]: - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', 'DATA ERROR', ( - self.client.to, thread_id)) - else: - self._owner.on_not_ok('connection timeout') - ids[self.fd] = [] - self.pollend() - - def do_connect(self): - errnum = 0 - try: - self._sock.connect(self._server[:2]) - self._sock.setblocking(False) - except Exception as ee: - errnum = ee.errno - errstr = ee.strerror - errors = (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK) - if 'WSAEINVAL' in errno.__dict__: - errors += (errno.WSAEINVAL,) - if errnum in errors: - return - - # win32 needs this - if errnum not in (0, 10056, errno.EISCONN) or self.state != 0: - log.error('Could not connect to %s: %s [%s]', str(self.host), - errnum, errstr) - self.connect_to_next_ip() - return - - # socket is already connected - self._sock.setblocking(False) - self.state = 1 # connected - # we are connected - self.on_connect(self) - - def pollout(self): - if self.state == 0: - self.do_connect() - return - app.idlequeue.remove_timeout(self.fd) - self._do_send() - - def pollend(self): - if self.state == 0: # error in connect()? - #self.disconnect() - self.connect_to_next_ip() - else: - self.state = -1 - self.disconnect() - - def pollin(self): - """ - Reads all pending incoming data. Call owner's disconnected() method if - appropriate - """ - received = '' - errnum = 0 - try: - # get as many bites, as possible, but not more than RECV_BUFSIZE - received = self._sock.recv(MAX_BUFF_LEN) - except Exception as e: - errnum = e.errno - # "received" will be empty anyhow - if errnum == ssl.SSL_ERROR_WANT_READ: - pass - elif errnum in [errno.ECONNRESET, errno.ENOTCONN, errno.ESHUTDOWN]: - self.pollend() - # don’t process result, in case it will raise an error - return - elif not received: - if errnum != ssl.SSL_ERROR_EOF: - # 8 EOF occurred in violation of protocol - self.pollend() - if self.state >= 0: - self.disconnect() - return - - if self.state < 0: - return - - received = received.decode('utf-8') - - if self.on_receive: - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - if received.strip(): - log.debug('received: %s', received) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event('', 'DATA RECEIVED', received) - self.on_receive(received) - else: - # This should never happed, so we need the debug - log.error('Unhandled data received: %s', received) - self.disconnect() - return True - - def disconnect(self, message=''): - """ - Close the socket - """ - app.idlequeue.remove_timeout(self.fd) - app.idlequeue.unplug_idle(self.fd) - try: - self._sock.shutdown(socket.SHUT_RDWR) - self._sock.close() - except socket.error: - # socket is already closed - pass - self.fd = -1 - self.state = -1 - if self._owner: - self._owner.on_disconnect() - - def _do_send(self): - if not self.sendbuff: - if not self.sendqueue: - return None # nothing to send - self.sendbuff, self.buff_is_message = self.sendqueue.pop(0) - self.sent_data = self.sendbuff - try: - send_count = self._sock.send(self.sendbuff) - if send_count: - self.sendbuff = self.sendbuff[send_count:] - if not self.sendbuff and not self.sendqueue: - if self.state < 0: - app.idlequeue.unplug_idle(self.fd) - self._on_send() - self.disconnect() - return - # we are not waiting for write - self._plug_idle() - self._on_send() - - except socket.error as e: - if e.errno == ssl.SSL_ERROR_WANT_WRITE: - return True - if self.state < 0: - self.disconnect() - return - self._on_send_failure() - return - if self._owner.sock_type == TYPE_CLIENT: - self.set_timeout(ACTIVITY_TIMEOUT_SECONDS) - return True - - def _plug_idle(self): - readable = self.state != 0 - writable = self.sendqueue or self.sendbuff - if self.writable != writable or self.readable != readable: - app.idlequeue.plug_idle(self, writable, readable) - - - def _on_send(self): - if self.sent_data and self.sent_data.strip(): - log.debug('sent: %s', self.sent_data) - if hasattr(self._owner, 'Dispatcher'): - self._owner.Dispatcher.Event( - '', 'DATA SENT', self.sent_data.decode('utf-8')) - self.sent_data = None - if self.buff_is_message: - self._owner.on_message_sent(self.fd) - self.buff_is_message = False - - def _on_send_failure(self): - log.error('Socket error while sending data') - self._owner.on_disconnect() - self.sent_data = None - -class ClientZeroconf: - def __init__(self, caller): - self.caller = caller - self.zeroconf = None - self.roster = None - self.last_msg = '' - self.connections = {} - self.recipient_to_hash = {} - self.ip_to_hash = {} - self.hash_to_port = {} - self.listener = None - self.ids_of_awaiting_messages = {} - self.disconnect_handlers = [] - self.disconnecting = False - - def connect(self, show, msg): - self.port = self.start_listener(self.caller.port) - if not self.port: - return False - self.zeroconf_init(show, msg) - if not self.zeroconf.connect(): - self.disconnect() - return None - self.roster = roster_zeroconf.Roster(self.zeroconf) - return True - - def remove_announce(self): - if self.zeroconf: - return self.zeroconf.remove_announce() - - def announce(self): - if self.zeroconf: - return self.zeroconf.announce() - - def set_show_msg(self, show, msg): - if self.zeroconf: - self.zeroconf.txt['msg'] = msg - self.last_msg = msg - return self.zeroconf.update_txt(show) - - def resolve_all(self): - if self.zeroconf: - return self.zeroconf.resolve_all() - - def reannounce(self, txt): - self.remove_announce() - self.zeroconf.txt = txt - self.zeroconf.port = self.port - self.zeroconf.username = self.caller.username - return self.announce() - - def zeroconf_init(self, show, msg): - self.zeroconf = zeroconf.Zeroconf(self.caller._on_new_service, - self.caller._on_remove_service, self.caller._on_name_conflictCB, - self.caller._on_disconnect, self.caller._on_error, - self.caller.username, self.caller.host, self.port) - self.zeroconf.txt['msg'] = msg - self.zeroconf.txt['status'] = show - self.zeroconf.txt['1st'] = self.caller.first - self.zeroconf.txt['last'] = self.caller.last - self.zeroconf.txt['jid'] = self.caller.jabber_id - self.zeroconf.txt['email'] = self.caller.email - self.zeroconf.username = self.caller.username - self.zeroconf.host = self.caller.host - self.zeroconf.port = self.port - self.last_msg = msg - - def disconnect(self): - # to avoid recursive calls - if self.disconnecting: - return - if self.listener: - self.listener.disconnect() - self.listener = None - if self.zeroconf: - self.zeroconf.disconnect() - self.zeroconf = None - if self.roster: - self.roster.zeroconf = None - self.roster._data = None - self.roster = None - self.disconnecting = True - for i in reversed(self.disconnect_handlers): - log.debug('Calling disconnect handler %s', i) - i() - self.disconnecting = False - - def start_disconnect(self): - self.disconnect() - - def kill_all_connections(self): - for connection in list(self.connections.values()): - connection.force_disconnect() - - def add_connection(self, connection, ip, port, recipient): - sock_hash = connection.sock_hash - if sock_hash not in self.connections: - self.connections[sock_hash] = connection - self.ip_to_hash[ip] = sock_hash - self.hash_to_port[sock_hash] = port - if recipient: - self.recipient_to_hash[recipient] = sock_hash - - def remove_connection(self, sock_hash): - if sock_hash in self.connections: - del self.connections[sock_hash] - for i, v in self.recipient_to_hash.items(): - if v == sock_hash: - self.recipient_to_hash.pop(i) - break - for i, v in self.ip_to_hash: - if v == sock_hash: - self.ip_to_hash.pop(i) - break - if sock_hash in self.hash_to_port: - del self.hash_to_port[sock_hash] - - def start_listener(self, port): - for p in range(port, port + 5): - self.listener = ZeroconfListener(p, self) - self.listener.bind() - if self.listener.started: - return p - self.listener = None - return False - - def getRoster(self): - if self.roster: - return self.roster.getRoster() - return {} - - def send(self, stanza, is_message=False, now=False, on_ok=None, - on_not_ok=None): - to = stanza.getTo() - if to is None: - # Can’t send undirected stanza over Zeroconf. - return -1 - to = to.bare - stanza.setFrom(self.roster.zeroconf.name) - - try: - item = self.roster[to] - except KeyError: - # Contact offline - return -1 - - # look for hashed connections - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return - - the_address = None - for address in item['addresses']: - if address['address'] in self.ip_to_hash: - the_address = address - if the_address and the_address['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[the_address['address']] - if self.hash_to_port[hash_] == the_address['port']: - conn = self.connections[hash_] - id_ = stanza.getID() or '' - if conn.add_stanza(stanza, is_message): - if on_ok: - on_ok(id_) - return - - # otherwise open new connection - if not stanza.getID(): - stanza.setID('zero') - addresses_ = [] - for address in item['addresses']: - addresses_ += [{'host': address['address'], 'address': address['address'], 'port': address['port']}] - P2PClient(None, addresses_, self, - [(stanza, is_message)], to, on_ok=on_ok, on_not_ok=on_not_ok) - - def RegisterDisconnectHandler(self, handler): - """ - Register handler that will be called on disconnect - """ - self.disconnect_handlers.append(handler) - - def UnregisterDisconnectHandler(self, handler): - """ - Unregister handler that is called on disconnect - """ - self.disconnect_handlers.remove(handler) - - def SendAndWaitForResponse(self, stanza, timeout=None, func=None, - args=None): - """ - Send stanza and wait for recipient's response to it. Will call - transports on_timeout callback if response is not retrieved in time - - Be aware: Only timeout of latest call of SendAndWait is active. - """ -# if timeout is None: -# timeout = DEFAULT_TIMEOUT_SECONDS - def on_ok(_waitid): -# if timeout: -# self._owner.set_timeout(timeout) - to = stanza.getTo() - to = app.get_jid_without_resource(to) - - try: - item = self.roster[to] - except KeyError: - # Contact offline - item = None - - conn = None - if to in self.recipient_to_hash: - conn = self.connections[self.recipient_to_hash[to]] - elif item: - the_address = None - for address in item['addresses']: - if address['address'] in self.ip_to_hash: - the_address = address - if the_address and the_address['address'] in self.ip_to_hash: - hash_ = self.ip_to_hash[the_address['address']] - if self.hash_to_port[hash_] == the_address['port']: - conn = self.connections[hash_] - if func: - conn.Dispatcher.on_responses[_waitid] = (func, args) - conn.onreceive(conn.Dispatcher._WaitForData) - conn.Dispatcher._expected[_waitid] = None - self.send(stanza, on_ok=on_ok) - - def SendAndCallForResponse(self, stanza, func=None, args=None): - """ - Put stanza on the wire and call back when recipient replies. Additional - callback arguments can be specified in args. - """ - self.SendAndWaitForResponse(stanza, 0, func, args) diff --git a/gajim/common/zeroconf/connection_handlers_zeroconf.py b/gajim/common/zeroconf/connection_handlers_zeroconf.py deleted file mode 100644 index 834b43286..000000000 --- a/gajim/common/zeroconf/connection_handlers_zeroconf.py +++ /dev/null @@ -1,125 +0,0 @@ -# Contributors for this file: -# - Yann Leboulanger <asterix@lagaule.org> -# - Nikos Kouremenos <nkour@jabber.org> -# - Dimitur Kirov <dkirov@gmail.com> -# - Travis Shirk <travis@pobox.com> -# - Stefan Bethge <stefan@lanpartei.de> -# -# 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 time -import logging - -from gajim.common import app - -from gajim.common.helpers import AdditionalDataDict -from gajim.common.modules.util import get_eme_message -from gajim.common.modules.misc import parse_correction -from gajim.common.modules.misc import parse_oob -from gajim.common.modules.misc import parse_xhtml - - -log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf') - - -class NetworkEvent: - pass - -class ConnectionHandlersZeroconf: - def _messageCB(self, con, stanza, properties): - """ - Called when we receive a message - """ - if properties.type.is_error: - return - log.info('Zeroconf MessageCB') - - # Don’t trust from attr set by sender - stanza.setFrom(con._owner.to) - - app.ged.raise_event(NetworkEvent( - 'raw-message-received', - conn=self, - stanza=stanza, - account=self.name)) - - type_ = stanza.getType() - if type_ is None: - type_ = 'normal' - - id_ = stanza.getID() - - fjid = str(stanza.getFrom()) - - jid, resource = app.get_room_and_nick_from_fjid(fjid) - - msgtxt = stanza.getBody() - - session = self.get_or_create_session(fjid, properties.thread) - - if properties.thread and not session.received_thread_id: - session.received_thread_id = True - - timestamp = time.time() - session.last_receive = timestamp - - additional_data = AdditionalDataDict() - parse_oob(properties, additional_data) - parse_xhtml(properties, additional_data) - - if properties.is_encrypted: - additional_data['encrypted'] = properties.encrypted.additional_data - else: - if properties.eme is not None: - msgtxt = get_eme_message(properties.eme) - - event_attr = { - 'conn': self, - 'stanza': stanza, - 'account': self.name, - 'additional_data': additional_data, - 'timestamp': time.time(), - 'fjid': fjid, - 'jid': jid, - 'resource': resource, - 'unique_id': id_, - 'correct_id': parse_correction(properties), - 'msgtxt': msgtxt, - 'session': session, - 'gc_control': None, - 'popup': False, - 'msg_log_id': None, - 'displaymarking': None, - 'stanza_id': id_, - 'properties': properties, - } - - app.ged.raise_event( - NetworkEvent('decrypted-message-received', **event_attr)) - - def _message_error_received(self, _con, _stanza, properties): - log.info(properties.error) - - app.storage.archive.set_message_error(app.get_jid_from_account(self.name), - properties.jid, - properties.id, - properties.error) - - app.ged.raise_event( - NetworkEvent('message-error', - account=self.name, - jid=properties.jid, - message_id=properties.id, - error=properties.error)) diff --git a/gajim/common/zeroconf/connection_zeroconf.py b/gajim/common/zeroconf/connection_zeroconf.py deleted file mode 100644 index 382b8f04c..000000000 --- a/gajim/common/zeroconf/connection_zeroconf.py +++ /dev/null @@ -1,603 +0,0 @@ -# Contributors for this file: -# - Yann Leboulanger <asterix@lagaule.org> -# - Nikos Kouremenos <nkour@jabber.org> -# - Dimitur Kirov <dkirov@gmail.com> -# - Travis Shirk <travis@pobox.com> -# - Stefan Bethge <stefan@lanpartei.de> -# -# Copyright (C) 2003-2014 Yann Leboulanger <asterix@lagaule.org> -# Copyright (C) 2003-2004 Vincent Hanquez <tab@snarc.org> -# Copyright (C) 2006 Nikos Kouremenos <nkour@jabber.org> -# Dimitur Kirov <dkirov@gmail.com> -# Travis Shirk <travis@pobox.com> -# Norman Rasmussen <norman@rasmussen.co.za> -# Stefan Bethge <stefan@lanpartei.de> -# -# This 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 socket -import getpass -import logging -import time - -import nbxmpp -from gi.repository import GLib - -from gajim.common import app -from gajim.common import modules -from gajim.common import helpers -from gajim.common import idle -from gajim.common.i18n import _ -from gajim.common.const import ClientState -from gajim.common.zeroconf import client_zeroconf -from gajim.common.zeroconf import zeroconf -from gajim.common.zeroconf.connection_handlers_zeroconf import ConnectionHandlersZeroconf - -log = logging.getLogger('gajim.c.connection_zeroconf') - - -class NetworkEvent: - pass - -class OurShowEvent(NetworkEvent): - - name = 'our-show' - - def init(self): - self.reconnect = False - - -class ConnectionLostEvent(NetworkEvent): - - name = 'connection-lost' - - def generate(self): - app.ged.raise_event(OurShowEvent( - None, - conn=self.conn, - show='offline')) - return True - - -class ConnectionZeroconf(ConnectionHandlersZeroconf): - def __init__(self, name): - ConnectionHandlersZeroconf.__init__(self) - # system username - self.username = None - self.server_resource = '' # zeroconf has no resource, fake an empty one - self.call_resolve_timeout = False - # we don't need a password, but must be non-empty - self.password = 'zeroconf' - self.autoconnect = False - self.httpupload = False - - self.name = name - self._modules = {} - self.connection = None # xmpppy ClientCommon instance - self.is_zeroconf = False - self.password = None - self.server_resource = helpers.get_resource(self.name) - self.priority = app.get_priority(name, 'offline') - self.time_to_reconnect = None - self._reconnect_timer_source = None - - self._state = ClientState.DISCONNECTED - self._status = 'offline' - self._status_message = '' - - # If handlers have been registered - self.handlers_registered = False - - self.pep = {} - - self.roster_supported = True - - self._stun_servers = [] # STUN servers of our jabber server - - # Tracks the calls of the connect_machine() method - self._connect_machine_calls = 0 - - self.get_config_values_or_default() - - self.is_zeroconf = True - - # Register all modules - modules.register_modules(self) - - def _set_state(self, state): - log.info('State: %s', state) - self._state = state - - @property - def state(self): - return self._state - - @property - def status(self): - return self._status - - @property - def status_message(self): - return self._status_message - - def _register_new_handlers(self, con): - for handler in modules.get_handlers(self): - if len(handler) == 5: - name, func, typ, ns, priority = handler - con.RegisterHandler(name, func, typ, ns, priority=priority) - else: - con.RegisterHandler(*handler) - self.handlers_registered = True - - def _unregister_new_handlers(self, con): - if not con: - return - for handler in modules.get_handlers(self): - if len(handler) > 4: - handler = handler[:4] - con.UnregisterHandler(*handler) - self.handlers_registered = False - - def get_module(self, name): - return modules.get(self.name, name) - - def quit(self, kill_core): - if kill_core and app.account_is_connected(self.name): - self.disconnect(reconnect=False) - - def new_account(self, name, config, sync=False): - """ - To be implemented by derived classes - """ - raise NotImplementedError - - def _on_new_account(self, con=None, con_type=None): - """ - To be implemented by derived classes - """ - raise NotImplementedError - - def change_status(self, show, msg, auto=False): - if not msg: - msg = '' - - self._status = show - self._status_message = msg - - if self._state.is_disconnected: - if show == 'offline': - return - - self.server_resource = helpers.get_resource(self.name) - self.connect_and_init(show, msg) - return - - if self._state.is_connecting or self._state.is_reconnect_scheduled: - if show == 'offline': - self.disconnect(reconnect=False) - elif self._state.is_reconnect_scheduled: - self.reconnect() - return - - # We are connected - if show == 'offline': - presence = self.get_module('Presence').get_presence( - typ='unavailable', - status=msg, - caps=False) - - self.connection.send(presence, now=True) - self.disconnect(reconnect=False) - return - - idle_time = None - if auto: - if app.is_installed('IDLE') and app.settings.get('autoaway'): - idle_sec = idle.Monitor.get_idle_sec() - idle_time = time.strftime( - '%Y-%m-%dT%H:%M:%SZ', - time.gmtime(time.time() - idle_sec)) - - self._update_status(show, msg, idle_time=idle_time) - - def get_config_values_or_default(self): - """ - Get name, host, port from config, or create zeroconf account with default - values - """ - self.host = socket.gethostname() - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'hostname', - self.host) - self.port = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'custom_port') - self.autoconnect = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'autoconnect') - self.sync_with_global_status = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'sync_with_global_status') - self.first = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'zeroconf_first_name') - self.last = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'zeroconf_last_name') - self.jabber_id = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'zeroconf_jabber_id') - self.email = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'zeroconf_email') - - if not self.username: - self.username = getpass.getuser() - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'name', - self.username) - else: - self.username = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'name') - - def get_own_jid(self, *args, **kwargs): - return nbxmpp.JID.from_string(self.username + '@' + self.host) - - def reconnect(self): - # Do not try to reco while we are already trying - self.time_to_reconnect = None - log.debug('reconnect') - - self.disconnect() - self.change_status(self._status, self._status_message) - - def disable_account(self): - self.disconnect() - - def _on_resolve_timeout(self): - if self._state.is_connected: - if not self.connection.resolve_all(): - self.disconnect() - return False - diffs = self.roster.getDiffs() - for key in diffs: - self.roster.setItem(key) - app.ged.raise_event(NetworkEvent( - 'roster-info', conn=self, jid=key, - nickname=self.roster.getName(key), sub='both', - ask='no', groups=self.roster.getGroups(key), - avatar_sha=None)) - self._on_presence(key) - #XXX open chat windows don't get refreshed (full name), add that - return self.call_resolve_timeout - - # callbacks called from zeroconf - def _on_new_service(self, jid): - self.roster.setItem(jid) - app.ged.raise_event(NetworkEvent( - 'roster-info', conn=self, jid=jid, - nickname=self.roster.getName(jid), sub='both', - ask='no', groups=self.roster.getGroups(jid), - avatar_sha=None)) - self._on_presence(jid) - - def _on_remove_service(self, jid): - self.roster.delItem(jid) - # 'NOTIFY' (account, (jid, status, status message, resource, priority, - # timestamp)) - self._on_presence(jid, show='offline', status='') - - def _on_presence(self, jid, show=None, status=None): - if status is None: - status = self.roster.getMessage(jid) - if show is None: - show = self.roster.getStatus(jid) - - ptype = 'unavailable' if show == 'offline' else None - - event_attrs = { - 'conn': self, - 'prio': 0, - 'need_add_in_roster': False, - 'popup': False, - 'ptype': ptype, - 'jid': jid, - 'resource': 'local', - 'id_': None, - 'fjid': jid, - 'timestamp': 0, - 'avatar_sha': None, - 'user_nick': '', - 'idle_time': None, - 'show': show, - 'new_show': show, - 'old_show': 0, - 'status': status, - 'contact_list': [], - 'contact': None, - } - - event_ = NetworkEvent('presence-received', **event_attrs) - - self._update_contact(event_) - - app.ged.raise_event(event_) - - def _update_contact(self, event): - jid = event.jid - - status_strings = ['offline', 'error', 'online', 'chat', 'away', - 'xa', 'dnd'] - - event.new_show = status_strings.index(event.show) - - contact = app.contacts.get_contact_strict(self.name, jid, '') - if contact is None: - contact = app.contacts.get_contact_strict(self.name, jid, 'local') - - if contact.show in status_strings: - event.old_show = status_strings.index(contact.show) - - # Update contact with presence data - contact.resource = 'local' - contact.show = event.show - contact.status = event.status - contact.priority = event.prio - contact.idle_time = event.idle_time - - event.contact = contact - - # It's not an agent - if event.old_show == 0 and event.new_show > 1: - if not jid in app.newly_added[self.name]: - app.newly_added[self.name].append(jid) - if jid in app.to_be_removed[self.name]: - app.to_be_removed[self.name].remove(jid) - elif event.old_show > 1 and event.new_show == 0 and self._state.is_connected: - if not jid in app.to_be_removed[self.name]: - app.to_be_removed[self.name].append(jid) - if jid in app.newly_added[self.name]: - app.newly_added[self.name].remove(jid) - - if event.ptype == 'unavailable': - # TODO: This causes problems when another - # resource signs off! - self.get_module('Bytestream').stop_all_active_file_transfers(contact) - - def _on_name_conflictCB(self, alt_name): - self.disconnect() - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - app.ged.raise_event( - NetworkEvent('zeroconf-name-conflict', - conn=self, - alt_name=alt_name)) - - def _on_error(self, message): - log.warning('avahi error: %s', message) - - def connect(self, show='online', msg=''): - self.get_config_values_or_default() - if not self.connection: - self.connection = client_zeroconf.ClientZeroconf(self) - if not zeroconf.test_zeroconf(): - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - self._status = 'offline' - app.ged.raise_event(ConnectionLostEvent(None, - conn=self, title=_('Could not connect to "%s"') % self.name, - msg=_('Please check if Avahi or Bonjour is installed.'))) - self.disconnect() - return - result = self.connection.connect(show, msg) - if not result: - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - self._status = 'offline' - if result is False: - app.ged.raise_event(ConnectionLostEvent(None, - conn=self, title=_('Could not start local service'), - msg=_('Unable to bind to port %d.') % self.port)) - else: # result is None - app.ged.raise_event(ConnectionLostEvent(None, - conn=self, title=_('Could not start local service'), - msg=_('Please check if avahi/bonjour-daemon is running.'))) - self.disconnect() - return - else: - self.connection.announce() - self.roster = self.connection.getRoster() - app.ged.raise_event(NetworkEvent('roster-received', conn=self, - roster=self.roster.copy(), received_from_server=True)) - - # display contacts already detected and resolved - for jid in self.roster.keys(): - app.ged.raise_event(NetworkEvent( - 'roster-info', conn=self, jid=jid, - nickname=self.roster.getName(jid), sub='both', - ask='no', groups=self.roster.getGroups(jid), - avatar_sha=None)) - self._on_presence(jid) - - self._status = show - - # refresh all contacts data every five seconds - self.call_resolve_timeout = True - GLib.timeout_add_seconds(5, self._on_resolve_timeout) - return True - - def disconnect(self, reconnect=True, immediately=True): - log.info('Start disconnecting zeroconf') - if reconnect: - self.time_to_reconnect = 5 - else: - self.time_to_reconnect = None - - self._set_state(ClientState.DISCONNECTED) - if self.connection: - self.connection.disconnect() - self.connection = None - # stop calling the timeout - self.call_resolve_timeout = False - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - - def _on_disconnect(self): - self._set_state(ClientState.DISCONNECTED) - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - - def reannounce(self): - if self._state.is_connected: - txt = {} - txt['1st'] = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'zeroconf_first_name') - txt['last'] = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'zeroconf_last_name') - txt['jid'] = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id') - txt['email'] = app.settings.get_account_setting( - app.ZEROCONF_ACC_NAME, 'zeroconf_email') - self.connection.reannounce(txt) - - def update_details(self) -> None: - if self.connection: - port = app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, - 'custom_port') - if port != self.port: - self.port = port - last_msg = self.connection.last_msg - self.disconnect() - if not self.connect(self._status, last_msg): - return - self.connection.announce() - else: - self.reannounce() - - def connect_and_init(self, show, msg): - # to check for errors from zeroconf - check = True - if not self.connect(show, msg): - return - - check = self.connection.announce() - - # stay offline when zeroconf does something wrong - if check: - self._set_state(ClientState.CONNECTED) - app.ged.raise_event(NetworkEvent('signed-in', conn=self)) - app.ged.raise_event(OurShowEvent(None, conn=self, - show=show)) - else: - # show notification that avahi or system bus is down - self._set_state(ClientState.DISCONNECTED) - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - self._status = 'offline' - app.ged.raise_event(ConnectionLostEvent(None, conn=self, - title=_('Could not change status of account "%s"') % self.name, - msg=_('Please check if avahi-daemon is running.'))) - - def _update_status(self, show, msg, idle_time=None): - if self.connection.set_show_msg(show, msg): - app.ged.raise_event(OurShowEvent(None, conn=self, - show=show)) - else: - # show notification that avahi or system bus is down - app.ged.raise_event(OurShowEvent(None, conn=self, - show='offline')) - self._status = 'offline' - app.ged.raise_event(ConnectionLostEvent(None, conn=self, - title=_('Could not change status of account "%s"') % self.name, - msg=_('Please check if avahi-daemon is running.'))) - - def send_message(self, message): - stanza = self.get_module('Message').build_message_stanza(message) - message.stanza = stanza - - if message.contact is None: - # Only Single Message should have no contact - self._send_message(message) - return - - method = message.contact.settings.get('encryption') - if not method: - self._send_message(message) - return - - app.plugin_manager.extension_point('encrypt%s' % method, - self, - message, - self._send_message) - - def _send_message(self, message): - def on_send_ok(stanza_id): - app.ged.raise_event( - NetworkEvent('messasge-sent', - jid=message.jid, - **vars(message))) - self.get_module('Message').log_message(message) - - def on_send_not_ok(reason): - reason += ' ' + _('Your message could not be sent.') - app.ged.raise_event(NetworkEvent( - 'zeroconf-error', - account=self.name, - jid=message.jid, - message=reason)) - - ret = self.connection.send( - message.stanza, message.message is not None, - on_ok=on_send_ok, on_not_ok=on_send_not_ok) - message.timestamp = time.time() - - if ret == -1: - # Contact Offline - error_message = _( - 'Contact is offline. Your message could not be sent.') - app.ged.raise_event(NetworkEvent( - 'zeroconf-error', - account=self.name, - jid=message.jid, - message=error_message)) - return - - def send_stanza(self, stanza): - # send a stanza untouched - if not self.connection: - return - if not isinstance(stanza, nbxmpp.Node): - stanza = nbxmpp.Protocol(node=stanza) - self.connection.send(stanza) - - def _event_dispatcher(self, realm, event, data): - if realm == '': - if event == 'STANZA RECEIVED': - app.ged.raise_event( - NetworkEvent('stanza-received', - conn=self, - stanza_str=str(data))) - elif event == 'DATA SENT': - app.ged.raise_event( - NetworkEvent('stanza-sent', - conn=self, - stanza_str=str(data))) - - if event == nbxmpp.transports.DATA_ERROR: - frm = data[0] - error_message = _( - 'Connection to host could not be established: ' - 'Timeout while sending data.') - app.ged.raise_event(NetworkEvent( - 'zeroconf-error', - account=self.name, - jid=frm, - message=error_message)) - - def cleanup(self): - pass diff --git a/gajim/common/zeroconf/roster_zeroconf.py b/gajim/common/zeroconf/roster_zeroconf.py deleted file mode 100644 index b4d66d7bc..000000000 --- a/gajim/common/zeroconf/roster_zeroconf.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> -# -# 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/>. - -from gajim.common.zeroconf.zeroconf import Constant, ConstantRI - - -class Roster: - def __init__(self, zeroconf): - self._data = None - self.zeroconf = zeroconf # our zeroconf instance - self.version = '' - - def update_roster(self): - for val in self.zeroconf.get_contacts().values(): - self.setItem(val[Constant.NAME]) - - def getRoster(self): - if self._data is None: - self._data = {} - self.update_roster() - return self - - def getDiffs(self): - """ - Update the roster with new data and return dict with jid -> new status - pairs to do notifications and stuff - """ - diffs = {} - old_data = self._data.copy() - self.update_roster() - for key in old_data.keys(): - if key in self._data: - if old_data[key] != self._data[key]: - diffs[key] = self._data[key]['status'] - return diffs - - def setItem(self, jid, name='', groups=''): - contact = self.zeroconf.get_contact(jid) - if not contact: - return - - addresses = [] - i = 0 - for ri in contact[Constant.RESOLVED_INFO]: - addresses += [{}] - addresses[i]['host'] = ri[ConstantRI.HOST] - addresses[i]['address'] = ri[ConstantRI.ADDRESS] - addresses[i]['port'] = ri[ConstantRI.PORT] - i += 1 - txt = contact[Constant.TXT] - - self._data[jid] = {} - self._data[jid]['ask'] = 'none' - self._data[jid]['subscription'] = 'both' - self._data[jid]['groups'] = [] - self._data[jid]['resources'] = {} - self._data[jid]['addresses'] = addresses - txt_dict = self.zeroconf.txt_array_to_dict(txt) - status = txt_dict.get('status', '') - if not status: - status = 'avail' - nm = txt_dict.get('1st', '') - if 'last' in txt_dict: - if nm != '': - nm += ' ' - nm += txt_dict['last'] - if nm: - self._data[jid]['name'] = nm - else: - self._data[jid]['name'] = jid - if status == 'avail': - status = 'online' - self._data[jid]['txt_dict'] = txt_dict - if 'msg' not in self._data[jid]['txt_dict']: - self._data[jid]['txt_dict']['msg'] = '' - self._data[jid]['status'] = status - self._data[jid]['show'] = status - - def setItemMulti(self, items): - for i in items: - self.setItem(jid=i['jid'], name=i['name'], groups=i['groups']) - - def delItem(self, jid): - if jid in self._data: - del self._data[jid] - - def getItem(self, jid): - if jid in self._data: - return self._data[jid] - - def __getitem__(self, jid): - return self._data[jid] - - def __setitem__(self, jid, value): - self._data[jid] = value - - def getItems(self): - # Return list of all [bare] JIDs that the roster currently tracks. - return self._data.keys() - - def keys(self): - return self._data.keys() - - def getRaw(self): - return self._data - - def getResources(self, jid): - return {} - - def getGroups(self, jid): - return self._data[jid]['groups'] - - def getName(self, jid): - if jid in self._data: - return self._data[jid]['name'] - - def getStatus(self, jid): - if jid in self._data: - return self._data[jid]['status'] - - def getMessage(self, jid): - if jid in self._data: - return self._data[jid]['txt_dict']['msg'] - - def getShow(self, jid): - return self.getStatus(jid) - - def getPriority(self, jid): - return 5 - - def getSubscription(self, jid): - return 'both' - - def Subscribe(self, jid): - pass - - def Unsubscribe(self, jid): - pass - - def Authorize(self, jid): - pass - - def Unauthorize(self, jid): - pass - - def copy(self): - return self._data.copy() diff --git a/gajim/common/zeroconf/zeroconf.py b/gajim/common/zeroconf/zeroconf.py deleted file mode 100644 index 674367ee0..000000000 --- a/gajim/common/zeroconf/zeroconf.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> -# -# 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/>. - -from typing import Any # pylint: disable=unused-import - -from enum import IntEnum, unique - - -@unique -class Constant(IntEnum): - NAME = 0 - DOMAIN = 1 - RESOLVED_INFO = 2 - BARE_NAME = 3 - TXT = 4 - -@unique -class ConstantRI(IntEnum): - INTERFACE = 0 - PROTOCOL = 1 - HOST = 2 - APROTOCOL = 3 - ADDRESS = 4 - PORT = 5 - -def test_avahi(): - try: - import gi - gi.require_version('Avahi', '0.6') - from gi.repository import Avahi # pylint: disable=unused-import - except (ImportError, ValueError): - return False - return True - -def test_bonjour(): - try: - import pybonjour # pylint: disable=unused-import - except Exception: - return False - return True - -def test_zeroconf(): - return test_avahi() or test_bonjour() - -if test_avahi(): - from gajim.common.zeroconf import zeroconf_avahi - Zeroconf = zeroconf_avahi.Zeroconf # type: Any -elif test_bonjour(): - from gajim.common.zeroconf import zeroconf_bonjour - Zeroconf = zeroconf_bonjour.Zeroconf diff --git a/gajim/common/zeroconf/zeroconf_avahi.py b/gajim/common/zeroconf/zeroconf_avahi.py deleted file mode 100644 index 49640d798..000000000 --- a/gajim/common/zeroconf/zeroconf_avahi.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> -# -# 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 Avahi -from gi.repository import Gio -from gi.repository import GLib - -from gajim.common.i18n import _ -from gajim.common.zeroconf.zeroconf import Constant, ConstantRI -from gajim.common.zeroconf.zeroconf_avahi_const import DBUS_NAME -from gajim.common.zeroconf.zeroconf_avahi_const import DBUS_INTERFACE_SERVER -from gajim.common.zeroconf.zeroconf_avahi_const import DBUS_INTERFACE_ENTRY_GROUP -from gajim.common.zeroconf.zeroconf_avahi_const import DBUS_INTERFACE_DOMAIN_BROWSER -from gajim.common.zeroconf.zeroconf_avahi_const import ServerState -from gajim.common.zeroconf.zeroconf_avahi_const import EntryGroup -from gajim.common.zeroconf.zeroconf_avahi_const import DomainBrowser -from gajim.common.zeroconf.zeroconf_avahi_const import Protocol -from gajim.common.zeroconf.zeroconf_avahi_const import Interface - -log = logging.getLogger('gajim.c.z.zeroconf_avahi') - - -class Zeroconf: - def __init__(self, new_service_cb, remove_service_cb, name_conflict_cb, - disconnected_cb, error_cb, name, host, port): - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = {} - self.name = None - - self.connected = False - self.announced = False - - #XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self._new_service_cb = new_service_cb - self._remove_service_cb = remove_service_cb - self._name_conflict_cb = name_conflict_cb - self._disconnected_cb = disconnected_cb - self._error_cb = error_cb - - self._server = None - self._avahi_client = None - self._service_browser = None - self._domain_browser = None - self._entrygroup = None - - self._sb_connections = [] - self._connections = {} - - self._contacts = {} - self._invalid_self_contact = {} - - @staticmethod - def _call(proxy, method_name): - try: - output = proxy.call_sync( - method_name, None, Gio.DBusCallFlags.NONE, -1, None) - if output: - return output[0] - except GLib.Error as error: - log.debug(error) - return None - - def _error_callback(self, error): - log.debug(error) - # timeouts are non-critical - if str(error) != 'Timeout reached': - self.disconnect() - self._disconnected_cb() - - def _new_service_callback(self, _browser, interface, protocol, name, stype, - domain, _flags): - log.debug('Found service %s in domain %s on %i.%i.', - name, domain, interface, protocol) - if not self.connected: - return - - # synchronous resolving - try: - output = self._server.call_sync( - 'ResolveService', - GLib.Variant('(iisssiu)', (interface, protocol, name, stype, - domain, Protocol.UNSPEC, 0)), - Gio.DBusCallFlags.NONE, -1, None) - self.service_resolved_callback(*output) - except GLib.Error as error: - log.debug('Error while resolving: %s', error) - - def _remove_service_callback(self, _browser, interface, protocol, name, - _stype, domain, _flags): - log.debug('Service %s in domain %s on %i.%i disappeared.', - name, domain, interface, protocol) - if not self.connected: - return - if name == self.name: - return - - for key in list(self._contacts.keys()): - val = self._contacts[key] - if val[Constant.BARE_NAME] == name: - # try to reduce instead of delete first - resolved_info = val[Constant.RESOLVED_INFO] - if len(resolved_info) > 1: - for i, _info in enumerate(resolved_info): - if resolved_info[i][ConstantRI.INTERFACE] == interface and resolved_info[i][ConstantRI.PROTOCOL] == protocol: - del self._contacts[key][Constant.RESOLVED_INFO][i] - # if still something left, don't remove - if len(self._contacts[key][Constant.RESOLVED_INFO]) > 1: - return - del self._contacts[key] - self._remove_service_cb(key) - return - - def _new_service_type(self, interface, protocol, stype, domain, _flags): - # Are we already browsing this domain for this type? - if self._service_browser: - return - - self._service_browser = Avahi.ServiceBrowser.new_full( - interface, protocol, stype, domain, 0) - - self._avahi_client = Avahi.Client(flags=0,) - self._avahi_client.start() - con = self._service_browser.connect('new_service', - self._new_service_callback) - self._sb_connections.append(con) - con = self._service_browser.connect('removed_service', - self._remove_service_callback) - self._sb_connections.append(con) - con = self._service_browser.connect('failure', self._error_callback) - self._sb_connections.append(con) - - self._service_browser.attach(self._avahi_client) - - def _new_domain_callback(self, interface, protocol, domain, _flags): - if domain != 'local': - self._browse_domain(interface, protocol, domain) - - @staticmethod - def txt_array_to_dict(txt_array): - txt_dict = {} - for array in txt_array: - item = bytes(array) - item = item.decode('utf-8') - item = item.split('=', 1) - - if item[0] and (item[0] not in txt_dict): - if len(item) == 1: - txt_dict[item[0]] = None - else: - txt_dict[item[0]] = item[1] - - return txt_dict - - @staticmethod - def dict_to_txt_array(txt_dict): - array = [] - - for key, value in txt_dict.items(): - item = '%s=%s' % (key, value) - item = item.encode('utf-8') - array.append(item) - - return array - - def service_resolved_callback(self, interface, protocol, name, _stype, - domain, host, aprotocol, address, port, txt, - _flags): - log.debug('Service data for service %s in domain %s on %i.%i:', - name, domain, interface, protocol) - log.debug('Host %s (%s), port %i, TXT data: %s', - host, address, port, self.txt_array_to_dict(txt)) - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name - - # we don't want to see ourselves in the list - if name != self.name: - resolved_info = [(interface, protocol, host, - aprotocol, address, int(port))] - if name in self._contacts: - # Decide whether to try to merge with existing resolved info: - old_name, old_domain, old_resolved_info, old_bare_name, _old_txt = self._contacts[name] - if name == old_name and domain == old_domain and bare_name == old_bare_name: - # Seems similar enough, try to merge resolved info: - for i, _info in enumerate(old_resolved_info): - # for now, keep a single record for each (interface, protocol) pair - # - # Note that, theoretically, we could both get IPv4 and - # IPv6 aprotocol responses via the same protocol, - # so this probably needs to be revised again. - if old_resolved_info[i][0:2] == (interface, protocol): - log.debug('Deleting resolved info for interface %s', - old_resolved_info[i]) - del old_resolved_info[i] - break - resolved_info = resolved_info + old_resolved_info - log.debug('Collected resolved info is now: %s', - resolved_info) - self._contacts[name] = (name, domain, resolved_info, bare_name, txt) - self._new_service_cb(name) - else: - # remember data - # In case this is not our own record but of another - # gajim instance on the same machine, - # it will be used when we get a new name. - self._invalid_self_contact[name] = ( - name, - domain, - (interface, protocol, host, aprotocol, address, int(port)), - bare_name, - txt) - - def _service_resolved_all_callback(self, _interface, _protocol, name, - _stype, _domain, _host, _aprotocol, - _address, _port, txt, _flags): - if not self.connected: - return - - if name.find('@') == -1: - name = name + '@' + name - # update TXT data only, as intended according to resolve_all comment - old_contact = self._contacts[name] - self._contacts[name] = old_contact[0:Constant.TXT] + (txt,) + old_contact[Constant.TXT+1:] - - def _service_add_fail_callback(self, err): - log.debug('Error while adding service. %s', str(err)) - if 'Local name collision' in str(err): - alternative_name = self._server.call_sync( - 'GetAlternativeServiceName', - GLib.Variant('(s)', (self.username,)), - Gio.DBusCallFlags.NONE, -1, None) - self._name_conflict_cb(alternative_name[0]) - return - self._error_cb(_('Error while adding service. %s') % str(err)) - self.disconnect() - - def _server_state_changed_callback(self, _connection, _sender_name, - _object_path, _interface_name, - _signal_name, parameters): - state, _ = parameters - log.debug('server state changed to %s', state) - if state == ServerState.RUNNING: - self._create_service() - elif state in (ServerState.COLLISION, - ServerState.REGISTERING): - self.disconnect() - if self._entrygroup: - self._call(self._entrygroup, 'Reset') - - def _entrygroup_state_changed_callback(self, _connection, _sender_name, - _object_path, _interface_name, - _signal_name, parameters): - state, _ = parameters - # the name is already present, so recreate - if state == EntryGroup.COLLISION: - log.debug('zeroconf.py: local name collision') - self._service_add_fail_callback('Local name collision') - elif state == EntryGroup.FAILURE: - self.disconnect() - self._call(self._entrygroup, 'Reset') - log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached (that ' - 'should not happen)') - - @staticmethod - def _replace_show(show): - if show in ['chat', 'online', '']: - return 'avail' - if show == 'xa': - return 'away' - return show - - def avahi_txt(self): - return self.dict_to_txt_array(self.txt) - - def _create_service(self): - try: - if not self._entrygroup: - # create an EntryGroup for publishing - object_path = self._server.call_sync( - 'EntryGroupNew', None, Gio.DBusCallFlags.NONE, -1, None) - - self._entrygroup = Gio.DBusProxy.new_for_bus_sync( - Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, None, - DBUS_NAME, *object_path, DBUS_INTERFACE_ENTRY_GROUP, None) - - connection = self._entrygroup.get_connection() - subscription = connection.signal_subscribe( - DBUS_NAME, DBUS_INTERFACE_ENTRY_GROUP, 'StateChanged', - *object_path, None, Gio.DBusSignalFlags.NONE, - self._entrygroup_state_changed_callback) - - self._connections[connection] = [subscription] - - txt = {} - - # remove empty keys - for key, val in self.txt.items(): - if val: - txt[key] = val - - txt['port.p2pj'] = self.port - txt['version'] = 1 - txt['txtvers'] = 1 - - # replace gajim's show messages with compatible ones - if 'status' in self.txt: - txt['status'] = self._replace_show(self.txt['status']) - else: - txt['status'] = 'avail' - - self.txt = txt - log.debug('Publishing service %s of type %s', - self.name, self.stype) - - try: - self._entrygroup.call_sync( - 'AddService', - GLib.Variant('(iiussssqaay)', (Interface.UNSPEC, - Protocol.UNSPEC, 0, - self.name, self.stype, '', - '', self.port, - self.avahi_txt())), - Gio.DBusCallFlags.NONE, -1, None) - except GLib.Error as error: - self._service_add_fail_callback(error) - return False - - try: - self._entrygroup.call_sync('Commit', None, - Gio.DBusCallFlags.NONE, -1, None) - except GLib.Error: - pass - - return True - except GLib.Error as error: - log.debug(error) - return False - - def announce(self): - if not self.connected: - return False - - state = self._server.call_sync( - 'GetState', None, Gio.DBusCallFlags.NONE, -1, None) - - if state[0] == ServerState.RUNNING: - if self._create_service(): - self.announced = True - return True - return False - return None - - def remove_announce(self): - if self.announced is False: - return False - - if self._call(self._entrygroup, 'GetState') != EntryGroup.FAILURE: - self._call(self._entrygroup, 'Reset') - self._call(self._entrygroup, 'Free') - self._entrygroup = None - self.announced = False - - return True - return False - - def _browse_domain(self, interface, protocol, domain): - self._new_service_type(interface, protocol, self.stype, domain, '') - - def _avahi_dbus_connect_cb(self, connection, sender_name, object_path, - interface_name, signal_name, parameters): - name, old_owner, new_owner = parameters - if name == DBUS_NAME: - if new_owner and not old_owner: - log.debug('We are connected to avahi-daemon') - else: - log.debug('Lost connection to avahi-daemon') - self.disconnect() - if self._disconnected_cb: - self._disconnected_cb() - - def _connect_dbus(self): - try: - proxy = Gio.DBusProxy.new_for_bus_sync( - Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, None, - 'org.freedesktop.DBus', '/org/freedesktop/DBus', - 'org.freedesktop.DBus', None) - - connection = proxy.get_connection() - subscription = connection.signal_subscribe( - 'org.freedesktop.DBus', 'org.freedesktop.DBus', - 'NameOwnerChanged', '/org/freedesktop/DBus', None, - Gio.DBusSignalFlags.NONE, self._avahi_dbus_connect_cb) - self._connections[connection] = [subscription] - except GLib.Error as error: - log.debug(error) - return False - else: - return True - - def _connect_avahi(self): - if not self._connect_dbus(): - return False - - if self._server: - return True - try: - self._server = Gio.DBusProxy.new_for_bus_sync( - Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, None, - DBUS_NAME, '/', DBUS_INTERFACE_SERVER, None) - - connection = self._server.get_connection() - subscription = connection.signal_subscribe( - DBUS_NAME, DBUS_INTERFACE_SERVER, 'StateChanged', '/', None, - Gio.DBusSignalFlags.NONE, self._server_state_changed_callback) - self._connections[connection] = [subscription] - except Exception as error: - # Avahi service is not present - self._server = None - log.debug(error) - return False - else: - return True - - def connect(self): - self.name = self.username + '@' + self.host # service name - if not self._connect_avahi(): - return False - - self.connected = True - # start browsing - if self.domain is None: - # Explicitly browse .local - self._browse_domain( - Interface.UNSPEC, Protocol.UNSPEC, 'local') - - # Browse for other browsable domains - object_path = self._server.call_sync( - 'DomainBrowserNew', - GLib.Variant('(iisiu)', (Interface.UNSPEC, Protocol.UNSPEC, '', - DomainBrowser.BROWSE, 0)), - Gio.DBusCallFlags.NONE, -1, None) - - self._domain_browser = Gio.DBusProxy.new_for_bus_sync( - Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, None, DBUS_NAME, - *object_path, DBUS_INTERFACE_DOMAIN_BROWSER, None) - - connection = self._domain_browser.get_connection() - subscription = connection.signal_subscribe( - DBUS_NAME, DBUS_INTERFACE_DOMAIN_BROWSER, 'ItemNew', - *object_path, None, Gio.DBusSignalFlags.NONE, - self._new_domain_callback) - self._connections[connection] = [subscription] - - subscription = connection.signal_subscribe( - DBUS_NAME, DBUS_INTERFACE_DOMAIN_BROWSER, 'Failure', - *object_path, None, Gio.DBusSignalFlags.NONE, - self._error_callback) - self._connections[connection].append(subscription) - else: - self._browse_domain( - Interface.UNSPEC, Protocol.UNSPEC, self.domain) - - return True - - def disconnect(self): - if self.connected: - self.connected = False - for connection, subscriptions in self._connections.items(): - for subscription in subscriptions: - connection.signal_unsubscribe(subscription) - for con in self._sb_connections: - self._service_browser.disconnect(con) - if self._domain_browser: - self._call(self._domain_browser, 'Free') - self.remove_announce() - self._server = None - self._service_browser = None - self._domain_browser = None - - # refresh txt data of all contacts manually (no callback available) - def resolve_all(self): - if not self.connected: - return False - for val in self._contacts.values(): - # get txt data from last recorded resolved info - # TODO: Better try to get it from last IPv6 mDNS, then last IPv4? - ri = val[Constant.RESOLVED_INFO][0] - output = self._server.call_sync( - 'ResolveService', - GLib.Variant('(iisssiu)', (ri[ConstantRI.INTERFACE], - ri[ConstantRI.PROTOCOL], - val[Constant.BARE_NAME], - self.stype, val[Constant.DOMAIN], - Protocol.UNSPEC, - 0)), - Gio.DBusCallFlags.NONE, - -1, - None - ) - self._service_resolved_all_callback(*output) - - return True - - def get_contacts(self): - return self._contacts - - def get_contact(self, jid): - if not jid in self._contacts: - return None - return self._contacts[jid] - - def update_txt(self, show=None): - if show: - self.txt['status'] = self._replace_show(show) - - txt = self.avahi_txt() - if self.connected and self._entrygroup: - try: - self._entrygroup.call_sync( - 'UpdateServiceTxt', - GLib.Variant('(iiusssaay)', (Interface.UNSPEC, - Protocol.UNSPEC, 0, self.name, - self.stype, '', txt)), - Gio.DBusCallFlags.NONE, -1, None) - except GLib.Error as error: - self._error_callback(error) - return False - - return True - return False diff --git a/gajim/common/zeroconf/zeroconf_avahi_const.py b/gajim/common/zeroconf/zeroconf_avahi_const.py deleted file mode 100644 index e0bd61e8f..000000000 --- a/gajim/common/zeroconf/zeroconf_avahi_const.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (C) 2018 Philipp Hörist <philipp AT hoerist.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/>. - - -from enum import IntEnum - - -DBUS_NAME = "org.freedesktop.Avahi" -DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server" -DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup" -DBUS_INTERFACE_DOMAIN_BROWSER = DBUS_NAME + ".DomainBrowser" - - -class ServerState(IntEnum): - INVALID = 0 - REGISTERING = 1 - RUNNING = 2 - COLLISION = 3 - FAILURE = 4 - - -class EntryGroup(IntEnum): - UNCOMMITTED = 0 - REGISTERING = 1 - ESTABLISHED = 2 - COLLISION = 3 - FAILURE = 4 - - -class DomainBrowser(IntEnum): - BROWSE = 0 - BROWSE_DEFAULT = 1 - REGISTER = 2 - REGISTER_DEFAULT = 3 - BROWSE_LEGACY = 4 - - -class Protocol(IntEnum): - UNSPEC = -1 - INET = 0 - INET6 = 1 - - -class Interface(IntEnum): - UNSPEC = -1 diff --git a/gajim/common/zeroconf/zeroconf_bonjour.py b/gajim/common/zeroconf/zeroconf_bonjour.py deleted file mode 100644 index d9489ec6e..000000000 --- a/gajim/common/zeroconf/zeroconf_bonjour.py +++ /dev/null @@ -1,458 +0,0 @@ -# Copyright (C) 2006 Stefan Bethge <stefan@lanpartei.de> -# Copyright (C) 2006 Philipp Hörist <philipp@hoerist.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/>. - -import logging -import select -import re - -from gajim.common.i18n import _ -from gajim.common.zeroconf.zeroconf import Constant - - -log = logging.getLogger('gajim.c.z.zeroconf_bonjour') - -try: - from pybonjour import kDNSServiceErr_NoError - from pybonjour import kDNSServiceErr_ServiceNotRunning - from pybonjour import kDNSServiceErr_NameConflict - from pybonjour import kDNSServiceInterfaceIndexAny - from pybonjour import kDNSServiceType_TXT - from pybonjour import kDNSServiceFlagsAdd - from pybonjour import kDNSServiceFlagsNoAutoRename - from pybonjour import BonjourError - from pybonjour import TXTRecord - from pybonjour import DNSServiceUpdateRecord - from pybonjour import DNSServiceResolve - from pybonjour import DNSServiceProcessResult - from pybonjour import DNSServiceGetAddrInfo - from pybonjour import DNSServiceQueryRecord - from pybonjour import DNSServiceBrowse - from pybonjour import DNSServiceRegister -except ImportError: - pass - -resolve_timeout = 1 - - -class Zeroconf: - def __init__(self, new_service_cb, remove_service_cb, name_conflict_cb, - _disconnected_cb, error_cb, name, host, port): - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = {} # service data - self.name = None - - self.connected = False - self.announced = False - - # XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self._new_service_cb = new_service_cb - self._remove_service_cb = remove_service_cb - self._name_conflict_cb = name_conflict_cb - self._error_cb = error_cb - - self._service_sdref = None - self._browse_sdref = None - - self._contacts = {} # all current local contacts with data - self._invalid_self_contact = {} - self._resolved_hosts = {} - self._resolved = [] - self._queried = [] - - def _browse_callback(self, _sdref, flags, interface, error_code, - service_name, regtype, reply_domain): - log.debug('Found service %s in domain %s on %i(type: %s).', - service_name, reply_domain, interface, regtype) - if not self.connected: - return - if error_code != kDNSServiceErr_NoError: - log.debug('Error in browse_callback: %s', str(error_code)) - return - if not flags & kDNSServiceFlagsAdd: - self._remove_service_callback(service_name) - return - - try: - # asynchronous resolving - resolve_sdref = None - resolve_sdref = DNSServiceResolve( - 0, interface, service_name, - regtype, reply_domain, self._service_resolved_callback) - - while not self._resolved: - ready = select.select([resolve_sdref], [], [], resolve_timeout) - if resolve_sdref not in ready[0]: - log.info('Resolve timed out') - break - DNSServiceProcessResult(resolve_sdref) - else: - self._resolved.pop() - - except BonjourError as error: - log.info('Error when resolving DNS: %s', error) - - finally: - if resolve_sdref: - resolve_sdref.close() - - def _remove_service_callback(self, name): - log.info('Service %s disappeared.', name) - if not self.connected: - return - if name != self.name: - for key in list(self._contacts.keys()): - if self._contacts[key][Constant.NAME] == name: - del self._contacts[key] - self._remove_service_cb(key) - return - - @staticmethod - def txt_array_to_dict(txt): - if not isinstance(txt, TXTRecord): - txt = TXTRecord.parse(txt) - return dict((v[0], v[1]) for v in txt) - - @staticmethod - def _parse_name(fullname): - log.debug('Parse name: %s', fullname) - # TODO: do proper decoding... - escaping = {r'\.': '.', - r'\032': ' ', - r'\064': '@', - } - - # Split on '.' but do not split on '\.' - result = re.split(r'(?<!\\\\)\.', fullname) - name = result[0] - protocol, domain = result[2:4] - - # Replace the escaped values - for src, trg in escaping.items(): - name = name.replace(src, trg) - - bare_name = name - if '@' not in name: - name = name + '@' + name - log.debug('End parse: %s %s %s %s', - name, bare_name, protocol, domain) - - return name, bare_name, protocol, domain - - def _query_txt_callback(self, _sdref, _flags, _interface, error_code, - hosttarget, _rrtype, _rrclass, rdata, _ttl): - - if error_code != kDNSServiceErr_NoError: - log.error('Error in query_record_callback: %s', str(error_code)) - return - - name = self._parse_name(hosttarget)[0] - - if name != self.name: - # update TXT data only, as intended according to - # resolve_all comment - old_contact = self._contacts[name] - self._contacts[name] = old_contact[0:Constant.TXT] + (rdata,) + old_contact[Constant.TXT+1:] - log.debug(self._contacts[name]) - - self._queried.append(True) - - def _getaddrinfo_callback(self, _sdref, _flags, interface, error_code, - hosttarget, address, _ttl): - if error_code != kDNSServiceErr_NoError: - log.error('Error in getaddrinfo_callback: %s', str(error_code)) - return - - fullname, port, txt_record = self._resolved_hosts[hosttarget] - - txt = TXTRecord.parse(txt_record) - ip = address[1] - - name, bare_name, protocol, domain = self._parse_name(fullname) - - log.info('Service data for service %s on %i:', - fullname, interface) - log.info('Host %s, ip %s, port %i, TXT data: %s', - hosttarget, ip, port, txt) - - if not self.connected: - return - - # we don't want to see ourselves in the list - if name != self.name: - resolved_info = [(interface, protocol, hosttarget, - fullname, ip, port)] - self._contacts[name] = (name, domain, resolved_info, - bare_name, txt_record) - - self._new_service_cb(name) - else: - # remember data - # In case this is not our own record but of another - # gajim instance on the same machine, - # it will be used when we get a new name. - self._invalid_self_contact[name] = \ - (name, domain, - (interface, protocol, hosttarget, fullname, ip, port), - bare_name, txt_record) - - self._queried.append(True) - - def _service_resolved_callback(self, _sdref, _flags, interface, - error_code, fullname, hosttarget, port, - txt_record): - if error_code != kDNSServiceErr_NoError: - log.error('Error in service_resolved_callback: %s', str(error_code)) - return - - self._resolved_hosts[hosttarget] = (fullname, port, txt_record) - - try: - getaddrinfo_sdref = \ - DNSServiceGetAddrInfo( - interfaceIndex=interface, - hostname=hosttarget, - callBack=self._getaddrinfo_callback) - - while not self._queried: - ready = select.select( - [getaddrinfo_sdref], [], [], resolve_timeout) - if getaddrinfo_sdref not in ready[0]: - log.warning('GetAddrInfo timed out') - break - DNSServiceProcessResult(getaddrinfo_sdref) - else: - self._queried.pop() - - except BonjourError as error: - if error.error_code == kDNSServiceErr_ServiceNotRunning: - log.info('Service not running') - else: - self._error_cb(_('Error while adding service. %s') % error) - - finally: - if getaddrinfo_sdref: - getaddrinfo_sdref.close() - - self._resolved.append(True) - - def service_added_callback(self, _sdref, _flags, error_code, - _name, _regtype, _domain): - if error_code == kDNSServiceErr_NoError: - log.info('Service successfully added') - - elif error_code == kDNSServiceErr_NameConflict: - log.error('Error while adding service. %s', error_code) - self._name_conflict_cb(self._get_alternativ_name(self.username)) - else: - error = _('Error while adding service. %s') % str(error_code) - self._error_cb(error) - - @staticmethod - def _get_alternativ_name(name): - if name[-2] == '-': - try: - number = int(name[-1]) - except Exception: - return '%s-1' % name - return '%s-%s' % (name[:-2], number + 1) - return '%s-1' % name - - @staticmethod - def _replace_show(show): - if show in ['chat', 'online', '']: - return 'avail' - if show == 'xa': - return 'away' - return show - - def _create_service(self): - txt = {} - - # remove empty keys - for key, val in self.txt.items(): - if val: - txt[key] = val - - txt['port.p2pj'] = self.port - txt['version'] = 1 - txt['txtvers'] = 1 - - # replace gajim's show messages with compatible ones - if 'status' in self.txt: - txt['status'] = self._replace_show(self.txt['status']) - else: - txt['status'] = 'avail' - self.txt = txt - try: - self._service_sdref = DNSServiceRegister( - flags=kDNSServiceFlagsNoAutoRename, - name=self.name, - regtype=self.stype, - port=self.port, - txtRecord=TXTRecord(self.txt, strict=True), - callBack=self.service_added_callback) - - log.info('Publishing service %s of type %s', self.name, self.stype) - - ready = select.select([self._service_sdref], [], []) - if self._service_sdref in ready[0]: - DNSServiceProcessResult(self._service_sdref) - - except BonjourError as error: - if error.errorCode == kDNSServiceErr_ServiceNotRunning: - log.info('Service not running') - else: - self._error_cb(_('Error while adding service. %s') % error) - self.disconnect() - - def announce(self): - if not self.connected: - return False - - self._create_service() - self.announced = True - return True - - def remove_announce(self): - if not self.announced: - return False - - if self._service_sdref is None: - return False - - try: - self._service_sdref.close() - self.announced = False - return True - except BonjourError as error: - log.error('Error when removing announce: %s', error) - return False - - def connect(self): - self.name = self.username + '@' + self.host # service name - - self.connected = True - - # start browsing - if self.browse_domain(): - return True - - self.disconnect() - return False - - def disconnect(self): - if self.connected: - self.connected = False - if self._browse_sdref is not None: - self._browse_sdref.close() - self._browse_sdref = None - self.remove_announce() - - def browse_domain(self, domain=None): - try: - self._browse_sdref = DNSServiceBrowse( - regtype=self.stype, - domain=domain, - callBack=self._browse_callback) - log.info('Starting to browse .local') - return True - except BonjourError as error: - if error.errorCode == kDNSServiceErr_ServiceNotRunning: - log.info('Service not running') - else: - log.error('Error while browsing for services. %s', error) - return False - - def browse_loop(self): - try: - ready = select.select([self._browse_sdref], [], [], 0) - if self._browse_sdref in ready[0]: - DNSServiceProcessResult(self._browse_sdref) - except BonjourError as error: - if error.errorCode == kDNSServiceErr_ServiceNotRunning: - log.info('Service not running') - return False - log.error('Error while browsing for services. %s', error) - return True - - # resolve_all() is called every X seconds and queries for new clients - # and monitors TXT records for changed status - def resolve_all(self): - if not self.connected: - return False - # for now put here as this is synchronous - if not self.browse_loop(): - return False - - # Monitor TXT Records with DNSServiceQueryRecord because - # its more efficient (see pybonjour documentation) - for val in self._contacts.values(): - bare_name = val[Constant.RESOLVED_INFO][0][Constant.BARE_NAME] - - try: - query_sdref = None - query_sdref = \ - DNSServiceQueryRecord( - interfaceIndex=kDNSServiceInterfaceIndexAny, - fullname=bare_name, - rrtype=kDNSServiceType_TXT, - callBack=self._query_txt_callback) - - while not self._queried: - ready = select.select( - [query_sdref], [], [], resolve_timeout) - if query_sdref not in ready[0]: - log.info('Query record timed out') - break - DNSServiceProcessResult(query_sdref) - else: - self._queried.pop() - - except BonjourError as error: - if error.errorCode == kDNSServiceErr_ServiceNotRunning: - log.info('Service not running') - return False - log.error('Error in query for TXT records. %s', error) - finally: - if query_sdref: - query_sdref.close() - - return True - - def get_contacts(self): - return self._contacts - - def get_contact(self, jid): - if jid not in self._contacts: - return None - return self._contacts[jid] - - def update_txt(self, show=None): - if show: - self.txt['status'] = self._replace_show(show) - - txt = TXTRecord(self.txt, strict=True) - try: - DNSServiceUpdateRecord(self._service_sdref, None, 0, txt) - except BonjourError as error: - log.error('Error when updating TXT Record: %s', error) - return False - return True diff --git a/gajim/data/gui/zeroconf_information_window.ui b/gajim/data/gui/zeroconf_information_window.ui deleted file mode 100644 index 2d3f4453b..000000000 --- a/gajim/data/gui/zeroconf_information_window.ui +++ /dev/null @@ -1,398 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.18.3 --> -<interface> - <requires lib="gtk+" version="3.12"/> - <object class="GtkWindow" id="zeroconf_information_window"> - <property name="can_focus">False</property> - <property name="border_width">12</property> - <property name="title" translatable="yes">Contact Information</property> - <property name="resizable">False</property> - <property name="type_hint">dialog</property> - <signal name="destroy" handler="on_zeroconf_information_window_destroy" swapped="no"/> - <signal name="key-press-event" handler="on_zeroconf_information_window_key_press_event" swapped="no"/> - <child> - <object class="GtkBox" id="vbox1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">12</property> - <child> - <object class="GtkLabel" id="nickname_label"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="xalign">0</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkNotebook" id="information_notebook"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkBox" id="hbox3"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">6</property> - <property name="spacing">12</property> - <child> - <object class="GtkGrid" id="grid1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> - <child> - <object class="GtkLabel" id="label51"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">Local jid:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label53"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">Resource:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label54"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">Status:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkCheckButton" id="log_history_checkbutton"> - <property name="label" translatable="yes">_Store conversation history</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="use_underline">True</property> - <property name="xalign">0</property> - <property name="active">True</property> - <property name="draw_indicator">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - <property name="width">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="local_jid_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkEventBox" id="resource_prio_label_eventbox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="visible_window">False</property> - <child> - <object class="GtkLabel" id="resource_prio_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEventBox" id="status_label_eventbox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkLabel" id="status_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - </child> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="vbox2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkEventBox" id="PHOTO_eventbox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="visible_window">False</property> - <child> - <object class="GtkImage" id="PHOTO_image"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="yalign">0</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <child type="tab"> - <object class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">Contact</property> - </object> - <packing> - <property name="tab_fill">False</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="grid2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">6</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> - <child> - <object class="GtkLabel" id="label59"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">First Name:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label58"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">Last Name:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label55"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">XMPP Address:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label56"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="label" translatable="yes">E-Mail:</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="first_name_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="last_name_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="jabber_id_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="email_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="hexpand">True</property> - <property name="xalign">0</property> - <property name="yalign">0</property> - <property name="xpad">5</property> - <property name="ypad">5</property> - <property name="wrap">True</property> - <property name="selectable">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="label57"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Personal</property> - </object> - <packing> - <property name="position">1</property> - <property name="tab_fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButtonBox" id="hbuttonbox1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="close_button"> - <property name="label">gtk-close</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_stock">True</property> - <signal name="clicked" handler="on_close_button_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - </child> - </object> -</interface> diff --git a/gajim/gtk/accounts.py b/gajim/gtk/accounts.py index 160d4b497..5a82e7271 100644 --- a/gajim/gtk/accounts.py +++ b/gajim/gtk/accounts.py @@ -17,7 +17,6 @@ from __future__ import annotations from typing import Any from typing import cast -import sys import locale import logging from collections import defaultdict @@ -33,7 +32,6 @@ from gajim.common import passwords from gajim.common.events import AccountDisonnected from gajim.common.i18n import _ from gajim.common.i18n import Q_ -from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf from .dialogs import DialogButton from .dialogs import ConfirmationDialog @@ -72,9 +70,6 @@ class AccountsWindow(Gtk.ApplicationWindow): self.add(box) for account in app.get_accounts_sorted(): - if account == 'Local': - # Disable zeroconf support until its working again - continue self.add_account(account, initial=True) self._menu.connect('menu-activated', self._on_menu_activated) @@ -125,12 +120,6 @@ class AccountsWindow(Gtk.ApplicationWindow): if not app.account_is_connected(account): return - if account == app.ZEROCONF_ACC_NAME: - zeroconf_conn = cast( - ConnectionZeroconf, app.connections[app.ZEROCONF_ACC_NAME]) - zeroconf_conn.update_details() - return - def relog(): app.connections[account].disconnect(gracefully=True, reconnect=True, @@ -149,22 +138,14 @@ class AccountsWindow(Gtk.ApplicationWindow): @staticmethod def _get_relogin_settings(account: str) -> list[str]: - if account == app.ZEROCONF_ACC_NAME: - settings = [ - 'zeroconf_first_name', - 'zeroconf_last_name', - 'zeroconf_jabber_id', - 'zeroconf_email' - ] - else: - settings = [ - 'client_cert', - 'proxy', - 'resource', - 'use_custom_host', - 'custom_host', - 'custom_port' - ] + settings = [ + 'client_cert', + 'proxy', + 'resource', + 'use_custom_host', + 'custom_host', + 'custom_port' + ] values: list[Any] = [] for setting in settings: @@ -173,10 +154,6 @@ class AccountsWindow(Gtk.ApplicationWindow): @staticmethod def on_remove_account(account: str) -> None: - if app.settings.get_account_setting(account, 'is_zeroconf'): - # Should never happen as button is insensitive - return - open_window('RemoveAccount', account=account) def remove_account(self, account: str) -> None: @@ -270,8 +247,6 @@ class AccountMenu(Gtk.Box): @staticmethod def _sort_func(row1: AccountRow, row2: AccountRow) -> int: - if row1.label == 'Local': - return -1 return locale.strcoll(row1.label.lower(), row2.label.lower()) def add_account(self, row: AccountRow) -> None: @@ -341,11 +316,10 @@ class AccountSubMenu(Gtk.ListBox): self.add(AccountLabelMenuItem(self, self._account)) self.add(BackMenuItem()) self.add(PageMenuItem('general', _('General'))) - if account != 'Local': - self.add(PageMenuItem('privacy', _('Privacy'))) - self.add(PageMenuItem('connection', _('Connection'))) - self.add(PageMenuItem('advanced', _('Advanced'))) - self.add(RemoveMenuItem()) + self.add(PageMenuItem('privacy', _('Privacy'))) + self.add(PageMenuItem('connection', _('Connection'))) + self.add(PageMenuItem('advanced', _('Advanced'))) + self.add(RemoveMenuItem()) @property def account(self) -> str: @@ -461,13 +435,10 @@ class Account: self._menu = menu self._settings = settings - if account == app.ZEROCONF_ACC_NAME: - self._settings.add_page(ZeroConfPage(account)) - else: - self._settings.add_page(GeneralPage(account)) - self._settings.add_page(ConnectionPage(account)) - self._settings.add_page(PrivacyPage(account)) - self._settings.add_page(AdvancedPage(account)) + self._settings.add_page(GeneralPage(account)) + self._settings.add_page(ConnectionPage(account)) + self._settings.add_page(PrivacyPage(account)) + self._settings.add_page(AdvancedPage(account)) self._account_row = AccountRow(account) self._menu.add_account(self._account_row) @@ -535,17 +506,6 @@ class AccountRow(Gtk.ListBoxRow): self._switch_state_label.set_valign(Gtk.Align.CENTER) self._set_label(account_enabled) - if (self._account == app.ZEROCONF_ACC_NAME and - not app.is_installed('ZEROCONF')): - self._switch.set_active(False) - self.set_activatable(False) - self.set_sensitive(False) - if sys.platform in ('win32', 'darwin'): - tooltip = _('Please check if Bonjour is installed.') - else: - tooltip = _('Please check if Avahi is installed.') - self.set_tooltip_text(tooltip) - self._switch.connect( 'state-set', self._on_enable_switch, self._account) @@ -946,55 +906,6 @@ class AdvancedPage(GenericSettingPage): GenericSettingPage.__init__(self, account, settings) -class ZeroConfPage(GenericSettingPage): - - name = 'general' - - def __init__(self, account: str) -> None: - - settings = [ - Setting(SettingKind.DIALOG, _('Profile'), - SettingType.DIALOG, - props={'dialog': ZeroconfProfileDialog}), - - Setting(SettingKind.SWITCH, _('Connect on startup'), - SettingType.ACCOUNT_CONFIG, 'autoconnect', - desc=_('Use environment variable')), - - Setting(SettingKind.SWITCH, - _('Save conversations for all contacts'), - SettingType.ACCOUNT_CONFIG, 'no_log_for', - desc=_('Store conversations on the harddrive')), - - Setting(SettingKind.SWITCH, _('Global Status'), - SettingType.ACCOUNT_CONFIG, 'sync_with_global_status', - desc=_('Synchronize the status of all accounts')), - ] - - GenericSettingPage.__init__(self, account, settings) - - -class ZeroconfProfileDialog(SettingsDialog): - def __init__(self, account: str, parent: Gtk.Window) -> None: - - settings = [ - Setting(SettingKind.ENTRY, _('First Name'), - SettingType.ACCOUNT_CONFIG, 'zeroconf_first_name'), - - Setting(SettingKind.ENTRY, _('Last Name'), - SettingType.ACCOUNT_CONFIG, 'zeroconf_last_name'), - - Setting(SettingKind.ENTRY, _('XMPP Address'), - SettingType.ACCOUNT_CONFIG, 'zeroconf_jabber_id'), - - Setting(SettingKind.ENTRY, _('Email'), - SettingType.ACCOUNT_CONFIG, 'zeroconf_email'), - ] - - SettingsDialog.__init__(self, parent, _('Profile'), - Gtk.DialogFlags.MODAL, settings, account) - - class PriorityDialog(SettingsDialog): def __init__(self, account: str, parent: Gtk.Window) -> None: diff --git a/gajim/gtk/adhoc_muc.py b/gajim/gtk/adhoc_muc.py index b90876fc5..fe26160d5 100644 --- a/gajim/gtk/adhoc_muc.py +++ b/gajim/gtk/adhoc_muc.py @@ -74,8 +74,6 @@ class AdhocMUC(Gtk.ApplicationWindow): def _add_possible_invitees(self): for acc in app.settings.get_active_accounts(): client = app.get_client(acc) - if client.is_zeroconf: - continue for contact in client.get_module('Roster').iter_contacts(): jid = str(contact.jid) # Add contact if it can be invited @@ -92,7 +90,7 @@ class AdhocMUC(Gtk.ApplicationWindow): def _is_invitable(self, contact): # All contacts BUT the following can be invited: - # ourself, gateway contacts, zeroconf contacts + # ourself, gateway contacts return (str(contact.jid) != str(self.jid) and contact.jid != str(self._client.get_own_jid().bare) and str(contact.jid) != app.get_jid_from_account(self.account) and diff --git a/gajim/gtk/application.py b/gajim/gtk/application.py index 024dbbeee..f3ba7f9ac 100644 --- a/gajim/gtk/application.py +++ b/gajim/gtk/application.py @@ -425,9 +425,6 @@ class GajimApplication(Gtk.Application, CoreApplication): def _get_account_actions(self, account: str) -> list[tuple[str, Any, str, str]]: - if account == 'Local': - return [] - # pylint: disable=line-too-long return [ ('-bookmarks', self._on_bookmarks_action, 'online', 's'), diff --git a/gajim/gtk/builder.pyi b/gajim/gtk/builder.pyi index 29770fd7d..7ddc321fc 100644 --- a/gajim/gtk/builder.pyi +++ b/gajim/gtk/builder.pyi @@ -131,40 +131,6 @@ class BookmarksBuilder(Builder): autojoin: Gtk.CellRendererToggle -class ZeroconfInformationWindowBuilder(Builder): - zeroconf_information_window: Gtk.Window - vbox1: Gtk.Box - nickname_label: Gtk.Label - information_notebook: Gtk.Notebook - hbox3: Gtk.Box - grid1: Gtk.Grid - label51: Gtk.Label - label53: Gtk.Label - label54: Gtk.Label - log_history_checkbutton: Gtk.CheckButton - local_jid_label: Gtk.Label - resource_prio_label_eventbox: Gtk.EventBox - resource_prio_label: Gtk.Label - status_label_eventbox: Gtk.EventBox - status_label: Gtk.Label - vbox2: Gtk.Box - PHOTO_eventbox: Gtk.EventBox - PHOTO_image: Gtk.Image - label3: Gtk.Label - grid2: Gtk.Grid - label59: Gtk.Label - label58: Gtk.Label - label55: Gtk.Label - label56: Gtk.Label - first_name_label: Gtk.Label - last_name_label: Gtk.Label - jabber_id_label: Gtk.Label - email_label: Gtk.Label - label57: Gtk.Label - hbuttonbox1: Gtk.ButtonBox - close_button: Gtk.Button - - class EmojiChooserBuilder(Builder): box: Gtk.Box search: Gtk.SearchEntry @@ -988,8 +954,6 @@ def get_builder(file_name: Literal['profile.ui'], widgets: list[str] = ...) -> P @overload def get_builder(file_name: Literal['bookmarks.ui'], widgets: list[str] = ...) -> BookmarksBuilder: ... @overload -def get_builder(file_name: Literal['zeroconf_information_window.ui'], widgets: list[str] = ...) -> ZeroconfInformationWindowBuilder: ... -@overload def get_builder(file_name: Literal['emoji_chooser.ui'], widgets: list[str] = ...) -> EmojiChooserBuilder: ... @overload def get_builder(file_name: Literal['manage_pep_services_window.ui'], widgets: list[str] = ...) -> ManagePepServicesWindowBuilder: ... diff --git a/gajim/gtk/controls/chat.py b/gajim/gtk/controls/chat.py index 3d048bdcb..e8aa2fcf7 100644 --- a/gajim/gtk/controls/chat.py +++ b/gajim/gtk/controls/chat.py @@ -145,7 +145,7 @@ class ChatControl(BaseControl): self.set_lock_image() self.xml.encryption_menu.set_menu_model(get_encryption_menu( - self.control_id, self._type, self.account == 'Local')) + self.control_id, self._type)) self.set_encryption_menu_icon() self.msg_textview.grab_focus() @@ -253,11 +253,8 @@ class ChatControl(BaseControl): self._get_action('send-marker-').change_state(state) # Convert to GC - if app.settings.get_account_setting(self.account, 'is_zeroconf'): - self._get_action('invite-contacts-').set_enabled(False) - else: - enabled = self.contact.supports(Namespace.MUC) and online - self._get_action('invite-contacts-').set_enabled(enabled) + enabled = self.contact.supports(Namespace.MUC) and online + self._get_action('invite-contacts-').set_enabled(enabled) # Information self._get_action('information-').set_enabled(online) diff --git a/gajim/gtk/features.py b/gajim/gtk/features.py index 9b8952666..871945d2b 100644 --- a/gajim/gtk/features.py +++ b/gajim/gtk/features.py @@ -118,14 +118,6 @@ class Features(Gtk.ApplicationWindow): _('Requires: libxss'), _('No additional requirements'), auto_status_enabled), - Feature(_('Bonjour / Zeroconf (Serverless Chat)'), - app.is_installed('ZEROCONF'), - _('Enables Gajim to automatically detected clients in a ' - 'local network for serverless chats'), - _('Requires: gir1.2-avahi-0.6'), - _('Requires: pybonjour and bonjour SDK running (%(url)s)') - % {'url': 'https://developer.apple.com/opensource/)'}, - None), Feature(_('Location detection'), app.is_installed('GEOCLUE'), _('Enables Gajim to be location-aware, if the user decides ' diff --git a/gajim/gtk/main.py b/gajim/gtk/main.py index 74c2f738e..1a8500ca3 100644 --- a/gajim/gtk/main.py +++ b/gajim/gtk/main.py @@ -663,9 +663,7 @@ class MainWindow(Gtk.ApplicationWindow, EventHelper): @staticmethod def _check_for_account() -> None: accounts = app.settings.get_accounts() - if (not accounts or accounts == ['Local'] and - not app.settings.get_account_setting('Local', 'active')): - # Either no account configured or only disabled Local account + if not accounts: def _open_wizard(): open_window('AccountWizard') diff --git a/gajim/gtk/menus.py b/gajim/gtk/menus.py index 08ac32643..94b374096 100644 --- a/gajim/gtk/menus.py +++ b/gajim/gtk/menus.py @@ -226,9 +226,7 @@ def build_accounts_menu() -> None: acc_menu.append_item(add_contact_item) for acc in accounts_list: label = escape_mnemonic(app.get_account_label(acc)) - if acc != 'Local': - acc_menu.append_submenu( - label, get_account_menu(acc)) + acc_menu.append_submenu(label, get_account_menu(acc)) else: acc_menu = get_account_menu(accounts_list[0]) modify_account_item = Gio.MenuItem.new(_('_Modify Account…'), @@ -243,7 +241,6 @@ def build_accounts_menu() -> None: def get_encryption_menu(control_id: str, control_type: ControlType, - zeroconf: bool = False ) -> Optional[Gio.Menu]: menu = Gio.Menu() menu.append( @@ -255,9 +252,6 @@ def get_encryption_menu(control_id: str, if control_type.is_privatechat: if not hasattr(plugin, 'allow_privatechat'): continue - if zeroconf: - if not hasattr(plugin, 'allow_zeroconf'): - continue menu_action = f'win.set-encryption-{control_id}::{name}' menu.append(name, menu_action) if menu.get_n_items() == 1: diff --git a/gajim/gtk/status_icon.py b/gajim/gtk/status_icon.py index ba22278b8..b3e770f52 100644 --- a/gajim/gtk/status_icon.py +++ b/gajim/gtk/status_icon.py @@ -183,7 +183,8 @@ class GtkMenuBackend(EventHelper): if not app.settings.get('main_window_skip_taskbar'): app.window.set_property('skip-taskbar-hint', False) - app.window.present_with_time(Gtk.get_current_event_time()) + # app.window.present_with_time(Gtk.get_current_event_time()) + app.app.activate() @staticmethod def _on_preferences(_widget: Gtk.MenuItem) -> None: diff --git a/gajim/gtk/zeroconf_vcard.py b/gajim/gtk/zeroconf_vcard.py deleted file mode 100644 index 7b3d4ddf6..000000000 --- a/gajim/gtk/zeroconf_vcard.py +++ /dev/null @@ -1,111 +0,0 @@ -# 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/>. - -from gi.repository import Gtk -from gi.repository import Gdk - -from gajim.common import helpers -from gajim.common import app -from gajim.common.i18n import _ - -from gajim.gui.builder import get_builder - - -class ZeroconfVcardWindow: - def __init__(self, contact, account, is_fake=False): - # the contact variable is the jid if vcard is true - self.xml = get_builder('zeroconf_information_window.ui') - self.window = self.xml.get_object('zeroconf_information_window') - - self.contact = contact - self.account = account - self.is_fake = is_fake - - self.fill_contact_page() - self.fill_personal_page() - - self.xml.connect_signals(self) - self.window.show_all() - - def on_zeroconf_information_window_destroy(self, widget): - del app.interface.instances[self.account]['infos'][self.contact.jid] - - def on_zeroconf_information_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - self.window.destroy() - - def set_value(self, entry_name, value): - try: - if value and entry_name == 'URL_label': - widget = Gtk.LinkButton(uri=value, label=value) - widget.set_alignment(0, 0) - table = self.xml.get_object('personal_info_table') - table.attach(widget, 1, 3, 2, 1) - else: - self.xml.get_object(entry_name).set_text(value) - except AttributeError: - pass - - def fill_status_label(self): - if self.xml.get_object('information_notebook').get_n_pages() < 2: - return - contact_list = app.contacts.get_contacts(self.account, self.contact.jid) - # stats holds show and status message - stats = '' - one = True # Are we adding the first line ? - if contact_list: - for c in contact_list: - if not one: - stats += '\n' - stats += helpers.get_uf_show(c.show) - if c.status: - stats += ': ' + c.status - one = False - else: # Maybe gc_vcard ? - stats = helpers.get_uf_show(self.contact.show) - if self.contact.status: - stats += ': ' + self.contact.status - status_label = self.xml.get_object('status_label') - status_label.set_text(stats) - status_label.set_tooltip_text(stats) - - def fill_contact_page(self): - self.xml.get_object('local_jid_label').set_text(self.contact.jid) - - resources = '%s (%s)' % (self.contact.resource, str( - self.contact.priority)) - uf_resources = self.contact.resource + _(' resource with priority ')\ - + str(self.contact.priority) - if not self.contact.status: - self.contact.status = '' - - self.xml.get_object('resource_prio_label').set_text(resources) - resource_prio_label_eventbox = self.xml.get_object( - 'resource_prio_label_eventbox') - resource_prio_label_eventbox.set_tooltip_text(uf_resources) - - self.fill_status_label() - - def fill_personal_page(self): - contact = app.connections[app.ZEROCONF_ACC_NAME].roster.getItem(self.contact.jid) - for key in ('1st', 'last', 'jid', 'email'): - if key not in contact['txt_dict']: - contact['txt_dict'][key] = '' - self.xml.get_object('first_name_label').set_text(contact['txt_dict']['1st']) - self.xml.get_object('last_name_label').set_text(contact['txt_dict']['last']) - self.xml.get_object('jabber_id_label').set_text(contact['txt_dict']['jid']) - self.xml.get_object('email_label').set_text(contact['txt_dict']['email']) - - def on_close_button_clicked(self, widget): - self.window.destroy() diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index fa1593994..f8447d3fe 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -71,7 +71,6 @@ from gajim.common.events import FileProgress from gajim.common.events import FileError from gajim.common.events import MucAdded from gajim.common.events import PasswordRequired -from gajim.common.zeroconf import connection_zeroconf from gajim.common.helpers import ask_for_status_message from gajim.common.structs import OutgoingMessage from gajim.common.i18n import _ @@ -88,7 +87,6 @@ from gajim.common.modules.httpupload import HTTPFileTransfer from gajim.gui.dialogs import DialogButton from gajim.gui.dialogs import ErrorDialog from gajim.gui.dialogs import ConfirmationDialog -from gajim.gui.dialogs import InputDialog from gajim.gui.filechoosers import FileChooserDialog from gajim.gui.filetransfer import FileTransfersWindow from gajim.gui.menus import build_accounts_menu @@ -112,11 +110,6 @@ class Interface: self.preview_manager = PreviewManager() - for account in app.settings.get_accounts(): - if app.settings.get_account_setting(account, 'is_zeroconf'): - app.ZEROCONF_ACC_NAME = account - break - app.idlequeue = idlequeue.get_idlequeue() # resolve and keep current record of resolved hosts app.socks5queue = socks5.SocksQueue( @@ -132,15 +125,8 @@ class Interface: self._create_core_handlers_list() self._register_core_handlers() - # self.create_zeroconf_default_config() - # if app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, 'active') \ - # and app.is_installed('ZEROCONF'): - # app.connections[app.ZEROCONF_ACC_NAME] = \ - # connection_zeroconf.ConnectionZeroconf(app.ZEROCONF_ACC_NAME) - for account in app.settings.get_accounts(): - if (not app.settings.get_account_setting(account, 'is_zeroconf') and - app.settings.get_account_setting(account, 'active')): + if app.settings.get_account_setting(account, 'active'): client = Client(account) app.connections[account] = client app.ged.register_event_handler( @@ -171,7 +157,6 @@ class Interface: 'http-auth-received': [self.handle_event_http_auth], 'password-required': [self.handle_event_password_required], 'client-cert-passphrase': [self.handle_event_client_cert_passphrase], - 'zeroconf-name-conflict': [self.handle_event_zc_name_conflict], 'signed-in': [self.handle_event_signed_in], 'presence-received': [self.handle_event_presence], 'our-show': [self.handle_event_status], @@ -264,29 +249,6 @@ class Interface: open_window('PasswordDialog', account=event.conn.name, event=event) @staticmethod - def handle_event_zc_name_conflict(event): - def _on_ok(new_name): - app.settings.set_account_setting(event.conn.name, 'name', new_name) - event.conn.username = new_name - event.conn.change_status( - event.conn.status, event.conn.status_message) - - def _on_cancel(*args): - event.conn.change_status('offline', '') - - InputDialog( - _('Username Conflict'), - _('Username Conflict'), - _('Please enter a new username for your local account'), - [DialogButton.make('Cancel', - callback=_on_cancel), - DialogButton.make('Accept', - text=_('_OK'), - callback=_on_ok)], - input_str=event.alt_name, - transient_for=app.window).show() - - @staticmethod def handle_event_signed_in(event): """ SIGNED_IN event is emitted when we sign in, so handle it @@ -692,11 +654,7 @@ class Interface: window.add_account(account) def enable_account(self, account: str) -> None: - if account == app.ZEROCONF_ACC_NAME: - app.connections[account] = connection_zeroconf.ConnectionZeroconf( - account) - else: - app.connections[account] = Client(account) + app.connections[account] = Client(account) app.plugin_manager.register_modules_for_account( app.connections[account]) @@ -713,10 +671,7 @@ class Interface: 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.settings.get_account_setting(account, + app.nicks[account] = app.settings.get_account_setting(account, 'name') app.block_signed_in_notifications[account] = True @@ -746,8 +701,6 @@ class Interface: app.ged.raise_event(AccountDisabled(account=account)) - if account == app.ZEROCONF_ACC_NAME: - app.connections[account].disable_account() app.connections[account].cleanup() del app.connections[account] del self.instances[account] @@ -942,36 +895,6 @@ class Interface: view.updateNamespace({'gajim': app}) app.ipython_window = window - def create_zeroconf_default_config(self) -> None: - if app.settings.get_account_setting(app.ZEROCONF_ACC_NAME, 'name'): - return - log.info('Creating zeroconf account') - app.settings.add_account(app.ZEROCONF_ACC_NAME) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'autoconnect', - True) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'no_log_for', - '') - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'password', - 'zeroconf') - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'sync_with_global_status', - True) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'custom_port', - 5298) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'is_zeroconf', - True) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'use_ft_proxies', - False) - app.settings.set_account_setting(app.ZEROCONF_ACC_NAME, - 'active', - False) - def run(self, _application: Gtk.Application) -> None: # Creating plugin manager from gajim import plugins diff --git a/gajim/plugins/pluginmanager.py b/gajim/plugins/pluginmanager.py index 99afcd595..cc0223756 100644 --- a/gajim/plugins/pluginmanager.py +++ b/gajim/plugins/pluginmanager.py @@ -500,8 +500,6 @@ class PluginManager(metaclass=Singleton): return for con in app.connections.values(): for module in plugin.modules: - if not module.zeroconf and con.name == 'Local': - continue instance, name = module.get_instance(con) modules.register_single_module(con, instance, name) @@ -613,8 +611,6 @@ class PluginManager(metaclass=Singleton): for module in plugin.modules: instance, name = module.get_instance(con) - if not module.zeroconf and con.name == 'Local': - continue modules.register_single_module(con, instance, name) for handler in instance.handlers: diff --git a/gajim/remote_control.py b/gajim/remote_control.py index c016a499e..2c36b13ad 100644 --- a/gajim/remote_control.py +++ b/gajim/remote_control.py @@ -797,16 +797,11 @@ class GajimRemote(Server): accounts = app.connections.keys() for acct in accounts: if app.account_is_available(acct): - if not app.connections[acct].is_zeroconf: - account = acct - break + account = acct + break if not account: return - if app.connections[account].is_zeroconf: - # zeroconf not support groupchats - return - app.interface.show_add_join_groupchat(account, room_jid, password=password) @@ -19,9 +19,6 @@ ignore_missing_imports = True [mypy-OpenSSL.*] ignore_missing_imports = True -[mypy-pybonjour.*] -ignore_missing_imports = True - [mypy-encodings.*] ignore_missing_imports = True diff --git a/win/_base.sh b/win/_base.sh index 9f1a4514f..d4aef121e 100644 --- a/win/_base.sh +++ b/win/_base.sh @@ -107,7 +107,6 @@ function install_deps { PIP_REQUIREMENTS="\ git+https://dev.gajim.org/gajim/python-nbxmpp.git -git+https://dev.gajim.org/lovetox/pybonjour-python3.git keyring python-gnupg python-axolotl diff --git a/win/dev_env.sh b/win/dev_env.sh index c09a89a16..66ac4ccc3 100644 --- a/win/dev_env.sh +++ b/win/dev_env.sh @@ -26,7 +26,6 @@ function main { PIP_REQUIREMENTS="\ git+https://dev.gajim.org/gajim/python-nbxmpp.git -git+https://dev.gajim.org/lovetox/pybonjour-python3.git python-axolotl python-gnupg keyring |