# 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 . import logging from collections import namedtuple from datetime import timedelta import nbxmpp from gi.repository import Gtk from gi.repository import Gdk from gajim.common import app from gajim.common import ged from gajim.common.i18n import _ log = logging.getLogger('gajim.gtk.serverinfo') class ServerInfoDialog(Gtk.Dialog): def __init__(self, account): super().__init__(title=_('Server Info'), transient_for=None, destroy_with_parent=True) self.account = account self.set_transient_for(app.interface.roster.window) self.set_resizable(False) grid = Gtk.Grid() grid.set_name('ServerInfoGrid') grid.set_row_spacing(10) grid.set_hexpand(True) self.info_listbox = Gtk.ListBox() self.info_listbox.set_selection_mode(Gtk.SelectionMode.NONE) self.info_listbox.set_header_func(self.header_func, 'Information') grid.attach(self.info_listbox, 0, 0, 1, 1) self.feature_listbox = Gtk.ListBox() self.feature_listbox.set_selection_mode(Gtk.SelectionMode.NONE) self.feature_listbox.set_header_func(self.header_func, 'Features') grid.attach(self.feature_listbox, 0, 1, 1, 1) self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard_button = Gtk.Button(halign=Gtk.Align.END) clp_image = Gtk.Image.new_from_icon_name('edit-copy-symbolic', Gtk.IconSize.BUTTON) clipboard_button.set_image(clp_image) clipboard_button.set_tooltip_text(_('Copy info to clipboard')) clipboard_button.connect('clicked', self.on_clipboard_button_clicked) box = self.get_content_area() box.pack_start(grid, True, True, 0) box.pack_start(clipboard_button, False, True, 0) box.set_property('margin', 12) box.set_spacing(18) self.connect('response', self.on_response) self.connect('destroy', self.on_destroy) app.ged.register_event_handler('version-result-received', ged.CORE, self._nec_version_result_received) app.ged.register_event_handler('server-disco-received', ged.GUI1, self._server_disco_received) self.version = '' self.uptime = '' self.hostname = app.get_hostname_from_account(account) con = app.connections[account] con.get_module('SoftwareVersion').request_os_info(self.hostname, None) self.request_last_activity() for feature in self.get_features(): self.add_feature(feature) for info in self.get_infos(): self.add_info(info) self.show_all() @staticmethod def header_func(row, before, user_data): if before: row.set_header(None) else: label = Gtk.Label(label=user_data) label.set_halign(Gtk.Align.START) row.set_header(label) @staticmethod def update(func, listbox): for index, item in enumerate(func()): row = listbox.get_row_at_index(index) row.get_child().update(item) row.set_tooltip_text(row.get_child().tooltip) def request_last_activity(self): if not app.account_is_connected(self.account): return con = app.connections[self.account] iq = nbxmpp.Iq(to=self.hostname, typ='get', queryNS=nbxmpp.NS_LAST) con.connection.SendAndCallForResponse(iq, self._on_last_activity) def _on_last_activity(self, stanza): if 'server_info' not in app.interface.instances[self.account]: # Window got closed in the meantime return if not nbxmpp.isResultNode(stanza): log.warning('Received malformed result: %s', stanza) return if stanza.getQueryNS() != nbxmpp.NS_LAST: log.warning('Wrong namespace on result: %s', stanza) return try: seconds = int(stanza.getQuery().getAttr('seconds')) except (ValueError, TypeError, AttributeError): log.exception('Received malformed last activity result') else: delta = timedelta(seconds=seconds) hours = 0 if seconds >= 3600: hours = delta.seconds // 3600 self.uptime = _('%(days)s days, %(hours)s hours') % { 'days': delta.days, 'hours': hours} self.update(self.get_infos, self.info_listbox) def _nec_version_result_received(self, obj): if obj.jid != self.hostname: return self.version = obj.client_info or _('Unknown') self.update(self.get_infos, self.info_listbox) def _server_disco_received(self, obj): self.update(self.get_features, self.feature_listbox) def add_feature(self, feature): item = FeatureItem(feature) self.feature_listbox.add(item) item.get_parent().set_tooltip_text(item.tooltip) def get_features(self): con = app.connections[self.account] Feature = namedtuple('Feature', ['name', 'available', 'tooltip', 'enabled']) carbons_enabled = app.config.get_per('accounts', self.account, 'enable_message_carbons') mam_enabled = app.config.get_per('accounts', self.account, 'sync_logs_with_server') return [ Feature('XEP-0016: Privacy Lists', con.get_module('PrivacyLists').supported, '', None), Feature('XEP-0045: Multi-User Chat', con.muc_jid, '', None), Feature('XEP-0054: vcard-temp', con.get_module('VCardTemp').supported, '', None), Feature('XEP-0163: Personal Eventing Protocol', con.get_module('PEP').supported, '', None), Feature('XEP-0163: #publish-options', con.get_module('PubSub').publish_options, '', None), Feature('XEP-0191: Blocking Command', con.get_module('Blocking').supported, nbxmpp.NS_BLOCKING, None), Feature('XEP-0198: Stream Management', con.sm.enabled, nbxmpp.NS_STREAM_MGMT, None), Feature('XEP-0258: Security Labels in XMPP', con.get_module('SecLabels').supported, nbxmpp.NS_SECLABEL, None), Feature('XEP-0280: Message Carbons', con.get_module('Carbons').supported, nbxmpp.NS_CARBONS, carbons_enabled), Feature('XEP-0313: Message Archive Management', con.get_module('MAM').archiving_namespace, con.get_module('MAM').archiving_namespace, mam_enabled), Feature('XEP-0363: HTTP File Upload', con.get_module('HTTPUpload').available, con.get_module('HTTPUpload').httpupload_namespace, None), Feature('XEP-0398: Avatar Conversion', con.avatar_conversion, '', None) ] def add_info(self, info): self.info_listbox.add(ServerInfoItem(info)) def get_infos(self): Info = namedtuple('Info', ['name', 'value', 'tooltip']) return [ Info(_('Hostname'), self.hostname, None), Info(_('Server Software'), self.version, None), Info(_('Server Uptime'), self.uptime, None)] def on_clipboard_button_clicked(self, widget): server_software = 'Server Software: %s\n' % self.get_infos()[1].value server_features = '' for feature in self.get_features(): if feature.available: available = 'Yes' else: available = 'No' if feature.tooltip != '': tooltip = '(%s)' % feature.tooltip else: tooltip = '' server_features += '%s: %s %s\n' % (feature.name, available, tooltip) clipboard_text = server_software + server_features self.clipboard.set_text(clipboard_text, -1) def on_response(self, dialog, response): if response == Gtk.ResponseType.OK: self.destroy() def on_destroy(self, *args): del app.interface.instances[self.account]['server_info'] app.ged.remove_event_handler('version-result-received', ged.CORE, self._nec_version_result_received) app.ged.remove_event_handler('server-disco-received', ged.GUI1, self._server_disco_received) class FeatureItem(Gtk.Grid): def __init__(self, feature): super().__init__() self.tooltip = feature.tooltip self.set_column_spacing(6) self.icon = Gtk.Image() self.feature_label = Gtk.Label(label=feature.name) self.set_feature(feature.available, feature.enabled) self.add(self.icon) self.add(self.feature_label) def set_feature(self, available, enabled): self.icon.get_style_context().remove_class('error-color') self.icon.get_style_context().remove_class('warning-color') self.icon.get_style_context().remove_class('success-color') if not available: self.icon.set_from_icon_name('window-close-symbolic', Gtk.IconSize.MENU) self.icon.get_style_context().add_class('error-color') elif enabled is False: self.icon.set_from_icon_name('dialog-warning-symbolic', Gtk.IconSize.MENU) self.tooltip += _('\nDisabled in config') self.icon.get_style_context().add_class('warning-color') else: self.icon.set_from_icon_name('emblem-ok-symbolic', Gtk.IconSize.MENU) self.icon.get_style_context().add_class('success-color') def update(self, feature): self.tooltip = feature.tooltip self.set_feature(feature.available, feature.enabled) class ServerInfoItem(Gtk.Grid): def __init__(self, info): super().__init__() self.tooltip = info.tooltip self.set_hexpand(True) self.insert_column(0) self.set_column_homogeneous(True) self.info = Gtk.Label(label=info.name) self.info.set_halign(Gtk.Align.START) self.info.set_hexpand(True) self.value = Gtk.Label(label=info.value) self.value.set_halign(Gtk.Align.START) self.value.set_hexpand(True) self.value.set_selectable(True) self.add(self.info) self.add(self.value) def update(self, info): self.value.set_text(info.value)