From c5ec3e45e4562246ba65ac2ca19eadfdfee627ca Mon Sep 17 00:00:00 2001 From: Julian Sparber Date: Fri, 9 Feb 2024 20:08:25 +0100 Subject: [PATCH] components: Drop telepathy client The telepathy client component has been unmaintained for a long time. Now that we rework the notifications massively it's time to drop the support for it. Part-of: --- .../widgets/_notifications.scss | 39 - js/js-resources.gresource.xml | 1 - js/misc/dependencies.js | 9 - js/ui/components/telepathyClient.js | 982 ------------------ js/ui/messageTray.js | 9 +- js/ui/sessionMode.js | 4 +- po/POTFILES.in | 1 - 7 files changed, 4 insertions(+), 1041 deletions(-) delete mode 100644 js/ui/components/telepathyClient.js diff --git a/data/theme/gnome-shell-sass/widgets/_notifications.scss b/data/theme/gnome-shell-sass/widgets/_notifications.scss index 9b358cfaa..a5497e20f 100644 --- a/data/theme/gnome-shell-sass/widgets/_notifications.scss +++ b/data/theme/gnome-shell-sass/widgets/_notifications.scss @@ -23,42 +23,3 @@ $notification_banner_width: 34em; @extend %bubble_button; } } - -// counter -.summary-source-counter { - font-size: $base_font_size - 1pt; - font-weight: bold; - height: 1.6em; - width: 1.6em; - -shell-counter-overlap-x: 3px; - -shell-counter-overlap-y: 3px; - background-color: $selected_bg_color; - color: $selected_fg_color; - border: 2px solid $fg_color; - box-shadow: 0 2px 2px rgba(0,0,0,0.5); - border-radius: 0.9em; // should be 0.8 but whatever; wish I could do 50%; -} - -// chat bubbles -.chat-body { spacing: 5px; } -.chat-response { margin: 5px; } -.chat-log-message { color: darken($fg_color,10%); } -.chat-new-group { padding-top: 1em; } -.chat-received { - padding-left: 4px; - &:rtl { padding-left: 0px; padding-right: 4px; } -} - -.chat-sent { - padding-left: 18pt; - color: lighten($fg_color, 15%); - &:rtl { padding-left: 0; padding-right: 18pt; } -} - -.chat-meta-message { - @extend %caption; - padding-left: 4px; - font-weight: bold; - color: lighten($fg_color,18%); - &:rtl { padding-left: 0; padding-right: 4px; } -} diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index ad83acc30..fda48d398 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -135,7 +135,6 @@ ui/components/keyring.js ui/components/networkAgent.js ui/components/polkitAgent.js - ui/components/telepathyClient.js ui/status/accessibility.js ui/status/autoRotate.js diff --git a/js/misc/dependencies.js b/js/misc/dependencies.js index c61753e65..d4837af04 100644 --- a/js/misc/dependencies.js +++ b/js/misc/dependencies.js @@ -63,12 +63,3 @@ try { } catch { console.debug('Malcontent is not available, parental controls integration will be disabled.'); } - - -try { - // Telepathy is optional, so catch any errors loading it - gi.require('TelepathyGLib', '0.12'); - gi.require('TelepathyLogger', '0.2'); -} catch { - console.debug('Telepathy is not available, chat integration will be disabled.'); -} diff --git a/js/ui/components/telepathyClient.js b/js/ui/components/telepathyClient.js deleted file mode 100644 index 9f1fa48df..000000000 --- a/js/ui/components/telepathyClient.js +++ /dev/null @@ -1,982 +0,0 @@ -// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -import Clutter from 'gi://Clutter'; -import Gio from 'gi://Gio'; -import GLib from 'gi://GLib'; -import GObject from 'gi://GObject'; -import St from 'gi://St'; - -let Tpl = null; -let Tp = null; -try { - ({default: Tp} = await import('gi://TelepathyGlib')); - ({default: Tpl} = await import('gi://TelepathyLogger')); - - Gio._promisify(Tp.Channel.prototype, 'close_async'); - Gio._promisify(Tp.TextChannel.prototype, 'send_message_async'); - Gio._promisify(Tp.ChannelDispatchOperation.prototype, 'claim_with_async'); - Gio._promisify(Tpl.LogManager.prototype, 'get_filtered_events_async'); -} catch { - console.debug('Skipping chat support, telepathy not found'); -} - -import * as History from '../../misc/history.js'; -import * as Main from '../main.js'; -import * as MessageList from '../messageList.js'; -import * as MessageTray from '../messageTray.js'; -import * as Params from '../../misc/params.js'; -import * as Util from '../../misc/util.js'; - -const HAVE_TP = Tp != null && Tpl != null; - -// See Notification.appendMessage -const SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes -const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes -const SCROLLBACK_RECENT_LENGTH = 20; -const SCROLLBACK_IDLE_LENGTH = 5; - -// See Source._displayPendingMessages -const SCROLLBACK_HISTORY_LINES = 10; - -// See Notification._onEntryChanged -const COMPOSING_STOP_TIMEOUT = 5; - -const CHAT_EXPAND_LINES = 12; - -const NotificationDirection = { - SENT: 'chat-sent', - RECEIVED: 'chat-received', -}; - -const ChatMessage = HAVE_TP ? GObject.registerClass({ - Properties: { - 'message-type': GObject.ParamSpec.int( - 'message-type', 'message-type', 'message-type', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - Math.min(...Object.values(Tp.ChannelTextMessageType)), - Math.max(...Object.values(Tp.ChannelTextMessageType)), - Tp.ChannelTextMessageType.NORMAL), - 'text': GObject.ParamSpec.string( - 'text', 'text', 'text', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - null), - 'sender': GObject.ParamSpec.string( - 'sender', 'sender', 'sender', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - null), - 'timestamp': GObject.ParamSpec.int64( - 'timestamp', 'timestamp', 'timestamp', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - 0, Number.MAX_SAFE_INTEGER, 0), - 'direction': GObject.ParamSpec.string( - 'direction', 'direction', 'direction', - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, - null), - }, -}, class ChatMessageClass extends GObject.Object { - static newFromTpMessage(tpMessage, direction) { - return new ChatMessage({ - 'message-type': tpMessage.get_message_type(), - 'text': tpMessage.to_text()[0], - 'sender': tpMessage.sender.alias, - 'timestamp': direction === NotificationDirection.RECEIVED - ? tpMessage.get_received_timestamp() : tpMessage.get_sent_timestamp(), - direction, - }); - } - - static newFromTplTextEvent(tplTextEvent) { - let direction = - tplTextEvent.get_sender().get_entity_type() === Tpl.EntityType.SELF - ? NotificationDirection.SENT : NotificationDirection.RECEIVED; - - return new ChatMessage({ - 'message-type': tplTextEvent.get_message_type(), - 'text': tplTextEvent.get_message(), - 'sender': tplTextEvent.get_sender().get_alias(), - 'timestamp': tplTextEvent.get_timestamp(), - direction, - }); - } -}) : null; - - -class TelepathyComponent { - constructor() { - this._client = null; - - if (!HAVE_TP) - return; // Telepathy isn't available - - this._client = new TelepathyClient(); - } - - enable() { - if (!this._client) - return; - - try { - this._client.register(); - } catch (e) { - throw new Error(`Could not register Telepathy client. Error: ${e}`); - } - - if (!this._client.account_manager.is_prepared(Tp.AccountManager.get_feature_quark_core())) - this._client.account_manager.prepare_async(null, null); - } - - disable() { - if (!this._client) - return; - - this._client.unregister(); - } -} - -const TelepathyClient = HAVE_TP ? GObject.registerClass( -class TelepathyClient extends Tp.BaseClient { - _init() { - // channel path -> ChatSource - this._chatSources = {}; - this._chatState = Tp.ChannelChatState.ACTIVE; - - // account path -> AccountNotification - this._accountNotifications = {}; - - // Define features we want - this._accountManager = Tp.AccountManager.dup(); - let factory = this._accountManager.get_factory(); - factory.add_account_features([Tp.Account.get_feature_quark_connection()]); - factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]); - factory.add_channel_features([Tp.Channel.get_feature_quark_contacts()]); - factory.add_contact_features([ - Tp.ContactFeature.ALIAS, - Tp.ContactFeature.AVATAR_DATA, - Tp.ContactFeature.PRESENCE, - Tp.ContactFeature.SUBSCRIPTION_STATES, - ]); - - // Set up a SimpleObserver, which will call _observeChannels whenever a - // channel matching its filters is detected. - // The second argument, recover, means _observeChannels will be run - // for any existing channel as well. - super._init({ - name: 'GnomeShell', - account_manager: this._accountManager, - uniquify_name: true, - }); - - // We only care about single-user text-based chats - let filter = {}; - filter[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT; - filter[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.HandleType.CONTACT; - - this.set_observer_recover(true); - this.add_observer_filter(filter); - this.add_approver_filter(filter); - this.add_handler_filter(filter); - - // Allow other clients (such as Empathy) to preempt our channels if - // needed - this.set_delegated_channels_callback( - this._delegatedChannelsCb.bind(this)); - } - - vfunc_observe_channels(...args) { - let [account, conn, channels, dispatchOp_, requests_, context] = args; - let len = channels.length; - for (let i = 0; i < len; i++) { - let channel = channels[i]; - let [targetHandle_, targetHandleType] = channel.get_handle(); - - if (channel.get_invalidated()) - continue; - - /* Only observe contact text channels */ - if (!(channel instanceof Tp.TextChannel) || - targetHandleType !== Tp.HandleType.CONTACT) - continue; - - this._createChatSource(account, conn, channel, channel.get_target_contact()); - } - - context.accept(); - } - - _createChatSource(account, conn, channel, contact) { - if (this._chatSources[channel.get_object_path()]) - return; - - let source = new ChatSource(account, conn, channel, contact, this); - - this._chatSources[channel.get_object_path()] = source; - source.connect('destroy', () => { - delete this._chatSources[channel.get_object_path()]; - }); - } - - vfunc_handle_channels(...args) { - let [account, conn, channels, requests_, userActionTime_, context] = args; - this._handlingChannels(account, conn, channels, true); - context.accept(); - } - - _handlingChannels(account, conn, channels, notify) { - let len = channels.length; - for (let i = 0; i < len; i++) { - let channel = channels[i]; - - // We can only handle text channel, so close any other channel - if (!(channel instanceof Tp.TextChannel)) { - channel.close_async(); - continue; - } - - if (channel.get_invalidated()) - continue; - - // 'notify' will be true when coming from an actual HandleChannels - // call, and not when from a successful Claim call. The point is - // we don't want to notify for a channel we just claimed which - // has no new messages (for example, a new channel which only has - // a delivery notification). We rely on _displayPendingMessages() - // and _messageReceived() to notify for new messages. - - // But we should still notify from HandleChannels because the - // Telepathy spec states that handlers must foreground channels - // in HandleChannels calls which are already being handled. - - if (notify && this.is_handling_channel(channel)) { - // We are already handling the channel, display the source - let source = this._chatSources[channel.get_object_path()]; - if (source) - source.showNotification(); - } - } - } - - vfunc_add_dispatch_operation(...args) { - let [account, conn, channels, dispatchOp, context] = args; - let channel = channels[0]; - let chanType = channel.get_channel_type(); - - if (channel.get_invalidated()) { - context.fail(new Tp.Error({ - code: Tp.Error.INVALID_ARGUMENT, - message: 'Channel is invalidated', - })); - return; - } - - if (chanType === Tp.IFACE_CHANNEL_TYPE_TEXT) { - this._approveTextChannel(account, conn, channel, dispatchOp, context); - } else { - context.fail(new Tp.Error({ - code: Tp.Error.INVALID_ARGUMENT, - message: 'Unsupported channel type', - })); - } - } - - async _approveTextChannel(account, conn, channel, dispatchOp, context) { - let [targetHandle_, targetHandleType] = channel.get_handle(); - - if (targetHandleType !== Tp.HandleType.CONTACT) { - context.fail(new Tp.Error({ - code: Tp.Error.INVALID_ARGUMENT, - message: 'Unsupported handle type', - })); - return; - } - - context.accept(); - - // Approve private text channels right away as we are going to handle it - try { - await dispatchOp.claim_with_async(this); - this._handlingChannels(account, conn, [channel], false); - } catch (err) { - log(`Failed to claim channel: ${err}`); - } - } - - _delegatedChannelsCb(_client, _channels) { - // Nothing to do as we don't make a distinction between observed and - // handled channels. - } -}) : null; - -const ChatSource = HAVE_TP ? GObject.registerClass( -class ChatSource extends MessageTray.Source { - constructor(account, conn, channel, contact, client) { - const appId = account.protocol_name === 'irc' - ? 'org.gnome.Polari' - : 'empathy'; - const policy = - new MessageTray.NotificationApplicationPolicy(appId); - - super({policy}); - - this._account = account; - this._contact = contact; - this._client = client; - - this._pendingMessages = []; - - this._conn = conn; - this._channel = channel; - - this._notifyTimeoutId = 0; - - this._presence = contact.get_presence_type(); - - this._channel.connectObject( - 'invalidated', this._channelClosed.bind(this), - 'message-sent', this._messageSent.bind(this), - 'message-received', this._messageReceived.bind(this), - 'pending-message-removed', this._pendingRemoved.bind(this), this); - - this._contact.connectObject( - 'notify::alias', this._updateAlias.bind(this), - 'notify::avatar-file', this._updateAvatarIcon.bind(this), this); - - this._updateAlias(); - this._updateAvatarIcon(); - // Add ourselves as a source. - Main.messageTray.add(this); - - this._getLogMessages(); - } - - _ensureNotification() { - if (this._notification) - return; - - this._notification = new ChatNotification(this); - this._notification.connectObject( - 'activated', this.open.bind(this), - 'destroy', () => (this._notification = null), - 'updated', () => { - if (this._banner && this._banner.expanded) - this._ackMessages(); - }, this); - this.pushNotification(this._notification); - } - - createBanner() { - this._banner = new ChatNotificationBanner(this._notification); - - // We ack messages when the user expands the new notification - this._banner.connectObject( - 'expanded', this._ackMessages.bind(this), - 'destroy', () => (this._banner = null), this); - - return this._banner; - } - - _updateAlias() { - let oldAlias = this.title; - let newAlias = this._contact.get_alias(); - - if (oldAlias === newAlias) - return; - - this.title = newAlias; - if (this._notification) - this._notification.appendAliasChange(oldAlias, newAlias); - } - - _updateAvatarIcon() { - let file = this._contact.get_avatar_file(); - if (file) { - if (this.icon instanceof Gio.FileIcon) - this.icon.file = file; - else - this.icon = new Gio.FileIcon({file}); - } else if (!(this.icon instanceof Gio.ThemedIcon)) { - this.iconName = 'avatar-default'; - } - - if (this._notification && this.icon !== this._notification.gicon) { - this._notification.update( - this._notification.title, - this._notification.bannerBodyText, - {gicon: this.icon}); - } - } - - open() { - Main.overview.hide(); - Main.panel.closeCalendar(); - - if (this._client.is_handling_channel(this._channel)) { - // We are handling the channel, try to pass it to Empathy or Polari - // (depending on the channel type) - // We don't check if either app is available - mission control will - // fallback to something else if activation fails - - let target; - if (this._channel.connection.protocol_name === 'irc') - target = 'org.freedesktop.Telepathy.Client.Polari'; - else - target = 'org.freedesktop.Telepathy.Client.Empathy.Chat'; - this._client.delegate_channels_async([this._channel], global.get_current_time(), target, null); - } else { - // We are not the handler, just ask to present the channel - let dbus = Tp.DBusDaemon.dup(); - let cd = Tp.ChannelDispatcher.new(dbus); - - cd.present_channel_async(this._channel, global.get_current_time(), null); - } - } - - async _getLogMessages() { - let logManager = Tpl.LogManager.dup_singleton(); - let entity = Tpl.Entity.new_from_tp_contact(this._contact, Tpl.EntityType.CONTACT); - - const [events] = await logManager.get_filtered_events_async( - this._account, entity, - Tpl.EventTypeMask.TEXT, SCROLLBACK_HISTORY_LINES, - null); - - let logMessages = events.map(e => ChatMessage.newFromTplTextEvent(e)); - this._ensureNotification(); - - let pendingTpMessages = this._channel.get_pending_messages(); - let pendingMessages = []; - - for (let i = 0; i < pendingTpMessages.length; i++) { - let message = pendingTpMessages[i]; - - if (message.get_message_type() === Tp.ChannelTextMessageType.DELIVERY_REPORT) - continue; - - pendingMessages.push(ChatMessage.newFromTpMessage(message, - NotificationDirection.RECEIVED)); - - this._pendingMessages.push(message); - } - - this.countUpdated(); - - let showTimestamp = false; - - for (let i = 0; i < logMessages.length; i++) { - let logMessage = logMessages[i]; - let isPending = false; - - // Skip any log messages that are also in pendingMessages - for (let j = 0; j < pendingMessages.length; j++) { - let pending = pendingMessages[j]; - if (logMessage.timestamp === pending.timestamp && logMessage.text === pending.text) { - isPending = true; - break; - } - } - - if (!isPending) { - showTimestamp = true; - this._notification.appendMessage(logMessage, true, ['chat-log-message']); - } - } - - if (showTimestamp) - this._notification.appendTimestamp(); - - for (let i = 0; i < pendingMessages.length; i++) - this._notification.appendMessage(pendingMessages[i], true); - - if (pendingMessages.length > 0) - this.showNotification(); - } - - destroy(reason) { - if (this._client.is_handling_channel(this._channel)) { - this._ackMessages(); - // The chat box has been destroyed so it can't - // handle the channel any more. - this._channel.close_async(); - } else { - // Don't indicate any unread messages when the notification - // that represents them has been destroyed. - this._pendingMessages = []; - this.countUpdated(); - } - - // Keep source alive while the channel is open - if (reason !== MessageTray.NotificationDestroyedReason.SOURCE_CLOSED) - return; - - if (this._destroyed) - return; - - this._destroyed = true; - this._channel.disconnectObject(this); - this._contact.disconnectObject(this); - - super.destroy(reason); - } - - _channelClosed() { - this.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED); - } - - /* All messages are new messages for Telepathy sources */ - get count() { - return this._pendingMessages.length; - } - - get unseenCount() { - return this.count; - } - - get countVisible() { - return this.count > 0; - } - - _messageReceived(channel, message) { - if (message.get_message_type() === Tp.ChannelTextMessageType.DELIVERY_REPORT) - return; - - this._ensureNotification(); - this._pendingMessages.push(message); - this.countUpdated(); - - message = ChatMessage.newFromTpMessage(message, - NotificationDirection.RECEIVED); - this._notification.appendMessage(message); - - // Wait a bit before notifying for the received message, a handler - // could ack it in the meantime. - if (this._notifyTimeoutId !== 0) - GLib.source_remove(this._notifyTimeoutId); - this._notifyTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, - this._notifyTimeout.bind(this)); - GLib.Source.set_name_by_id(this._notifyTimeoutId, '[gnome-shell] this._notifyTimeout'); - } - - _notifyTimeout() { - if (this._pendingMessages.length !== 0) - this.showNotification(); - - this._notifyTimeoutId = 0; - - return GLib.SOURCE_REMOVE; - } - - // This is called for both messages we send from - // our client and other clients as well. - _messageSent(channel, message, _flags, _token) { - this._ensureNotification(); - message = ChatMessage.newFromTpMessage(message, - NotificationDirection.SENT); - this._notification.appendMessage(message); - } - - showNotification() { - super.showNotification(this._notification); - } - - respond(text) { - let type; - if (text.slice(0, 4) === '/me ') { - type = Tp.ChannelTextMessageType.ACTION; - text = text.slice(4); - } else { - type = Tp.ChannelTextMessageType.NORMAL; - } - - let msg = Tp.ClientMessage.new_text(type, text); - this._channel.send_message_async(msg, 0); - } - - setChatState(state) { - // We don't want to send COMPOSING every time a letter is typed into - // the entry. We send the state only when it changes. Telepathy/Empathy - // might change it behind our back if the user is using both - // gnome-shell's entry and the Empathy conversation window. We could - // keep track of it with the ChatStateChanged signal but it is good - // enough right now. - if (state !== this._chatState) { - this._chatState = state; - this._channel.set_chat_state_async(state, null); - } - } - - _pendingRemoved(channel, message) { - let idx = this._pendingMessages.indexOf(message); - - if (idx >= 0) { - this._pendingMessages.splice(idx, 1); - this.countUpdated(); - } - - if (this._pendingMessages.length === 0 && - this._banner && !this._banner.expanded) - this._banner.hide(); - } - - _ackMessages() { - // Don't clear our messages here, tp-glib will send a - // 'pending-message-removed' for each one. - this._channel.ack_all_pending_messages_async(null); - } -}) : null; - -const ChatNotificationMessage = HAVE_TP ? GObject.registerClass( -class ChatNotificationMessage extends GObject.Object { - _init(props = {}) { - super._init(); - this.set(props); - } -}) : null; - -const ChatNotification = HAVE_TP ? GObject.registerClass({ - Signals: { - 'message-removed': {param_types: [ChatNotificationMessage.$gtype]}, - 'message-added': {param_types: [ChatNotificationMessage.$gtype]}, - 'timestamp-changed': {param_types: [ChatNotificationMessage.$gtype]}, - }, -}, class ChatNotification extends MessageTray.Notification { - constructor(source) { - super(source, source.title, null, null); - this.setUrgency(MessageTray.Urgency.HIGH); - this.setResident(true); - - this.messages = []; - this._timestampTimeoutId = 0; - } - - destroy(reason) { - if (this._timestampTimeoutId) - GLib.source_remove(this._timestampTimeoutId); - this._timestampTimeoutId = 0; - super.destroy(reason); - } - - /** - * @param {object} message - An object with the properties - * {string} message.text - the body of the message, - * {Tp.ChannelTextMessageType} message.messageType - the type - * {string} message.sender - the name of the sender, - * {number} message.timestamp - the time the message was sent - * {NotificationDirection} message.direction - a #NotificationDirection - * - * @param {bool} noTimestamp - Whether to add a timestamp. If %true, - * no timestamp will be added, regardless of the difference since - * the last timestamp - */ - appendMessage(message, noTimestamp) { - let messageBody = GLib.markup_escape_text(message.text, -1); - let styles = [message.direction]; - - if (message.messageType === Tp.ChannelTextMessageType.ACTION) { - let senderAlias = GLib.markup_escape_text(message.sender, -1); - messageBody = `${senderAlias} ${messageBody}`; - styles.push('chat-action'); - } - - if (message.direction === NotificationDirection.RECEIVED) { - this.update(this.source.title, messageBody, { - datetime: GLib.DateTime.new_from_unix_local(message.timestamp), - bannerMarkup: true, - }); - } - - let group = message.direction === NotificationDirection.RECEIVED - ? 'received' : 'sent'; - - this._append({ - body: messageBody, - group, - styles, - timestamp: message.timestamp, - noTimestamp, - }); - } - - _filterMessages() { - if (this.messages.length < 1) - return; - - let lastMessageTime = this.messages[0].timestamp; - let currentTime = Date.now() / 1000; - - // Keep the scrollback from growing too long. If the most - // recent message (before the one we just added) is within - // SCROLLBACK_RECENT_TIME, we will keep - // SCROLLBACK_RECENT_LENGTH previous messages. Otherwise - // we'll keep SCROLLBACK_IDLE_LENGTH messages. - - let maxLength = lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME - ? SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH; - - let filteredHistory = this.messages.filter(item => item.realMessage); - if (filteredHistory.length > maxLength) { - let lastMessageToKeep = filteredHistory[maxLength]; - let expired = this.messages.splice(this.messages.indexOf(lastMessageToKeep)); - for (let i = 0; i < expired.length; i++) - this.emit('message-removed', expired[i]); - } - } - - /** - * @param {object} props - An object with the properties: - * {string} props.body - The text of the message. - * {string} props.group - The group of the message, one of: - * 'received', 'sent', 'meta'. - * {string[]} props.styles - Style class names for the message to have. - * {number} props.timestamp - The timestamp of the message. - * {bool} props.noTimestamp - suppress timestamp signal? - */ - _append(props) { - let currentTime = Date.now() / 1000; - props = Params.parse(props, { - body: null, - group: null, - styles: [], - timestamp: currentTime, - noTimestamp: false, - }); - const {noTimestamp} = props; - delete props.noTimestamp; - - // Reset the old message timeout - if (this._timestampTimeoutId) - GLib.source_remove(this._timestampTimeoutId); - this._timestampTimeoutId = 0; - - let message = new ChatNotificationMessage({ - realMessage: props.group !== 'meta', - showTimestamp: false, - ...props, - }); - - this.messages.unshift(message); - this.emit('message-added', message); - - if (!noTimestamp) { - let timestamp = props.timestamp; - if (timestamp < currentTime - SCROLLBACK_IMMEDIATE_TIME) { - this.appendTimestamp(); - } else { - // Schedule a new timestamp in SCROLLBACK_IMMEDIATE_TIME - // from the timestamp of the message. - this._timestampTimeoutId = GLib.timeout_add_seconds( - GLib.PRIORITY_DEFAULT, - SCROLLBACK_IMMEDIATE_TIME - (currentTime - timestamp), - this.appendTimestamp.bind(this)); - GLib.Source.set_name_by_id(this._timestampTimeoutId, '[gnome-shell] this.appendTimestamp'); - } - } - - this._filterMessages(); - } - - appendTimestamp() { - this._timestampTimeoutId = 0; - - this.messages[0].showTimestamp = true; - this.emit('timestamp-changed', this.messages[0]); - - this._filterMessages(); - - return GLib.SOURCE_REMOVE; - } - - appendAliasChange(oldAlias, newAlias) { - oldAlias = GLib.markup_escape_text(oldAlias, -1); - newAlias = GLib.markup_escape_text(newAlias, -1); - - /* Translators: this is the other person changing their old IM name to their new - IM name. */ - const message = `${ - _('%s is now known as %s').format(oldAlias, newAlias)}`; - - this._append({ - body: message, - group: 'meta', - styles: ['chat-meta-message'], - }); - - this._filterMessages(); - } -}) : null; - -const ChatLineBox = GObject.registerClass( -class ChatLineBox extends St.BoxLayout { - vfunc_get_preferred_height(forWidth) { - let [, natHeight] = super.vfunc_get_preferred_height(forWidth); - return [natHeight, natHeight]; - } -}); - -const ChatNotificationBanner = GObject.registerClass( -class ChatNotificationBanner extends MessageTray.NotificationBanner { - _init(notification) { - super._init(notification); - - this._responseEntry = new St.Entry({ - style_class: 'chat-response', - x_expand: true, - can_focus: true, - }); - this._responseEntry.clutter_text.connect('activate', this._onEntryActivated.bind(this)); - this._responseEntry.clutter_text.connect('text-changed', this._onEntryChanged.bind(this)); - this.setActionArea(this._responseEntry); - - this._responseEntry.clutter_text.connect('key-focus-in', () => { - this.focused = true; - }); - this._responseEntry.clutter_text.connect('key-focus-out', () => { - this.focused = false; - this.emit('unfocused'); - }); - - this._contentArea = new St.BoxLayout({ - style_class: 'chat-body', - vertical: true, - }); - this._scrollArea = new St.ScrollView({ - style_class: 'chat-scrollview vfade', - visible: this.expanded, - child: this._contentArea, - }); - - this.setExpandedBody(this._scrollArea); - this.setExpandedLines(CHAT_EXPAND_LINES); - - this._lastGroup = null; - - // Keep track of the bottom position for the current adjustment and - // force a scroll to the bottom if things change while we were at the - // bottom - this._oldMaxScrollValue = this._scrollArea.vadjustment.value; - this._scrollArea.vadjustment.connect('changed', adjustment => { - if (adjustment.value === this._oldMaxScrollValue) - this.scrollTo(St.Side.BOTTOM); - this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size); - }); - - this._inputHistory = new History.HistoryManager({entry: this._responseEntry.clutter_text}); - - this._composingTimeoutId = 0; - - this._messageActors = new Map(); - - this.notification.connectObject( - 'timestamp-changed', (n, message) => this._updateTimestamp(message), - 'message-added', (n, message) => this._addMessage(message), - 'message-removed', (n, message) => { - let actor = this._messageActors.get(message); - if (this._messageActors.delete(message)) - actor.destroy(); - }, this); - - for (let i = this.notification.messages.length - 1; i >= 0; i--) - this._addMessage(this.notification.messages[i]); - } - - scrollTo(side) { - let adjustment = this._scrollArea.vadjustment; - if (side === St.Side.TOP) - adjustment.value = adjustment.lower; - else if (side === St.Side.BOTTOM) - adjustment.value = adjustment.upper; - } - - hide() { - this.emit('done-displaying'); - } - - _addMessage(message) { - let body = new MessageList.URLHighlighter(message.body, true, true); - - let styles = message.styles; - for (let i = 0; i < styles.length; i++) - body.add_style_class_name(styles[i]); - - let group = message.group; - if (group !== this._lastGroup) { - this._lastGroup = group; - body.add_style_class_name('chat-new-group'); - } - - let lineBox = new ChatLineBox(); - lineBox.add_child(body); - this._contentArea.add_child(lineBox); - this._messageActors.set(message, lineBox); - - this._updateTimestamp(message); - } - - _updateTimestamp(message) { - let actor = this._messageActors.get(message); - if (!actor) - return; - - while (actor.get_n_children() > 1) - actor.get_child_at_index(1).destroy(); - - if (message.showTimestamp) { - let lastMessageTime = message.timestamp; - let lastMessageDate = new Date(lastMessageTime * 1000); - - let timeLabel = Util.createTimeLabel(lastMessageDate); - timeLabel.style_class = 'chat-meta-message'; - timeLabel.x_expand = timeLabel.y_expand = true; - timeLabel.x_align = timeLabel.y_align = Clutter.ActorAlign.END; - - actor.add_child(timeLabel); - } - } - - _onEntryActivated() { - let text = this._responseEntry.get_text(); - if (text === '') - return; - - this._inputHistory.addItem(text); - - // Telepathy sends out the Sent signal for us. - // see Source._messageSent - this._responseEntry.set_text(''); - this.notification.source.respond(text); - } - - _composingStopTimeout() { - this._composingTimeoutId = 0; - - this.notification.source.setChatState(Tp.ChannelChatState.PAUSED); - - return GLib.SOURCE_REMOVE; - } - - _onEntryChanged() { - let text = this._responseEntry.get_text(); - - // If we're typing, we want to send COMPOSING. - // If we empty the entry, we want to send ACTIVE. - // If we've stopped typing for COMPOSING_STOP_TIMEOUT - // seconds, we want to send PAUSED. - - // Remove composing timeout. - if (this._composingTimeoutId > 0) { - GLib.source_remove(this._composingTimeoutId); - this._composingTimeoutId = 0; - } - - if (text !== '') { - this.notification.source.setChatState(Tp.ChannelChatState.COMPOSING); - - this._composingTimeoutId = GLib.timeout_add_seconds( - GLib.PRIORITY_DEFAULT, - COMPOSING_STOP_TIMEOUT, - this._composingStopTimeout.bind(this)); - GLib.Source.set_name_by_id(this._composingTimeoutId, '[gnome-shell] this._composingStopTimeout'); - } else { - this.notification.source.setChatState(Tp.ChannelChatState.ACTIVE); - } - } -}); - -export const Component = TelepathyComponent; diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index 163d3eab5..8e1d1ed10 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -59,8 +59,7 @@ export const NotificationDestroyedReason = { // Message tray has its custom Urgency enumeration. LOW, NORMAL and CRITICAL // urgency values map to the corresponding values for the notifications received -// through the notification daemon. HIGH urgency value is used for chats received -// through the Telepathy client. +// through the notification daemon. /** @enum {number} */ export const Urgency = { LOW: 0, @@ -529,7 +528,6 @@ SignalTracker.registerDestroyableType(Notification); export const NotificationBanner = GObject.registerClass({ Signals: { 'done-displaying': {}, - 'unfocused': {}, }, }, class NotificationBanner extends Calendar.NotificationMessage { _init(notification) { @@ -1125,7 +1123,6 @@ export const MessageTray = GObject.registerClass({ let expired = (this._userActiveWhileNotificationShown && this._notificationTimeoutId === 0 && this._notification.urgency !== Urgency.CRITICAL && - !this._banner.focused && !this._pointerInNotification) || this._notificationExpired; let mustClose = this._notificationRemoved || !hasNotifications || expired; @@ -1166,9 +1163,7 @@ export const MessageTray = GObject.registerClass({ } this._banner = this._notification.createBanner(); - this._banner.connectObject( - 'done-displaying', this._escapeTray.bind(this), - 'unfocused', () => this._updateState(), this); + this._banner.connectObject('done-displaying', this._escapeTray.bind(this), this); this._bannerBin.add_child(this._banner); diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js index 9c80532b6..895b507e1 100644 --- a/js/ui/sessionMode.js +++ b/js/ui/sessionMode.js @@ -13,7 +13,7 @@ import * as Config from '../misc/config.js'; const DEFAULT_MODE = 'restrictive'; const USER_SESSION_COMPONENTS = [ - 'polkitAgent', 'telepathyClient', 'keyring', + 'polkitAgent', 'keyring', 'autorunManager', 'automountManager', ]; @@ -69,7 +69,7 @@ const _modes = { 'unlock-dialog': { isLocked: true, unlockDialog: undefined, - components: ['polkitAgent', 'telepathyClient'], + components: ['polkitAgent'], panel: { left: [], center: [], diff --git a/po/POTFILES.in b/po/POTFILES.in index 35cb77c69..0a9aa2f8d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -28,7 +28,6 @@ js/ui/components/autorunManager.js js/ui/components/keyring.js js/ui/components/networkAgent.js js/ui/components/polkitAgent.js -js/ui/components/telepathyClient.js js/ui/ctrlAltTab.js js/ui/dash.js js/ui/dateMenu.js